Code of the Day

Operating Systems Concepts

What an operating system actually does — processes, threads, scheduling, virtual memory, context switching, and the system-call boundary between user code and the kernel.

Operating Systems12 min read
By the end of this lesson you will be able to:
  • Explain the two main roles of an OS — hardware abstraction and resource management
  • Distinguish a process from a thread and describe what state each carries
  • Compare FIFO, round-robin, and priority scheduling and identify their failure modes
  • Explain what virtual memory is and why it allows programs to address more memory than is physically present
  • Describe what happens during a context switch and why frequent switching has overhead
  • Identify the role of a system call as the boundary between user code and the kernel

Your program never talks to hardware directly. Between your code and the CPU, the disks, the network card, and the display sits the operating system — a piece of software that abstracts over the machine so that every program sees a clean, consistent interface regardless of which physical device it is running on. Understanding what the OS does explains a large fraction of the performance characteristics and failure modes you will encounter in real systems.

What an OS does

The OS has two fundamental jobs.

The first is hardware abstraction. A program reads from a file using the same read() system call whether the file lives on a USB stick, an NVMe SSD, a network share, or a RAM disk. The OS presents a unified virtual interface and translates it to the correct driver calls underneath. This is why code written on one machine can run on another with different hardware.

The second is resource management. Multiple programs run on the same machine simultaneously, competing for CPU time, memory, and I/O bandwidth. The OS is the arbiter: it decides which program runs when, how much memory each gets, and who gets to write to a shared file. Without this arbitration, programs would corrupt each other's memory and monopolise the CPU.

The part of the OS that handles these responsibilities directly is the kernel. The kernel runs in a privileged CPU mode (ring 0 on x86) that has unrestricted access to hardware. User programs run in an unprivileged mode (ring 3) and can only access hardware through the kernel — via system calls.

Processes and threads

A process is an instance of a running program. The OS gives each process the illusion that it owns the entire machine: its own address space (a private view of memory), its own open file descriptors, its own signal handlers, and its own program counter. Two processes cannot accidentally overwrite each other's memory because their address spaces are isolated by the hardware's memory management unit (MMU).

A thread is a unit of execution within a process. All threads in a process share the same address space and file descriptors, but each thread has its own:

  • Program counter — where in the code this thread currently is.
  • Stack — its own call stack and local variables.
  • Register set — the CPU register values for this thread.

Because threads share memory, communication between threads is fast (just a shared variable) but dangerous (a data race can corrupt that shared variable). Communication between processes is safer (isolated address spaces prevent corruption) but slower (requires an OS-mediated channel like a pipe or socket).

Process A                    Process B
+------------------+         +------------------+
| Virtual memory   |         | Virtual memory   |  <- isolated
| Thread 1: PC, SP |         | Thread 1: PC, SP |
| Thread 2: PC, SP |         +------------------+
| Shared: heap,    |
|   file handles   |
+------------------+

Modern languages have varying concurrency models on top of OS threads. Go uses goroutines (lightweight green threads multiplexed onto OS threads). Node.js uses a single OS thread with an event loop. Python's CPython has the GIL, which prevents true parallel execution of Python bytecode across threads. These are all different strategies for managing the cost of OS thread creation and context switching.

Scheduling

The CPU can run only one thread per core at a time. Scheduling is the OS policy that decides which ready thread runs next. Three classic algorithms illustrate the design space:

First-In, First-Out (FIFO / FCFS) — run each process to completion before starting the next. Simple to implement but catastrophic when a long job arrives first: a 60-second batch job blocks all short interactive jobs behind it ("convoy effect").

Round-robin — give each process a fixed time slice (typically 1–10 ms), then preempt it and move to the next process in a circular queue. Every process makes progress; no single process can monopolise the CPU. The cost is context switching overhead: switching threads has a measurable price (flushing pipeline state, TLB entries, cache lines — see below). Very short slices increase responsiveness but spend more time switching than running.

Priority scheduling — assign each process a priority level; always run the highest-priority ready process. Effective when some work (OS interrupt handlers, real-time tasks) genuinely must run first. The risk is starvation: low- priority processes may never run if high-priority ones keep arriving. Real schedulers add aging — slowly raising the priority of processes that have been waiting — to prevent this.

Modern OS schedulers (Linux CFS, Windows HPET scheduler) are sophisticated hybrids that blend priority, fairness, and interactivity heuristics. The principles above are their building blocks.

Virtual memory and page tables

Each process believes it has access to a large, contiguous address space — on a 64-bit system, up to 2⁴⁸ bytes (roughly 256 TB). Physical DRAM is far smaller. Virtual memory is the mechanism that creates this illusion.

The address space is divided into fixed-size pages (typically 4 KB). A page table maps each virtual page number to either a physical frame in DRAM or a marker saying "this page is not currently in RAM." The hardware Memory Management Unit (MMU) translates every virtual address to a physical address on the fly, using the page table as a lookup.

When a process accesses a virtual address whose page is not in DRAM, a page fault interrupt fires. The OS handles it by loading the missing page from disk (swap space), updating the page table, and resuming the process. From the process's perspective, nothing happened — but it paid a disk-latency penalty.

Virtual address  →  [MMU + page table]  →  Physical address
  0x00401000               |                  0x01F34000   (in DRAM)
  0x7FFC0000               |                  page fault → load from disk

Page tables also enforce protection: the OS marks pages as readable, writable, or executable, and the MMU enforces this. A stack overflow that writes past the last allocated stack page triggers a segmentation fault because the page beyond the stack is marked not-writable — or not mapped at all.

Context switching overhead

When the OS decides to switch from one thread to another, it performs a context switch:

  1. Save the current thread's register set, program counter, and stack pointer to its thread control block (TCB) in kernel memory.
  2. Load the next thread's registers, PC, and SP from its TCB.
  3. If the threads belong to different processes, also switch the page table pointer — the MMU now translates addresses through the new process's page table.

The raw register save/restore is fast (dozens of cycles). The real cost is indirect: the CPU's TLB (Translation Lookaside Buffer — a cache of recent virtual- to-physical translations) is often flushed on a process switch, causing subsequent memory accesses to pay page-table walk penalties. Cache lines belonging to the previous thread are evicted as the new thread warms its own working set.

Benchmarks typically put the fully-loaded cost of a process context switch at 1–10 µs. At a 10 ms time slice this is a 0.01–0.1% overhead — negligible. But a program that creates thousands of threads, each blocking almost immediately, can spend more time in context switches than doing real work. This is the argument for event-loop concurrency and lightweight coroutines.

System calls as the kernel boundary

User code cannot directly read from a disk, open a network socket, or allocate pages of memory — these are hardware operations gated by the privilege boundary. To cross it, user code executes a system call (a syscall instruction on x86-64 or svc on ARM).

The CPU switches to kernel mode, the kernel validates the request and performs the operation, then switches back to user mode and returns the result. This mode switch is not free: it flushes some CPU state and pays a round-trip through the kernel's dispatch table. A system call costs roughly 50–500 ns on modern hardware.

User space         |  Kernel space
                   |
read(fd, buf, n) --+---> sys_read()
                   |       → file-system layer
                   |       → block device driver
                   |       ← data copied to buf
                   +<--- return n (bytes read)

High-performance I/O libraries (Linux io_uring, Windows IOCP) batch multiple system calls into a single crossing to amortise this cost. The principle is the same: every crossing of the user/kernel boundary has overhead, so cross it as rarely as necessary.

"User space" and "kernel space" are not just an abstraction — they correspond to different CPU privilege levels enforced in hardware. A bug in user code cannot corrupt kernel memory. A bug in kernel code (a kernel module, a driver) can corrupt anything and cause a kernel panic (Windows: BSOD). This is why device drivers are among the most carefully reviewed code in existence.

Where to go next

Processes and threads communicate over networks as well as shared memory. The Networking Fundamentals track covers the layered model that the OS's network stack implements, and how TCP turns raw packet delivery into reliable streams. For a deeper look at how concurrency is managed in a specific language, see the Go track (goroutines + channels) or the Rust track (thread-safety via the type system).

Knowledge check

  1. 1.
    Which part of the operating system runs in privileged CPU mode and has direct access to hardware?
  2. 2.
    Which of the following are private to each thread within a process (not shared with other threads)?
  3. 3.
    Round-robin scheduling can cause low-priority processes to starve indefinitely.
Finished reading? Mark it complete to track your progress.

On this page