Operating Systems cheat sheet
A scannable Operating Systems reference: 32 short snippets across 14 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Processes and threads | A process is a running program with its own virtual address space. A thread is an execution context inside a process | lesson |
| Memory and virtual memory | Every process believes it owns a contiguous range of memory. The MMU translates each access through page tables to a | lesson |
| Files, permissions and I/O | On Unix a filename is an entry in a directory that points to an inode, which holds the metadata and the block map. The | lesson |
| What an operating system does | The kernel runs with full access to hardware, memory and devices. Ordinary programs run in user space with none of it | lesson |
| CPU scheduling: how the CPU is shared | A thread is runnable when it wants the CPU, running when it has one, and blocked when it is waiting for I/O, a lock or | lesson |
| Synchronisation: locks, mutexes and semaphores | Two threads that read, modify and write the same memory can interleave. The fix is to make the read-modify-write | lesson |
| Races, deadlocks and starvation | A data race is two threads accessing the same memory with at least one write and no synchronisation. The behaviour is | lesson |
| Inter-process communication | Pipes and FIFOs, signals and their handlers, shared memory with semaphores, message queues, and local sockets as the | lesson |
| Concurrency models: threads, async and processes | Every model is one of two strategies for the same problem: block and let the scheduler manage the wait, or never block | lesson |
| Monitoring and troubleshooting | ps and top for a snapshot, /proc and /sys for the details, strace for what a process is asking for, lsof for what it | lesson |
| Storage and file systems | Copy-on-write file systems never overwrite a block in place, which is what makes snapshots cheap: a snapshot is a | lesson |
| Booting, init systems and services | Firmware and bootloaders, how the kernel starts the first process, systemd units versus init scripts, targets and | lesson |
| Containers, namespaces and cgroups | A memory limit triggers an OOM kill of a process inside the cgroup, whereas an unbounded container can trigger a | lesson |
| Virtualisation and hypervisors | Type 1 and type 2 hypervisors, hardware virtualisation extensions, paravirtualised drivers, live migration, and why a | lesson |
Quick snippets
Processes and threads
What a process owns that a thread shares
ps -eLf | head # one line per THREAD (-L), shows LWP column
ps -ef --forest # process tree, reveals parent/child structure
cat /proc/self/status | grep -E 'Threads|VmRSS|VmSize'
How processes talk
# pipeline: two processes, one byte stream
grep -c ERROR /var/log/app.log
# a named pipe crossed by unrelated processes
mkfifo /tmp/demo && (echo hello > /tmp/demo &) && cat /tmp/demo
# send a signal by name instead of guessing the number
kill -HUP $(cat /run/app.pid) # 1 = SIGHUP, 2 = SIGINT, 9 = SIGKILL, 15 = SIGTERMFull lesson: Processes and threads →
Memory and virtual memory
Inside a virtual address space
free -h # total, used, buff/cache, available
cat /proc/meminfo | head -5
vmstat 1 5 # watch si/so: swap-in and swap-out per second
ulimit -a # per-process limits, including stack size (Kb)
Paging, faults and the working set
# faults per second, split minor/major (-s summary)
/usr/bin/time -v ./myprogram 2>&1 | grep -E 'Maximum resident|page faults'
Fragmentation and the OOM killer
# who is actually using memory, and how much is reclaimable
ps -eo pid,rss,vsz,comm --sort=-rss | head -10
# is a process leaking, or just caching? watch RSS over time
while true; do ps -o rss= -p 1234; sleep 5; doneFull lesson: Memory and virtual memory →
Files, permissions and I/O
Files, inodes and links
ls -li notes.txt # -i prints the inode number
stat notes.txt # size, blocks, link count, access/modify/change times
ln notes.txt copy.txt # hard link: same inode, link count becomes 2
ln -s notes.txt link.txt # symbolic link: a small file containing a path
rm notes.txt # the inode survives while link count > 0
lsof +L1 # files with link count 0 still open somewhere
Permissions that matter
chmod 644 config.yml # rw-r--r--, typical for a config file
chmod 600 id_ed25519 # rw-------, required by ssh for private keys
chmod 755 scripts/run.sh # rwxr-xr-x
chown -R app:app /srv/app
umask # bits removed from new files, e.g. 022
# who am I, and what can I actually do?
id && sudo -l
Buffered, unbuffered and durable writes
import os
# buffered: fast, data is in the page cache after write() returns
with open("out.txt", "w", encoding="utf-8") as f:
f.write("hello")
f.flush() # push Python buffer -> kernel
os.fsync(f.fileno()) # push kernel buffer -> device
# an atomic replace: write a temp file, fsync it, then rename
os.replace("out.txt.tmp", "out.txt") # rename is atomic within a filesystemFull lesson: Files, permissions and I/O →
What an operating system does
What a system call costs
#include <unistd.h>
// a library call that becomes a system call
ssize_t n = write(1, "hello\n", 6);
// under the hood on x86-64 Linux:
// rax = 1 (the syscall number for write)
// rdi = 1, rsi = buf, rdx = 6
// syscall (the instruction that traps into the kernel)
// the return value arrives in rax, and errno is set on failure
What a system call costs
# count the system calls a program makes
strace -c ls >/dev/null
# % time seconds usecs/call calls errors syscall
# ...
# 0.001234 12 1000 read
# see them as they happen, with arguments
strace -f -e trace=openat,read,write -o /tmp/trace.txt lsFull lesson: What an operating system does →
CPU scheduling: how the CPU is shared
Runnable, running, blocked
ps -eo pid,stat,pcpu,pmem,comm --sort=-pcpu | head
# STAT R running S sleeping D uninterruptible
# T stopped Z zombie
uptime
# 10:30:01 up 4 days, 1 user, load average: 8.12, 4.03, 2.00
# the three figures are 1, 5 and 15 minute averages
Time slices, priorities and preemption
nice -n 10 ./batch-job # start at a lower priority
renice -n 5 -p 12345 # change a running process
taskset -c 0-3 ./server # restrict to cores 0 through 3
chrt -f 50 ./realtime-worker # FIFO real-time class, priority 50
# what the running thread is waiting for
cat /proc/12345/status | grep -E "State|voluntary"
cat /proc/12345/sched | head
Reading load average without being misled
nproc # compare load against the core count
uptime
vmstat 1 5 # r = runnable, b = blocked, then CPU breakdown
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 1 0 812345 12000 640000 0 0 12 240 500 1200 15 3 80 2 0
# ^wa = I/O waitFull lesson: CPU scheduling: how the CPU is shared →
Synchronisation: locks, mutexes and semaphores
The critical section
// broken: the increment is three operations, not one
counter++;
// roughly:
// load counter -> register
// add 1, register
// store register -> counter
// two threads can interleave between the load and the store, losing one update
Atomics and lock-free code
#include <stdatomic.h>
atomic_int counter = 0;
void bump(void) {
// single indivisible increment, no lock needed
atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
}
// compare-and-swap is the building block of lock-free algorithms
int expected = 0;
atomic_compare_exchange_strong(&counter, &expected, 42);Full lesson: Synchronisation: locks, mutexes and semaphores →
Races, deadlocks and starvation
Finding a race
# ThreadSanitizer: compile and run with instrumentation
gcc -fsanitize=thread -g -O1 program.c -o program && ./program
# WARNING: ThreadSanitizer: data race
# Write of size 4 at 0x... by thread T2:
# #0 increment counter.c:12
# Helgrind, part of Valgrind, for binaries you cannot rebuild
valgrind --tool=helgrind ./program
# Go has it built in
go test -race ./...
go run -race main.go
Live-lock, starvation and priority inversion
# find threads stuck waiting on a lock
gdb -p 12345 -batch -ex "thread apply all bt" 2>/dev/null | grep -B2 -A5 "pthread_mutex_lock\|__lll_lock_wait"
# the kernel reports hung tasks after a timeout
dmesg | grep -i "hung_task\|blocked for more than"
cat /proc/sys/kernel/hung_task_timeout_secsFull lesson: Races, deadlocks and starvation →
Inter-process communication
Signals are notifications, not messages
import signal, os
def handler(signum, frame):
# do NOT do real work here; set a flag or write one byte
os.write(wake_fd, b"x")
signal.signal(signal.SIGTERM, handler) # graceful shutdown request
signal.signal(signal.SIGHUP, handler) # traditionally reload configuration
# never install a handler for SIGKILL or SIGSTOP; the kernel ignores the requestFull lesson: Inter-process communication →
Concurrency models: threads, async and processes
Choosing for a workload
# check what a worker process is actually doing
ps -eLf | grep gunicorn | head # threads per process
ss -tan state established | wc -l # connections held open
top -H -p 12345 # per-thread CPU inside one processFull lesson: Concurrency models: threads, async and processes →
Monitoring and troubleshooting
/proc and /sys as the ground truth
cat /proc/12345/status | grep -E "State|VmRSS|Threads|FDSize"
cat /proc/12345/limits | grep -i "open files"
ls -l /proc/12345/fd | head # what the process has open
cat /proc/12345/wchan; echo # what kernel function it is blocked in
cat /proc/12345/stack # needs privilege; kernel stack
cat /proc/12345/io # bytes read and written
cat /proc/meminfo | grep -E "MemTotal|MemAvailable|SwapTotal|SwapFree"
cat /proc/loadavg
cat /proc/pressure/io # pressure stall information
Diagnosing a hung process
ps -o pid,stat,wchan:32,etime -p 12345
cat /proc/12345/wchan; echo
gdb -p 12345 -batch -ex "thread apply all bt" 2>/dev/null | head -80
strace -p 12345 -f -tt -T -o /tmp/attach.txt &
sleep 3; kill %1
# kernel-side: hung task reports
dmesg -T | tail -40 | grep -i "blocked for more than"Full lesson: Monitoring and troubleshooting →
Storage and file systems
From a write() to a platter or a cell
application write(fd, buf, n)
|
page cache <- the write returns here, often before it is durable
|
file system blocks, journal, metadata
|
block layer merges and orders requests, does I/O scheduling
|
device driver / device SSD or spinning disk
fsync(fd) forces the data out of the page cache;
durability requires it, and it costs a device round trip.
From a write() to a platter or a cell
sync # flush all dirty pages
fstrim -av # inform the SSD which blocks are free
mount | grep -E "ext4|xfs|btrfs|apfs"
stat -f / # file system type and block size
SSD behaviour worth knowing
# check device health and endurance
smartctl -a /dev/nvme0 | grep -E "Percentage Used|Data Units Written|Media Errors"
smartctl -a /dev/sda | grep -E "Reallocated|Pending|Wear_Leveling"
# is a periodic trim enabled?
systemctl status fstrim.timer
# block sizes and alignment
lsblk -o NAME,SIZE,PHY-SEC,LOG-SEC,MOUNTPOINTFull lesson: Storage and file systems →
Booting, init systems and services
The boot sequence
systemd-analyze # total boot time and the biggest stages
systemd-analyze blame | head -15 # slowest units
systemd-analyze critical-chain # the path that determined the boot time
journalctl -b # this boot's log
journalctl -b -1 # previous boot, useful after a failed one
journalctl -b --list-boots # available boots
systemd units and where they differ from init scripts
systemctl daemon-reload # after editing a unit file
systemctl enable --now api.service
systemctl status api.service
systemctl restart api.service
systemctl list-dependencies api.service
journalctl -u api.service -f # follow one service
journalctl -u api.service --since "1 hour ago" -p err
Targets and troubleshooting a boot
systemctl --failed
systemctl list-units --state=failed
systemctl cat api.service # the effective unit plus drop-ins
systemctl show api.service -p Environment -p Restart
# a temporary override rather than editing the packaged unit
# systemctl edit api.serviceFull lesson: Booting, init systems and services →
Containers, namespaces and cgroups
One namespace per kind of isolation
# the same view a container runtime builds, done by hand
unshare --pid --fork --mount-proc --uts --ipc --net bash
# inside: a new PID 1, a new hostname, no host network interfaces yet
# what the current process can see
ls -l /proc/self/ns/
readlink /proc/self/ns/net
lsns | head
cgroups constrain resources
# inspect the cgroup of a process and its limits
cat /proc/self/cgroup
systemd-cgtop
# a slice with CPU and memory limits
systemctl set-property user-1000.slice MemoryMax=4G CPUQuota=200%
# the kernel's own view
cat /sys/fs/cgroup/system.slice/api.service/memory.max
cat /sys/fs/cgroup/system.slice/api.service/cpu.max
cat /sys/fs/cgroup/system.slice/api.service/memory.current
cat /sys/fs/cgroup/system.slice/api.service/memory.events
Images and why they are layered
# dependencies first, source last: this keeps the expensive layer cached
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
# a non-root runtime user costs nothing and removes a whole class of risk
USER node
ENTRYPOINT ["node", "dist/server.js"]Full lesson: Containers, namespaces and cgroups →
Virtualisation and hypervisors
Hardware support and paravirtualisation
# is hardware virtualisation available?
egrep -c '(vmx|svm)' /proc/cpuinfo
lsmod | grep kvm
virsh list --all
virsh dominfo myvm | head
# inside a guest: is it virtualised, and which hypervisor?
systemd-detect-virt
dmidecode -s system-product-name 2>/dev/null
Live migration and guest performance
# a live migration with libvirt
virsh migrate --live --persistent --undefinesource myvm \
qemu+ssh://dest-host/system
# resource contention shows up as steal time inside the guest
top -b -n1 | head -3
# %Cpu(s): ... 0.0 st
# st is time the guest wanted to run but the host did not schedule itFull lesson: Virtualisation and hypervisors →
FAQ
Is this Operating Systems cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
Algorithms Data Structures Computer Networks Character Encodings Hashing & Checksums Data Formats
Last refreshed 2026-09-27.