Free Operating systems & runtimes lessons
Many streams of work share finite processors
Threads in one process commonly share code, heap, and open resources while keeping registers and stacks of their own. The scheduler chooses runnable threads for available cores, preempts them to share time, and leaves blocked threads asleep until an event makes progress possible.
The kernel preserves the outgoing thread’s architectural state, updates accounting and address-space context if needed, selects a runnable successor, and restores its state. Cache and translation state may no longer match the new workload, so a switch has indirect costs beyond saving registers.
Isolation, translation, paging, and faults
Page tables map virtual pages to physical frames with permissions and status. A translation lookaside buffer caches recent mappings. A missing or disallowed mapping triggers a page fault so the kernel can reject access, create a page, load data, or update a copy-on-write mapping.
After a process fork, parent and child can initially share read-only mappings to the same frames. When either writes, a protection fault lets the kernel allocate and copy only that page, then grant the writer a private mapping. Unmodified pages never pay the copy cost.
Files, descriptors, buffering, and persistence
User code places arguments according to an ABI and traps into the kernel. The kernel validates permissions and addresses, performs or starts the operation, then returns a result. File descriptors are process-local handles to kernel-managed open objects; paths are names resolved through a filesystem namespace.
A library may buffer in user space; the kernel may buffer in memory; the device may have its own cache. A successful write can mean bytes were accepted at one boundary, not that stable media now contains them. Flush and synchronization operations strengthen the promise, subject to filesystem and hardware contracts.
Atomicity, waiting, deadlock, and isolation
An operation that looks single in source may compile into several loads and stores that interleave. Locks establish mutual exclusion; condition variables coordinate state changes; atomics provide specified indivisible operations and ordering. Deadlock can arise when tasks wait in a cycle for resources held by one another.
For a bounded queue, the useful invariant connects buffer contents, head, tail, and count. A lock protects the transition that changes those fields together. Producers wait while full; consumers wait while empty; state changes signal waiters. Locking one field but not the invariant still permits impossible combined states.