Learn Operating Systems

From “what is an operating system for?” down to page tables and epoll. Every lesson starts from the problem the mechanism solves, asks “why does this exist?”, labels what is conceptual versus Linux/Unix/Windows-specific, and most carry a simulator you can step through and break.

How Programs Run

What an OS is for, and what happens between `./server` and the first instruction on a CPU: loader, address space, process, scheduler.

Why? — Several programs want the same CPU, memory, disk and NIC. Who coordinates them?

Processes

A process is a running program with an identity, an address space, a state, open resources and a parent. The process table, the state machine, fork and exec.

Why? — How does one executable become three isolated running instances?

Threads, Async & Event Loops

Threads inside a process, concurrency vs parallelism, threads vs async vs processes, and how C++, JavaScript/TypeScript and Python each map onto the OS.

Why? — Why can a single core run a thousand tasks "at once", and why is that not parallelism?

Threads: Several Instruction Streams in One Process
▶ interactive

A thread is an independently scheduled instruction stream inside a process: it has its own stack, registers and instruction pointer, and shares everything else — heap, globals, descriptors — with its siblings, which makes threads cheap to create and communicate through, and easy to corrupt.

Process versus Thread
▶ interactive

Processes buy isolation at the price of expensive creation, expensive switching and explicit communication; threads buy cheap sharing at the price of shared failure — and every runtime, browser and database picks a point on that line for a reason.

Threads versus Async versus Processes

There are three ways to have many things in flight — a thread per task, an event loop that parks tasks while they wait, or a process per task — and the choice is decided by whether the work is CPU-bound or I/O-bound, how many tasks you need, and what it costs to have one of them fail.

Concurrency versus Parallelism
▶ interactive

Concurrency is having several tasks in progress at once, achieved on a single core by interleaving them in time slices; parallelism is executing several at the same instant, which requires several cores — a single core is always concurrent and never parallel.

How C++, JavaScript and Python Map onto the OS
▶ interactive

C++ exposes OS threads directly; JavaScript hides them behind an event loop per realm and reaches the OS through the runtime’s own threads; CPython wraps OS threads but serialises bytecode with the GIL in its default build — three different contracts over the same kernel.

The Event Loop
▶ interactive

An event loop is a single thread that repeatedly takes the next completed event from a queue and runs its handler to completion; the runtime and the OS do the waiting elsewhere, so one thread can hold thousands of in-flight operations as long as no handler blocks it.

Async I/O: What `await readFile()` Actually Does
▶ interactive

An `await` on an I/O call hands the request to the runtime, which hands it to the OS or a helper thread, frees the calling thread to run other work, and resumes the function as a continuation when the kernel reports completion — with mechanisms that differ between files and sockets and between Node, Python and C++.

CPU Scheduling

A hundred runnable processes, eight cores. Ready queues, time slices, priority, preemption, fairness, and what a context switch actually saves and restores.

Why? — There are 100 runnable processes and 8 cores. Who runs, and for how long?

System Calls & Kernel Mode

Why applications cannot touch hardware directly, the user/kernel boundary, and what a system call costs.

Why? — Why can’t my program just write to the disk itself?

Stack & Heap

The process memory layout, stack frames pushed and popped by function calls, the heap under `new`/`malloc`/object allocation, and what a stack overflow really is.

Why? — Where does a local variable live, where does an object live, and why does recursion have a limit?

Virtual Memory & Paging

Every process believes it owns a huge private memory. Pages, frames, page tables, the TLB, page faults, memory pressure, memory mapping and copy-on-write.

Why? — Multiple processes each believe they have their own large continuous memory. How?

Why Virtual Memory?

Every process on the machine believes it owns a large, private, contiguous memory starting at the same address; one level of indirection — a per-process map from virtual pages to physical frames — makes that belief true and buys isolation, relocation, lazy allocation, sharing and protection in a single mechanism.

The Virtual Address Space
▶ interactive

Process A’s address 0x1000 and process B’s address 0x1000 are two different bytes because each process has its own page table; the address space they see is 48 bits wide, split between a user half and a kernel half that is mapped into every process but unreachable from user mode.

Paging

Memory is managed in fixed-size pages: each virtual page maps to a physical frame through an entry carrying present, read/write/execute, dirty and accessed bits, and the "not present" state is not an error but the hook the kernel uses to allocate, share and swap lazily.

Page Tables
▶ interactive

The page table is the per-process map from virtual pages to frames; because a flat table for a 48-bit space would be 512 GB, it is a four-level radix tree that exists only where the address space is populated, walked by the hardware from a base register the kernel loads at each context switch.

The TLB
▶ interactive

Walking four levels of page table on every load would make memory five times slower; the translation lookaside buffer caches recent virtual-to-physical translations so that the walk happens once per page per working set — and its reach, its flushes and its tagging decide how expensive huge heaps and context switches really are.

Page Faults
▶ interactive

When a translation finds no present page, the CPU traps to the kernel, which classifies the fault — minor (page exists or can be made cheaply: lazy allocation, page cache, copy-on-write), major (read from disk or swap), or invalid (SIGSEGV) — fixes the page table, and resumes the same instruction.

Memory Pressure, Swap and the OOM Killer
▶ interactive

When processes want more memory than the machine has, the kernel reclaims: it drops clean page-cache pages, writes dirty ones back, pushes cold anonymous pages to swap, and — if the working sets still do not fit — kills a process; "free memory" was never the number that mattered.

Memory Mapping
▶ interactive

mmap makes a file appear as a range of the address space — reads become page faults served from the page cache, writes dirty shared pages the kernel flushes later — which is why databases and high-performance systems love it and why its unpredictable stalls and lack of write control make some of them avoid it.

Copy-on-Write
▶ interactive

After fork the parent and child share every page read-only; the first write to a page faults, the kernel copies just that page, and the two processes diverge one page at a time — so forking a 10 GB process is cheap until someone writes, which is exactly the property Redis snapshots and process spawning depend on.

Files, File Systems & Descriptors

Paths, files, directories, metadata, permissions, offsets; the descriptor table; how a path becomes blocks on storage; inodes on Unix-style systems.

Why? — What is the difference between a filename and a file — and what is FD 3?

I/O

Follow a `read()` from the call to the SSD and back; blocking, non-blocking, asynchronous and multiplexed I/O; and why a file and a socket are the same kind of thing.

Why? — How can one thread wait for 10,000 sockets without spinning?

Concurrency, Synchronization & Deadlocks

Race conditions, lost updates, visibility, critical sections, mutexes, semaphores, atomics, and the four ingredients of a deadlock.

Why? — Two threads both add one to a counter and the result is one. Why?

A Taxonomy of Concurrency Bugs
▶ interactive

Concurrency bugs come in seven recognisable shapes — race, lost update, visibility/ordering, deadlock, livelock, starvation, priority inversion — each with a distinct mechanism and a distinct production symptom, and naming the shape is most of the diagnosis.

Race Conditions
▶ interactive

`counter++` is three instructions — load, add, store — and if two threads interleave between them one increment is lost; races are timing-dependent by nature, and the same read-then-act shape on the file system (TOCTOU) is a security bug rather than a counting bug.

Critical Sections

A critical section is the stretch of code that touches shared state and must not interleave with another such stretch; synchronisation exists to enforce that, and its cost is decided by how much you put inside, how long you hold it, and how many threads want it — which Amdahl’s law turns into a hard ceiling on speedup.

Mutexes
▶ interactive

A mutex is a lock with an owner: the thread that locks it must unlock it, a second thread that wants it waits — spinning briefly or sleeping in the kernel via a futex — and the uncontended path is a single atomic instruction that never enters the kernel.

Semaphores and Condition Variables
▶ interactive

A semaphore is a counter with blocking decrement and non-blocking increment and no notion of an owner — the right tool for "at most N at once" and for signalling between threads — while a mutex owns and a condition variable waits for a predicate; the three are different primitives, not interchangeable spellings.

Atomic Operations
▶ interactive

CPUs provide indivisible read-modify-write instructions — fetch-and-add, compare-and-swap, load-linked/store-conditional — that make a lock-free counter possible and every lock implementable; the subtleties are the ABA problem and the memory-ordering flags that say what else becomes visible when an atomic does.

Deadlocks
▶ interactive

A deadlock needs four things at once — mutual exclusion, hold-and-wait, no preemption, circular wait — and the last is a cycle in the waits-for graph, which is why detection is depth-first search, prevention is imposing an order on the graph, and a database deadlock detector runs the same algorithm on transactions.

Inter-Process Communication

Pipes, shared memory, message queues, signals and sockets — compared on speed, isolation, complexity and local-vs-remote.

Why? — Two isolated processes need to talk. What are the options and what does each cost?

Sockets

The socket abstraction as the OS sees it: a descriptor with buffers behind it, and the bridge into the Networking domain.

Why? — What does the kernel actually give me when I call `socket()`?

Containers & the OS

Containers are not small virtual machines: namespaces, control groups, layered filesystems, shared host kernel, and how that differs from a hypervisor.

Why? — If a container has no kernel of its own, what is actually isolating it?

OS Internals Lab

A configurable OS simulator — cores, processes, threads, RAM, I/O, locks — and a panel of buttons to break it, then diagnose the failure.

Why? — What happens to the scheduler, memory and I/O when I exhaust something?

OS Debugging & Capstone

High CPU, high memory, hangs, too many open files: the diagnosis playbook, and the capstone — explain a 50,000-connection server layer by layer, then diagnose what was injected.

Why? — The process is at 100% CPU. What are the four different things that could mean?

OS + Networking Together

Follow `send()` through the socket API, the kernel, the transport stack and the NIC to a server that wakes up in `recv()`; build a tiny server from blocking to event-driven; buffers, backpressure, zero-copy and a combined failure simulator.

Why? — What actually happens between writing `send()` and another machine’s process waking up?

Follow send() Through the OS to recv()
OS + Net▶ interactive

Between `send()` returning in one process and `recv()` returning in another there are two kernels, two NICs, three copies, a congestion gate, a routing decision and at least one context switch — and every one of them is a place where bytes wait.

Build a Tiny Server: V0 to V5
OS + Net▶ interactive

Six versions of the same server, each one born from the specific failure of the previous one: a single request, a blocking loop, a thread per client, a pool, non-blocking sockets, and finally an event loop.

The Blocking Server
OS + Net

accept → read → process → write → next: the simplest correct server, and the clearest demonstration that a blocking call parks the whole program on one client’s behaviour.

Thread per Connection
OS + Net

Give every client its own thread and let the scheduler interleave them: the code stays sequential and the OS supplies the concurrency — until the number of threads becomes the workload.

The Thread Pool Server
OS + Net

A fixed set of workers pulling connections from a bounded queue: thread cost becomes a constant, overload becomes a queue length you can see, and the slow client returns as "one slow request occupies a worker".

The Event-Driven Server
OS + Net

One thread, many non-blocking sockets, and a kernel API that says which ones are ready: the server sleeps until something happens and then does exactly the work that is possible — as long as nothing in it ever blocks.

C10K: Ten Thousand Connections, Then a Million
OS + Net▶ interactive

The limits are concrete and countable: threads, descriptors, kernel socket memory, wake-up cost, ephemeral ports, middlebox state — and each has a mechanism that moved it, which is why the number went from 10K to 10M without the laws of physics changing.

The Buffer Chain
OS + Net▶ interactive

Application buffer → socket send buffer → device queue → wire → NIC ring → socket receive buffer → application: a chain of bounded queues in which every full buffer pushes back on the one above, sized by bandwidth × delay and dangerous when oversized.

What Happens When the Receiver Is Slow
OS + Net▶ interactive

A fast sender and a slow reader: the receive buffer fills, the window closes, the send buffer fills, and the sender’s `write()` blocks, returns EAGAIN, returns false, or awaits — depending only on which I/O model it chose. Buffer in user space instead and it fails by running out of memory.

Zero-Copy: Serving a File Without Touching It
OS + Net▶ interactive

Serving a file the naive way copies it four times and crosses the user/kernel boundary four times; `sendfile`, `splice`, scatter-gather DMA and, at the extreme, kernel bypass remove the copies the CPU does not need to make — until TLS puts one back.

Memory Mapping, the Page Cache and Network I/O
OS + Net

High-throughput systems are built by letting the page cache be the shared buffer between disk, process and NIC, and by batching every crossing of the user/kernel boundary: Kafka’s log, a database’s buffer pool, `writev`, and io_uring.

Combined Failure Simulator: Break a Layer, Watch It Propagate
OS + Net▶ interactive

Application → kernel → network → remote: exhaust descriptors, fill a buffer, block a thread, drop packets, add latency, kill the process, restart the server — and follow each failure across the layers to the symptom the user sees and the tool that proves it.

Capstone: Three Seconds from Warsaw
OS + Net▶ interactive

A user in Warsaw opens your application hosted in another region and the page takes three seconds: enumerate every layer where the time could be, assign each to the domain that explains it, put a typical cost and a measurement next to each, and bisect.