Your technical library
Operating Systems
25 modules · 149 topics
About this book
Operating Systems
This course teaches what actually happens underneath your code — when a process starts, a thread blocks, memory runs out, a file is "saved", or a container boots — from the bare processor up, so that you can reason about performance, debug a production incident at the kernel boundary, and argue a design choice (threads or processes, epoll or io_uring, a journal or copy-on-write, a VM or a container) with mechanisms and numbers rather than folklore. By "the operating system" it means, above all, the kernel — the one program with full authority over the hardware; the shell, libraries, daemons and desktop shipped with it appear only as programs that use the kernel. It is written for a working software engineer who uses Linux, Docker, threads and async every day but was never taught what lies beneath them. No operating-systems course is assumed, and nothing is used before the topic that teaches it. It builds on three other courses on this site: Data Structures (trees, queues and ring buffers the kernel is built from), System Design (the latency numbers and logs it assumes) and, where probability appears, Mathematics. Roughly a third of the original course — the internals a staff engineer meets when the illusion is the bug — now lives in the companion course Operating Systems in Depth; topics this course opens but leaves to it are named as they come up.
An operating system sells three illusions — every program has the processor to itself, has memory to itself, and has storage that survives crashes — and keeps one promise: that one program cannot harm another. The course builds each from the problem that forces it. The machine beneath the operating system opens with a map of the whole computer — its hardware, the layers of software on it, and where the operating system sits — then gives the minimum hardware picture, in plain words: instructions, the call stack, caches and multiple cores. The kernel boundary adds privilege, traps, system calls and interrupts, and shows how programs run at full speed without escaping control. Processes and CPU scheduling build the first illusion: the kernel's record of a running program, fork, exec, signals, sessions and threads, and the policies — up to Linux's EEVDF — that decide who runs next on which core.
Address translation, memory beyond RAM and the memory API and kernel allocators build the second illusion: page tables and TLBs, demand paging and replacement, swap, the OOM killer, mmap, copy-on-write, and how malloc works. Program loading applies all of it end to end — the linker that builds an executable, then the kernel that maps it into a fresh address space. Races and locks, waiting and deadlock and scalable concurrency teach what goes wrong when executions share memory and how to make it right, from atomic instructions and futexes to deadlock. Inter-process communication connects processes through pipes, shared memory and sockets.
The third illusion runs from devices and drivers and storage devices and the block layer through files and directories, the page cache and durability (including exactly what fsync promises) and file system implementation, to crash consistency — fsck, journaling and the contract applications build on it. The applied modules then take the kernel as you meet it in production: event-driven and asynchronous I/O (epoll and the event loop), observing the kernel (load average, profilers and method), protection, virtual machines and containers, followed by distributed file systems. The course closes with case studies that trace one boot, one read() and one accepted connection through everything before. Along the way each topic says what it inherits, what it leaves open — naming the Operating Systems in Depth topic where one answers it — who designed the idea and when, and how it shows up in decisions engineers make.
149 of 149 topics are authored. Read in dependency order. Planned lessons are labelled and excluded from progress; references cover authored material.
Read at your own pace. Each topic is one continuous page; mark it done as you finish.
Start with 01.1 · The computer in pictures: its parts, what sits inside what, and where a running program livesReview what you have finished↷ 0 skipped topics- 01.1The computer in pictures: its parts, what sits inside what, and where a running program lives
- 01.2The layers of a computer: hardware, the operating system and applications, and where each sits
- 01.3The processor's view of a program: instructions, registers and the program counter
- 01.4The call stack: how a function call returns
- 01.5CPU caches: why locality decides speed
- 01.6Multicore processors: shared memory, private caches and cache coherence
- 02.1How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux
- 02.2The operating system's contract: abstraction, sharing and protection
- 02.3CPU privilege modes: the one bit the OS depends on
- 02.4Traps and exceptions: how the hardware hands control to the kernel
- 02.5System calls: a controlled entry into the kernel
- 02.6Interrupts: events from outside the running program
- 02.7Limited direct execution: running programs fast without losing control
- 02.8Kernel structures: monolithic, layered, microkernel, hybrid and modules
- 03.1The process: the kernel's record of a program in execution
- 03.2Context switching: saving one process, loading another
- 03.3File descriptors: a process's handles on kernel objects
- 03.4fork: creating a process by cloning
- 03.5exec: replacing a process's program
- 03.6exit and wait: reaping processes, zombies and orphans
- 03.7How a shell runs a command: why fork and exec are separate
- 03.8Threads: several threads sharing one process
- 03.9Signals: asynchronous notifications to a process
- 03.10Sessions, process groups and job control: which processes Ctrl-C reaches
- 04.1Scheduling metrics: turnaround, response and fairness pull apart
- 04.2FIFO, SJF and STCF: minimising turnaround, and the convoy effect
- 04.3Round robin: response time, and the quantum trade-off
- 04.4The multi-level feedback queue: scheduling without knowing the future
- 04.5Lottery scheduling: proportional share by random draw
- 04.6Stride scheduling: deterministic proportional share
- 04.7The Completely Fair Scheduler: virtual runtime in a red-black tree
- 04.8EEVDF: eligibility and virtual deadlines
- 04.9EEVDF in the kernel: the augmented tree, placement and delayed dequeue
- 05.1The address space: a private view of memory
- 05.2Base and bounds: the simplest translation
- 05.3Segmentation: a base and bound per region
- 05.4Free-space management: free lists, fit policies and fragmentation
- 05.5Paging: fixed-size pages remove the contiguous-image requirement
- 05.6Multi-level page tables: materialise only what is used
- 05.7The TLB: caching translations, hits and misses, and the cost of a walk
- 05.8Address-space identifiers: keeping translations across a switch
- 06.1Demand paging: page faults and a disk-backed address space
- 06.2Optimal page replacement: Belady's bound
- 06.3FIFO replacement and Belady's anomaly
- 06.4LRU replacement: recency, its guarantee and its cost
- 06.5The clock algorithm: approximating LRU with a referenced bit
- 06.6The working-set model: how much memory a process needs
- 06.7Thrashing: when paging replaces computing
- 06.8Swap in practice: swap devices, compressed memory, and whether a server should swap
- 06.9Memory overcommit and the OOM killer
- 06.10Paging numericals: address fields, frame bits, effective access time and replacement grids
- 09.1Race conditions: shared state, wrong answers
- 09.2Critical sections: mutual exclusion, progress and bounded waiting
- 09.3Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional
- 09.4Spinlocks: busy-waiting for short critical sections
- 09.5Fair spinlocks: ticket locks and MCS queue locks
- 09.6Spinlocks and interrupts: why a holder must not be interrupted
- 09.7Memory ordering: store buffers and relaxed memory models
- 09.8Sleeping locks and wait queues
- 09.9Futexes: a user-space fast path with kernel waiting
- 09.10Adaptive locking: spin, then sleep
- 09.11Priority inversion: priority inheritance and ceilings
- 09.12Reader–writer locks: sharing reads, and who starves
- 10.1Condition variables: waiting for a condition, not a lock
- 10.2Semaphores: a counter that waits
- 10.3Monitors: synchronisation as a language construct
- 10.4Deadlock: the four necessary conditions
- 10.5Deadlock prevention: lock ordering and its enforcement
- 10.6Deadlock avoidance: the Banker's algorithm
- 10.7Deadlock detection and recovery
- 10.8Classic synchronisation problems: dining philosophers, readers–writers, bakery and the sleeping barber
- 10.9Concurrency bug patterns: atomicity and order violations
- 12.1Pipes: a kernel buffer between two descriptors
- 12.2Shared-memory IPC: mapping the same pages into two processes
- 12.3Message passing: queues with boundaries
- 12.4Sockets: the kernel's communication endpoint behind a descriptor
- 12.5Socket buffers, back-pressure and readiness: what a successful send promises
- 12.6Unix domain sockets: local sockets that pass descriptors
- 13.1Device registers and memory-mapped I/O
- 13.2Polling versus interrupt-driven I/O, and receive livelock
- 13.3Direct memory access: devices that copy for themselves
- 13.4PCIe and MSI-X: how a device is found, mapped and heard from
- 13.5Device drivers: the kernel's largest and least trusted code
- 13.6Device files: /dev, major and minor numbers, and why a disk is a file
- 16.1The page cache: file data cached in memory
- 16.2Memory-mapped files: file pages as memory
- 16.3Readahead: detecting sequential access
- 16.4Writeback: when dirty pages reach the disk
- 16.5fsync and the durability contract
- 16.6When fsync fails: how write errors are reported, and lost
- 16.7Direct I/O: bypassing the page cache
- 17.1On-disk layout: superblock, bitmaps, inode table and data
- 17.2File allocation methods: contiguous, linked, FAT and indexed
- 17.3Block maps and indirect blocks: how big a file can be
- 17.4Extents: describing contiguous runs
- 17.5Directory implementation: lists, hashed trees and a traced open
- 17.6Block groups and locality: the Fast File System's idea
- 23.1Namespaces: per-process views of global resources
- 23.2Mount propagation: when one namespace's mount appears in another
- 23.3Control groups: a hierarchy for resource control
- 23.4CPU bandwidth control: quotas, periods and throttling
- 23.5Memory cgroups: limits, reclaim and OOM inside a container
- 23.6Container images and overlay file systems
01.2The layers of a computer: hardware, the operating system and applications, and where each sits
01.3The processor's view of a program: instructions, registers and the program counter
01.4The call stack: how a function call returns
01.5CPU caches: why locality decides speed
01.6Multicore processors: shared memory, private caches and cache coherence
02.1How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux
02.2The operating system's contract: abstraction, sharing and protection
02.3CPU privilege modes: the one bit the OS depends on
02.4Traps and exceptions: how the hardware hands control to the kernel
02.5System calls: a controlled entry into the kernel
02.6Interrupts: events from outside the running program
02.7Limited direct execution: running programs fast without losing control
02.8Kernel structures: monolithic, layered, microkernel, hybrid and modules
03.1The process: the kernel's record of a program in execution
03.2Context switching: saving one process, loading another
03.3File descriptors: a process's handles on kernel objects
03.4fork: creating a process by cloning
03.5exec: replacing a process's program
03.6exit and wait: reaping processes, zombies and orphans
03.7How a shell runs a command: why fork and exec are separate
03.8Threads: several threads sharing one process
03.9Signals: asynchronous notifications to a process
03.10Sessions, process groups and job control: which processes Ctrl-C reaches
04.1Scheduling metrics: turnaround, response and fairness pull apart
04.2FIFO, SJF and STCF: minimising turnaround, and the convoy effect
04.3Round robin: response time, and the quantum trade-off
04.4The multi-level feedback queue: scheduling without knowing the future
04.5Lottery scheduling: proportional share by random draw
04.6Stride scheduling: deterministic proportional share
04.7The Completely Fair Scheduler: virtual runtime in a red-black tree
04.8EEVDF: eligibility and virtual deadlines
04.9EEVDF in the kernel: the augmented tree, placement and delayed dequeue
05.1The address space: a private view of memory
05.2Base and bounds: the simplest translation
05.3Segmentation: a base and bound per region
05.4Free-space management: free lists, fit policies and fragmentation
05.5Paging: fixed-size pages remove the contiguous-image requirement
05.6Multi-level page tables: materialise only what is used
05.7The TLB: caching translations, hits and misses, and the cost of a walk
05.8Address-space identifiers: keeping translations across a switch
06.1Demand paging: page faults and a disk-backed address space
06.2Optimal page replacement: Belady's bound
06.3FIFO replacement and Belady's anomaly
06.4LRU replacement: recency, its guarantee and its cost
06.5The clock algorithm: approximating LRU with a referenced bit
06.6The working-set model: how much memory a process needs
06.7Thrashing: when paging replaces computing
06.8Swap in practice: swap devices, compressed memory, and whether a server should swap
06.9Memory overcommit and the OOM killer
06.10Paging numericals: address fields, frame bits, effective access time and replacement grids
07.1Copy-on-write: sharing pages until one side writes
07.2mmap and virtual memory areas: asking the kernel for address space
07.3The buddy allocator: physical pages in power-of-two blocks
07.4Implementing malloc: bins, brk and mmap thresholds
08.1From source to executable: object files, symbols and the static linker
08.2The ELF executable format: segments, sections and the entry point
08.3Inside exec: building a new address space
08.4Dynamic linking: shared libraries, ld.so, PLT and GOT
09.1Race conditions: shared state, wrong answers
09.2Critical sections: mutual exclusion, progress and bounded waiting
09.3Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional
09.4Spinlocks: busy-waiting for short critical sections
09.5Fair spinlocks: ticket locks and MCS queue locks
09.6Spinlocks and interrupts: why a holder must not be interrupted
09.7Memory ordering: store buffers and relaxed memory models
09.8Sleeping locks and wait queues
09.9Futexes: a user-space fast path with kernel waiting
09.10Adaptive locking: spin, then sleep
09.11Priority inversion: priority inheritance and ceilings
09.12Reader–writer locks: sharing reads, and who starves
10.1Condition variables: waiting for a condition, not a lock
10.2Semaphores: a counter that waits
10.3Monitors: synchronisation as a language construct
10.4Deadlock: the four necessary conditions
10.5Deadlock prevention: lock ordering and its enforcement
10.6Deadlock avoidance: the Banker's algorithm
10.7Deadlock detection and recovery
10.8Classic synchronisation problems: dining philosophers, readers–writers, bakery and the sleeping barber
10.9Concurrency bug patterns: atomicity and order violations
11.1Why one lock stops scaling: serial fraction and lock queueing
11.2Lock granularity: dividing a lock by data, end, node and value
11.3False sharing: independent data, shared cache lines
12.1Pipes: a kernel buffer between two descriptors
12.2Shared-memory IPC: mapping the same pages into two processes
12.3Message passing: queues with boundaries
12.4Sockets: the kernel's communication endpoint behind a descriptor
12.5Socket buffers, back-pressure and readiness: what a successful send promises
12.6Unix domain sockets: local sockets that pass descriptors
13.1Device registers and memory-mapped I/O
13.2Polling versus interrupt-driven I/O, and receive livelock
13.3Direct memory access: devices that copy for themselves
13.4PCIe and MSI-X: how a device is found, mapped and heard from
13.5Device drivers: the kernel's largest and least trusted code
13.6Device files: /dev, major and minor numbers, and why a disk is a file
14.1Hard disk drives: seek, rotation and transfer
14.2Disk scheduling: six policies on one request queue
14.3Flash translation layers: erase blocks and out-of-place writes
14.4The block layer: bios, requests and blk-mq
14.5RAID 0, 1 and 10: striping and mirroring
15.1Files and inodes: named bytes and their metadata
15.2Directories and path resolution
15.3Hard links, symbolic links and unlink
15.4Unix file permissions: owner, group, other
15.5Mounting: grafting file systems into one tree
15.6The virtual file system: one interface, many file systems
16.1The page cache: file data cached in memory
16.2Memory-mapped files: file pages as memory
16.3Readahead: detecting sequential access
16.4Writeback: when dirty pages reach the disk
16.5fsync and the durability contract
16.6When fsync fails: how write errors are reported, and lost
16.7Direct I/O: bypassing the page cache
17.1On-disk layout: superblock, bitmaps, inode table and data
17.2File allocation methods: contiguous, linked, FAT and indexed
17.3Block maps and indirect blocks: how big a file can be
17.4Extents: describing contiguous runs
17.5Directory implementation: lists, hashed trees and a traced open
17.6Block groups and locality: the Fast File System's idea
18.1The crash consistency problem
18.2fsck: repairing after a crash
18.3Data journaling: write intent first, then checkpoint
18.4Journaling modes: data, ordered and writeback
18.5Application crash consistency: atomic file replacement
19.1Non-blocking I/O: calls that return instead of waiting
19.2select and poll: asking which descriptors are ready
19.3epoll: a persistent interest set and a ready list
19.4Event loops: the reactor pattern and where threads still win
20.1Load average: an exponentially damped count of runnable and uninterruptible tasks
20.2Sampling profilers: perf, hardware counters and flame graphs
20.3The USE method: utilisation, saturation and errors per resource
20.4Benchmarking the OS: what you actually measured
21.1Protection domains and the access matrix
21.2Access control lists versus capabilities
21.3setuid and the confused deputy
21.4Linux capabilities: splitting root
22.1Virtualisation requirements: Popek–Goldberg and x86's problem
22.2Hardware-assisted virtualisation: VT-x, AMD-V and KVM
22.3I/O virtualisation: emulation, virtio and SR-IOV
23.1Namespaces: per-process views of global resources
23.2Mount propagation: when one namespace's mount appears in another
23.3Control groups: a hierarchy for resource control
23.4CPU bandwidth control: quotas, periods and throttling
23.5Memory cgroups: limits, reclaim and OOM inside a container
23.6Container images and overlay file systems
24.1NFS: a stateless remote file system
25.1Case study: from power-on to the first process
25.2Case study: the life of a read()
25.3Case study: accepting and serving a connection
Skipped topics
Moved past without finishing. A skipped topic still closes its module, and Continue goes on without it — open one to read it or mark it understood.
Glossary
Terms explained by the authored lessons.
Life of a read() ledger
One read(2) crosses every module of the course: syscall entry → fd table → page cache → extent map → bio → NVMe SQE → DMA → MSI-X completion → wakeup → copy_to_user.
More detail
The cost ledger sums exactly: ~1.5 µs served from the page cache, ~100 µs on an NVMe miss, ~13 ms on a disk seek. Each hop names its home topic — miss handling is where the whole storage stack pays rent.
Accept-path syscalls
listen() queues completed handshakes, epoll reports readiness, accept4() hands back a descriptor — the serving path is a ledger of syscalls, copies and context switches.
More detail
sendfile removes the user-space copy; accept queues and epoll batching remove the idle wakeups. The per-request ledger is the whole event-loop module compressed into one request.
NFS statelessness
File handles + idempotent RPCs: the server keeps no per-client state, so a client just retries after a crash — statelessness trades consistency for recovery.
More detail
The cost lands on the client: attribute caches go stale inside their timeout window, and writes need a COMMIT (RPC 21) to be durable. v3’s 22-procedure protocol fixes failure recovery by making every request replayable.
Linux namespaces
Eight kinds of per-process resource views — mount, PID, network, IPC, UTS, user, cgroup, time — clone/unshare/setns make them.
More detail
A namespace is an indirection layer: the same kernel object gets a different name inside each view. PID namespaces nest so one process is pid 1 inside and ordinary outside; the init process doubles as the reaper for the whole namespace.
cgroup v2 subtree control
Resource control is a hierarchy: parent cgroups delegate controllers to children with cgroup.subtree_control, and a cgroup with processes cannot export domain controllers.
More detail
The no-internal-process rule forces a clean shape — leaves hold processes, interior nodes hold policy. v1’s parallel trees collapsed into v2’s single hierarchy because delegating the same resource along two trees had no sane semantics.
cpu.max bandwidth window
quota/period caps CPU time — a group that burns its quota is throttled for the rest of the period, and the tail latency lands in the throttle window.
More detail
cpu.stat counts throttled periods separately from running ones: 100ms quota in a 100ms period means a burst can stall up to a full window. Raising period smooths the tail at the price of coarser enforcement.
memory.max vs memory.high
Hard limit vs throttle: crossing memory.max OOM-kills inside the cgroup; crossing memory.high just slows reclaim pressure onto the group.
More detail
The ladder of thresholds (low → high → max) separates protection from pressure from enforcement — memory.low protects a group’s workingset from reclaim by others. The OOM-killer’s victim choice scores against the group’s limit, not the system’s.
OverlayFS upper/lower
A union mount stacks a writable upper over read-only lowers — container images are shared lowers, each container’s writes stay private.
More detail
Copy-up on first write is the hidden cost: a small edit to a big file pays a whole-file copy. Whiteouts encode deletes in the upper layer, and image pulls deduplicate layers across containers on the same host.
Popek–Goldberg conditions
A machine is virtualisable if every sensitive instruction is privileged — trap-and-emulate then simulates the guest exactly.
More detail
x86 circa-2000 failed the test (Robin & Irvine counted 17 sensitive-unprivileged instructions); binary translation and paravirtualisation were the workarounds before hardware support arrived.
Root/non-root modes and VMCS
VT-x adds a mode pair and a control structure: VM entry loads guest state, VM exit traps to the hypervisor on a configured reason.
More detail
KVM turns Linux into the hypervisor — the guest runs in non-root, exits route through the KVM loop to QEMU for device emulation. Exit cost × exit rate is the whole performance story.
Virtio split virtqueue
I/O virtualisation’s spectrum: full emulation (an exit per register touch) → virtio rings (batched descriptors, shared memory) → SR-IOV passthrough (the guest owns a real function).
More detail
Virtqueues put the ring in shared memory so a batch costs one notification, not one exit — measured exits/packet drop from ~1 to 0.06 in the worked batch model. vhost moves the virtio backend into the kernel; SR-IOV removes it entirely behind the IOMMU.
Access matrix
Subjects × objects × rights — the general model every permission system encodes, too sparse to store directly.
More detail
Unix mode bits are one cell-compression scheme; least privilege, complete mediation and fail-safe defaults are the principles the matrix makes precise.
ACL vs capability
Store the matrix by column (the object lists who may reach it) or by row (the subject carries unforgeable tokens) — delegation and revocation invert between them.
More detail
POSIX ACLs and file descriptors are the two encodings you already use; a capability delegates by passing the token, while an ACL delegates by editing the object. Revocation is free in one model and a hunt in the other; ambient authority is the ACL model’s blind spot.
setuid and the confused deputy
A program running with its owner’s rights acts with borrowed authority — the deputy that can be tricked into misusing it.
More detail
Real/effective/saved UIDs let passwd write /etc/shadow; dropping privilege correctly needs all three. The compiler-into-TMPDIR attack is the pattern: the deputy checks the caller’s authority but acts on its own.
Linux capabilities
Root split into ~40 independent powers — CAP_NET_BIND_SERVICE without CAP_SYS_ADMIN.
More detail
Permitted/effective/inheritable sets transform at exec by rules that decide what a setuid-root binary actually keeps. File capabilities attach the set to the binary, so a daemon can bind port 80 without owning the machine.
Sampling profilers
Statistical attribution: interrupt the CPU periodically, record the stack, and let frequency do the work.
More detail
Timer sampling is cheap but blind to its own period; PMU overflow sampling counts real events. Error bars are binomial — n = z²p(1−p)/e² samples for ±e at confidence z — and unwinding is the choice between frame pointers, DWARF and ORC.
USE method
Utilisation, saturation, errors — three questions asked of every resource before deeper digging.
More detail
Utilisation is time-busy; saturation is queued work (run queue depth, PSI stall); errors are the explicit counter. USE catches the burst hidden inside an average — the figure shows how a 50%-busy disk can carry a 100%-saturated window.
Coordinated omission
A benchmark that sends the next request only after the last returns measures a system that cannot fall behind — latency looks better than a real open-loop client ever sees.
More detail
The fix is open-loop arrival and service-time vs response-time separation; Tene’s QCon result is the canonical example. Every load generator that waits synchronously inherits the bias.
EAGAIN contract
A non-blocking call returns a verdict, not a wait — the caller must retry, and the loop’s cost is pure CPU.
More detail
O_NONBLOCK turns a blocking read into a question; busy-polling burns U = np/Δ of a core per descriptor. The thread-per-connection alternative fails at scale because a thread’s cost is a stack plus a scheduler slot, not zero.
fd_set and pollfd
select and poll both re-register the whole watch set every call — Θ(n_watched) per wakeup regardless of how many are ready.
More detail
fd_set is consumed by the call and capped at 1024; pollfd separates requested events from returned revents so the array survives. The cost is the scan, not the wait — it scales with what you watch, not what fires.
epoll interest set
A persistent kernel list + a ready list: registration happens once, wakeups enqueue readiness, epoll_wait pays O(n_ready).
More detail
Wait-queue callbacks on the file move a descriptor to the ready list — the kernel never scans. Edge-triggered requires drain-to-EAGAIN discipline (the pipe-hang example); EPOLLEXCLUSIVE splits wakeups to kill the thundering herd.
Reactor loop
Readiness notifications become a program structure: one loop, state machines, non-blocking handlers — any blocking call stalls every connection it serves.
More detail
A 5 ms blocking read delays all queued work behind it; libuv’s answer is a small threadpool (default 4) for genuinely blocking calls. One loop per core removes the shared-state question entirely.
Crash window
The span between partial write and crash — the file system’s correctness is defined by what can survive it.
More detail
One logical update fans out into several disk writes; any suffix of them may land. Append corrupts data, lose-a-block leaks it, and a torn write leaves the block half-old, half-new — the state enumeration is the argument.
fsck pass structure
Replay nothing, check everything: a slow offline pass that walks every inode, bitmap and directory to repair inconsistent pointers.
More detail
Reference counts get reconciled (nlink, block bitmaps, inode bitmaps) and unreachables get filed into lost+found. The cost is proportional to capacity, not damage — minutes on small disks, unbounded on modern ones, which is why journaling replaced it.
Journal transaction record
TxBegin, the changed blocks, TxEnd — three writes that make one logical update atomic on replay.
More detail
Commit ordering is the correctness: data/metadata to the journal, TxEnd last, then checkpoint to home locations. Replay idempotence means re-running an already-applied transaction is harmless.
Journaling modes
data=journal, ordered, writeback — three guarantees priced by how much goes through the journal.
More detail
Metadata-only journaling plus ordered data-before-commit is the ext4 default: it keeps old blocks unreachable after a crash without paying double writes for file contents. Writeback loses even that; full journal costs the most and protects the most.
Atomic rename pattern
write-temp + fsync + rename + fsync-dir — the only update protocol applications can trust across crashes.
More detail
Overwrite-in-place enumerates 2ⁿ−2 corrupt states; rename gives exactly two (old or new). The directory fsync after rename is the step everyone forgets — the name itself must reach the disk.
vsfs five-region layout
Superblock, bitmaps, inode table and data blocks — one repetitive shape replicated across groups.
More detail
The layout answers “where does inode 32’s bytes live” with pure arithmetic: table block + (ino − 1)/per-block to get the sector. xv6’s variant renames the regions but keeps the division of labour.
Direct and indirect blocks
An inode’s block map is a tiered index: a few direct pointers, then trees of pointer blocks.
More detail
Capacity is B(d+p+p²+p³) — one block address fanning out quadratically. I/O per offset climbs with tier: direct is one read, triple-indirect costs four. Sparse files leave holes as zero pages for free.
Extents
An (offset, start, length) record replaces the per-block map — one entry covers a whole run.
More detail
ext4 packs the extent tree root inside the inode and grows through extent nodes; uninitialized extents reserve space without leaking stale bytes. The cost shows up at fragmentation, where runs shrink and the index reverts toward per-block.
Directory as index
A directory file is a scan of variable-length entries; ext4’s htree turns the scan into a hash probe for large directories.
More detail
ext4_dir_entry_2 packs name→inode records; dx_root/dx_node hash filenames (siphash) into leaf blocks. The open path’s real cost is the I/O count per component, not the entry format.
Cylinder groups and SEER locality
FFS replicates metadata across disk cylinder groups so a directory’s files cluster near their inode.
More detail
McKusick’s FFS measured file-system locality: most files are small, directories are used together. The 48 KB/1 MB large-file rule caps one file’s group residency to preserve space for siblings — locality bought with a 10% capacity reserve.
ELF program vs section headers
Two tables on one file: sections describe contents for the linker, program headers describe what must exist in memory for the loader.
More detail
exec needs a map, not a table of contents — the program header carries loadable segments with permissions and p_vaddr ≡ p_offset (mod p_align) so each segment lands page-aligned. .bss exists only in headers: file bytes end, memory continues zeroed.
binfmt selection and teardown
exec copies arguments first, asks each format handler in turn, then tears down the old address space one-directionally.
More detail
Arguments land before a format is chosen so the new image can see them; once a handler commits, there is no old program to return to. The initial stack is the argument-passing ABI: argv, envp and the auxiliary vector.
PLT lazy binding
A stub calls through a GOT entry that points back into the resolver until the first call fills it.
More detail
The PLT defers relocation cost to first use — the table you filled is also a target, which is why RELRO/BIND_NOW exist. What “first definition wins” means: symbol interposition lets an earlier object override even the owner’s own references.
Breadth-first library search
ld.so resolves DT_NEEDED breadth-first and takes the first definition of each symbol it sees.
More detail
Load order becomes semantics: LD_PRELOAD works by inserting itself early in the search order, and a library’s own calls can be intercepted by a later-loaded sibling.
Page cache
File pages cached in memory under a per-inode (offset → folio) index — the XArray.
More detail
read() is a lookup then a copy; the cache deliberately occupies otherwise-idle memory and yields it under pressure. Expected read cost is a hit-rate question, and a cache can never help the first read.
MAP_SHARED vs MAP_PRIVATE
File pages mapped into the address space: shared writes publish to the cache, private writes copy-on-write a divergent page.
More detail
msync flushes a range but is not the durability contract. The edges are SIGBUS on accessing past a truncated end and the zeroed partial last page.
Readahead window
file_ra_state grows an async prefetch window as sequential evidence accumulates.
More detail
A flagged folio acts as the tripwire that triggers the next window; fadvise/madvise let the caller declare the pattern instead. The arithmetic: one large request replaces a page fault or read per page.
Dirty throttling
A control loop that slows writers once dirty pages pass a fraction of dirtyable memory.
More detail
Buffered writes are a promise the flusher threads keep; the throttle shapes how much dirty data can accumulate — the loss window between write() and media. The balance protects readers and devices, not your data.
fsync contract
write() succeeding means “in the page cache”; fsync/fdatasync is the syscall that moves the promise to media.
More detail
fdatasync skips metadata readers cannot see; a name lives in its directory, so durability of a create needs the directory fsynced too. The device cache sits below the filesystem — fsync flushes it, and an fsync can still lie about the past on misbehaving hardware.
O_DIRECT
Bypass the page cache entirely — one cache manages the bytes, the application takes the hit-rate question over.
More detail
Alignment is a contract (logical-block granularity, discoverable via STATX_DIOALIGN/BLKSSZGET); EINVAL or silent buffered fallback are the failure modes. Direct is not durable — fsync is still owed.
Inode
The metadata object — number, size, owner, timestamps, block locations — that describes a file; data blocks carry the bytes.
More detail
stat reports the inode, not the content. Names live in directories and point at inodes; the inode itself has no name.
Open-file description
The view object open() creates — status flags plus the file offset that read and write advance.
More detail
Two descriptors can share one description (after fork or dup), so their reads advance a common offset; a second open() gets an independent offset.
Directory
A file whose content is name→inode entries — the whole naming layer reduces to that mapping.
More detail
Resolution walks one lookup per component from / or the working directory; openat names the starting point explicitly. Each lookup is a scan of one directory file.
Hard link
A second directory entry for the same inode — the file persists until the last name is unlinked and the last open ends.
More detail
link() cannot name a directory or cross filesystems. The open-but-unlinked file is a real state: the inode outlives its names, which is how anonymous temporary files work.
Symbolic link
A small file containing a path, substituted into resolution wherever it appears.
More detail
A symlink can point across filesystems and at directories, but it can dangle — the target path may name nothing. Resolution bounds the substitution chain (ELOOP) to kill loops.
Credentials and the three-class check
A process carries uid/gids, not a name; each inode grants owner, group and other in parallel.
More detail
The check picks exactly one class — owner bits decide for the owner even when group would deny. Directories reuse the bits with different verbs: x is search, w is create/remove, r is listing.
umask and the sticky bit
New files get their mode through the process’s umask; the sticky bit on a directory restricts removal to the file’s owner.
More detail
umask subtracts bits at creat/open; the sticky bit is what makes shared /tmp safe. Together they are the permission model’s configuration surface.
Mount point and bind mount
A directory lends its name to another filesystem’s root; a bind mount grafts a subtree that already has a name.
More detail
The mount table records grafts so one tree covers many devices. Propagation types decide whether a new mount is visible in other namespaces — the detail that makes container mounts private.
VFS objects
Superblock, inode, dentry and file — one interface over many filesystems, each object carrying an operations table.
More detail
ext4, tmpfs, NFS and procfs all answer read through the same surface: the objects dispatch per-filesystem work through ops tables. Resolution stitches dentries; the objects buy a uniform API, not uniform performance.
dcache
The dentry cache — resolved name→dentry pairs that make the common path lookup cheap.
More detail
Without it every open() re-scans every directory on its path. Expected step cost is h·c_hit + (1−h)·c_miss, so hit rate — not lookup speed — decides what resolution costs.
Disk access time
Seek, rotation and transfer are three waits in series — the first two dominate small I/O.
More detail
Magnetic geometry decides where a byte lives; a random 4 KB read is hundreds of times slower per byte than sequential because positioning, not transfer, is the cost. Drive internals — remapping, zoned density — blur the clean numbers.
SSTF and the elevator
Shortest-seek-first inherits SJF’s starvation; SCAN/C-LOOK bound the wait by serving in position order.
More detail
The elevator algorithms trade a little positioning for a guarantee every request is reached. Positioning time is more than distance — rotation matters too, which is what SPTF weighs.
Flash translation layer
A log-structured map inside the device that turns page updates into out-of-place writes on erase blocks.
More detail
Erase blocks are big, program pages small, and a page cannot be rewritten in place — so the FTL writes elsewhere and remaps. A direct map fails immediately; hybrid mapping trades translation-table memory for copying.
Merge operations
Switch, partial and full merges are the FTL’s three ways to reclaim a block, priced by how much live data they copy.
More detail
A switch merge works only when the log block fully shadows its data block; a full merge copies every live page. The merge choice is where GC cost is actually decided.
bio and request
A bio describes I/O; a request performs it — merging happens between the two.
More detail
Adjacent bios coalesce into one request, which is where most of the block layer’s value sits. Requests then flow through per-CPU software queues mapped onto hardware queues (blk-mq) and are found at completion by tag, not search.
RAID levels by scorecard
Striping, mirroring and parity are points on one capacity–throughput–reliability trade.
More detail
RAID 0 spends nothing and survives nothing; RAID 1 halves capacity for a mirror; RAID 10 stripes mirrors and survives more second failures than RAID 01’s mirrored stripes. S/R/T scorecards make the comparison mechanical.
MMIO
Device registers aliased into the physical address space, driven by ordinary loads and stores.
More detail
The access crosses three namespaces — virtual, physical, bus — and the target is memory that is not memory: reads can have side effects and the region cannot be cached or reordered like DRAM. Registers concentrate authority, so they stay kernel-only.
Status, command and data registers
The three-register protocol every device interface reduces to.
More detail
The driver polls status, writes command, then moves data. Every richer interface — descriptor rings, doorbells — is this protocol scaled; count register touches, not bytes, when costing it.
Receive livelock
Interrupts arriving faster than they can be serviced starve all real work — throughput collapses at overload.
More detail
Mogul’s measurements: interrupts run at the highest priority, so each packet’s fixed IRQ cost is paid before any packet is processed. Beyond saturation the system is busy doing nothing.
NAPI hybrid
Take one interrupt, disable it, then poll the device under a budget until the queue drains.
More detail
Interrupts handle the idle end, polling handles the loaded end — the hybrid removes the saturation threshold rather than moving it. The poll budget bounds both latency and how long the CPU serves one device.
Interrupt coalescing
Batch several arrivals into one interrupt to amortise its fixed cost.
More detail
Coalescing stretches the overload threshold by dividing c_i by k, but adds latency per batch — it moves the cliff, the hybrid removes it.
Bus mastering
The device itself reads and writes memory — the CPU programs the transfer, then leaves.
More detail
Programmed I/O pays a register access per word; DMA pays a setup cost once per transfer. The saving is a whole copy the CPU never performs.
Descriptor and bus addresses
A shared ring of commands the device consumes, each naming a buffer by its bus address.
More detail
Buffers must be pinned and translated — a virtual address means nothing to hardware. The descriptor ring is the status/command/data protocol expressed in memory.
Class interfaces
Block, network and character contracts absorb most of driver variety behind three shapes.
More detail
Uniform discovery over non-uniform hardware: the kernel asks the class, the driver answers for its device. New interfaces get added where the three genuinely cannot express the device.
Driver fault share
Drivers are the kernel’s largest body of code and its least trusted — measured to hold the majority of its bugs.
More detail
Chou’s SOSP’01 study found driver error rates several times the rest of the kernel. The count motivates containment: IOMMU bounds what a driver bug can reach; user-space drivers trade performance for a crash boundary.
Pipe
A bounded kernel ring buffer reached through a read descriptor and a write descriptor.
More detail
Reads block while empty and writes block while full — the wait at each end is the flow control, and capacity sizes only the burst slack. Both ends are reference-counted: EOF and SIGPIPE are the signals those counts send.
PIPE_BUF atomicity
Writes of at most PIPE_BUF bytes to a pipe or FIFO are indivisible.
More detail
The bound is what makes a shared write end usable for records — concurrent small writes never interleave. Larger writes may split arbitrarily, so framing above the bound is the application’s problem.
FIFO
A pipe object attached to a filesystem name so unrelated processes can rendezvous.
More detail
mkfifo creates the name; open-for-read and open-for-write meet at the same kernel object a pipe() call would have made. The name persists; the buffer exists only while ends are open.
shm_open and MAP_SHARED
A named kernel object whose frames are mapped, unchanged, into each process’s address space.
More detail
shm_open names it, ftruncate sizes it, mmap with MAP_SHARED shares the pages — the transfer path is then ordinary loads and stores with no per-byte kernel copy.
Process-shared synchroniser
A mutex or futex that lives inside the shared region and works across address spaces.
More detail
PTHREAD_PROCESS_SHARED mutexes and futexes key on the physical word, so they name the same state in every mapping. A private-process synchroniser placed in shared memory coordinates nothing.
Robust mutex and EOWNERDEAD
A mutex that reports a holder’s death instead of leaving waiters blocked forever.
More detail
Acquisition returns EOWNERDEAD plus a duty to repair the protected state and call consistent(); skipping it poisons the lock permanently with ENOTRECOVERABLE.
POSIX message queue
A bounded kernel list of discrete, prioritised messages — framing pipes erase and shared memory never had.
More detail
mq_send/mq_receive move whole messages. The queue pays the same two copies as a pipe; what it buys is boundaries, priority order and a blocking discipline on a bounded capacity.
mq_receive selection rule
Returns the oldest message of the highest priority present — one whole message at a time.
More detail
Priority orders the queue; FIFO orders equal priorities. A reader that wants strict chronological order must send everything at one priority.
Socket
An endpoint object created by domain and type before any peer exists.
More detail
bind names the endpoint; listen and connect choose who initiates. accept returns a second socket drawn from a bounded queue — the listener and the connection are different objects.
Listen backlog
The bound on connections waiting for accept — capped by somaxconn.
More detail
A completed handshake sits in the queue until accepted; when the queue fills, new handshakes are refused or dropped. Backlog absorbs burst arrivals, not a sustained rate above the accept loop.
Send and receive buffers
Two bounded buffers per connection turn a rate mismatch into blocking, not loss.
More detail
send blocks when its buffer is full; recv blocks when its buffer is empty. Readiness APIs report the state of those objects, and errors are reported deferred at the next call.
Unix-domain naming
Three kinds of name for one object: filesystem pathname, unnamed pair, abstract socket.
More detail
Pathnames persist and need unlink; abstract names (Linux, a leading NUL byte) live in a separate namespace with no filesystem entry; socketpair needs no name at all.
SCM_RIGHTS
Ancillary data that passes open file descriptions, not descriptor numbers, over a Unix socket.
More detail
The kernel installs the received description as a new descriptor in the receiver — an integer copied as data would name nothing there. This is how a privileged daemon hands a client an opened resource.
Lock granularity
How much data one lock covers — from a whole structure down to a bucket or node.
More detail
Finer granularity buys parallel access at the price of more lock records, more acquisition order obligations and new deadlock edges. The serial fraction, not the lock count, sets the speedup ceiling.
Hand-over-hand locking
Acquire the next node’s lock before releasing the current one during traversal.
More detail
The coupling preserves reachability of the node being locked but serialises concurrent traversals over the same prefix — the throughput gain lives in the disjoint subtrees, not the shared head.
Approximate (sloppy) counter
Per-CPU local counts folded into a global count only when a threshold demands it.
More detail
Accuracy degrades to ±(S × P) for threshold S on P CPUs in the classic design. Reads pay a global lock; the trade is exactness for update contention.
Big kernel lock
The historical single lock serialising most of the Linux kernel.
More detail
Removed at 2.6.39 after a decades-long push to per-subsystem locking. Its lesson generalises: a global lock is a migration path, not an end state.
False sharing
Independent variables on one cache line forcing coherence transfers between cores.
More detail
Writes on different words of one line still invalidate each other’s copies. Padding to a line boundary or per-CPU allocation removes the ping-pong; the variables were never logically shared.
Per-CPU variable
A datum replicated per processor so each owner writes only its own copy.
More detail
Eliminates coherence traffic for that datum at the cost of an aggregate-read pass and T × footprint. Correct only when readers accept a merged or sampled value.
Condition variable
A named queue for sleeping until a predicate over shared state may hold.
More detail
Waiting atomically releases the associated lock; returning waiters reacquire it before proceeding. A condition is checked, never stored, in the variable itself.
Mesa semantics
A wakeup makes a waiter runnable; the predicate can be false again by the time it runs.
More detail
Signal and broadcast both require re-testing in a loop. Direct handoff can transfer ownership to a woken waiter but is a distinct, stronger discipline.
Lost wakeup
A sleeper blocks after the notification it needed has already been issued.
More detail
Sleep must be atomic with lock release, or a wakeup landing between predicate check and block is missed. Missed wakeups are a protocol failure, not a scheduling accident.
Semaphore
A nonnegative counter with atomic P (wait) and V (post) operations.
More detail
The invariant s = s₀ + n_V − n_P holds only if every caller honours the discipline. A semaphore admits no inspection of which thread released it or why.
Binary, counting and ordering semaphores
Exclusion, resource counting and event ordering are three different roles for one primitive.
More detail
A binary semaphore used for ordering (starting at 0) is not a lock; a counting semaphore used as a mutex is not an ordering device. Role confusion produces subtle deadlocks.
Turnstile and barrier
A turnstile forces each thread through a wait; a barrier composed of two turnstiles synchronises n threads.
More detail
The reusable barrier needs two phases: reusing one turnstile lets a fast thread lap a slow one and pass twice. Counting arrivals alone does not order departures.
Monitor
A language construct packaging a lock, shared state and condition queues behind an implicit entry protocol.
More detail
The implicit entry lock serialises method bodies but does not choose a handoff discipline. Java’s synchronized/wait is Mesa-style; signal-and-urgent-wait (Hoare) is not.
Hoare-style signalling
A signal immediately hands the entry lock to a signalled waiter, which resumes under its expected predicate.
More detail
The signaller then waits. J { c.wait } J ∧ B_c and J ∧ B_c { c.signal } J characterise the proof contract; Mesa’s weaker guarantee forces the while-loop idiom.
The four deadlock conditions
Mutual exclusion, hold-and-wait, no preemption and circular wait are jointly necessary for deadlock.
More detail
Each condition alone is insufficient; deadlock needs all four simultaneously. Denying any one of them prevents deadlock, which is why prevention proofs name the condition they remove.
Resource-allocation graph
A bipartite graph of request and assignment edges over threads and resources.
More detail
A cycle is necessary but not sufficient when resources have multiple instances; sufficiency additionally needs every resource in the cycle to have exactly one instance.
Dining philosophers
Five threads each need two shared forks — the canonical simultaneous-hold deadlock.
More detail
Symmetric pickup order is what makes the cycle reachable. Breaking symmetry (one reversed philosopher, a waiter, or global ordering) each removes a different condition.
Lock ordering
A global partial order over locks that every acquisition must respect.
More detail
A consistent order makes the lock graph a DAG, so circular wait cannot arise. Dynamic order discovery and hidden nested acquisitions are the practical failure modes.
Trylock and back-off
Attempt acquisition non-blockingly; on failure release held locks and retry.
More detail
It breaks hold-and-wait but adds livelock risk and forces unwinding of partial work. Bounded retries need a live progress argument, not just a probability claim.
lockdep
Linux runtime validator that watches lock acquisitions as a class graph.
More detail
It flags an order violation the first time an inverted edge appears — it proves correctness for observed behaviour only, not for unexecuted interleavings.
Safe state
A state from which some schedule lets every thread complete with its declared maximum.
More detail
An unsafe state is not yet a deadlock — it means the system can no longer promise completion. Avoidance keeps the system in safe states by pretending each grant.
Banker's algorithm
Pretend each resource grant, then check that some completion order still exists.
More detail
Each check runs an n × m simulated-completion search. The scheme needs declared maximums and a fixed resource pool — requirements real workloads rarely satisfy.
Wait-for graph
The resource graph collapsed to thread-to-thread edges u → v when u waits on a resource v holds.
More detail
A cycle in the wait-for graph is deadlock regardless of instance counts. Keeping it current costs an update on every wait edge, which is why detection runs periodically.
Victim selection and rollback
Choose a thread to abort or roll back so its resources unblock the cycle.
More detail
The victim’s cost and starvation bound matter: repeatedly picking the cheapest thread starves it. Rollback needs a checkpoint the application actually provides.
Atomicity and order violations
The two dominant non-deadlock bug classes in the Lu et al. study of real applications.
More detail
Atomicity violations (unprotected check-then-use) and order violations (missing happens-before) cover ~97% of the studied non-deadlock bugs — and almost all reproduce with two threads.
Race condition and data race
Schedule-dependent incorrect behavior is broader than a language-defined data race.
More detail
The lost-update simulator assumes atomic instruction steps and sequential consistency. Conflicting unsynchronized ordinary C++ accesses can instead produce undefined behavior; atomic accesses can still implement a higher-level race.
Critical section
A region whose protected invariant needs coordinated access.
More detail
A protocol must cover every conflicting participant and the entire invariant, not merely one write or one processor.
Mutual exclusion, progress and bounded waiting
Safety, eventual selection and bounded overtaking are different obligations.
More detail
Bounded waiting counts competing admissions, not elapsed time. Eventual scheduling and finite holds remain explicit assumptions.
Peterson protocol
Two participants combine interest flags with a tie-breaking concession.
More detail
The proof assumes indivisible sequentially consistent accesses. Ordinary language variables or buffered machine stores do not inherit that proof automatically.
Atomic read–modify–write
One indivisible read and value transition at a supported location.
More detail
Exchange, compare-and-swap and fetch-and-add provide different transitions. Atomicity does not imply fairness, bounded latency or a multiword transaction.
Compare-and-swap
Replace a value only if the observed value matches an expectation.
More detail
A failed update must recompute and revalidate from the new observation. Equality does not certify that the value never changed in between.
Load-reserved/store-conditional
A write attempt depends on a still-valid reservation.
More detail
RISC-V SC returns zero for success and nonzero for failure and consumes the reservation either way. Constrained-loop eventuality is not per-hart starvation freedom.
Sequential consistency
Observations fit one shared interleaving preserving each participant’s program order.
More detail
Per-location cache coherence and individually atomic accesses do not by themselves establish this cross-location model.
Store buffer and forwarding
Pending stores can await publication while local loads use the newest matching own store.
More detail
The restricted x86-TSO model uses FIFO publication and newest-own-write forwarding. A different-address load can proceed while an earlier store remains buffered.
Acquire/release ordering
Synchronization roles that carry earlier protected writes to a later observer.
More detail
An acquire that observes the relevant release can establish publication. The operation names alone do not impose a full fence or validate later unsynchronized mutations.
Spinlock
A contender remains executing while waiting for ownership.
More detail
Combine an atomic claim with ordering. True kernel spinning locks prohibit sleeping; local interrupt safety is separate from cross-CPU exclusion.
Ticket and MCS locks
Order contenders by ticket assignment or queued nodes.
More detail
Ticket waiters poll one serving counter; MCS waiters poll their own nodes. Both depend on scheduled participants completing their obligations; FIFO does not bound elapsed waiting.
Wait queue and wakeup
Record blocked interest and make waiters runnable when progress may be possible.
More detail
Waking is neither immediate execution nor necessarily ownership. Free-and-recheck and direct handoff are distinct policies.
Lost wakeup
A waiter sleeps after the notification it needed has already occurred.
More detail
Registration and scheduler-state publication must coordinate with wakeup. Park must not overwrite an early runnable transition with blocked state.
Futex
User-memory ownership decisions combined with kernel compare-and-block waiting.
More detail
Classic Linux futex words are aligned 32-bit values. WAIT/WAKE alone supplies neither owner tracking nor FIFO admission; the ordinary three-state model retains a conservative wake obligation.
Adaptive locking
Use a bounded spin and available evidence before taking a sleeping path.
More detail
A fixed budget equal to block/resume CPU cost is two-competitive only in the stated independent-wait model. Owner-running hints do not decide ownership or guarantee latency.
Priority inversion and inheritance
High-priority work depends on a lower-priority owner; donation lets urgency follow that dependency.
More detail
Inheritance propagates transitively and changes when dependencies disappear. It does not repair lock cycles, shorten I/O or supply a universal blocking-time bound.
Original priority-ceiling protocol
Lock admission must outrank ceilings of locks held by other tasks.
More detail
A ceiling is the highest priority of any potential user. Its single-critical-section blocking theorem has fixed-priority uniprocessor and resource-discipline assumptions; immediate-ceiling variants are not interchangeable.
Reader–writer lock
Compatible read ownership can overlap; writes require exclusive ownership.
More detail
Read-only refers to the protected invariant, not an operation’s name. Shared reader admission may still modify centralized metadata.
Reader preference, writer preference and FIFO batching
Admission policies trade bypass against class starvation.
More detail
Readers can indefinitely extend a reader phase; continuous writers can exclude readers. FIFO leading-reader batches prevent later-request bypass under finite-hold and eventual-scheduling assumptions.
Copy-on-write
Share unchanged private contents until a write requires divergence.
More detail
Both parent and child lose writable access to a shared frame. A COW fault copies for a shared owner or promotes an exclusive owner; immutable pages must not gain write permission.
COW marker and page reference count
Deferred write entitlement and ownership accounting serve different purposes.
More detail
The marker distinguishes private writable data from immutable code. The teaching reference count tracks mapping owners; production page references include other uses and pins.
Virtual memory area
A contiguous virtual interval with compatible permissions and backing policy.
More detail
A VMA can contain many absent pages. Splitting its policy record does not necessarily allocate or copy data frames.
mmap, mprotect and munmap
Create mappings, change access policy and remove mappings.
More detail
These operate at page-covered granularity. PROT_NONE retains a mapped reservation; a hole has no mapping. MAP_FIXED can overwrite existing mappings.
Maple tree
Linux range index optimized for non-overlapping intervals.
More detail
Linux v6.12 documents a B-tree with range iteration and optional gap searching. Its inclusive range endpoints differ from half-open VMA notation.
Buddy order
A block size expressed as a power-of-two count of base pages.
More detail
An order-k block contains 2^k pages. XOR of its aligned page offset with 2^k identifies a candidate buddy, whose state and allocation domain still require validation.
Buddy coalescing
Recombine free same-order siblings into their parent block.
More detail
Adjacent equally sized blocks need not be siblings. Total free capacity does not establish an eligible high-order contiguous extent.
Malloc chunk and bin
Allocator-managed extent and size-indexed collection of reusable chunks.
More detail
Chunk extent includes bookkeeping and slack. Bins locate a fit, while physical boundary information locates neighbors for coalescing.
Boundary tag and PREV_INUSE
Extent metadata permits locating adjacent chunks safely.
More detail
In glibc 2.40 PREV_INUSE describes the previous chunk. A previous-size field is meaningful under the matching free-state condition; it must not be read blindly.
Malloc mmap threshold
A policy input for choosing direct mappings when more backing is needed.
More detail
glibc 2.40 can adapt the initial threshold. Existing free space can satisfy larger requests, so a size above the initial default does not guarantee a syscall.
Demand paging
Supply page contents when an access first requires them.
More detail
Legal region, permitted operation and current residency are distinct. Populate successfully before publishing the mapping, then retry the faulting instruction.
Backing and swap space
Sources or preserved copies from which nonresident page contents can be supplied.
More detail
Backing may be file contents, zero-fill or saved private data. A swap location is not a physical frame number, and not every page fault requires a storage read.
Minor and major faults
Faults resolved without page-in I/O versus faults requiring I/O.
More detail
Minor does not mean free. Exact counters follow OS accounting, while a TLB miss can occur without either class of page fault.
Reference string
An ordered sequence of requested page identities.
More detail
Replacement comparisons hold the trace, frame count, initial state and cost model fixed. Multiprocess page identities must distinguish contexts or identify real shared backing.
MIN replacement
Evict the resident page whose next reference is furthest in the future.
More detail
Offline optimality minimizes unit-cost faults for equal pages and interchangeable frames. It does not automatically minimize dirty writes or elapsed time.
FIFO page replacement
Evict the oldest resident admission without refreshing order on hits.
More detail
Reloading a previously evicted page creates a new admission. More capacity changes the miss history and can change later queue order unfavorably.
Belady’s anomaly
A policy incurs more faults after receiving more frames on the same trace.
More detail
The canonical FIFO trace has nine faults at three frames and ten at four. This is not a claim that extra RAM generally hurts performance.
Stack property
Resident sets remain nested across capacities after every shared reference prefix.
More detail
With compatible initial states, every larger-cache miss is also a smaller-cache miss, giving nonincreasing fault counts. This stack is unrelated to a call stack.
LRU page replacement
Evict the resident page whose latest reference is oldest.
More detail
Exact recency gives the stack property, but not a guarantee of beating FIFO on every trace. Maintaining OS-visible order on every memory reference is costly.
Reuse distance
Distinct other pages referenced between consecutive uses of a page.
More detail
Under this convention a repeated page hits in k-frame exact LRU when distance is less than k. A first touch remains a cold miss.
Competitive paging bound
A worst-case comparison between online faults and offline optimal faults.
More detail
Sleator–Tarjan compares LRU with k frames to MIN with h ≤ k, including an initial-state allowance. The bound is not a latency prediction.
Clock and second chance
Scan a frame ring, clearing referenced bits and selecting an unreferenced candidate.
More detail
A hit sets its bit without moving the hand. The quiescent all-set case needs k+1 inspections; concurrent references and ineligible frames need additional progress controls.
Dirty page and writeback
Changed contents not yet preserved in backing, and the operation that preserves them.
More detail
Submission is not completion. Reuse requires successful preservation when needed, handling redirtying and safe retirement of old mappings.
Two-handed clock
Separate a clearing visit from a later eligibility check.
More detail
At constant shared scan speed v and separation d frame positions, the observation interval is d/v seconds. Variable movement changes that interval.
Working set
Distinct pages referenced during a stated recent window.
More detail
Denning uses process time; the executable lesson uses the last Δ references. The estimate is not resident size or guaranteed future demand.
Page-fault-frequency control
Adjust memory allocation using observed faults per explicitly named denominator.
More detail
A threshold band reduces oscillation. Compulsory faults, phase changes and unavailable spare frames limit what growth can accomplish.
Thrashing
Severe loss of useful progress through excessive paging or reclaim.
More detail
Full RAM alone is not evidence. Repeated displacement of active pages can leave CPUs idle while paging work and storage waits grow.
Pressure Stall Information
Linux elapsed-time accounting for resource stalls.
More detail
Memory some tracks partial stalls; full tracks simultaneous stalls of all non-idle tasks in scope. Overlapping task waits must not be added as elapsed pressure.
Load control
Limit admitted work so its active footprint can make progress.
More detail
Reducing concurrency can outperform increasing runnable work during memory pressure. Suspending a task alone does not release its physical pages.
Memory commitment
Accounting for memory the system may need to provide under its promise rules.
More detail
Commit charge differs from virtual size and resident usage. Linux strict admission includes configured allowance and applicable reserves.
Memory overcommit
Admission policy that can accept more potential demand than conservatively backed capacity.
More detail
Optimism helps sparse reservations but moves some failures later. Strict accounting does not remove local allocation constraints or guarantee absence of OOM.
OOM badness and adjustment
Versioned victim preference combining eligible-task memory usage and policy.
More detail
Linux v6.12 counts resident, swap and page-table pages plus an integer-scaled adjustment. Adjustment −1000 excludes a task before scoring.
Address space
A context-specific virtual view of memory and access permissions.
More detail
The same numeric VA can reach different physical bytes in different contexts. Intentional shared mappings do not imply that all memory is shared.
Base and bounds
Relocation and containment using a protected base and size.
More detail
The teaching model adds base to VA only after checking the entire access fits. A size bound is not an ending physical address.
Memory-management unit
Hardware responsible for address interpretation and protection.
More detail
Its interface depends on the architecture: historical base-and-bounds is not the contemporary Sv39 page-table mechanism.
Segment
A contiguous virtual region with its own relocation and permissions.
More detail
Independent placement avoids reserving inter-region gaps. Each physical segment remains contiguous in this model, creating variable-size allocation constraints.
Free list
Bookkeeping for intervals available for allocation.
More detail
An embedded list stores nodes in the free blocks themselves. Splitting must leave enough bytes for a usable node or absorb the remainder.
Splitting and coalescing
Carving a free interval and reuniting adjacent released intervals.
More detail
List adjacency alone is not address adjacency. Coalescing cannot cross live storage, and release returns headers and padding as well as payload.
Internal and external fragmentation
Unused allocated capacity versus separated free intervals that cannot satisfy a request.
More detail
Keep metadata overhead distinct from slack. A largest-hole ratio describes a snapshot, not the future fraction of unusable storage.
First, best and worst fit
Choose the first suitable, smallest suitable or largest free interval.
More detail
First fit depends on list order. None of the names establishes universal performance; request sizes, lifetimes and representation matter.
Page and frame
Equal-sized aligned virtual and physical allocation units.
More detail
Neighbouring virtual pages can map to nonadjacent frames. Fixed-size frame allocation does not remove heap fragmentation or guarantee large contiguous physical runs.
Page-table entry
A mapping record or internal table pointer in an architecture-defined format.
More detail
Frame number, permissions and state have distinct roles. Conceptual validity and residency need not be separate hardware bits.
Multi-level page table
A VPN-prefix tree that omits unused subtrees.
More detail
Metadata depends on distinct occupied prefixes, not only mapped-page count. Base-page Sv39 walks three nine-bit indices.
Canonical address
An address satisfying the implemented format’s upper-bit rules.
More detail
Sv39 requires bits 63–39 to equal bit 38. Canonical does not mean mapped, resident or user-accessible.
TLB
A cache of address translations and protection information.
More detail
A hit still checks permission. A miss can refill from an already resident mapping without a page fault or disk access.
ASID
A hardware tag distinguishing address spaces in translation caches.
More detail
It need not equal an OS PID. Recycling a finite tag requires retiring stale translations before the new identity uses it.
TLB reach
Nominal virtual coverage represented by cached translations.
More detail
Entries times page size applies to one uniform page size. Conflicts, replacement and competing contexts can reduce effective coverage.
Turnaround time
Completion time minus arrival time.
More detail
The scheduling mean usually weights each job equally. Completed-only statistics can hide unfinished or starving work.
Scheduling response time
First CPU execution time minus arrival time.
More detail
This is not application end-to-end latency. A tiny first turn can improve the metric without producing useful output.
SJF and STCF
Shortest job first is nonpreemptive; shortest time-to-completion first reconsiders remaining demand.
More detail
The batch exchange proof assumes common arrival, known demands and unweighted turnaround. Unknown lengths and interactive goals require different choices.
Quantum
Maximum useful CPU service offered in one turn.
More detail
Shorter turns offer more frequent opportunities but increase handoff overhead. Delay bounds also need bounded competition and interference.
MLFQ allotment
Cumulative CPU service at a priority before demotion.
More detail
An allotment can span several turns and I/O-separated bursts. Blocking must not erase it in the anti-gaming policy.
Priority boost
Periodic return to high priority with renewed allotment.
More detail
A wait bound additionally needs bounded population and turn costs, limited interference and queue progress preserved across boosts.
Lottery ticket
Relative entitlement in a random scheduling draw.
More detail
An owner with t of T runnable tickets wins with probability t/T. Expected proportional service is not a finite-window quota.
Ticket currency
Local entitlement units backed by an outside allocation.
More detail
A group subdivides its rights without inflating its outside share. Changing denominations alone does not change entitlement.
Stride and pass
Inverse-ticket virtual step and accumulated virtual progress.
More detail
Select minimum pass and advance by its stride. Static bounds require stated initialisation; new arrivals need a live reference.
Virtual runtime
Execution time normalised inversely by scheduling weight.
More detail
Larger weight advances virtual runtime more slowly for equal service. Historical CFS orders waiting entities by this progress.
Nice value
Input mapped to relative fair-scheduler weight.
More detail
Higher nice reduces weight. It is neither a fixed CPU percentage nor an absolute consumption cap.
EEVDF lag
Ideal service minus actual service.
More detail
Nonnegative lag establishes eligibility; earliest virtual deadline orders eligible requests. Lag is not time since wake-up.
Virtual deadline
Virtual endpoint of a service request.
More detail
Request-start virtual runtime plus requested service divided by normalised weight. Not a wall-clock completion guarantee.
Process
A program instance with execution state and kernel-managed resources.
More detail
Its record links memory, saved continuations, identity, scheduling state and open resources. Ready means eligible, not currently executing.
Context switch
Saving one execution continuation and restoring another.
More detail
A trap need not switch processes. xv6 swtch transfers a kernel continuation using ra, sp and saved registers; the user trap frame is separate.
Fork
Creating a child that resumes after the same call as its parent.
More detail
Success returns the child PID to the parent and zero to the child. Ordinary private memory starts equal but later writes are independent; inherited kernel resources may remain shared.
Exec
Replacing the program image without creating a new process identity.
More detail
Success does not return to the old image. Descriptor and credential inheritance have explicit exceptions; common early failures leave the old image available.
Zombie
A terminated child whose retained result has not yet been reaped.
More detail
It is not runnable. Orphanhood instead describes a child whose parent exited; an orphan may still be alive.
Open-file description
A kernel open instance shared by descriptor aliases.
More detail
It holds the offset and file status flags. Fork copies descriptor-table entries without creating independent open-instance offsets; separate opens normally do.
Close-on-exec
A per-descriptor flag requesting closure on successful image replacement.
More detail
It is not a shared open-file-description status flag. Equal-slot dup2 leaves descriptor flags unchanged, unlike duplication into a distinct slot.
Spawn file actions
Ordered open, close and duplication requests applied before child execution.
More detail
posix_spawn can express redirection. It is often simpler than arbitrary post-fork setup when the launch fits its supported actions and attributes.
Pending signal
A generated signal awaiting delivery.
More detail
Blocking defers delivery; disposition chooses the response. Repeated instances of the same pending standard signal coalesce, so notification count is not event count.
Thread
An execution with its own continuation and stack inside a shared process.
More detail
Threads share memory and the descriptor table. Stack ownership does not protect its bytes from other threads; kernel-scheduled user threads are distinct from kernel workers.
Kernel
The privileged core enforcing access to shared resources.
More detail
User-space libraries can request its services but cannot replace hardware-enforced isolation merely by exposing functions.
Privilege mode
Protected processor state determining which operations are permitted.
More detail
RISC-V U, S and M distinguish authority. Memory access permissions complement instruction and control-register checks.
Trap frame
Protected storage for execution state preserved by entry software.
More detail
RISC-V hardware records control information in CSRs; it does not automatically save every general register or select a kernel stack.
System-call ABI
The request and result convention across a privilege boundary.
More detail
It specifies entry, service number, arguments and return values. A source-language wrapper can translate the raw return convention.
Interrupt masking
Suppression of delivery for selected interrupt sources or classes.
More detail
Pending work may be delivered later. Local masking does not stop another core accessing shared memory.
Limited direct execution
Direct user instruction execution bounded by protected kernel transitions.
More detail
Privilege, safe trap paths and independent control recovery work together. A timer provides a mechanism, not a fairness policy.
Instruction set architecture
The contract defining instruction effects on visible machine state.
More detail
Distinguish this contract from a core implementation that overlaps or rearranges internal work.
Program counter
The address identifying the next instruction in the execution model.
More detail
Sequential advance depends on instruction length; branches and jumps select another address.
Register
Processor-local storage selected by instruction operands.
More detail
General-purpose registers hold values and addresses; the PC records control state. Register width does not establish installed memory capacity.
Byte address
An integer naming one eight-bit memory position.
More detail
A w-bit address can name 2^w byte positions. Implemented, mapped and accessible addresses can be a smaller set.
Stack frame
Storage reserved for one live function invocation.
More detail
It may contain saved registers, local data and stack arguments. Releasing it ends the lifetime without necessarily erasing its bytes.
Calling convention
The agreement for arguments, results, register preservation and stack layout.
More detail
Separately compiled functions must agree. The standard RISC-V integer ABI uses a downward-growing stack aligned to 16 bytes.
Caller-saved register
A register whose live value the caller must preserve across a call.
More detail
The callee may overwrite it. RISC-V ra is caller-saved, so a function making a nested call preserves its own live incoming return link.
Callee-saved register
A register the callee must restore if it changes it.
More detail
This moves preservation work to functions that use the register, rather than forcing every caller to save everything.
Relocatable object
Code and data packaged with information needed for later linking.
More detail
Sections, symbols and relocations let compilation finish before final addresses are known.
Relocation
A typed instruction to patch an output location after addresses are resolved.
More detail
Its type defines the reference point, addend, encoding, range and alignment requirements. Resolving a name alone is insufficient.
Cache line
An aligned block transferred and tracked by a cache.
More detail
The line can contain several objects or part of one. Tags identify its memory block and valid bits establish whether it can serve a hit.
Associativity
The number of candidate line slots in each cache set.
More detail
More ways can remove placement conflicts at fixed capacity, but add lookup hardware and replacement work.
Locality
Reuse of recently accessed or nearby addresses in an access sequence.
More detail
Temporal locality reuses data; spatial locality uses neighbours fetched in the same line. Allocating a small object does not by itself ensure either.
Cache coherence
Coordination of cached copies and writes to the same memory location.
More detail
Coherence does not make a multi-instruction update atomic or establish every ordering across distinct locations.
MESI
A teaching protocol with Modified, Exclusive, Shared and Invalid states.
More detail
Its stable states encode permission and dirty-data responsibility. Real protocols require transient states and may extend the model.
motherboard
That board is the motherboard.
Go to The computer in pictures: its parts, what sits inside what, and where a running program lives →bus
The parts talk to each other over buses.
Go to The computer in pictures: its parts, what sits inside what, and where a running program lives →boot loader
The boot loader is the small program the firmware runs to find the kernel on the disk and load it into RAM; on Linux PCs it is usually GRUB (GRand Unified Bootloader).
Go to The computer in pictures: its parts, what sits inside what, and where a running program lives →symmetric multiprocessing
From then on every core is equal; this design, one shared kernel and no master core, is called symmetric multiprocessing (SMP).
Go to The computer in pictures: its parts, what sits inside what, and where a running program lives →memory controller
The memory controller and DRAM.
Go to The layers of a computer: hardware, the operating system and applications, and where each sits →daemon
Your service, your editor, and also the programs that feel like "the system": the shell (the program that reads each command you type in a terminal, starts the program it names and waits for it to finish — bash or zsh, for example), systemd (the system daemon: on most Linux distributions the first process started, which starts the others), and sshd (the Secure Shell daemon, the remote-login server).
Go to The layers of a computer: hardware, the operating system and applications, and where each sits →interface
A layer is useful because it offers an interface: a set of operations and the promised effect of each, which the layer above relies on without knowing how the layer below keeps the promise.
Go to The layers of a computer: hardware, the operating system and applications, and where each sits →address
An address is the number of one byte.
Go to The processor's view of a program: instructions, registers and the program counter →little-endian
The bytes are stored lowest first (little-endian: the lowest byte sits at the lowest address — the Computer Architecture course's byte order and alignment builds it), so addi a1, a1, 3 — add immediate, the constant 3 sitting inside the instruction itself, just as ld and sd are load and store doubleword, RISC-V's name for 64 bits — is the 32-bit number 0x00358593 (a 0x prefix means hexadecimal, base sixteen).
Go to The processor's view of a program: instructions, registers and the program counter →cycle
A cycle is one tick of the processor's clock — the unit hardware costs are counted in.
Go to The processor's view of a program: instructions, registers and the program counter →link register
The function ends with ret, which jumps to whatever address is in ra. (call and ret are assembler shorthands. ret is jalr x0, 0(ra) — "jump and link register": jump to the address in ra, and write the link into x0, the register that always reads 0, which throws it away. call inner becomes jal ra, inner — jump and link — when inner is close enough, and otherwise a pair of instructions, auipc then jalr, that does th…
Go to The call stack: how a function call returns →call stack
A region of memory called the call stack, or just the stack, holds the frames one after another, and a register, the stack pointer (sp), holds the address of the newest frame's edge.
Go to The call stack: how a function call returns →stack pointer
A region of memory called the call stack, or just the stack, holds the frames one after another, and a register, the stack pointer (sp), holds the address of the newest frame's edge.
Go to The call stack: how a function call returns →leaf function
A function that calls no other function is a leaf function, and it can often skip the frame entirely.
Go to The call stack: how a function call returns →Application binary interface (ABI)
It is part of a larger contract, the application binary interface (ABI), which lets separately compiled machine code work together.
Go to The call stack: how a function call returns →frame pointer
A frame pointer is a register (s0 on RISC-V, rbp, the base pointer, on x86-64) that points into the current frame and links to the caller's, making the frames a chain.
Go to The call stack: how a function call returns →user stack
This is the user stack, the one this topic has traced.
Go to The call stack: how a function call returns →kernel stack
So the kernel gives every thread a second stack in memory only the kernel can touch, its kernel stack, and switches sp to it on every entry into the kernel.
Go to The call stack: how a function call returns →Cache hit
A hit means the line is already in the cache, so the access is fast.
Go to CPU caches: why locality decides speed →Cache miss
A miss means the line must be fetched from the next level down, and the core waits.
Go to CPU caches: why locality decides speed →Temporal locality
Temporal locality: data used recently is likely to be used again soon — a loop counter, the top of the stack, a hot object.
Go to CPU caches: why locality decides speed →Spatial locality
Spatial locality: data next to what was just used is likely to be used soon — the next element of an array, the next field of a struct.
Go to CPU caches: why locality decides speed →Locality of reference
Locality of reference is the tendency of a program, over a short stretch of time, to concentrate its memory accesses on a small set of addresses: the same ones again (temporal locality) and ones near those it just used (spatial locality).
Go to CPU caches: why locality decides speed →Average memory access time
To turn misses into time, take the average over the two outcomes: every access pays the hit time, and the misses additionally pay the penalty, so the average cost of an access is — the average memory access time, where is the miss rate, the fraction of accesses that miss. (The derivation as an expected value, and its extension across cache levels, is the Compute…
Go to CPU caches: why locality decides speed →tag
A tag on each slot records which line it currently holds, and the cache compares just its own set's tags to decide hit or miss — the fewer it must compare, the faster it is.
Go to CPU caches: why locality decides speed →direct-mapped cache
A cache with one way per set is a direct-mapped cache: each line has exactly one slot it can use.
Go to CPU caches: why locality decides speed →Compulsory miss
Misses come in three kinds, and each has a different fix (the full taxonomy is the Computer Architecture course's classifying cache misses): Kind of missWhy it happensWhat helps Compulsory (cold)First use of a line; no cache can have it yetPrefetching; touching less data CapacityThe data in use at once is bigger than the cacheWorking on smaller pieces at a time; smaller data ConflictLines that fit in total but com…
Go to CPU caches: why locality decides speed →Capacity miss
Misses come in three kinds, and each has a different fix (the full taxonomy is the Computer Architecture course's classifying cache misses): Kind of missWhy it happensWhat helps Compulsory (cold)First use of a line; no cache can have it yetPrefetching; touching less data CapacityThe data in use at once is bigger than the cacheWorking on smaller pieces at a time; smaller data ConflictLines that fit in total but com…
Go to CPU caches: why locality decides speed →Conflict miss
Misses come in three kinds, and each has a different fix (the full taxonomy is the Computer Architecture course's classifying cache misses): Kind of missWhy it happensWhat helps Compulsory (cold)First use of a line; no cache can have it yetPrefetching; touching less data CapacityThe data in use at once is bigger than the cacheWorking on smaller pieces at a time; smaller data ConflictLines that fit in total but com…
Go to CPU caches: why locality decides speed →Write-back cache
Most caches today are write-back: a store changes only the cached line and marks it dirty, and the new bytes are written to the next level later, when the line is evicted.
Go to CPU caches: why locality decides speed →Write-through cache
The alternative, a write-through cache, passes every store straight on to the next level. (Both policies and what happens on a store miss are the Computer Architecture course's cache writes and replacement.) Either way, "written to the cache" means only that the processor has it — not that the data is safe on disk.
Go to CPU caches: why locality decides speed →core
A core is one complete executor of instructions, with its own registers and program counter.
Go to Multicore processors: shared memory, private caches and cache coherence →hardware thread
A hardware thread is one set of registers and program counter that a core can run.
Go to Multicore processors: shared memory, private caches and cache coherence →simultaneous multithreading
With simultaneous multithreading (SMT; Intel calls it hyper-threading — built properly in the Computer Architecture course's simultaneous multithreading), one core runs two hardware threads at once, sharing the core's arithmetic units and caches between them.
Go to Multicore processors: shared memory, private caches and cache coherence →logical CPU
A logical CPU is what the operating system sees and schedules as a CPU: a core, or one hardware thread of a core.
Go to Multicore processors: shared memory, private caches and cache coherence →memory consistency
The rules for how writes to different locations become visible are a separate promise, called memory consistency, which memory ordering takes up.
Go to Multicore processors: shared memory, private caches and cache coherence →batch monitor
A batch monitor was a resident supervisor program that ran queued jobs one after another, each to completion, so the processor never waited for a human.
Go to How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux →Time-sharing
Time-sharing is running several users' programs on one processor by giving each a short burst of execution in turn, switching often enough that every user at a terminal sees a computer that responds to them as if it were theirs alone.
Go to How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux →computer utility
It was meant to be the software of a computer utility: a machine that many users reach remotely and that runs, in the paper's words, "continuously and reliably 7 days a week, 24 hours a day in a way similar to telephone or power systems".
Go to How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux →POSIX
POSIX, the Portable Operating System Interface, is the IEEE standard that specifies the Unix programming interface — process creation and control, signals, files and directories, pipes and the C library around them — so that one program's source code works on any system that conforms.
Go to How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux →xv6
So this course also reads a kernel small enough to follow end to end. xv6 is a small teaching operating system written at MIT in the summer of 2006 by Russ Cox, Frans Kaashoek and Robert Morris, modelled on the Sixth Edition of Bell Labs Unix and later ported to RISC-V for MIT's undergraduate operating-systems class.
Go to How operating systems got their shape: from batch monitors to time-sharing, Unix and Linux →User space
User space is everything that runs without that authority: your applications and the libraries linked into them, including the C library.
Go to The operating system's contract: abstraction, sharing and protection →Protection
Protection is the guarantee that ordinary programs cannot bypass the kernel or change the data it uses to keep its promises.
Go to The operating system's contract: abstraction, sharing and protection →mechanism
A mechanism is the part of a system that provides an ability, such as saving one execution and running another; a policy is the part that decides when and for whom that ability is used.
Go to The operating system's contract: abstraction, sharing and protection →policy
A mechanism is the part of a system that provides an ability, such as saving one execution and running another; a policy is the part that decides when and for whom that ability is used.
Go to The operating system's contract: abstraction, sharing and protection →user mode
In user mode the processor runs application code and refuses anything that would change the rules of the machine.
Go to CPU privilege modes: the one bit the OS depends on →supervisor mode
In supervisor mode it runs the kernel and allows those things.
Go to CPU privilege modes: the one bit the OS depends on →privileged instruction
A privileged instruction is one the processor runs only in a sufficiently privileged mode; tried in user mode, it is refused and control goes to the kernel instead, which is what happened to cli.
Go to CPU privilege modes: the one bit the OS depends on →Control and status register
On RISC-V, most of those controls live in control and status registers (CSRs): special registers, separate from the ordinary ones, each of which states the lowest mode allowed to read or write it.
Go to CPU privilege modes: the one bit the OS depends on →trap
A trap is a hardware transfer of control from the running code to a handler the kernel set up in advance, entering a more privileged mode and recording where execution was and why — whether the cause is a failing instruction, a deliberate request for service, or an outside event.
Go to Traps and exceptions: how the hardware hands control to the kernel →exception
An exception is a trap caused by the instruction that is running.
Go to Traps and exceptions: how the hardware hands control to the kernel →system call
A system call is a request from a program to the kernel to perform an operation the program lacks the authority to perform itself, made through a deliberate trap that enters the kernel only at a trusted entry point and returns a result.
Go to System calls: a controlled entry into the kernel →library wrapper
That function is a library wrapper: an ordinary function that puts the arguments in the right registers, executes the trapping instruction and translates the result.
Go to System calls: a controlled entry into the kernel →errno
The wrapper turned that into the C convention: return −1 and store 9 in errno, a variable (one per thread) where the C library leaves the error code of the last failed call.
Go to System calls: a controlled entry into the kernel →dispatch table
A dispatch table is an array of pointers to kernel functions, indexed by service number.
Go to System calls: a controlled entry into the kernel →Time-of-check-to-time-of-use (TOCTOU)
A time-of-check-to-time-of-use (TOCTOU) bug is exactly that: a value is checked, then read again for use, and has changed in between.
Go to System calls: a controlled entry into the kernel →Polling
Polling means repeatedly reading a device's status to check whether its work is done.
Go to Interrupts: events from outside the running program →interrupt request
An interrupt request is a signal from a device or timer asking the processor to stop what it is running and enter the kernel; the kernel then looks at the device to see what actually happened.
Go to Interrupts: events from outside the running program →interrupt
An interrupt is a trap caused by an event outside the running instruction stream, such as a device finishing work or a timer expiring.
Go to Interrupts: events from outside the running program →interrupt controller
An interrupt controller is the hardware between them: it collects requests from all the sources, remembers which are waiting, and sends each to a CPU according to its settings and priorities.
Go to Interrupts: events from outside the running program →Masking
Masking an interrupt means temporarily not delivering it — for example while the kernel is in the middle of trap entry.
Go to Interrupts: events from outside the running program →inter-processor interrupt
An inter-processor interrupt (IPI) is an interrupt one CPU sends to another when the kernel needs a different core to act — tell it to reschedule, run a function, or flush stale memory-mapping entries on other CPUs (a TLB shootdown, built much later).
Go to Interrupts: events from outside the running program →kernel structure
A kernel structure is the decision about which of those run in privileged mode, in one shared address space — and therefore which of them can take the whole machine down with one bug.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →Monolithic kernel
A monolithic kernel links every service into a single privileged program in one address space.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →Layered kernel
Dijkstra's THE multiprogramming system (Eindhoven, 1968) organised its internals as a layered kernel: a strict stack where layer i may call only layer i−1.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →Microkernel
A microkernel keeps privileged mode for the smallest set of mechanisms that cannot safely be delegated — typically address spaces, threads and scheduling, and inter-process communication — and runs file systems, drivers and the network stack as ordinary user-space server processes.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →server
A microkernel keeps privileged mode for the smallest set of mechanisms that cannot safely be delegated — typically address spaces, threads and scheduling, and inter-process communication — and runs file systems, drivers and the network stack as ordinary user-space server processes.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →Loadable kernel module
A loadable kernel module is code a running kernel accepts into itself: a driver or file system shipped separately and linked in on demand, after boot.
Go to Kernel structures: monolithic, layered, microkernel, hybrid and modules →process control block
A process control block, or PCB, is the kernel's record of one process, kept in kernel memory where the process cannot change it.
Go to The process: the kernel's record of a program in execution →Running (process state)
Running means it currently runs on that CPU, whether in user code or kernel code on its behalf.
Go to The process: the kernel's record of a program in execution →Ready (process state)
Ready means it could run but is not selected.
Go to The process: the kernel's record of a program in execution →Blocked (process state)
Blocked means some awaited condition must change before it can proceed.
Go to The process: the kernel's record of a program in execution →scheduler
The scheduler, the kernel code that decides which ready process gets a CPU next, selects a ready process.
Go to The process: the kernel's record of a program in execution →preempted
A running process may be preempted (taken off the CPU by the kernel, typically when the timer interrupt fires, although it could still use the CPU) back to ready, block awaiting work, or terminate.
Go to The process: the kernel's record of a program in execution →continuation
A continuation is the saved work needed to resume a call chain.
Go to Context switching: saving one process, loading another →file descriptor
A file descriptor is a small nonnegative integer that indexes the calling process's table of open references to kernel objects — files, pipes, sockets, devices.
Go to File descriptors: a process's handles on kernel objects →file offset
An open-file description is a kernel-managed open instance holding state such as current file offset and file status flags.
Go to File descriptors: a process's handles on kernel objects →process identifier
The process identifier (PID) is the number the kernel uses to name a process.
Go to fork: creating a process by cloning →process tree
Repeated creation forms a process tree.
Go to fork: creating a process by cloning →program image
Exec names a family of interfaces that replace the calling process's program image.
Go to exec: replacing a process's program →entry point
The old stack, heap and code mappings are replaced; control starts at the new image's entry point, the address of its first instruction.
Go to exec: replacing a process's program →argv
At the Linux interface, execve takes an executable path, argv and envp. argv is an array of pointers (addresses) to argument strings, ending with a null (zero) pointer so the reader knows where it stops; argv[0] conventionally names the invoked program. envp is an array in the same form of environment strings such as NAME=value, the settings a process inherits from its parent.
Go to exec: replacing a process's program →envp
At the Linux interface, execve takes an executable path, argv and envp. argv is an array of pointers (addresses) to argument strings, ending with a null (zero) pointer so the reader knows where it stops; argv[0] conventionally names the invoked program. envp is an array in the same form of environment strings such as NAME=value, the settings a process inherits from its parent.
Go to exec: replacing a process's program →Reaping
Reaping means collecting a child's termination information and releasing the retained process record.
Go to exit and wait: reaping processes, zombies and orphans →orphan
An orphan is a child whose parent has exited; the child may still be executing normally.
Go to exit and wait: reaping processes, zombies and orphans →subreaper
Linux reparents such children to the nearest eligible ancestor subreaper, a process that has asked to collect its descendants, or otherwise to init, the first user process the kernel starts, with PID 1. xv6 uses init.
Go to exit and wait: reaping processes, zombies and orphans →init
Linux reparents such children to the nearest eligible ancestor subreaper, a process that has asked to collect its descendants, or otherwise to init, the first user process the kernel starts, with PID 1. xv6 uses init.
Go to exit and wait: reaping processes, zombies and orphans →foreground command
Consider a foreground command with standard output redirected to a file: echo hello > out.txt (echo prints its arguments, and > tells the shell to send standard output to the named file).
Go to How a shell runs a command: why fork and exec are separate →file actions
posix_spawn, the POSIX interface that creates a process and starts a program in one call, can combine process creation and program start with ordered file actions: requests to open, close and duplicate descriptors in the child before the new image starts.
Go to How a shell runs a command: why fork and exec are separate →Thread-local storage
Thread-local storage gives separately addressed values for selected variables; it is a naming mechanism, not an isolation boundary.
Go to Threads: several threads sharing one process →thread control block
A thread control block, or TCB, is the conceptual record of one thread's registers, stack, identity and scheduling state.
Go to Threads: several threads sharing one process →virtual reservation
A virtual reservation is a range of addresses promised to a process, which the kernel backs with physical memory only when the process first touches it; address spaces and demand paging explain how.
Go to Threads: several threads sharing one process →Resident memory
Resident memory is the portion currently held in physical memory.
Go to Threads: several threads sharing one process →Terminal driver
It first passes through the terminal driver, a part of the kernel that keeps a line of typed text, handles backspace, and, while a mode called ISIG (the terminal's signal-generating mode, on by default) is set, treats a few byte values as commands to the kernel rather than as text.
Go to Signals: asynchronous notifications to a process →signal
A signal is a small, named notification that the kernel records against a process (or one of its threads) and later acts on at a moment the process did not choose: by running a default action such as terminating it, by discarding the notification, or by interrupting it to run a handler function it installed.
Go to Signals: asynchronous notifications to a process →Signal generation
Generation records that an event has produced a signal for a target.
Go to Signals: asynchronous notifications to a process →Signal delivery
Delivery performs the selected response.
Go to Signals: asynchronous notifications to a process →Signal disposition
A signal's disposition selects its response: use the default action, ignore it, or catch it with a handler function, a function in the program that the kernel calls when the signal is delivered.
Go to Signals: asynchronous notifications to a process →signal mask
A signal mask is the set of signals whose delivery is blocked for a thread.
Go to Signals: asynchronous notifications to a process →Signal frame
For a caught signal, the kernel removes the pending flag, saves the interrupted user context in a signal frame (a block of saved registers and the signal mask, on the process's own stack unless it asked for another), arranges the handler's arguments and the user program counter, and returns into the handler.
Go to Signals: asynchronous notifications to a process →coalesce
For ordinary standard signals, repeated generations while the same signal remains pending coalesce, meaning they combine into one pending instance.
Go to Signals: asynchronous notifications to a process →process group
A process group is a set of processes that share a process group ID (PGID), so that a signal can be sent to all of them at once.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →job
A shell creates one process group for each command or pipeline it runs, which is why a process group is also called a job.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →process group leader
The process whose PID equals its group's PGID is the process group leader; for a shell command that is the first process the shell started for it.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →session
A session is a set of process groups, typically everything started from one login or one terminal window.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →session leader
A process creates a new session with setsid() (set session ID): it becomes the session leader, its session ID is its own PID, and it is alone in a new process group of its own.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →pseudo-terminal
Today it is almost always a pseudo-terminal: a pair of connected devices, one end driven by a terminal emulator (the program that draws a terminal window) or by sshd (the SSH daemon, the server that accepts secure-shell remote logins), the other end behaving exactly like a classical terminal for the shell and its jobs.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →controlling terminal
A session's controlling terminal is the terminal it is attached to; it is acquired when the session leader first opens a terminal, and a terminal controls at most one session.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →foreground process group
At any moment exactly one process group of that session is the terminal's foreground process group: the one that may read from the terminal and that receives the signals the terminal's keys generate.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →Job control
Job control is the shell's ability to stop a job, resume it, and move it between foreground and background, built entirely from the pieces above.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →Orphaned process group
If a process's exit leaves a group orphaned — no member any longer has a parent in the same session outside the group, so no shell remains that could resume it — and some member of that group is stopped, the kernel sends every member SIGHUP followed by SIGCONT.
Go to Sessions, process groups and job control: which processes Ctrl-C reaches →Job (scheduling)
In these scheduling topics a job is one schedulable unit of CPU work in a teaching workload.
Go to Scheduling metrics: turnaround, response and fairness pull apart →CPU burst
A CPU burst is an interval of CPU work before the job blocks (stops to wait for something such as a disk read, the blocked state) or finishes.
Go to Scheduling metrics: turnaround, response and fairness pull apart →preemption
The scheduler may pause a job and resume it later; that is preemption.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Long-term scheduler
The long-term scheduler, or job scheduler, decides which submitted work becomes a process at all — it sets the degree of multiprogramming, the count of processes sharing memory.
Go to Scheduling metrics: turnaround, response and fairness pull apart →degree of multiprogramming
The long-term scheduler, or job scheduler, decides which submitted work becomes a process at all — it sets the degree of multiprogramming, the count of processes sharing memory.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Short-term scheduler
The short-term scheduler, also called the CPU scheduler or dispatcher, is the one this module studies: at every tick or wake-up (a blocked task becoming ready again) it picks which ready process runs on a core next, a decision that lasts milliseconds at most.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Medium-term scheduler
Between the two sits the medium-term scheduler, whose decision lasts seconds to minutes: which processes stay in memory and which are set aside.
Go to Scheduling metrics: turnaround, response and fairness pull apart →response time
Its response time, in the scheduling sense, is the time from its arrival to the first moment it is given the CPU.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Throughput
Throughput is completions per unit of wall-clock time (elapsed real time) in a specified interval.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Utilisation
Utilisation is the fraction of that interval during which the CPU is busy under a specified accounting convention.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Fairness
Fairness compares actual CPU service with a declared entitlement while jobs are runnable.
Go to Scheduling metrics: turnaround, response and fairness pull apart →Starvation
Starvation means an eligible job can wait indefinitely while other work keeps receiving service.
Go to Scheduling metrics: turnaround, response and fairness pull apart →First In, First Out (FIFO)
First In, First Out (FIFO), also called First Come, First Served, selects the earliest-arriving ready job and runs its CPU burst without preemption.
Go to FIFO, SJF and STCF: minimising turnaround, and the convoy effect →convoy effect
A convoy effect occurs when many small consumers wait behind a large resource consumer.
Go to FIFO, SJF and STCF: minimising turnaround, and the convoy effect →Shortest Job First (SJF)
Shortest Job First (SJF) instead chooses the smallest known CPU demand among ready jobs whenever the CPU becomes available, then runs that job nonpreemptively.
Go to FIFO, SJF and STCF: minimising turnaround, and the convoy effect →oracle
This lesson gives SJF an oracle: an ideal source that already knows each job's CPU demand, so we can study the policy without pretending a real scheduler can see the future.
Go to FIFO, SJF and STCF: minimising turnaround, and the convoy effect →Shortest Time-to-Completion First (STCF)
Shortest Time-to-Completion First (STCF), also called shortest remaining time first, chooses the ready job with the least remaining CPU demand and can preempt on a new arrival.
Go to FIFO, SJF and STCF: minimising turnaround, and the convoy effect →Round robin
Round robin (RR) maintains a first-in, first-out ready queue.
Go to Round robin: response time, and the quantum trade-off →Multi-level feedback queue (MLFQ)
A multi-level feedback queue (MLFQ) is a scheduler with several ready queues of different priority.
Go to The multi-level feedback queue: scheduling without knowing the future →allotment
An allotment is the total CPU service a job may consume at one priority before demotion.
Go to The multi-level feedback queue: scheduling without knowing the future →wake-up
A wake-up means that a blocked job becomes ready again.
Go to The multi-level feedback queue: scheduling without knowing the future →Proportional-share scheduling
Proportional-share scheduling gives each competing job a share of the CPU in proportion to a weight assigned to it, relative to the weights of the other jobs that are currently runnable.
Go to Lottery scheduling: proportional share by random draw →ticket
Lottery scheduling, introduced by Carl Waldspurger and William Weihl of MIT at the first OSDI conference in 1994 (OSDI, the USENIX Symposium on Operating Systems Design and Implementation), who prototyped it inside a research operating-system kernel, expresses weights as tickets.
Go to Lottery scheduling: proportional share by random draw →Ticket transfer
Ticket transfer temporarily lends a client's tickets to work it depends on.
Go to Lottery scheduling: proportional share by random draw →Ticket inflation
Ticket inflation means issuing additional rights.
Go to Lottery scheduling: proportional share by random draw →stride
Its stride is , measured in an artificial virtual-time unit per allocation.
Go to Stride scheduling: deterministic proportional share →pass
Its pass tracks accumulated virtual progress.
Go to Stride scheduling: deterministic proportional share →Completely Fair Scheduler (CFS)
The Completely Fair Scheduler (CFS) discussed here is Linux's fair-scheduling design as it stood in Linux 6.5, its last version before EEVDF.
Go to The Completely Fair Scheduler: virtual runtime in a red-black tree →scheduling entity
A scheduling entity is whatever competes for the CPU; in this lesson it is one task (a group of tasks can also be one entity, as noted above).
Go to The Completely Fair Scheduler: virtual runtime in a red-black tree →min_vruntime
The queue also keeps min_vruntime, a monotonic reference (one that never decreases) following the low end of virtual progress.
Go to The Completely Fair Scheduler: virtual runtime in a red-black tree →target latency
A target latency is a desired service period, not a hard response deadline.
Go to The Completely Fair Scheduler: virtual runtime in a red-black tree →minimum granularity
The minimum granularity is the shortest slice the scheduler aims to give before switching.
Go to The Completely Fair Scheduler: virtual runtime in a red-black tree →Earliest Eligible Virtual Deadline First (EEVDF)
Earliest Eligible Virtual Deadline First (EEVDF) is the proportional-share policy that, among tasks that are not ahead of their fair share of service, runs the one whose current request has the earliest virtual deadline.
Go to EEVDF: eligibility and virtual deadlines →lag
A lag is ideal service minus actual service: positive lag means service is owed; negative lag means the task has already received more than its current ideal allocation.
Go to EEVDF: eligibility and virtual deadlines →eligible
Here eligible means “owed enough service to be considered”; it does not mean a runnable task is guaranteed an immediate turn.
Go to EEVDF: eligibility and virtual deadlines →service request
A service request is a chosen positive amount of real CPU time for the next slice.
Go to EEVDF: eligibility and virtual deadlines →Augmented tree
An augmented tree is a search tree in which every node also stores a summary of the subtree below it, recomputed on every insertion, removal and rotation, so that a search can decide from the summary alone whether a whole subtree is worth entering.
Go to EEVDF in the kernel: the augmented tree, placement and delayed dequeue →virtual lag
When a task leaves the queue the kernel records its virtual lag, , a duration of virtual time (the real lag is ), clamped to a limit of twice its request or one scheduler tick, whichever is longer, converted to virtual time.
Go to EEVDF in the kernel: the augmented tree, placement and delayed dequeue →Delayed dequeue
Delayed dequeue (DELAY_DEQUEUE, Linux 6.12) keeps a task that blocks while its lag is negative on the run queue, marked as delayed, so that it keeps competing on paper, without ever running, until its lag has burned off.
Go to EEVDF in the kernel: the augmented tree, placement and delayed dequeue →virtual address
A virtual address (VA) is an address a program uses, interpreted through the mappings the kernel has installed for the process that is running; the kernel keeps those mappings in a page table, which paging builds.
Go to The address space: a private view of memory →physical address
A physical address (PA) names a location in the machine's physical address map, which can include devices as well as RAM; this lesson uses RAM only.
Go to The address space: a private view of memory →mapping
A mapping ties a virtual address, or a range of them, to the place where its bytes really live, and says which operations are allowed there.
Go to The address space: a private view of memory →heap
Heap — the heap is memory the program asks for while it runs, such as the blocks malloc hands out, and keeps until it gives each block back, in any order.
Go to The address space: a private view of memory →Dynamic relocation (base and bounds)
Base and bounds, also called dynamic relocation, translates every address of the running process by adding one protected base, after checking it against one protected bound.
Go to Base and bounds: the simplest translation →Memory-management unit (MMU)
The memory-management unit (MMU) is the part of the processor that turns virtual addresses into physical ones and checks that each access is allowed.
Go to Base and bounds: the simplest translation →Segmentation
Segmentation translates each address using the segment it falls in, instead of one base and bound for the whole process.
Go to Segmentation: a base and bound per region →External fragmentation
External fragmentation is free space that exists between allocated regions but is divided into intervals too small to satisfy a requested contiguous allocation, even when the free total would suffice.
Go to Segmentation: a base and bound per region →Compaction
Compaction fixes this by moving live segments together so their holes join, updating each moved segment's base.
Go to Segmentation: a base and bound per region →allocator
An allocator is the code that manages a region of memory: it hands out blocks that do not overlap and takes them back when they are released.
Go to Free-space management: free lists, fit policies and fragmentation →embedded free list
When those nodes are stored inside the holes themselves, it is an embedded free list.
Go to Free-space management: free lists, fit policies and fragmentation →Splitting
Splitting cuts the requested block off the front of a hole and leaves the rest of the hole free.
Go to Free-space management: free lists, fit policies and fragmentation →Coalescing
Coalescing is the reverse: when two free holes sit directly next to each other in memory — one ends exactly where the other begins — the allocator merges them into one larger hole.
Go to Free-space management: free lists, fit policies and fragmentation →Header (allocator)
The allocator's notes about a block are called its header.
Go to Free-space management: free lists, fit policies and fragmentation →Alignment
Slack — Alignment means every address handed out is a multiple of some boundary, here 8, because hardware reads aligned data faster or only reads it aligned (byte order and alignment explains why).
Go to Free-space management: free lists, fit policies and fragmentation →First fit
First fit takes the first hole in the list that is big enough; our list is ordered by address.
Go to Free-space management: free lists, fit policies and fragmentation →Best fit
Best fit takes the smallest hole that is big enough, so the least space is left over.
Go to Free-space management: free lists, fit policies and fragmentation →Worst fit
Worst fit takes the largest hole, hoping the big leftover stays useful.
Go to Free-space management: free lists, fit policies and fragmentation →Internal fragmentation
Internal fragmentation is space inside a handed-out block that the program did not ask for and cannot use, such as alignment slack and leftovers too small to become holes.
Go to Free-space management: free lists, fit policies and fragmentation →page
A page is one such chunk of virtual addresses, starting at a multiple of its size.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Frame (page frame)
A frame is a chunk of RAM of the same size, also starting at a multiple of it. (This frame has nothing to do with a stack frame; the course uses the word for both.)
Go to Paging: fixed-size pages remove the contiguous-image requirement →Paging
Paging is translation in fixed-size units: each virtual page is mapped independently to a physical frame, so neighbouring virtual pages need not occupy neighbouring physical frames.
Go to Paging: fixed-size pages remove the contiguous-image requirement →page table
To remember which frame each page went to, every address space has a page table.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Page-table entry (PTE)
Each page-table entry (PTE) describes one page's mapping: its frame and what may be done with it.
Go to Paging: fixed-size pages remove the contiguous-image requirement →linear page table
The simplest shape is a linear page table: one array, with entry number n describing page number n.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Virtual page number (VPN)
Dividing an address VA by P splits it in two: the virtual page number (VPN), which page it is in, and the offset o, how far into that page.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Physical frame number (PFN)
The physical frame number (PFN) names a frame; times P, it is the frame's first byte.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Referenced bit
Referenced and dirty — a referenced (or accessed) bit that the hardware sets when the page is used, and a dirty bit it sets when the page is written.
Go to Paging: fixed-size pages remove the contiguous-image requirement →dirty bit
Referenced and dirty — a referenced (or accessed) bit that the hardware sets when the page is used, and a dirty bit it sets when the page is written.
Go to Paging: fixed-size pages remove the contiguous-image requirement →Leaf entry (page table)
A multi-level page table is a tree whose internal entries point to further table pages and whose leaf entries describe mapped data pages, indexed by successive groups of bits of the virtual page number.
Go to Multi-level page tables: materialise only what is used →page directory
A page directory is one of the inner tables.
Go to Multi-level page tables: materialise only what is used →Translation lookaside buffer (TLB)
A translation lookaside buffer (TLB) is a cache of recently used address translations, with their protection information, consulted on every memory access before the page table.
Go to The TLB: caching translations, hits and misses, and the cost of a walk →TLB miss
On a TLB miss the answer is not in the cache, and someone has to look it up.
Go to The TLB: caching translations, hits and misses, and the cost of a walk →Flush (TLB)
That flush keeps things correct, but it throws away useful work too.
Go to Address-space identifiers: keeping translations across a switch →Address-space identifier (ASID)
An address-space identifier (ASID) is a hardware tag stored with each cached translation, saying which address space it belongs to.
Go to Address-space identifiers: keeping translations across a switch →present bit
A present bit in the entry is the simplest way to picture the third question: set means a usable mapping to a frame, clear means not in RAM.
Go to Demand paging: page faults and a disk-backed address space →Anonymous memory
Anonymous memory is memory not backed by any ordinary file, such as a program's heap and stack.
Go to Demand paging: page faults and a disk-backed address space →Swap space
Swap space is that somewhere: a reserved area on a device, or a file, managed by the OS.
Go to Demand paging: page faults and a disk-backed address space →Backing store
A page's backing store is the source its contents can be obtained from while it is not in RAM: a file, zeros, or a swap slot.
Go to Demand paging: page faults and a disk-backed address space →page fault
A page fault is the exception the hardware raises when an access cannot be completed through the current page-table entry: the page is absent, or the entry forbids the operation.
Go to Demand paging: page faults and a disk-backed address space →minor fault
A minor fault is resolved without reading the page in from storage: zero-filling a new page, or mapping contents already in RAM.
Go to Demand paging: page faults and a disk-backed address space →major fault
A major fault needs that read.
Go to Demand paging: page faults and a disk-backed address space →Offline algorithm
An offline algorithm may look at the entire future string before it chooses.
Go to Optimal page replacement: Belady's bound →Online algorithm
An online algorithm sees only the requests already made.
Go to Optimal page replacement: Belady's bound →MIN (optimal replacement)
MIN, also called optimal, furthest-in-future or Belady's replacement, evicts on each full-cache miss the resident page whose next request occurs latest in the reference string.
Go to Optimal page replacement: Belady's bound →FIFO replacement
First-in, first-out replacement keeps resident pages in a queue ordered by when each was last admitted to memory.
Go to FIFO replacement and Belady's anomaly →Belady's anomaly
Belady's anomaly is an increase in a replacement policy's fault count on an unchanged reference string when it is given more frames.
Go to FIFO replacement and Belady's anomaly →LRU replacement
Least-recently-used replacement evicts, on a full-cache miss, the resident page whose most recent reference lies furthest in the past.
Go to LRU replacement: recency, its guarantee and its cost →competitive bound
A competitive bound limits an online policy's (one that sees only the requests so far) cost on every input in terms of the best offline policy's cost on that input, plus a fixed allowance.
Go to LRU replacement: recency, its guarantee and its cost →resource augmentation
Giving LRU more frames than MIN is called resource augmentation.
Go to LRU replacement: recency, its guarantee and its cost →miss-rate curve
A miss-rate curve maps frame count to the fraction of references that fault, for a fixed workload.
Go to LRU replacement: recency, its guarantee and its cost →second chance
The page has been given a second chance.
Go to The clock algorithm: approximating LRU with a referenced bit →Clock replacement
Clock replacement, also called second-chance replacement, keeps frames in a circular list with a moving hand; on each needed eviction the hand clears set referenced bits as it passes and evicts the first page whose bit is already clear.
Go to The clock algorithm: approximating LRU with a referenced bit →dirty page
A dirty page holds changes not yet saved to its backing store, so the OS must write those bytes out before reusing its frame.
Go to The clock algorithm: approximating LRU with a referenced bit →clean page
A clean page can be rebuilt from its backing store, so it can be dropped without writing any data.
Go to The clock algorithm: approximating LRU with a referenced bit →resident set
A process's resident set is the collection of its pages currently in RAM, the pages RSS counts.
Go to The working-set model: how much memory a process needs →process time
His model looks at the pages used over a recent stretch of process time: time during which that process is actually running, not counting time it is suspended.
Go to The working-set model: how much memory a process needs →Reclaim
Reclaim means recovering physical memory for reuse: dropping contents that can be rebuilt, or saving changed contents elsewhere first.
Go to Thrashing: when paging replaces computing →Global replacement
Global replacement picks victims from one shared pool, so one process growing can evict another's useful page.
Go to Thrashing: when paging replaces computing →Local replacement
Local replacement keeps each process within its own allocation.
Go to Thrashing: when paging replaces computing →Pressure Stall Information (PSI)
Linux's Pressure Stall Information (PSI) reports time lost to waiting on a resource.
Go to Thrashing: when paging replaces computing →Some pressure (PSI)
some: the share of elapsed time when at least one task was stalled on memory.
Go to Thrashing: when paging replaces computing →Full pressure (PSI)
full: the share of elapsed time when every non-idle task was stalled at once.
Go to Thrashing: when paging replaces computing →swap area
A swap area is a disk partition or a regular file that the kernel uses as backing store for swapped-out pages.
Go to Swap in practice: swap devices, compressed memory, and whether a server should swap →zswap
zswap, merged in Linux 3.11 (2013), is a compressed cache in front of an ordinary swap device.
Go to Swap in practice: swap devices, compressed memory, and whether a server should swap →zram
zram is a driver (kernel code that operates a device; zram's device is simulated in RAM) that left the kernel's staging area (the part of the source tree for code not yet judged ready) and was declared stable in Linux 3.14 (2014).
Go to Swap in practice: swap devices, compressed memory, and whether a server should swap →swap storm
A swap storm is that loop on a real machine: pages stream out to swap and back in continuously, and useful work collapses.
Go to Swap in practice: swap devices, compressed memory, and whether a server should swap →Virtual size
Virtual size counts the address-space regions a process has reserved, including parts not in RAM.
Go to Memory overcommit and the OOM killer →Commit charge
Commit charge is the kernel's running total of the memory it has promised.
Go to Memory overcommit and the OOM killer →Overcommit
Overcommit is accepting memory commitments whose total may exceed the RAM and swap that could back them if every promise were used at once.
Go to Memory overcommit and the OOM killer →Out of memory (OOM)
An out-of-memory (OOM) condition occurs when an allocation cannot be satisfied within the memory it is allowed to use, even after the kernel's recovery attempts.
Go to Memory overcommit and the OOM killer →OOM killer
The OOM killer then selects eligible work to terminate so that memory can be recovered.
Go to Memory overcommit and the OOM killer →oom_score_adj
It then adds a scaled oom_score_adj, a per-process preference written to /proc/<pid>/oom_score_adj, in the range −1000 to +1000.
Go to Memory overcommit and the OOM killer →page offset
A 4 KiB page is bytes, so the low 12 bits are the page offset.
Go to Paging numericals: address fields, frame bits, effective access time and replacement grids →physical page number
Given a physical memory size, find the number of frames, and from it the width of the physical page number (PFN) field; then the PTE size, and the index split that follows from it.
Go to Paging numericals: address fields, frame bits, effective access time and replacement grids →COW marker
A COW marker records that this read-only mapping may become writable once it is private.
Go to Copy-on-write: sharing pages until one side writes →Reference count (page)
A reference count records how many modeled mappings own a frame.
Go to Copy-on-write: sharing pages until one side writes →sole-owner promotion
This sole-owner promotion is how xv6's page-fault handling describes the case.
Go to Copy-on-write: sharing pages until one side writes →program break
The traditional program break is the end of one unbroken heap region, moved by the brk ("break") system call (and its library wrapper sbrk).
Go to mmap and virtual memory areas: asking the kernel for address space →Virtual memory area (VMA)
A virtual memory area is the kernel's record of one contiguous range of a process's address space whose pages share the same permissions and backing; each line of /proc/<pid>/maps shows one.
Go to mmap and virtual memory areas: asking the kernel for address space →object file
An object file is one compiled piece of a program: machine code and data that cannot run yet, plus the notes needed to join them with other pieces.
Go to From source to executable: object files, symbols and the static linker →Section
Sections — named blocks of bytes, one purpose each: .text — machine code.
Go to From source to executable: object files, symbols and the static linker →symbol table
A symbol table — the names this file defines, and the names it uses but does not define.
Go to From source to executable: object files, symbols and the static linker →addend
It records a place in a section to patch, the symbol whose address belongs there, how to compute the patch, and a small constant adjustment called the addend.
Go to From source to executable: object files, symbols and the static linker →static linker
A static linker combines object files into one executable at build time, filling in every address the compiler left blank.
Go to From source to executable: object files, symbols and the static linker →ELF header
Every ELF file begins, always at offset zero, with a fixed-size ELF header.
Go to The ELF executable format: segments, sections and the entry point →section header table
The section header table is the linker's book: it records what the file is made of.
Go to The ELF executable format: segments, sections and the entry point →program header table
The program header table is the loader's book: it records what must exist in memory.
Go to The ELF executable format: segments, sections and the entry point →Segment (ELF)
An ELF segment is a contiguous range of a program's memory image, described by one program header entry: where its bytes come from in the file, where they go in virtual memory, how large it is in the file and in memory, and which access permissions it needs.
Go to The ELF executable format: segments, sections and the entry point →binfmt handler
It keeps a list of format handlers — binfmt handlers: one for scripts, one for ELF, a few for older formats.
Go to Inside exec: building a new address space →auxiliary vector
The auxiliary vector is a list of (id, value) pairs the kernel leaves on a new program's stack, telling it facts about itself and the machine.
Go to Inside exec: building a new address space →initial process stack
The initial process stack is the kernel's hand-off to a new program: starting at the stack pointer, argc, the NULL-terminated argv pointer array, the NULL-terminated envp pointer array and the AT_NULL-terminated auxiliary vector, with the strings they point to stored higher up.
Go to Inside exec: building a new address space →Dynamic linking
Dynamic linking defers part of linking to program start or first use: the static linker records which libraries and symbols a program needs, and a dynamic linker running inside the new process maps those libraries and binds the program's references to their definitions.
Go to Dynamic linking: shared libraries, ld.so, PLT and GOT →dynamic linker
The dynamic linker is the program that finishes the link inside a new process.
Go to Dynamic linking: shared libraries, ld.so, PLT and GOT →Interposition
Interposition is an earlier object's definition of a name standing in for a later object's, for every reference resolved through the lookup scope.
Go to Dynamic linking: shared libraries, ld.so, PLT and GOT →Procedure linkage table (PLT)
The procedure linkage table (PLT) is a table of small stubs, one per imported function, that every call to that function goes through.
Go to Dynamic linking: shared libraries, ld.so, PLT and GOT →lazy binding
It lets each function's lookup wait until the function is first called; deferring it this way is called lazy binding.
Go to Dynamic linking: shared libraries, ld.so, PLT and GOT →interleaving
An interleaving is one way of mixing the steps of several threads into a single order, while keeping each thread's own steps in their order.
Go to Race conditions: shared state, wrong answers →race condition
A race condition is a bug whose result depends on how the threads' steps happen to interleave.
Go to Race conditions: shared state, wrong answers →lost update
This is a lost update: one operation overwrites shared state using a value it read before another operation's completed change.
Go to Race conditions: shared state, wrong answers →invariant
An invariant is a property that must still hold after each operation.
Go to Race conditions: shared state, wrong answers →Mutual exclusion
Mutual exclusion means at most one thread is inside the critical section at a time.
Go to Race conditions: shared state, wrong answers →Atomicity
Atomicity is the matching promise about the operation itself: it is seen as one indivisible step, so no other thread can ever see it half done.
Go to Race conditions: shared state, wrong answers →lock
A lock is an object a thread takes (acquires) before touching shared data and gives back (releases) afterwards; only one thread can hold it at a time.
Go to Race conditions: shared state, wrong answers →data race
In C, C++, Java, Go and Rust, a data race is two accesses to the same location from different threads, at least one of them a write, that the language's synchronisation rules leave unordered.
Go to Race conditions: shared state, wrong answers →remainder section
The remainder section is everything else a participant does, outside the protocol and the protected region.
Go to Critical sections: mutual exclusion, progress and bounded waiting →Progress (critical sections)
Progress — if nobody is inside and some participants want in, the protocol does not put off choosing one of them forever.
Go to Critical sections: mutual exclusion, progress and bounded waiting →Bounded waiting
Bounded waiting — once a participant has announced that it wants in, the others can enter only a limited number of times before it does.
Go to Critical sections: mutual exclusion, progress and bounded waiting →critical-section problem
The critical-section problem is to design entry and exit procedures around a region of code so that three properties hold for every permitted schedule: mutual exclusion (at most one participant inside), progress (if the region is free and some participants want it, one of them is admitted without waiting on a participant in its remainder section), and bounded waiting (after a participant requests entry, others are…
Go to Critical sections: mutual exclusion, progress and bounded waiting →model checker
A model checker is a program that does exactly this, trying every schedule.
Go to Critical sections: mutual exclusion, progress and bounded waiting →Test-and-set
Test-and-set is the simplest such instruction: it writes "occupied" to a flag and returns the flag's previous value, in one step.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →Exchange (atomic)
An exchange is the general form: write any value you supply and return the old one.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →Fetch-and-add
Fetch-and-add adds an amount in one atomic step and returns the previous value.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →Load-reserved/store-conditional (LR/SC)
Load-reserved (LR) reads a location and sets up a reservation on it.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →reservation set
A reservation set is the stretch of memory the hardware watches for that load.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →ABA problem
Where this beats CAS is the ABA pattern: a location changes from value A to B and back to A.
Go to Atomic instructions: test-and-set, compare-and-swap, load-reserved/store-conditional →busy waiting
Waiting like this is called busy waiting: the caller keeps running while it waits.
Go to Spinlocks: busy-waiting for short critical sections →test-and-test-and-set
A test-and-test-and-set lock fixes most of that.
Go to Spinlocks: busy-waiting for short critical sections →ticket lock
A ticket lock hands each caller an increasing ticket number with fetch-and-add, and keeps a separate serving counter that names whose turn it is.
Go to Fair spinlocks: ticket locks and MCS queue locks →serving counter
A ticket lock hands each caller an increasing ticket number with fetch-and-add, and keeps a separate serving counter that names whose turn it is.
Go to Fair spinlocks: ticket locks and MCS queue locks →head-of-line blocking
This is head-of-line blocking: the first one in line holds up everyone behind it.
Go to Fair spinlocks: ticket locks and MCS queue locks →queued spinlock
A queued spinlock instead gives each waiter its own queue node with its own waiting flag, so a handoff normally changes only the next waiter's flag.
Go to Fair spinlocks: ticket locks and MCS queue locks →MCS lock
The MCS lock is a queued spinlock in which each waiter supplies its own queue node and spins only on a flag in that node; the lock itself holds just an atomic pointer to the queue's tail, and each owner hands off by clearing its successor's flag.
Go to Fair spinlocks: ticket locks and MCS queue locks →Program order
Program order is the order of operations within one participant, as its code lists them.
Go to Memory ordering: store buffers and relaxed memory models →store buffer
A store buffer holds a CPU's pending writes while the CPU keeps executing.
Go to Memory ordering: store buffers and relaxed memory models →store forwarding
That check of its own buffer is store forwarding.
Go to Memory ordering: store buffers and relaxed memory models →memory consistency model
A memory consistency model is the rule, part of a processor architecture or a programming language, that specifies which values each load may return when several participants access shared memory — equivalently, which combinations of observations a multi-threaded program may make.
Go to Memory ordering: store buffers and relaxed memory models →sequentially consistent
A multiprocessor is sequentially consistent if the result of any execution is the same as if the operations of all the processors were executed in some single sequential order, and the operations of each processor appear in that order in the sequence its program specifies.
Go to Memory ordering: store buffers and relaxed memory models →memory fence
A memory fence is an instruction that forces a stated ordering on memory operations.
Go to Memory ordering: store buffers and relaxed memory models →Release (memory ordering)
A release store guarantees that every earlier read and write of its thread becomes visible no later than the store itself.
Go to Memory ordering: store buffers and relaxed memory models →Acquire (memory ordering)
An acquire load guarantees that no later read or write of its thread is performed before it.
Go to Memory ordering: store buffers and relaxed memory models →sleeping lock
A sleeping lock lets a contending thread become blocked until an event makes acquisition worth retrying.
Go to Sleeping locks and wait queues →mutex
A mutex provides mutually exclusive ownership; a blocking mutex combines that ownership rule with a sleeping path.
Go to Sleeping locks and wait queues →wait queue
A wait queue records which threads are waiting for a particular event or object.
Go to Sleeping locks and wait queues →wakeup
A wakeup changes a waiting thread into a runnable one; it does not guarantee that the thread runs at once, or that it gets the lock.
Go to Sleeping locks and wait queues →Guard (mutex)
Contenders and the releasing owner all change those fields at the same time, so a short internal spinlock, called the guard, protects them:
Go to Sleeping locks and wait queues →wait channel
An xv6 wait channel names an event, often by an object's address.
Go to Sleeping locks and wait queues →barging
That is barging.
Go to Sleeping locks and wait queues →spurious wakeup
A spurious wakeup, from the caller's point of view, is a return from waiting without its condition being true; retrying under the guard handles it.
Go to Sleeping locks and wait queues →thundering herd
That burst is called a thundering herd.
Go to Sleeping locks and wait queues →hybrid lock
A hybrid lock spins for a limited time before falling back to its sleeping path.
Go to Adaptive locking: spin, then sleep →adaptive policy
An adaptive policy goes further and tunes that choice from what it observes, such as recent waits or whether the owner is running.
Go to Adaptive locking: spin, then sleep →offline optimum
An offline optimum knows t in advance.
Go to Adaptive locking: spin, then sleep →online policy
An online policy does not know t: while the lock stays held, it keeps having to decide whether another moment of spinning is worth paying for.
Go to Adaptive locking: spin, then sleep →competitive ratio
An online policy has competitive ratio c if, for every possible input, its cost is at most c times the cost of the offline optimum on that input; a policy with ratio 2 is called 2-competitive.
Go to Adaptive locking: spin, then sleep →Optimistic spinning
Optimistic spinning means treating a running owner as evidence that release may be near.
Go to Adaptive locking: spin, then sleep →Priority inversion
Priority inversion occurs when higher-priority work must wait for lower-priority work.
Go to Priority inversion: priority inheritance and ceilings →uncontrolled inversion
The avoidable kind is uncontrolled inversion, where unrelated tasks of intermediate priority delay the owner, and so delay the high-priority waiter too.
Go to Priority inversion: priority inheritance and ceilings →priority inheritance
Under priority inheritance (PI), a task that holds a lock runs at the highest priority of any task blocked waiting for it, for as long as that task waits, and returns to its own priority when the dependency ends.
Go to Priority inversion: priority inheritance and ceilings →base priority
The owner's base priority stays its assigned rank; its effective priority also counts any urgency it has inherited right now.
Go to Priority inversion: priority inheritance and ceilings →effective priority
The owner's base priority stays its assigned rank; its effective priority also counts any urgency it has inherited right now.
Go to Priority inversion: priority inheritance and ceilings →priority ceiling
A lock's priority ceiling is the highest base priority of any task that may use it.
Go to Priority inversion: priority inheritance and ceilings →Reader preference
Reader preference lets a new reader join the readers already inside, even while a writer waits.
Go to Reader–writer locks: sharing reads, and who starves →Writer preference
Writer preference stops admitting new readers as soon as a writer is waiting.
Go to Reader–writer locks: sharing reads, and who starves →FIFO reader-batching
A FIFO reader-batching policy is a useful third option.
Go to Reader–writer locks: sharing reads, and who starves →Signal (condition variable)
signal — wakes one waiting thread, if any are queued. (This signal is a call on a condition variable, not a Unix signal delivered to a process.)
Go to Condition variables: waiting for a condition, not a lock →broadcast
broadcast — wakes every waiting thread.
Go to Condition variables: waiting for a condition, not a lock →Hoare semantics
Under Hoare semantics, a signal immediately switches to the woken thread, which resumes holding the mutex with its condition still true.
Go to Condition variables: waiting for a condition, not a lock →covering condition
The broadcast pattern is called a covering condition: one broadcast covers all the different conditions the woken threads might be waiting for.
Go to Condition variables: waiting for a condition, not a lock →binary semaphore
This is a binary semaphore: P is acquire, V is release, and the cap is mutual exclusion.
Go to Semaphores: a counter that waits →ordering semaphore
This is an ordering semaphore: there is nothing to hand out yet, so P sleeps until someone else produces a unit.
Go to Semaphores: a counter that waits →rendezvous
Take a rendezvous: thread A's statement a1 must come before B's b2, and B's b1 must come before A's a2.
Go to Semaphores: a counter that waits →turnstile
A turnstile — a semaphore used as a gate that opens once and lets a queue file through.
Go to Semaphores: a counter that waits →reusable barrier
A reusable barrier fixes this with a second turnstile that shuts the gate again after the last thread passes — two phases, two semaphores.
Go to Semaphores: a counter that waits →monitor invariant
A monitor invariant — a property of the monitor's data that is true on entry and that every procedure makes true again before it leaves.
Go to Monitors: synchronisation as a language construct →deadlock
A deadlock is a state in which each thread of a set is waiting for an event that only another member of the set can produce — a release, a message, a freed slot — so no member can ever produce it.
Go to Deadlock: the four necessary conditions →Livelock
Livelock — threads keep running retries that keep failing.
Go to Deadlock: the four necessary conditions →request edge
A thread waiting for a resource type adds a request edge ; an instance of given to adds an assignment edge .
Go to Deadlock: the four necessary conditions →assignment edge
A thread waiting for a resource type adds a request edge ; an instance of given to adds an assignment edge .
Go to Deadlock: the four necessary conditions →need
A thread's need is its declared maximum minus what it already holds.
Go to Deadlock avoidance: the Banker's algorithm →footman
Downey's name for the spare-seat semaphore is the footman.
Go to Classic synchronisation problems: dining philosophers, readers–writers, bakery and the sleeping barber →bakery algorithm
His bakery algorithm works like the numbered tickets at a bakery counter.
Go to Classic synchronisation problems: dining philosophers, readers–writers, bakery and the sleeping barber →data locking
In data locking, a structure is partitioned and each partition gets its own lock, so operations on different partitions run at the same time.
Go to Lock granularity: dividing a lock by data, end, node and value →existence guarantee
An existence guarantee is a promise that an object will not be freed while a thread that has found it is still using it.
Go to Lock granularity: dividing a lock by data, end, node and value →approximate counter
An approximate counter gives each CPU a local counter behind a local lock, plus one global counter behind a global lock.
Go to Lock granularity: dividing a lock by data, end, node and value →byte stream
A byte stream delivers bytes in exactly the order they were written, with no message boundaries, no seeking (jumping to a position) and no pushing bytes back.
Go to Pipes: a kernel buffer between two descriptors →end-of-file
The last write end closes → readers get end-of-file: read returns 0.
Go to Pipes: a kernel buffer between two descriptors →SIGPIPE
The last read end closes → a write raises SIGPIPE, the signal that reports a write to a pipe with no reader.
Go to Pipes: a kernel buffer between two descriptors →PIPE_BUF
So POSIX draws a line: a write of at most PIPE_BUF ("pipe buffer") bytes is atomic.
Go to Pipes: a kernel buffer between two descriptors →shared memory object
A shared memory object is a named, file-like object whose pages exist to be mapped by several processes.
Go to Shared-memory IPC: mapping the same pages into two processes →robust mutex
A robust mutex is a mutex that reports its owner's death instead of staying locked forever: when the holding thread or process dies, the next acquirer obtains the lock together with an indication that the protected data may be inconsistent, and must repair it or declare the mutex unrecoverable.
Go to Shared-memory IPC: mapping the same pages into two processes →message queue
A POSIX message queue is a bounded list of discrete messages held in the kernel.
Go to Message passing: queues with boundaries →mq_msgsize
mq_msgsize — the most bytes one message may hold.
Go to Message passing: queues with boundaries →mq_maxmsg
mq_maxmsg — the most messages the queue may hold.
Go to Message passing: queues with boundaries →socket (communication endpoint)
A socket is a kernel object representing one endpoint of a communication channel, reached through a file descriptor.
Go to Sockets: the kernel's communication endpoint behind a descriptor →passive socket
listen instead marks the socket passive: it will never carry data itself.
Go to Sockets: the kernel's communication endpoint behind a descriptor →backlog
It is the backlog: the limit on connections the protocol has completed but the process has not yet accepted.
Go to Sockets: the kernel's communication endpoint behind a descriptor →send buffer
Each connected socket owns a send buffer and a receive buffer in kernel memory. send or write puts bytes in the send buffer; success means the kernel queued them, not that the peer has read them. read or recv drains the receive buffer.
Go to Socket buffers, back-pressure and readiness: what a successful send promises →receive buffer
Each connected socket owns a send buffer and a receive buffer in kernel memory. send or write puts bytes in the send buffer; success means the kernel queued them, not that the peer has read them. read or recv drains the receive buffer.
Go to Socket buffers, back-pressure and readiness: what a successful send promises →low-water mark
recv on an empty buffer blocks until at least SO_RCVLOWAT bytes arrive, or the peer disconnects. (SO_ marks a socket option; RCVLOWAT is "receive low-water".) That amount is the low-water mark, the minimum a read waits for; it starts at 1.
Go to Socket buffers, back-pressure and readiness: what a successful send promises →abstract socket address
An abstract address starts with a null byte (a byte whose value is zero, which C uses to end a string) in sun_path[0], the first byte of the path field.
Go to Unix domain sockets: local sockets that pass descriptors →ancillary data
sendmsg and recvmsg (send and receive message) can attach ancillary data: control information carried beside the byte payload.
Go to Unix domain sockets: local sockets that pass descriptors →peer credentials
SO_PEERCRED is not a message at all: getsockopt reads the connected peer's peer credentials — the process ID, user ID and group ID the kernel recorded for the other end — as they were when connect, listen or socketpair created the connection.
Go to Unix domain sockets: local sockets that pass descriptors →hardware interface
A device's hardware interface is the part the rest of the system can see: its set of registers, plus the protocol that gives every read and write of them a meaning.
Go to Device registers and memory-mapped I/O →programmed I/O
When the CPU itself carries the payload, moving it piece by piece through the data register as in the second line above, the scheme is called programmed I/O.
Go to Device registers and memory-mapped I/O →sectors
A disk is read and written in fixed-size units of 512 bytes called sectors, each numbered from the start of the disk; that number is the LBA, or logical block address (hard disk drives draws them).
Go to Device registers and memory-mapped I/O →port
This is the older door. x86's in and out move bytes, words or longs between a CPU register and a numbered port: a small flat namespace of its own, separate from memory.
Go to Device registers and memory-mapped I/O →Memory-mapped I/O
Memory-mapped I/O (MMIO) wires a range of physical addresses to a device instead of to RAM, so an ordinary load reads a device register and an ordinary store writes one: the address decoder routes the access to hardware rather than memory.
Go to Device registers and memory-mapped I/O →base address registers
A PCI device's base address registers (BARs) record where it will listen.
Go to Device registers and memory-mapped I/O →Direct memory access
Direct memory access (DMA) is the transfer of data between a device and main memory by the device itself — acting as a bus master that issues its own memory reads and writes — rather than by the CPU loading and storing each word.
Go to Direct memory access: devices that copy for themselves →bus master
Direct memory access (DMA) is the transfer of data between a device and main memory by the device itself — acting as a bus master that issues its own memory reads and writes — rather than by the CPU loading and storing each word.
Go to Direct memory access: devices that copy for themselves →descriptor ring
A descriptor ring is a circular array in ordinary memory, shared by driver and device, in which each entry (a descriptor) names a buffer's address and length.
Go to Direct memory access: devices that copy for themselves →doorbell
A doorbell is a single device-register write by which the driver tells the device that new descriptors are ready.
Go to Direct memory access: devices that copy for themselves →bus address
A bus address is the address a device forms to reach memory on its side of the host bridge.
Go to Direct memory access: devices that copy for themselves →pinning
That lifetime is what pinning means: between map and unmap the frame cannot be recycled, moved or swapped out.
Go to Direct memory access: devices that copy for themselves →DMA mask
The driver tells the kernel the widest address its device can use — the DMA mask — and the kernel picks a buffer within that reach, or fails.
Go to Direct memory access: devices that copy for themselves →PCI functions
Each PCI device exposes one or more PCI functions — a network card with two ports may present two.
Go to PCIe and MSI-X: how a device is found, mapped and heard from →configuration space
Each function has a configuration space: a small block of standard registers the kernel can read before it knows anything about the device.
Go to PCIe and MSI-X: how a device is found, mapped and heard from →message-signalled interrupt
A message-signalled interrupt (MSI) is, in the kernel documentation's words, "a write from the device to a special address which causes an interrupt to be received by the CPU": the device writes a vector number to an address the interrupt controller decodes, instead of asserting a wire.
Go to PCIe and MSI-X: how a device is found, mapped and heard from →MSI-X
MSI-X, added in PCI 3.0, allows between 1 and 2048, each with its own address and data, each maskable on its own.
Go to PCIe and MSI-X: how a device is found, mapped and heard from →device driver
A device driver is kernel code that owns one kind of device: it programs that device's registers, descriptors and interrupts, and presents it to the rest of the kernel through a standard interface — a character device, a block device or a network interface — so that nothing else needs to know the device's peculiarities.
Go to Device drivers: the kernel's largest and least trusted code →Bus (device model)
In the device model, a bus keeps two lists: the devices discovered on it, and the drivers registered for it.
Go to Device drivers: the kernel's largest and least trusted code →Probe (driver)
A driver's probe is the function the bus calls after a match.
Go to Device drivers: the kernel's largest and least trusted code →Character device
A character device is accessed as a stream of bytes, like a file: open, close, read, write, and a catch-all ioctl (I/O control) for device-specific commands.
Go to Device drivers: the kernel's largest and least trusted code →Block device
A block device holds fixed-size blocks that can be read in any order, and exists to carry filesystems.
Go to Device drivers: the kernel's largest and least trusted code →device file
A device file (special file, device node) is a name in the file tree whose metadata records a device type — character or block — and a device number instead of data.
Go to Device files: /dev, major and minor numbers, and why a disk is a file →major number
The major number picks the driver: it indexes the kernel's table of registered character or block drivers.
Go to Device files: /dev, major and minor numbers, and why a disk is a file →minor number
The minor number is passed to that driver and picks one of the devices it serves — a particular terminal, a particular disk, or a partition on it.
Go to Device files: /dev, major and minor numbers, and why a disk is a file →Sector
A sector is the smallest unit a disk reads or writes: the drive moves whole sectors only, never part of one.
Go to Hard disk drives: seek, rotation and transfer →Logical block address
A logical block address (LBA) is the number the host uses to name a block; the drive maps each LBA to a physical place on the platters.
Go to Hard disk drives: seek, rotation and transfer →Seek time
The access time of a disk request is the sum of three waits: the seek, moving the arm to the target track (it speeds up, coasts, then settles over the track); the rotational delay, waiting for the target sector to spin under the head (half a rotation on average for a randomly placed sector); and the transfer, reading or writing the sectors as they pass under the head.
Go to Hard disk drives: seek, rotation and transfer →Rotational delay
The access time of a disk request is the sum of three waits: the seek, moving the arm to the target track (it speeds up, coasts, then settles over the track); the rotational delay, waiting for the target sector to spin under the head (half a rotation on average for a randomly placed sector); and the transfer, reading or writing the sectors as they pass under the head.
Go to Hard disk drives: seek, rotation and transfer →IOPS
Same arithmetic on the Cheetah: ms per read, MB/s, about 166 IOPS — I/O operations per second, the unit spec sheets use for random rates.
Go to Hard disk drives: seek, rotation and transfer →Shortest seek time first
Shortest seek time first (SSTF) always serves the pending request nearest the head's current position.
Go to Disk scheduling: six policies on one request queue →SCAN (elevator algorithm)
SCAN, the elevator algorithm, sweeps the head across the surface in one direction, serving queued requests as it passes them.
Go to Disk scheduling: six policies on one request queue →Erase block
Erase blocks — roughly 128–256 KiB each.
Go to Flash translation layers: erase blocks and out-of-place writes →Write amplification
Write amplification is the number of flash writes the device performs per write the host asked for.
Go to Flash translation layers: erase blocks and out-of-place writes →Out-of-place write
It never updates a page in place: it makes out-of-place writes, putting each update in a fresh physical page, marking the old copy invalid, and keeping a logical-to-physical map of where each logical block currently lives.
Go to Flash translation layers: erase blocks and out-of-place writes →block layer
The block layer is the part of the kernel that every disk, SSD and virtual disk sits behind.
Go to The block layer: bios, requests and blk-mq →bio
A bio describes a piece of block I/O: an operation, a starting sector, and the memory segments holding the data.
Go to The block layer: bios, requests and blk-mq →Request (block layer)
A request is the unit the block layer hands to a driver: one or more bios covering one contiguous sector range, within the device's limits, identified by a tag.
Go to The block layer: bios, requests and blk-mq →blk-mq
blk-mq, Linux's multi-queue block layer, replaces the single request queue with two levels: per-CPU software staging queues where bios are merged and optionally scheduled, mapped onto hardware dispatch queues that correspond to the device's own submission queues.
Go to The block layer: bios, requests and blk-mq →Plug (block layer)
So a thread's bios are first collected in a per-task plug: a short private list, drained to the queue in one pass.
Go to The block layer: bios, requests and blk-mq →Tag (block layer)
Every request in flight carries a tag: an integer from 0 to one less than the queue depth, given out once by the block layer and echoed back by the driver on completion.
Go to The block layer: bios, requests and blk-mq →RAID
A RAID — redundant array of inexpensive disks — is a set of disks presented as one block device, with data laid out across them so that the array is faster than one disk, survives the failure of some of its disks, or both.
Go to RAID 0, 1 and 10: striping and mirroring →Fail-stop
Throughout this topic the fault model is fail-stop: a disk is either working — every block readable and writable — or entirely dead, and the array's controller can tell which.
Go to RAID 0, 1 and 10: striping and mirroring →Chunk (RAID)
A chunk is the run of blocks placed on one disk before moving to the next.
Go to RAID 0, 1 and 10: striping and mirroring →stripe
A stripe is one row of chunks across all the disks.
Go to RAID 0, 1 and 10: striping and mirroring →Consistent-update problem
This is the consistent-update problem: a logical write that touches several disks cannot complete as one indivisible step.
Go to RAID 0, 1 and 10: striping and mirroring →file
A file is a linear array of bytes that can be read or written.
Go to Files and inodes: named bytes and their metadata →inode number
Every file also has a low-level name that the file system assigns: its inode number (or i-number), unique within that file system.
Go to Files and inodes: named bytes and their metadata →metadata
A record like this is metadata: data about the data — who owns the file, how long it is — as opposed to the bytes it holds.
Go to Files and inodes: named bytes and their metadata →Directory entry
A directory is itself a file, with its own inode, whose content has a fixed format: a sequence of directory entries, each pairing a user-readable name with an inode number.
Go to Directories and path resolution →Absolute pathname
A pathname is absolute when it begins with /.
Go to Directories and path resolution →Relative pathname
Otherwise it is relative, and the walk starts at the process's current working directory: a directory each process holds a reference to, inherited across fork and changed by chdir (change directory).
Go to Directories and path resolution →current working directory
Otherwise it is relative, and the walk starts at the process's current working directory: a directory each process holds a reference to, inherited across fork and changed by chdir (change directory).
Go to Directories and path resolution →Path resolution
Path resolution is the kernel's walk from a pathname to an inode: it takes the pathname's components left to right and looks each one up in the directory the previous one reached.
Go to Directories and path resolution →link count
The inode counts how many entries point at it in its link count, shown as st_nlink and as the second column of ls -l (the long listing).
Go to Hard links, symbolic links and unlink →atomic
A step is atomic when every other process sees the state before it or the state after it, never something in between.
Go to Hard links, symbolic links and unlink →dangling link
Rename or delete the target and the link becomes a dangling link: open fails with ENOENT, while lstat (the l is for link: it reports the link itself, not its target) still happily reports the link.
Go to Hard links, symbolic links and unlink →credentials
A process's credentials are the set of integer user and group IDs it carries, which the kernel compares with a file's owner and group to decide what the process may do.
Go to Unix file permissions: owner, group, other →effective user ID
Effective IDs — an effective user ID and effective group ID, the ones the kernel looks at when deciding what the process may do.
Go to Unix file permissions: owner, group, other →Mode bits
A file's mode bits are the low twelve bits of st_mode: three read-write-execute triplets — for the owner, the group and everyone else — plus three special bits.
Go to Unix file permissions: owner, group, other →Search permission
Reaching /srv/team/report.txt needs search permission — the execute bit, on a directory — on every directory along the way, and only then does the file's own triplet answer the read:
Go to Unix file permissions: owner, group, other →sticky bit
The sticky bit fixes this.
Go to Unix file permissions: owner, group, other →umask
A program creating a file does not choose its final mode; it states a ceiling. open and mkdir (make directory) take a mode argument — by convention 0666 for files and 0777 for directories — and the process's umask (file-creation mask) clears bits from it.
Go to Unix file permissions: owner, group, other →Set-user-ID bit
A file with the set-user-ID bit set makes execve (the system call underneath exec) run it with the file owner's ID as its effective user ID.
Go to Unix file permissions: owner, group, other →mount point
A mount point is a directory a file system has been attached at.
Go to Mounting: grafting file systems into one tree →mount table
The mount table is the kernel's list of mounts: each entry records which file system is mounted on which directory, plus flags such as read-only.
Go to Mounting: grafting file systems into one tree →bind mount
A bind mount attaches an existing subtree at a second place in the tree, so both names reach the same files. mount --bind /data /mnt/data makes one, where /data is any directory that already exists.
Go to Mounting: grafting file systems into one tree →virtual file system
The virtual file system (VFS, sometimes "virtual filesystem switch") is the kernel layer that implements the file system calls once, in terms of a small set of generic objects, and reaches each concrete file system only through tables of functions that file system supplies.
Go to The virtual file system: one interface, many file systems →Superblock (VFS)
Superblock — a superblock stands for one mounted file system.
Go to The virtual file system: one interface, many file systems →File object (VFS)
File — a file object is one open instance. open allocates it; it points at the dentry, holds the position, and gets its f_op (file operations) table copied from the inode.
Go to The virtual file system: one interface, many file systems →dentry
A dentry (directory entry, in memory) ties one component name to an inode, under a parent dentry: three fields — the name, the parent and the inode pointer.
Go to The virtual file system: one interface, many file systems →negative dentry
A negative dentry has a null inode pointer: it records that the name does not exist.
Go to The virtual file system: one interface, many file systems →Dentry cache (dcache)
The dentry cache (dcache) is a hash table keyed by (parent dentry, name), with collisions handled by chaining as hash chaining derived.
Go to The virtual file system: one interface, many file systems →page index
Each slot is named by (which file, which page): the inode plus the page index, the file offset divided by the page size.
Go to The page cache: file data cached in memory →XArray
Today the same structure wears an array-shaped interface: the XArray (Matthew Wilcox, Linux 4.20) presents the trie as an array of pointers that grows by itself — xa_load(i_pages, i) reads slot , xa_store writes it — with the locking built in.
Go to The page cache: file data cached in memory →folio
A folio is the kernel's handle for a block of one or more physically contiguous pages managed as one unit.
Go to The page cache: file data cached in memory →Reclaimable memory
Page-cache pages are reclaimable.
Go to The page cache: file data cached in memory →file-backed mapping
A file-backed mapping is a range of virtual addresses whose VMA records a file and a starting offset, so that each page of the range stands for one page of the file.
Go to Memory-mapped files: file pages as memory →shared mapping
MAP_SHARED, a shared mapping.
Go to Memory-mapped files: file pages as memory →private mapping
MAP_PRIVATE, a private mapping.
Go to Memory-mapped files: file pages as memory →Readahead
Readahead is the kernel reading file pages into the page cache before they are requested, on the evidence of a sequential access pattern.
Go to Readahead: detecting sequential access →Writeback
Writeback is the kernel's deferred writing of dirty page-cache pages to storage, on its own schedule and under its own limits, after the write calls that dirtied them have returned.
Go to Writeback: when dirty pages reach the disk →Flusher thread
Each has one or more writeback contexts (bdi_writeback), whose work runs on kernel worker threads: the flusher threads.
Go to Writeback: when dirty pages reach the disk →dirtyable memory
The volume limits are fractions of dirtyable memory: the memory that could reasonably hold dirty file data.
Go to Writeback: when dirty pages reach the disk →fsync
fsync(fd) transfers all modified in-memory data and metadata of the file referred to by fd to its storage device, including a flush of the device's volatile write cache where it has one, and blocks until the device reports the transfer complete.
Go to fsync and the durability contract →fdatasync
fdatasync(fd) is fsync's weaker, faster twin: it flushes the file's data plus only the metadata a later read of that data needs — the file size, so the new bytes can be reached — but not the mtime (modification time) or similar bookkeeping.
Go to fsync and the durability contract →durable-rename protocol
The durable-rename protocol updates a file's contents atomically and durably in four steps: write the new data to a temporary file in the same directory, fsync the temporary file, rename it over the target, and fsync the directory.
Go to fsync and the durability contract →volatile write cache
A drive with a volatile write cache reports a write complete before the data is on media: "complete" means "in the drive's RAM", not "on the platter" or "in flash".
Go to fsync and the durability contract →group commit
In real systems: is the batch size folded into one flush, one for a serial committer and N for group commit (several transactions' log records made durable by one flush). is what makes the hardware decisive.
Go to fsync and the durability contract →errseq_t
An errseq_t (error sequence) is a 32-bit value the kernel uses to record the latest error in one place and let any number of watchers each find out whether it changed since they last looked.
Go to When fsync fails: how write errors are reported, and lost →Direct I/O
Direct I/O moves file data by DMA straight between the device and the process's own buffer, skipping the page cache, so the application's pool is the only cache of the data.
Go to Direct I/O: bypassing the page cache →partition
How does a whole file system fit onto an array of blocks? (A partition is a slice of a disk, one unbroken run of blocks, that is formatted and mounted as its own file system.
Go to On-disk layout: superblock, bitmaps, inode table and data →data region
In the toy, the last 56 of the 64 blocks form the data region.
Go to On-disk layout: superblock, bitmaps, inode table and data →inode table
The inode table is an unbroken run of blocks holding every inode of the file system as an array of same-sized records, one after another.
Go to On-disk layout: superblock, bitmaps, inode table and data →Allocation bitmap
An allocation bitmap spends one bit per object: 0 for free, 1 for used.
Go to On-disk layout: superblock, bitmaps, inode table and data →inode bitmap
The toy needs two: an inode bitmap over the inode table and a data bitmap over the data region.
Go to On-disk layout: superblock, bitmaps, inode table and data →data bitmap
The toy needs two: an inode bitmap over the inode table and a data bitmap over the data region.
Go to On-disk layout: superblock, bitmaps, inode table and data →superblock
The superblock is the block at a known position on the volume that describes the file system as a whole: its type (identified by a magic number, a fixed constant the format stores at a known offset so that software can recognise it), its size, how many inodes and data blocks it has, and where each on-disk structure begins.
Go to On-disk layout: superblock, bitmaps, inode table and data →allocation method
An allocation method is a file system's rule for naming which data blocks belong to a file, and in what order.
Go to File allocation methods: contiguous, linked, FAT and indexed →Contiguous allocation
Contiguous allocation stores each file as one run of blocks next to each other.
Go to File allocation methods: contiguous, linked, FAT and indexed →Linked allocation
Linked allocation drops the need for a run.
Go to File allocation methods: contiguous, linked, FAT and indexed →File Allocation Table
The File Allocation Table (FAT) keeps the linked shape but moves every next-pointer out of the data blocks into one array, indexed by block number.
Go to File allocation methods: contiguous, linked, FAT and indexed →Indexed allocation
Indexed allocation gives each file an index block: an array whose entry names the file's -th block.
Go to File allocation methods: contiguous, linked, FAT and indexed →direct pointer
A direct pointer is a disk address in the inode that names one data block.
Go to Block maps and indirect blocks: how big a file can be →indirect block
An indirect block is a data block filled with block numbers instead of file bytes.
Go to Block maps and indirect blocks: how big a file can be →Double-indirect pointer
a double-indirect pointer names a block of indirect-block addresses, covering data blocks;
Go to Block maps and indirect blocks: how big a file can be →multi-level index
A multi-level index maps a file's block numbers to disk blocks through a fixed set of inode slots: direct pointers name data blocks, and each indirect pointer names a block of pointers one level further from the data, so each extra level multiplies the reachable file size by the number of pointers per block.
Go to Block maps and indirect blocks: how big a file can be →Sparse file
The same structure makes sparse files nearly free.
Go to Block maps and indirect blocks: how big a file can be →extent
An extent is one record naming a contiguous run of a file's blocks: the first file block it covers, the physical block where the run starts, and a length in blocks.
Go to Extents: describing contiguous runs →Uninitialized extent
A value above 32,768 marks an uninitialized extent: space reserved but never written, for example by fallocate (file allocate), the call a program uses to claim disk blocks for later.
Go to Extents: describing contiguous runs →htree
An htree is ext4's hashed directory index: a shallow tree, stored inside the directory's own data blocks, that maps ranges of a hash of the file name to the leaf blocks holding the entries whose names hash into each range.
Go to Directory implementation: lists, hashed trees and a traced open →Fast File System
In 1984 Marshall Kirk McKusick, William Joy, Samuel Leffler and Robert Fabry of the Computer Systems Research Group at the University of California, Berkeley, published "A Fast File System for UNIX".
Go to Block groups and locality: the Fast File System's idea →Cylinder group
A cylinder group is a set of consecutive cylinders (a cylinder being the tracks at one arm position across all platters) that carries a complete slice of the file system's metadata: a copy of the superblock, the group's inode and data bitmaps, its slice of the inode table, and a summary of free blocks by rotational position.
Go to Block groups and locality: the Fast File System's idea →block group
A block group is a consecutive range of a volume's blocks that carries its own slice of the file system's metadata (a superblock copy on some groups, its own inode and data bitmaps, and its own part of the inode table) so that files can be placed with their metadata close by.
Go to Block groups and locality: the Fast File System's idea →Fragment (FFS)
FFS fragments split a block into 2, 4 or 8 addressable pieces, each no smaller than a disk sector, so a 1 KB file need not use up a whole 4 KB block.
Go to Block groups and locality: the Fast File System's idea →structural inconsistency
A structural inconsistency is two on-disk structures disagreeing about the same fact, like the bitmap and the inode arguing over whether block 5 is in use.
Go to The crash consistency problem →space leak
A space leak is storage marked as in use that nothing points to, so it can never be handed out again.
Go to The crash consistency problem →crash consistent
A file system is crash consistent when, after a crash at any instant, the on-disk image it recovers to reflects each file-system operation's changes to its own structures either completely or not at all — so those structures agree with each other — even though the device completes only one block write at a time.
Go to The crash consistency problem →torn state
A torn state is any post-crash image that reflects only part of one operation.
Go to The crash consistency problem →fsck
fsck — the file system checker — is a program that reads every on-disk structure and rewrites whichever ones contradict each other.
Go to fsck: repairing after a crash →Journaling
Journaling is write-ahead logging applied to a file system: before changing any block in place, the file system writes a transaction — a begin marker, the new contents of every block the operation changes, and a commit marker — to a reserved on-disk region called the journal.
Go to Data journaling: write intent first, then checkpoint →transaction
Journaling is write-ahead logging applied to a file system: before changing any block in place, the file system writes a transaction — a begin marker, the new contents of every block the operation changes, and a commit marker — to a reserved on-disk region called the journal.
Go to Data journaling: write intent first, then checkpoint →journal
Journaling is write-ahead logging applied to a file system: before changing any block in place, the file system writes a transaction — a begin marker, the new contents of every block the operation changes, and a commit marker — to a reserved on-disk region called the journal.
Go to Data journaling: write intent first, then checkpoint →checkpointing
Only once the whole transaction is durable does it write those blocks to their home locations (the addresses they permanently occupy), a step called checkpointing — a use of the word cousins with the database's checkpoint, the same "advance the durable frontier" idea in a different log.
Go to Data journaling: write intent first, then checkpoint →data journaling
The form defined here, in which the transaction carries the file's data blocks as well as its metadata, is data journaling; the metadata-only form, where the log holds no user data, is a variation the lesson on journaling modes builds from it.
Go to Data journaling: write intent first, then checkpoint →Ordered journaling
Ordered journaling — ext4's default, data=ordered — journals only metadata and adds one rule: a file's data must reach the disk before the transaction's metadata may commit.
Go to Journaling modes: data, ordered and writeback →writeback journaling
Under writeback journaling — ext4's data=writeback — the file system journals metadata exactly as ordered mode does, but lets dirty data be written at any time, even after the transaction has committed.
Go to Journaling modes: data, ordered and writeback →revoke record
One fix is never to reuse a freed block until the transaction that freed it has been checkpointed out of the journal, which delays reuse by a commit interval. ext3 added a different fix, which jbd2 keeps: a new record type, the revoke record, written into the transaction that frees the block and marking its journaled copies dead.
Go to Journaling modes: data, ordered and writeback →persistence properties
A file system's persistence properties say which in-between states a crash can leave on disk: which operations are atomic, and which reach the disk in the order they were issued.
Go to Application crash consistency: atomic file replacement →Atomic file replacement
Atomic file replacement is the protocol that updates a file so a crash leaves either the complete old contents or the complete new contents under its name: write the new contents to a temporary file in the same directory, fsync it, rename it over the target, then fsync the directory.
Go to Application crash consistency: atomic file replacement →File status flags
O_NONBLOCK is one of the file status flags, and where it lives matters.
Go to Non-blocking I/O: calls that return instead of waiting →EAGAIN
It fails with EAGAIN, the error number meaning "try again" (on Linux, EWOULDBLOCK is the same value).
Go to Non-blocking I/O: calls that return instead of waiting →Non-blocking I/O
Non-blocking I/O is the mode, set per open file description with O_NONBLOCK, in which a read, write, accept or connect that cannot make progress immediately returns an error (EAGAIN, or EINPROGRESS for connect) instead of putting the calling thread to sleep.
Go to Non-blocking I/O: calls that return instead of waiting →busy polling
That is busy polling, and it pays a system call per descriptor on every pass:
Go to Non-blocking I/O: calls that return instead of waiting →Readiness
A descriptor is ready for an operation when performing that operation now would not block: readable when a read would return data, end-of-file or an error at once, writable when a write of some bytes would be accepted at once.
Go to select and poll: asking which descriptors are ready →readiness multiplexing
Readiness describes the present state of the object, not a promise about the next call, and readiness multiplexing is asking the kernel which of many descriptors are ready in one call.
Go to select and poll: asking which descriptors are ready →epoll instance
An epoll instance is a kernel data structure, reached through an ordinary file descriptor.
Go to epoll: a persistent interest set and a ready list →Interest list (epoll)
It holds two lists: the interest list of descriptors the process has registered, and the ready list — references to the entries of the interest list that have events pending right now.
Go to epoll: a persistent interest set and a ready list →Ready list (epoll)
It holds two lists: the interest list of descriptors the process has registered, and the ready list — references to the entries of the interest list that have events pending right now.
Go to epoll: a persistent interest set and a ready list →level-triggered
An entry is level-triggered, the default, when it is reported for as long as its condition holds — which is how poll behaves, so level-triggered epoll is a faster poll that drops in wherever poll worked.
Go to epoll: a persistent interest set and a ready list →edge-triggered
With the EPOLLET flag an entry is edge-triggered: an event is delivered when the object's state changes, and not again until another change arrives.
Go to epoll: a persistent interest set and a ready list →event loop
An event loop is a single thread that repeatedly waits for events — readiness notifications, timer expiries, completions — and dispatches each to a handler that runs to completion without blocking before the loop waits again.
Go to Event loops: the reactor pattern and where threads still win →Reactor pattern
The reactor pattern is the event loop driven by readiness: the handler is told that an operation can proceed, and performs it itself.
Go to Event loops: the reactor pattern and where threads still win →Cooperative scheduling
The price is that this scheduler is cooperative, not preemptive.
Go to Event loops: the reactor pattern and where threads still win →manual stack management
Atul Adya and colleagues at Microsoft Research named the chore of building it manual stack management in 2002: the part of a thread's stack that mattered is rebuilt by hand, per connection, in a data structure the loop can find again.
Go to Event loops: the reactor pattern and where threads still win →Uninterruptible sleep
A task in uninterruptible sleep is in a wait that ignores signals until the event it waits for arrives.
Go to Load average: an exponentially damped count of runnable and uninterruptible tasks →exponentially damped moving average
That is an exponentially damped moving average: the weights on older samples shrink as a geometric sequence (sequences and series has the sums used below).
Go to Load average: an exponentially damped count of runnable and uninterruptible tasks →load average
The Linux load average is a triple of exponentially damped moving averages, with time constants of 1, 5 and 15 minutes, of the number of tasks that are running, runnable, or in uninterruptible sleep, summed over all CPUs and sampled every five seconds.
Go to Load average: an exponentially damped count of runnable and uninterruptible tasks →sampling profiler
A sampling profiler does not watch calls; it interrupts the machine at a chosen rate and records what was running.
Go to Sampling profilers: perf, hardware counters and flame graphs →performance counter
A performance counter — a register in the CPU's performance-monitoring unit (PMU), the hardware performance counters that count retired instructions (ones that have finished executing), cycles or cache misses — is set up with a period.
Go to Sampling profilers: perf, hardware counters and flame graphs →flame graph
A flame graph draws folded stacks as stacked boxes: each box is a function, its width is its inclusive sample count (its own samples plus its callees'), and its height is its depth in the stack.
Go to Sampling profilers: perf, hardware counters and flame graphs →Saturation (resource)
Saturation: the amount of extra work the resource could not serve — usually a queue length, or time spent waiting to be served.
Go to The USE method: utilisation, saturation and errors per resource →active benchmarking
Gregg's remedy, active benchmarking, means configuring a long, steady-state run — hours, not seconds — and analysing every component with other tools while it executes, to find the run's true limiter.
Go to Benchmarking the OS: what you actually measured →principal
A principal is the party to which permissions are granted: the unit of accountability, the computer's stand-in for a person or service outside it.
Go to Protection domains and the access matrix →Protection domain
A protection domain is the set of objects, each with the permissions to use it, that a computation may currently access.
Go to Protection domains and the access matrix →Fail-safe defaults
Base access decisions on permission rather than exclusion (a principle the paper credits to Glaser, 1965).
Go to Protection domains and the access matrix →Complete mediation
Every access to every object is checked for authority, every time.
Go to Protection domains and the access matrix →reference monitor
A checking point that cannot be bypassed, that sees every access, and that is small enough to audit is called a reference monitor in the security literature.
Go to Protection domains and the access matrix →Least privilege
Every program and every user should operate with the least set of privileges necessary for the job.
Go to Protection domains and the access matrix →discretionary access control
Because the owner decides, the Unix model is discretionary access control (DAC): each object's owner sets its policy at their own discretion.
Go to Protection domains and the access matrix →Access control list (ACL)
An access control list (ACL) packs the access matrix by column: each object carries, beside its data, the list of principals permitted to use it and the rights each holds.
Go to Access control lists versus capabilities →capability
A capability is an unforgeable ticket which, when presented, is incontestable proof that the presenter is authorized to use the object the ticket names; the ticket also lists which uses it permits.
Go to Access control lists versus capabilities →ambient authority
A process wields ambient authority when it exercises authority simply by being who it is, without naming that authority in the request.
Go to Access control lists versus capabilities →real user ID
The real user ID says who owns the process: the accounting identity — who is billed for it and who may signal it.
Go to setuid and the confused deputy →saved set-user-ID
The saved set-user-ID is a parking slot that records what the effective ID was, so a process that dropped privilege can get it back.
Go to setuid and the confused deputy →confused deputy
A confused deputy is a program that holds authority of its own and acts on requests from less-privileged callers, and that can be tricked into applying its own authority to an object the caller named but could not itself have accessed.
Go to setuid and the confused deputy →Linux capability
A Linux capability is one named bit in a fixed set that partitions the superuser's privileges; the kernel checks the relevant bit in a thread's effective capability set, instead of checking for effective user ID 0, before a privileged operation.
Go to Linux capabilities: splitting root →trap-and-emulate
A monitor works by trap-and-emulate when it runs a guest operating system deprivileged, so that each privileged operation it attempts traps into the VMM, which emulates that operation against the guest's virtual machine state and resumes the guest.
Go to Virtualisation requirements: Popek–Goldberg and x86's problem →virtual machine monitor
A virtual machine monitor, also called a hypervisor, is a control program that gives each guest an environment essentially identical to the real machine, keeps complete control of the machine's resources, and lets a statistically dominant subset of the guest's instructions run directly on the processor.
Go to Virtualisation requirements: Popek–Goldberg and x86's problem →hypervisor
A virtual machine monitor, also called a hypervisor, is a control program that gives each guest an environment essentially identical to the real machine, keeps complete control of the machine's resources, and lets a statistically dominant subset of the guest's instructions run directly on the processor.
Go to Virtualisation requirements: Popek–Goldberg and x86's problem →Sensitive instruction
Sensitive instructions can break the illusion, in one of two ways.
Go to Virtualisation requirements: Popek–Goldberg and x86's problem →VMX root operation
VMX root operation is where the VMM runs, with full hardware access.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →VMX non-root operation
VMX non-root operation is where the guest runs: restricted, but in the way that matters — the guest's CPL can be 0, so an unmodified kernel runs at the privilege it was written for, while the operations the monitor has chosen still leave the guest.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →VM entry
VM entries are transitions from the VMM's root mode into the guest's non-root mode, performed by VMLAUNCH and VMRESUME.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →VM exit
VM exits are the transitions back, triggered by conditions the hardware detects in the guest.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →virtual-machine control structure
Each virtual CPU's context lives in a virtual-machine control structure (VMCS; AMD's equivalent is the VMCB): a 4-KiB-aligned region of memory, named by a 64-bit physical pointer, holding the guest's saved state, the monitor's own state and the controls that decide which guest events exit.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →shadow page tables
Both monitors had to see the fault because each kept shadow page tables — its own copies of the guest's page tables with guest-physical addresses already translated to real ones — so each had to learn of every guest page fault and page-table change.
Go to Hardware-assisted virtualisation: VT-x, AMD-V and KVM →Virtio
Virtio is a device contract designed for exactly this constraint, introduced by Rusty Russell in 2008 for Linux guests and now a standard of OASIS, a standards body.
Go to I/O virtualisation: emulation, virtio and SR-IOV →virtqueue
A virtqueue is a ring buffer in shared memory with three areas: a descriptor area of fixed-size slots naming guest buffers; a driver area, the available ring, where the guest's driver publishes work for the device; and a device area, the used ring, where the device publishes finished work.
Go to I/O virtualisation: emulation, virtio and SR-IOV →vhost
vhost moves the device model's datapath — the code the packet data itself passes through, as opposed to setup and control — into the host kernel.
Go to I/O virtualisation: emulation, virtio and SR-IOV →Virtual function
An SR-IOV (single-root I/O virtualisation, a PCI Express standard) network card exposes virtual functions — lightweight PCI functions with their own registers and queues. (A PCI function is one independently addressable device on the bus, and one card may present several.) The hypervisor assigns one virtual function (VF) to the guest, whose own driver programs it directly.
Go to I/O virtualisation: emulation, virtio and SR-IOV →IOMMU group
Devices come in IOMMU groups — the smallest sets of devices the hardware guarantees to isolate from each other — and a group is the unit of assignment.
Go to I/O virtualisation: emulation, virtio and SR-IOV →namespace
A namespace wraps a global system resource so that the processes inside it appear to have their own isolated instance of it: changes made by members are visible to co-members and invisible to everyone else.
Go to Namespaces: per-process views of global resources →peer group
A peer group is a set of mounts, in one mount namespace or several, that mirror each other's mount and unmount events: a mount or unmount made directly under any member is repeated under every other member.
Go to Mount propagation: when one namespace's mount appears in another →Mount propagation
Mount propagation is the kernel's repetition of a mount or unmount event under the other mounts that are linked to the mount where it happened; it never copies file contents, it only repeats the graft.
Go to Mount propagation: when one namespace's mount appears in another →propagation type
Whether and how a mount takes part is its propagation type, a setting stored with the mount and changed by the mount command (mount --make-shared and its siblings).
Go to Mount propagation: when one namespace's mount appears in another →control group
A control group (cgroup for short, never capitalised) is a named set of processes, organised in a hierarchy, to which the kernel applies resource accounting and limits as a unit; a controller is the per-resource policy — CPU, memory, I/O, process count — that divides a cgroup's resource among its children.
Go to Control groups: a hierarchy for resource control →cgroup
A control group (cgroup for short, never capitalised) is a named set of processes, organised in a hierarchy, to which the kernel applies resource accounting and limits as a unit; a controller is the per-resource policy — CPU, memory, I/O, process count — that divides a cgroup's resource among its children.
Go to Control groups: a hierarchy for resource control →cgroup controller
A control group (cgroup for short, never capitalised) is a named set of processes, organised in a hierarchy, to which the kernel applies resource accounting and limits as a unit; a controller is the per-resource policy — CPU, memory, I/O, process count — that divides a cgroup's resource among its children.
Go to Control groups: a hierarchy for resource control →CPU bandwidth control
Bandwidth control caps a cgroup's CPU use: within each accounting period, the cgroup may use at most quota microseconds of CPU time, counted across all its threads on all cores.
Go to CPU bandwidth control: quotas, periods and throttling →Memory charge
A memory area is charged — counted against a cgroup's usage and limits — to the cgroup whose process first brought it into memory, and it stays charged to that cgroup until it is released.
Go to Memory cgroups: limits, reclaim and OOM inside a container →container image
A container image in this format is not a file-system blob but a recipe for building one: a manifest listing a configuration and an ordered stack of layers, each layer recording only what changed from the layers below it.
Go to Container images and overlay file systems →Whiteout
Removals appear as whiteout entries: an empty regular file named .wh.<basename> (wh for whiteout) beside the path it deletes.
Go to Container images and overlay file systems →Overlay file system
An overlay file system (overlayfs) combines a writable upper directory tree over one or more read-only lower trees into one merged view: names are looked up in the upper first, and changes are only ever written to the upper.
Go to Container images and overlay file systems →copy-up
A copy-up moves a file from a lower layer into the upper layer before its first change, so the change can be made there while the lower copy stays untouched.
Go to Container images and overlay file systems →stateless protocol
A stateless protocol is one in which the server keeps no memory of past requests from a client: every request carries all the information needed to perform it, so the server can crash, restart and serve the next request with no recovery step.
Go to NFS: a stateless remote file system →File handle (NFS)
A file handle is the name an NFS request uses for a file or directory.
Go to NFS: a stateless remote file system →Close-to-open consistency
Flush-on-close (close-to-open consistency): when a client closes a file it wrote, it first sends all its dirty data to the server, so any open anywhere after that close sees the new version.
Go to NFS: a stateless remote file system →attribute cache
The fix is an attribute cache: attributes the client fetched are trusted for a short timeout , so repeated use within that window asks the server nothing.
Go to NFS: a stateless remote file system →Mechanism versus workload
Decisions supported by the authored lessons.
Static or dynamic linking?
Static wins on dependency surface and deployability; dynamic wins on shared memory and library updates.
More detail
M_static = N·S pays the library per binary; dynamic pays once but inherits search order, interposition and version coupling. A statically linked binary is also the only kind that survives an empty filesystem.
read(), mmap or both?
Copy per call versus fault per page — the access pattern picks the winner.
More detail
mmap wins on repeated random access and when kernel and user must share data structures; read() wins on one sequential pass and on code simplicity. msync is a flush, fsync remains the durability contract.
Buffered, direct or zero-copy?
The page cache, an application-managed path, or page references — three answers to where file bytes sit.
More detail
Buffered is the default because the kernel already runs the hit-rate policy; O_DIRECT is for applications that already cache (databases); sendfile/splice/MSG_ZEROCOPY are for movers, not users, of bytes — and only past a size threshold.
How much durability per write?
write() is a promise, fdatasync is a checkpoint, fsync on the directory completes a create.
More detail
Paying per write buys latency isolation at throughput cost; batching amortises the flush but widens the loss window. The contract ends at media — a name needs its directory fsynced and the device cache sits below everything.
Hard or symbolic link?
A hard link names the inode itself; a symlink stores a path that can rot.
More detail
Hard links share content and lifetime — no dangling, but no directories and no crossing filesystems. Symlinks reach anywhere and can point at nothing. Deleting through a hard link is just unlink; the inode stays.
Bind mount or symlink?
A bind mount grafts the subtree itself; a symlink names a path that resolves to it.
More detail
A bind mount makes the target part of the namespace — chroot and mount-namespace boundaries included — where a symlink depends on the new context resolving the path the same way.
Nine bits or an ACL?
Owner/group/other covers the common cases; per-user grants need ACLs the classic model lacks.
More detail
The three-class check is compact and fast but cannot say “user X may read” — that requires an access-control list or capability scheme layered on top.
SSTF, SCAN or C-LOOK?
Greedy positioning is fast but starves; elevator sweeps bound the wait.
More detail
SSTF inherits SJF’s flaw — requests far from the head can wait forever. SCAN and C-LOOK serve in position order so every request is reached; C-LOOK skips the empty return sweep.
Page, block or hybrid FTL mapping?
Mapping granularity is the flash trade: memory for the table against copying for merges.
More detail
Page-level maps are fast but cost ~1 GiB of tables per TiB; block-level maps are tiny but force full merges. Hybrid and demand-based (DFTL) schemes pick the middle.
Poll, interrupt or hybrid?
Polling pays the whole wait; interrupts pay a fixed path per event; the hybrid takes both halves.
More detail
Pure polling wins only when the answer is usually ready now; pure interrupts lose at overload to livelock. Interrupt-then-poll-under-a-budget (NAPI) is the standard answer for high-rate devices.
PIO or DMA?
Programmed I/O for a handful of words; bus mastering for transfers big enough to amortise setup.
More detail
DMA saves a whole CPU copy but costs descriptor setup, pinning and translation. Control traffic stays PIO; bulk data moves by descriptor ring.
Copy the bytes or share the pages?
Pipe and queue copies are the price of a managed channel; shared memory deletes them.
More detail
Sharing removes 2βN per byte but hands the synchronisation protocol, robust-mutex repair and channel structure to the application. Small transfers rarely repay the fixed costs.
Stream, queue or shared memory?
Choose by framing: pipes erase boundaries, queues keep them, shared memory never had them.
More detail
A queue’s priority and whole-message delivery beat a pipe when records matter; both lose to shared memory on bulk payload. The composite — message pointing at shared pages — is the bulk-data pattern.
Unix domain or TCP loopback?
Same socket object, different name space — the local domain is the simpler kernel object.
More detail
Unix sockets skip protocol processing, admit pathname and abstract names, and uniquely carry SCM_RIGHTS descriptor passing. TCP loopback remains the answer when the protocol stack itself is what you need.
Coarse or fine locking?
Coarsen until the serial fraction stops binding, then stop — finer is not free.
More detail
Every additional lock is an ordering obligation and a deadlock edge candidate. Split only where contention is measured; hand-over-hand traversal rarely pays on shallow structures.
Pad, replicate or tolerate?
Fix false sharing only where coherence traffic is actually measured.
More detail
Padding costs footprint and cache capacity; per-CPU copies need a merge step for reads. Confirm a shared-line bounce exists before paying either.
Condition variables or semaphores?
Prefer the primitive that names the relationship being expressed.
More detail
Condition variables express a predicate over shared state with explicit rechecking; semaphores express counts and event orderings. A counting semaphore used for arbitrary predicates loses the predicate; a CV used as a counter reimplements one.
Signal or broadcast?
Broadcast when different waiters expect different predicates or any waiter might consume the wake.
More detail
Signal suffices for a uniform queue and a single consumable condition. Broadcast trades extra wakeups for safety when predicates differ; covering conditions are the disciplined middle ground.
Prevent, avoid or detect deadlock?
Choose by how much information and control the system actually has.
More detail
Ordering and negating a condition are cheap but constrain design; avoidance needs declared maximums real code rarely knows; detection plus recovery pays runtime cost and needs rollback-able victims.
Spin or block?
Match execution context and expected hold behavior before optimizing overhead.
More detail
Short nonblocking kernel regions may require spinning. Long or uncertain holds favor a sleeping mutex where permitted; a bounded adaptive spin does not replace the safe sleep protocol.
Plain exchange, tickets or queued spinning?
Trade footprint, admission ordering and shared polling traffic.
More detail
Rare contention favors a simple lock. Tickets order admission; MCS changes polling locality. Neither FIFO nor locality guarantees that a descheduled owner will run.
Ordinary mutex or priority-aware protocol?
Use explicit dependency-aware scheduling when mixed priorities and deadlines require it.
More detail
PI removes intermediate-priority interference under suitable assumptions. Ceiling protocols add admission restrictions; neither configuration is a substitute for a full resource and timing analysis.
Mutex or reader–writer admission?
Measure actual compatible read overlap and metadata cost.
More detail
Tiny reads, frequent writers or little concurrency can favor a mutex. Reader/writer preference can starve the other class; specify queue policy and evaluate writer latency separately.
Eager copy or COW?
Compare distinct diverging pages with fault and metadata costs.
More detail
COW avoids copying discarded or unchanged pages. Eager copying can win when most pages will immediately diverge or predictable later latency matters.
Object allocation or separate mapping?
Match granularity to lifetime and protection needs.
More detail
Many tiny objects benefit from an allocator-owned region. Large independently released buffers or protected reservations can justify explicit mmap ranges.
Populate eagerly or on demand?
Trade upfront work and residency against first-touch latency.
More detail
Sparse unused regions benefit from deferred population. Known imminent use may justify eager work, without implying immunity from later reclaim.
Exact recency or sampled access evidence?
Compare saved faults with observation and metadata cost.
More detail
Exact LRU has precise nesting guarantees; clock reduces observation cost but does not inherit those guarantees merely by approximating recency.
Change victims or reduce admitted work?
Determine whether avoidable choices or aggregate active demand is the main problem.
More detail
MIN provides an offline count yardstick. Sustained pressure and oversized working sets can require fewer concurrent jobs, not a more elaborate victim rule.
Optimistic commitment or early rejection?
Choose a failure boundary compatible with sparse demand and recovery needs.
More detail
Strict accounting reduces late promise failures but does not guarantee all physical allocations. Application budgets and bounded concurrency remain necessary.
Variable segments or fixed pages?
Choose protection and placement granularity with allocation costs visible.
More detail
Segments use compact region descriptors but require contiguous variable-size physical storage. Pages scatter frames while adding table metadata and tail slack.
Flush on switch or retain ASID-tagged entries?
Retain translations when measured reuse justifies identity management.
More detail
Flushes simplify correctness for infrequent switches. Tags need safe reuse and do not prevent capacity eviction or stale mappings after edits.
Short jobs first or early turns?
Choose the metric before the scheduler.
More detail
SJF minimises mean turnaround under its batch assumptions; RR offers bounded turns without a length oracle. First service is not useful completion.
Lottery or deterministic tracking?
Random draws simplify entitlement; history reduces short-run fluctuation.
More detail
Lottery offers expected shares, not finite wait guarantees. Stride needs progress counters and careful arrival placement.
Fork and exec or posix_spawn?
Use the smallest launch API that expresses the required setup.
More detail
Spawn file actions support redirection. Fork permits more general composition, but a multithreaded parent imposes strict child-side safety limits before exec.
Duplicate a descriptor or open again?
Duplicate for a shared open instance; open again for an independent offset.
More detail
Separate opens do not isolate underlying file contents. A duplicate is an alias, not a copy of data or an independent seek position.
One thread, multiple threads or processes?
Choose intentional sharing and failure boundaries before comparing overhead.
More detail
One thread avoids coordination for short sequential work. Threads share heap and descriptors; processes can provide stronger fault and authority separation. Stack reservation is not resident memory.
One syscall per item or a batch?
Amortise fixed crossing costs when delayed individual results are acceptable.
More detail
Batching can reduce crossings but increase latency and memory use. Preserve partial-result and error semantics.
Recursion or an explicit loop?
Use nesting when its clarity is worth a bounded live-frame budget.
More detail
Compute depth from actual frame sizes and available stack. A loop or valid tail-call optimisation avoids accumulating frames for suitable computations.
More capacity or more cache ways?
Identify placement conflicts before assuming total capacity is exhausted.
More detail
The two-block trace fits in capacity but maps both blocks to one direct-mapped slot; two-way placement removes repeated conflicts.
Split work across cores or keep it local?
Balance independent work against shared-line ownership transfers.
More detail
Small tightly shared work can lose to one core. Batching writes reduces handoffs when delayed combination is acceptable.