---
title: "Operating Systems overview"
description: "Watch what an operating system actually does — share one CPU among many processes with FCFS, SJF, round robin and MLFQ, translate every memory address through a page table and TLB, turn a path into bytes through directories and inodes, and lose an update to a race between two threads — animated step by step alongside the code."
source: https://vizlearn.app/cs/os
---

# Operating System Visualizations

An operating system spends its life arbitrating between programs that all believe they own the machine. These visualizations animate four of those arbitrations: **which process gets the CPU next**, **where in physical memory a program's addresses actually point**, **how a path on disk becomes bytes**, and **what happens when two threads reach for the same variable at once**. Each lesson runs step by step against its real code, so you can watch a short job jump the queue, a CPU-hog get quietly demoted, a load trap into the kernel and go to disk, or a delete that removes a name and leaves every byte where it was.

They come from one illusion. A process is written as though it has a processor to itself, a memory to itself, and files that simply *are* their names. It has none of those, and the operating system's job is to keep the story consistent while dozens of other processes are told the same thing.

## Sharing the CPU

A computer almost always has more work than processors — often one CPU and dozens of runnable processes. The scheduler is the referee, and its policy is its entire personality: one optimizes throughput, another responsiveness, another fairness, and they openly disagree about who deserves the CPU next.

Every policy answers the same question and differs only in two choices:

- **Who gets picked.** By arrival order, by shortest remaining work, by whose turn it is in a rotation, or by a priority that changes as the OS watches how each process behaves.
- **Whether it can be interrupted.** A *non-preemptive* policy lets a process run until it blocks or finishes; a *preemptive* one can yank the CPU away mid-run — which is what makes responsiveness possible and context switches expensive.

Read those two choices off any policy and you can predict its behavior: its average wait, whether a long job can starve a short one, and how it feels to sit in front of it.

| Policy | Picks next by | Preemptive | The trade it makes |
|---|---|---|---|
| FCFS | arrival order | No | dead simple; one long job stalls everyone behind it (the convoy effect) |
| SJF | shortest burst | No | minimizes average wait, but long jobs can starve |
| Round Robin | a fixed time slice, rotating | Yes | fair and responsive; the slice size trades latency against switch overhead |
| MLFQ | priority, demoting CPU-hogs | Yes | approximates SJF without knowing job lengths — the practical default |

The table is really a search for one policy that is fast, fair, and doesn't need to predict the future. FCFS is fair but slow; SJF is fast but needs to know job lengths and can starve; round robin is responsive but oblivious to length. MLFQ is the working compromise — it *learns* each process's behavior and schedules accordingly.

## Sharing Memory

The other illusion is the address. Every pointer a program holds is virtual, and on every load and store the hardware translates it: split the address into a page number and an offset, look the page number up, swap it for a physical frame number, keep the offset.

That lookup has three rungs, and they are orders of magnitude apart — roughly a cycle if the translation is cached in the TLB, a hundred if the CPU has to read the page table (which lives in RAM, so the lookup is itself a memory access), and around a million if the page is not in memory at all and the kernel has to go to disk. That last case is a **page fault**, and it is where two halves of this collection meet: the CPU cannot usefully wait a million cycles, so the kernel runs somebody else. A page fault is a scheduling decision wearing a memory-access costume.

Once RAM is full, that fault forces a second decision — **page replacement**. Somebody resident has to leave, and the policy that picks the victim is, at the level of the machine, one sticky note per frame. FIFO writes the arrival time on it and never touches it again. LRU rewrites it on every hit. Clock writes a single bit and lets a rotating hand wipe it. On the same twelve accesses through the same three frames, that difference alone is worth two disk trips, which is why the last of those notes is what real kernels keep.

| Policy | Remembers per frame | Evicts | Where it breaks |
|---|---|---|---|
| FIFO | the access that loaded the page | the oldest arrival | ignores hits entirely, so it evicts hot pages — and can fault *more* with more memory (Bélády's anomaly) |
| LRU | the access that last touched the page | the coldest page | a loop one page wider than RAM faults on every access |
| Clock | one reference bit | the first page whose bit is still 0 | with every bit set it degenerates into FIFO |

## Naming Data on Disk

Memory is not where anything survives a reboot, and the layer that makes data outlast a process invents its own illusion: that a file is a thing with a name. It is not. A file is an **inode** — a small record holding the type, the size, the link count and the block numbers — and the name is a row in the data of the directory above it, mapping that name to an inode number.

Once you see that split, the cost model follows from it. Resolving a path is a loop of two reads per component (the directory's inode, then the directory's data), so opening `/home/user/notes.txt` costs seven disk reads before a single byte of the file arrives, and the file's size never enters the arithmetic. So does the behaviour of `rm`, which edits the *parent directory* and decrements a counter, and only frees blocks when that counter reaches zero — which is why a hard link survives the deletion of the name it was copied from, and why nothing is erased even then.

## Sharing One Variable

The last arbitration is the one the operating system hands back to you. Two threads inside a single process share their memory by design, and the moment they touch the same variable, the scheduler's freedom to switch between any two machine instructions becomes your problem. `counter = counter + 1` is three instructions — load, add, store — and a switch landing in the middle lets a second thread read a value that is about to be overwritten. The increment is computed, stored, and then quietly erased, with nothing anywhere reporting it.

A mutex fixes that by making the critical section un-interleavable rather than by taming the scheduler: a thread that finds the lock held blocks instead of interleaving, so the same hostile schedule that loses two updates now loses none. The lock brings its own failure with it, though. Two locks taken in opposite orders, one preemption in between, and both threads wait on each other forever.

## A Recommended Learning Order

Start with **process scheduling**, which puts FCFS, shortest-job-first, and round robin side by side on the same workload. Running the identical set of processes under all three is the fastest way to feel the trade-offs — the convoy stall under FCFS, the starvation risk under SJF, the steady rotation of round robin — because the only thing that changed is the policy.

Then **MLFQ scheduling**, the multi-level feedback queue that real operating systems actually use: it starts every process at high priority and demotes the ones that hog the CPU, approximating shortest-job-first behavior without ever being told how long a job will run. It is the synthesis the first lesson sets up.

Then **virtual memory**, which is the other half of the machine. It animates the full translation — address split, TLB probe, page-table walk, page fault, disk — over a scale model small enough to watch, and it ends by drawing the run's cost on one bar, where three disk trips fill the whole width and every translation the hardware did is a sliver a pixel wide. It also shows what a segfault really is: the same page-table walk, ending at a mapping that was never made.

Then **page replacement**, which picks up exactly where virtual memory stops. That lesson is built so RAM can never fill; this one starts from the moment it has. It runs one reference string under FIFO, LRU and Clock and counts the faults each policy pays, and it ships the two failure cases as presets: a FIFO run that gets *worse* when you give it a fourth frame, and an LRU run on a four-page loop through three frames that faults on all twelve accesses.

Then **file systems**, which take the same bookkeeping idea one level down, onto storage. Bitmaps, a table of records, and pointers to blocks — the machinery will look familiar after virtual memory — but here the lookup is driven by a name rather than an address, and the lesson ends on the counter that decides whether deleting a name deletes anything at all.

Threads and synchronization can be taken any time after process scheduling, and it is the one that will change how you write code. It turns the scheduler from a service into a hazard: the same context switches, pointed at a single shared counter, with a default run that expects 6, ends at 4, and marks on the CPU strip exactly where the two missing updates died.

## All Operating System Visualizations

Pick a lesson below to open its interactive player — change the workload or the access pattern, step through the timeline, and watch the code highlight move in lockstep with the animation.
