Computer Science › How Computers Actually Work
The process abstraction turns one physical CPU into the illusion of many — a running program's memory, registers, and open files packaged into a single unit the OS can pause, resume, and juggle among many others.
Unix's separation of "create a copy of me" (fork) from "replace my memory with a different program" (exec) — a genuinely strange two-step design whose strangeness is exactly what makes shells and pipes possible.
A process is always in exactly one of a handful of states — running, ready, or blocked — and the mechanism that lets the OS save one process's CPU state and load another's is the context switch, the machinery limited direct execution is built on.
Before comparing scheduling policies, fixing what "good" even means — turnaround time, response time, and throughput often trade off against each other, so no single policy wins on every metric at once.
First-come-first-served is trivially fair but lets one long job block every short job behind it — the convoy effect — while shortest-job-first fixes average turnaround time provably, at the cost of needing to know the future.
Giving every job a small, fixed time slice and cycling through them in turn — excellent for response time, worse for turnaround time than SJF, and the first policy in this discipline that doesn't need to predict the future.
Letting some jobs jump the queue solves the problem of urgent work waiting behind trivial work, but opens a new one — a low-priority job can wait forever if higher-priority work never stops arriving.
The scheduler real operating systems actually approximate: multiple priority queues, jobs demoted for using too much CPU and promoted periodically to prevent starvation — learning a job's behavior from its recent history instead of asking it up front.
A thread is a separately schedulable stream of execution that shares its process's address space with its sibling threads — the exact systems-level mechanics that the shared-state concurrency model, introduced conceptually elsewhere, is actually built from.
The formal statement of the race-condition hazard already met as a mental model: a critical section is any code touching shared state, and mutual exclusion is the guarantee that at most one thread is inside it at a time.
A lock only works if acquiring it is itself immune to the same race condition it prevents — which is why real locks are built on hardware-provided atomic instructions like test-and-set and compare-and-swap, not on ordinary load/store.
A lock alone can't let a thread wait for some condition to become true without busy-spinning — a condition variable adds exactly that: a queue a thread can sleep on, and a signal another thread sends when the wait is over.
A single integer counter with two atomic operations, wait and signal, general enough to implement a lock, a condition variable's signaling, or a bound on how many threads may proceed at once — Dijkstra's one primitive that does the job of several.
Locks and semaphores solve races but open a new failure mode: threads waiting on each other in a cycle, forever — deadlock, detectable by building a resource-allocation graph and running the exact same cycle-detection algorithm already covered for graphs in general.
Every process sees its own private, contiguous address space starting at address zero — an illusion the OS maintains on top of one shared physical RAM, and the reason a bug in one program's pointers can't corrupt another program's memory.
The simplest possible hardware support for virtualized memory: one register holding where a process's memory starts, another holding how big it is — every address a program uses gets relocated and checked against the bound automatically.
Splitting an address space into independently relocated segments (code, stack, heap) avoids wasting memory on the unused gap between a small heap and a small stack — at the cost of external fragmentation, the same free-space-management problem a general-purpose allocator faces.
Instead of a few large, variable-sized segments, chop memory into many small, fixed-size pages — eliminating external fragmentation entirely, at the cost of needing a page table to record where every single page actually lives in physical memory.
Looking up a page table entry in memory on every single memory access would make paging catastrophically slow — the TLB is a small, fully associative hardware cache of recent translations, applying the same caching idea already covered for data, now to address translation itself.
When physical memory is full and a new page must be brought in, something has to be evicted — FIFO, optimal, and LRU are three different answers to "which page," compared on the exact same reference string to see how many page faults each one actually causes.
Loading pages only when they're actually touched lets a process run with less physical memory than its full address space needs — until too many processes compete for too little memory and the system spends more time paging than computing, a collapse called thrashing.
A file system's real job is turning a flat array of disk blocks into named, hierarchical files — an inode holds one file's metadata and the block pointers that locate its data, while a directory is just a special file mapping names to inode numbers.
A crash in the middle of a multi-block update (a new inode, a new data block, an updated bitmap) can leave a file system's on-disk structures inconsistent — journaling fixes this by writing the intended changes to a log first, making the whole update recoverable as a single atomic step.
Every mechanism in this discipline working together on one concrete timeline: a shell forks and execs a program, the scheduler picks it, it touches a page that isn't resident yet, and the resulting page fault ends in a real disk read through the file system.
Part of the Computer Science curriculum.
The process abstraction turns one physical CPU into the illusion of many — a running program's memory, registers, and open files packaged into a single unit the OS can pause, resume, and juggle among many others.
Unix's separation of "create a copy of me" (fork) from "replace my memory with a different program" (exec) — a genuinely strange two-step design whose strangeness is exactly what makes shells and pipes possible.
A process is always in exactly one of a handful of states — running, ready, or blocked — and the mechanism that lets the OS save one process's CPU state and load another's is the context switch, the machinery limited direct execution is built on.
Before comparing scheduling policies, fixing what "good" even means — turnaround time, response time, and throughput often trade off against each other, so no single policy wins on every metric at once.
First-come-first-served is trivially fair but lets one long job block every short job behind it — the convoy effect — while shortest-job-first fixes average turnaround time provably, at the cost of needing to know the future.
Giving every job a small, fixed time slice and cycling through them in turn — excellent for response time, worse for turnaround time than SJF, and the first policy in this discipline that doesn't need to predict the future.
Letting some jobs jump the queue solves the problem of urgent work waiting behind trivial work, but opens a new one — a low-priority job can wait forever if higher-priority work never stops arriving.
The scheduler real operating systems actually approximate: multiple priority queues, jobs demoted for using too much CPU and promoted periodically to prevent starvation — learning a job's behavior from its recent history instead of asking it up front.
A thread is a separately schedulable stream of execution that shares its process's address space with its sibling threads — the exact systems-level mechanics that the shared-state concurrency model, introduced conceptually elsewhere, is actually built from.
The formal statement of the race-condition hazard already met as a mental model: a critical section is any code touching shared state, and mutual exclusion is the guarantee that at most one thread is inside it at a time.
A lock only works if acquiring it is itself immune to the same race condition it prevents — which is why real locks are built on hardware-provided atomic instructions like test-and-set and compare-and-swap, not on ordinary load/store.
A lock alone can't let a thread wait for some condition to become true without busy-spinning — a condition variable adds exactly that: a queue a thread can sleep on, and a signal another thread sends when the wait is over.
A single integer counter with two atomic operations, wait and signal, general enough to implement a lock, a condition variable's signaling, or a bound on how many threads may proceed at once — Dijkstra's one primitive that does the job of several.
Locks and semaphores solve races but open a new failure mode: threads waiting on each other in a cycle, forever — deadlock, detectable by building a resource-allocation graph and running the exact same cycle-detection algorithm already covered for graphs in general.
Every process sees its own private, contiguous address space starting at address zero — an illusion the OS maintains on top of one shared physical RAM, and the reason a bug in one program's pointers can't corrupt another program's memory.
The simplest possible hardware support for virtualized memory: one register holding where a process's memory starts, another holding how big it is — every address a program uses gets relocated and checked against the bound automatically.
Splitting an address space into independently relocated segments (code, stack, heap) avoids wasting memory on the unused gap between a small heap and a small stack — at the cost of external fragmentation, the same free-space-management problem a general-purpose allocator faces.
Instead of a few large, variable-sized segments, chop memory into many small, fixed-size pages — eliminating external fragmentation entirely, at the cost of needing a page table to record where every single page actually lives in physical memory.
Looking up a page table entry in memory on every single memory access would make paging catastrophically slow — the TLB is a small, fully associative hardware cache of recent translations, applying the same caching idea already covered for data, now to address translation itself.
When physical memory is full and a new page must be brought in, something has to be evicted — FIFO, optimal, and LRU are three different answers to "which page," compared on the exact same reference string to see how many page faults each one actually causes.
Loading pages only when they're actually touched lets a process run with less physical memory than its full address space needs — until too many processes compete for too little memory and the system spends more time paging than computing, a collapse called thrashing.
A file system's real job is turning a flat array of disk blocks into named, hierarchical files — an inode holds one file's metadata and the block pointers that locate its data, while a directory is just a special file mapping names to inode numbers.
A crash in the middle of a multi-block update (a new inode, a new data block, an updated bitmap) can leave a file system's on-disk structures inconsistent — journaling fixes this by writing the intended changes to a log first, making the whole update recoverable as a single atomic step.
Every mechanism in this discipline working together on one concrete timeline: a shell forks and execs a program, the scheduler picks it, it touches a page that isn't resident yet, and the resulting page fault ends in a real disk read through the file system.