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

TopicWhat it covers
Processes and threadsA process is a running program with its own virtual address space. A thread is an execution context inside a processlesson
Memory and virtual memoryEvery process believes it owns a contiguous range of memory. The MMU translates each access through page tables to alesson
Files, permissions and I/OOn Unix a filename is an entry in a directory that points to an inode, which holds the metadata and the block map. Thelesson
What an operating system doesThe kernel runs with full access to hardware, memory and devices. Ordinary programs run in user space with none of itlesson
CPU scheduling: how the CPU is sharedA thread is runnable when it wants the CPU, running when it has one, and blocked when it is waiting for I/O, a lock orlesson
Synchronisation: locks, mutexes and semaphoresTwo threads that read, modify and write the same memory can interleave. The fix is to make the read-modify-writelesson
Races, deadlocks and starvationA data race is two threads accessing the same memory with at least one write and no synchronisation. The behaviour islesson
Inter-process communicationPipes and FIFOs, signals and their handlers, shared memory with semaphores, message queues, and local sockets as thelesson
Concurrency models: threads, async and processesEvery model is one of two strategies for the same problem: block and let the scheduler manage the wait, or never blocklesson
Monitoring and troubleshootingps and top for a snapshot, /proc and /sys for the details, strace for what a process is asking for, lsof for what itlesson
Storage and file systemsCopy-on-write file systems never overwrite a block in place, which is what makes snapshots cheap: a snapshot is alesson
Booting, init systems and servicesFirmware and bootloaders, how the kernel starts the first process, systemd units versus init scripts, targets andlesson
Containers, namespaces and cgroupsA memory limit triggers an OOM kill of a process inside the cgroup, whereas an unbounded container can trigger alesson
Virtualisation and hypervisorsType 1 and type 2 hypervisors, hardware virtualisation extensions, paravirtualised drivers, live migration, and why alesson

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 = SIGTERM

Full 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; done

Full 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 filesystem

Full 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 ls

Full 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 wait

Full 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_secs

Full 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 request

Full 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 process

Full 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,MOUNTPOINT

Full 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.service

Full 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 it

Full lesson: Virtualisation and hypervisors →

FAQ

Is this Operating Systems cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 14 lessons of the Operating Systems course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full Operating Systems course — it carries the worked explanations, the edge cases and the exercises behind every line here.

Algorithms Data Structures Computer Networks Character Encodings Hashing & Checksums Data Formats

Last refreshed 2026-09-27.