Operating Systems Cheat Sheet
“Symptom says X → think Y.” One line per need; click a row to open the lesson.
Processes & threads
Same binary, three instances, three different states->A program is a file; a process is a running instance with its own PID, address space and descriptorsWhat does `./server` actually do before `main()` runs?->Shell `fork`s, child `exec`s the ELF/PE, loader maps segments, dynamic linker resolves symbols, then `_start` → `main``fork()` of a large process is slow->Copy-on-write page tables — pages are shared until written; consider `posix_spawn` / `vfork``fork()` fails with `EAGAIN`, `ps` shows `<defunct>`->Zombies: the parent never called `wait()`; PIDs are exhausted, not memoryCrash in one worker must not take down the others->Processes: separate address spaces; threads share one and die together"Is Python single-threaded? Can Node use multiple cores?"->Runtime-specific: CPython threads run but the GIL serialises bytecode; Node has one JS thread plus a libuv pool and worker threads`setTimeout` / `setInterval` fires late->The event loop is blocked by a long synchronous task; timers only run between callbacksThousands of mostly-idle connections, few cores->Async I/O on an event loop — one thread waits on all of them; a thread per connection burns stacks and switches"It runs concurrently, so it must be faster"->Concurrency is interleaving; parallelism is simultaneous cores — one core never gives you parallelism
Scheduling
100 runnable tasks, 8 cores — who runs?->The scheduler: ready queues, priorities and time slices; everything else is waiting on I/O, not the CPUHigh load average but CPU usage is low->Load counts runnable **and** (Linux) uninterruptible tasks — look for `D`-state processes waiting on disk or NFSLatency-sensitive service starved by batch jobs->Priority / `nice`, or a separate cgroup with a CPU weight; on Windows, process priority classesMore threads than cores and throughput went **down**->Context-switch and cache-eviction cost; size the pool to cores for CPU-bound work
Memory
Where does a local variable live? Where does an object live?->Locals in the current stack frame; anything that outlives the call goes on the heapSegfault at the same recursion depth every time->Stack overflow: the guard page below the stack was hit; the main thread has ~8 MB, worker threads often far lessReturning a pointer to a local, then reading garbage->The frame was popped; the stack slot is reused by the next call`malloc` was fast, then suddenly slow->The allocator ran out of arena and asked the kernel (`brk`/`mmap`); large allocations map fresh zeroed pagesCode / data / heap / stack — what is the layout?->Text, data, BSS, heap growing up, mmap region, stack growing down — with ASLR shifting the bases
Virtual memory
Two processes print the same pointer value for different data->Virtual addresses: each process has its own page table mapping the same number to different frames`VSZ` is huge, `RSS` is small — which is real?->Virtual size is reserved address space; resident set is what is actually backed by frames"How does one 64-bit address turn into a frame?"->Multi-level page table walk: an index per level, then the offset within the 4 KB pageRandom memory access is 10× slower than sequential->TLB misses: every new page costs a page-table walk; keep hot data within few pages or use huge pagesFirst touch of a big array is slow, second is fast->Minor page faults on demand-zero pages; the kernel maps frames lazilyCPU at 3%, disk busy, everything takes seconds->Memory pressure: the page cache is gone and the box is swapping — thrashingRSS grows forever->A leak or an unbounded queue; the OOM killer arrives when swap is gone tooRead a 20 GB file without 20 GB of RAM->`mmap` it: pages come in on demand and are evicted under pressure; `MapViewOfFile` on WindowsEight forked workers, `RSS` sums to 8× the parent->Copy-on-write shares the pages; `RSS` double-counts them — use `PSS` / `smem`
Files & I/O
`EMFILE` / "too many open files"->Descriptor leak first, `ulimit -n` second — every socket, file and pipe end is a descriptorFile deleted, disk space did not come back->A process still holds the descriptor; the inode lives until the last reference closes (`lsof +L1`)`open()` fails with `EACCES` while `ls` shows the file->Path resolution needs execute (search) permission on every directory in the path, not just the fileSecond read of a file is instant->The page cache: the first read went to the SSD, the second came from RAM`write()` returned, data lost on power cut->`write` fills the page cache; only `fsync` / `fdatasync` (Windows `FlushFileBuffers`) forces it to storageOne thread must wait on 10,000 sockets->Readiness multiplexing: `epoll` (Linux), `kqueue` (BSD/macOS), IOCP (Windows) — not one thread each`select()` breaks above 1024 descriptors->`FD_SETSIZE` limit and O(n) scanning; move to `poll` at least, `epoll`/`kqueue` in practiceStuck in `D` state, `kill -9` does nothing->Uninterruptible sleep inside a syscall waiting on disk/NFS — the signal is delivered when the I/O returnsFiles, pipes, sockets, devices — same API?->All descriptors: `read`/`write`/`close` and the same readiness interfaces (`/dev`, `/proc` included)
Concurrency
Counter incremented twice, result is one->Lost update: `load → add → store` interleaved; make it atomic or lock itBug disappears when you add a `print`->A timing-dependent race — logging changed the interleaving; the bug is still thereOne thread never sees the flag the other thread set->Visibility: no synchronisation means no ordering guarantee; use an atomic with acquire/release or a lockWhich lines exactly need the lock?->The critical section: the smallest region that reads-then-writes shared stateMutual exclusion for one owner->Mutex — owned, released by the locker; `pthread_mutex`, `std::mutex`, `CRITICAL_SECTION` on WindowsAt most N concurrent users of a resource pool->Counting semaphore — no owner, `wait`/`post` from different threads is allowedProcess alive, 0% CPU, every thread in `futex_wait`->Deadlock: two locks taken in opposite orders — dump the stacks and draw the wait-for graph
IPC & sockets
Two isolated processes need to talk->Pick by cost: pipe (simple, byte stream) → shared memory (fastest, needs sync) → socket (works across machines)`cmd1 | cmd2` — how does the data cross?->A kernel pipe buffer (64 KB on Linux); the writer blocks when it is full, the reader gets `EOF` on closeMove gigabytes between processes without copying->Shared memory (`shm_open` + `mmap`, Windows file mapping) with a semaphore or futex to coordinateContainer stops with `SIGKILL` after 10 s->Handle `SIGTERM` to drain; `kill -9` cannot be caught, blocked or ignoredProcess dies writing to a closed socket->`SIGPIPE` — the default action terminates; ignore it or use `MSG_NOSIGNAL`What did `socket()` actually give me?->A descriptor with kernel send and receive buffers and a state machine behind it — `send()` copies into the buffer, delivery is asynchronous
Containers
Container sees its own PID 1, its own `eth0`, its own `/`->Namespaces: PID, net, mount, UTS, user — the same kernel, a different viewContainer killed at exactly 512 MB->A cgroup memory limit; the OOM killer is per cgroup, and the page cache counts against it`uname -r` inside the container matches the host->Containers share the host kernel; only a VM boots its ownNeed a different kernel, or untrusted multi-tenant isolation->A VM (or a micro-VM like Firecracker) — the hypervisor boundary, not a namespace
Debugging
100% CPU, mostly system time->Syscall storm or spin-lock contention in the kernel (`futex`) — `strace -c`, `perf top`100% CPU on one core, service still answers->A single thread in an infinite loop; find it with `top -H` and dump its stackWhere do I start when the process is slow, hung or dying?->CPU → memory → I/O wait → locks → descriptors, in that order; each has one commandExplain a 50,000-connection server layer by layer->Descriptors, socket buffers, `epoll`, a small thread pool, and the memory each connection actually costsWhich mode does a syscall run in, and what does it cost?->A trap to kernel mode, ~100 ns–1 µs plus cache effects; batch syscalls, never one per byte"Why can’t my program just write to the disk itself?"->Only kernel mode may touch devices; user mode asks via a syscall so the kernel can share and protect