Part 418 min read

BIOS and the OS as Middleman

The first program that runs, the program that runs after it, and why your computer has both. The boot sequence as a moral lesson in layered systems.

What you will learn

  • Walk the boot sequence from power-on to login prompt, knowing what each layer does
  • Explain why a computer needs an OS to switch between applications
  • Reason about memory allocation, loaders, linkers, and virtual addresses
  • Recognise deadlock and the scheduling problems an OS quietly solves

BIOS and the OS as Middleman

We have spent a great deal of time on hardware. Cables, signals, NICs, RAM, CPUs, GPUs, interconnects. None of those, on their own, do anything you would recognise as computing. Press the power button on a freshly assembled machine with no operating system installed and you will get, at best, a confused error message about "no bootable device."

This chapter is about the program that sits between the hardware and you. It is the first piece of software that runs after power-on, and the last piece you can credibly avoid learning.

The BIOS — the program before there were programs.

When you press the power button, the CPU does not start by running your code, or the operating system, or anything you wrote. It runs the BIOS — Basic Input/Output System. The BIOS lives in a small flash chip on the motherboard. It is provided by the motherboard manufacturer, not by Microsoft or Apple or Canonical. It is, in a real sense, the first layer of operating system on a PC: it knows enough about the hardware to configure it, and not much else.

What does the BIOS do?

It tests the basic hardware. RAM, CPU, peripherals — is everything responding? This is the "POST" (Power-On Self-Test) you sometimes see flash by during a boot.

It configures the motherboard. It tells the chipset which devices are present, which interrupts they will use, which slices of address space they get for memory-mapped I/O. We saw the consumer side of this in the wire-to-RAM chapter: when the kernel later accesses a NIC's registers via MMIO, those addresses were carved out by the BIOS.

It hands off to the boot loader. Every bootable disk has, at sector 0 of its lowest cylinder, a small program — the boot record. The BIOS reads this code into memory and jumps to it. From that moment on, the BIOS's job is done. The boot loader takes over and decides what to load next.

That decision is usually: load the operating system's kernel.

The boot loader, the kernel, and the rest of the cast.

The boot loader is a tiny, hardware-aware program whose sole job is to find the kernel on disk, load it into RAM, and start it running. On Linux it is usually GRUB. On modern systems it has been partially replaced by UEFI, which combines BIOS and boot loader responsibilities, but the shape of the handoff is the same.

The kernel is the first "real" piece of the operating system. It sets up memory management, initialises drivers, mounts the root filesystem, starts the first user-space process (init or systemd), and from there the rest of the system fans out — daemons, login prompts, eventually a desktop or a shell prompt.

A senior engineer can name every step in this chain. A junior engineer can run dmesg and feel mildly surprised at how much was happening before they logged in.

Why a computer needs an operating system at all.

Here is a question worth chewing on. Could you run a program directly on a CPU, with no operating system in between?

The honest answer is: yes. Embedded systems do this all the time. A microwave's controller, a router's firmware, an early game console — they all ran a single program directly on hardware. Boot loader, application, hardware. Done.

The problem starts when you want to do more than one thing.

Imagine a computer with no OS. To switch from a word processor to a spreadsheet, you would have to: write a custom boot loader for the spreadsheet, reboot the machine, wait for it to come up, do your work, save it, then reboot to get back to the word processor. Every "application switch" is a full restart. There is no clipboard. There is no shared file system. There is no concept of running two things at once.

Now imagine a computer with multiple peripherals — a keyboard, a printer, a network card, a screen, a USB device. Every application that wants to use any of them has to ship its own driver for every model of every device. There is no shared resource manager.

Now imagine a computer with sensitive data on it. One application accidentally writes to the wrong memory address and corrupts another application's memory, or reads another application's passwords. There is no isolation.

These problems — switching, sharing, and isolating — are the ones the operating system exists to solve. It is the middleman. It says: applications, you do not get to touch the hardware directly. You ask me, and I will mediate. In return, you get the ability to coexist with other applications, share peripherals, and be protected from each other.

Memory management — where every program thinks it lives at address zero.

The OS's most consequential job, in my opinion, is managing memory.

Here is the problem. Every program, when compiled, assumes it gets to start at some specific memory address — historically, address zero. If two programs are loaded at the same time and both think they own address zero, they will overwrite each other's data, code, and stacks within microseconds. The system will crash, or worse, silently corrupt itself.

The OS solves this with a layer of indirection. Each program runs inside its own virtual address space. From the program's perspective, it has access to a clean, contiguous range of addresses starting at zero. Behind the scenes, the OS (with hardware assistance from the CPU's MMU — memory management unit) translates those virtual addresses into the actual physical RAM addresses they map to. Two programs both write to "address zero" — and the OS quietly routes those writes to different physical locations.

The mechanism that loads a program into memory and rewrites its address references is called the loader and linker. The loader copies the program into RAM at whatever physical address was available. The linker resolves references in the program — function calls, library imports, variable addresses — so that they point to the right places in the loaded copy.

This is also why, when memory runs out, the OS can extend physical RAM by spilling onto disk. Swap space is a region of disk reserved for this overflow. The OS moves cold memory pages (less-recently used) out to disk to free up physical RAM for the active process. When the cold pages are needed again, the OS brings them back and (if necessary) swaps something else out. This is called paging, and the algorithms that decide which pages to swap (LRU, clock, working set) are an entire field of their own.

Swap works. It is also dramatically slower than RAM. Once a system starts swapping heavily, you can feel it. Applications stall. The cursor hesitates. The disk activity light goes solid. This is the OS doing its best to keep everything alive at the cost of speed. Senior engineers know to watch for this: if your application is "slow" but the CPU is idle, look at the swap.

Scheduling and the problem of priorities.

The CPU is the most contested resource in the machine. Many processes want time on it. The OS has to decide who runs when.

This is the scheduler. It is a kernel component that picks, every few milliseconds, which runnable process gets the CPU next. The decision is based on a combination of priority, fairness, recent CPU usage, and whether the process is interactive (typing, mouse) or batch (computation).

When a kernel-level program has too low a priority compared to a user-level program, you see the symptoms immediately: the cursor stutters, the screen freezes, the system feels "stuck." What is actually happening is that the kernel is not getting enough scheduler time to do its housekeeping. If you have ever wondered why your laptop sometimes feels locked up even though there is nothing visibly wrong, the answer is often a runaway process at high priority. The OS is doing its job; the priorities are wrong.

There are knobs to tune this — nice, renice, real-time priority classes, cgroup CPU shares — but the default scheduler works well enough most of the time. The job of a senior engineer is to know it exists, recognise the symptoms, and understand the levers.

Deadlock and the algorithm of last resort.

When many processes share many resources, you get deadlock. A holds resource X and waits for Y. B holds Y and waits for X. Neither will let go. Neither can proceed.

The OS prevents this in a few ways. One is to enforce ordering — if every process always acquires resources in the same order, deadlock cannot occur. Another is to detect cycles in the resource-allocation graph and break one of them — usually by killing the process. Another is to require processes to declare in advance which resources they will need, so the OS can grant or refuse the request without committing.

The deeper lesson — and this generalises beyond operating systems — is that whenever you have multiple actors sharing scarce resources, you need a protocol for arbitration. Without one, the system eventually deadlocks. With one, the system either runs smoothly or fails predictably.

We will see this pattern again. In distributed systems. In databases. In real life. Wherever there are limited resources and competing demands, the question is the same: who decides, and how?

Wrapping the layer cake.

The OS, in summary, sits between the hardware and your code. It boots after the BIOS, it manages every resource on the machine, it gives every process its own view of memory, it schedules CPU time, it arbitrates conflicts, and it shuts down cleanly when you ask it to.

Above the kernel sits the shell — the program you actually interact with. The shell is the bridge between you and the kernel. It might be a graphical desktop on a Mac. It might be a terminal on a server. It might be a TTY on a single-user Linux system in single-user mode. The kernel does the work; the shell is where you talk to it.

The next chapter is about the philosophy that makes Linux feel different from every other OS you have used. Then we will look at processes in depth, then at virtual machines and containers. The thread that ties them all together is the one we just laid out: the OS is what turns a machine into a computer.

Push On It

  1. Boot a Linux distribution (in a VM is fine — we will get to those in chapter 4 of this part). Run dmesg immediately after login. Read every line until you reach the login prompt. Annotate, for at least 20 lines, what just happened.
  2. Look up your machine's BIOS or UEFI settings (reboot, press the appropriate key — F2, Del, etc.). Browse them. Note the device boot order, the secure boot setting, and any settings related to virtualisation. Do not change anything you do not understand.
  3. Read about virtual memory and the role of the MMU. Walk the chain from a malloc() in your code to a physical page in DRAM. Where does the OS make the address translation happen, and where does the hardware help it?
  4. Look up sysctl vm.swappiness on a Linux system. What does the current value mean? What changes when you raise or lower it? Read at least one informed argument for setting it to a non-default value.

Watch the Boot

Boot any Linux distribution in single-user mode (or watch a verbose boot log via `dmesg` after booting normally). Identify, in order: BIOS handoff, kernel load, device discovery, init system handoff, daemon startup. You are watching a layered system come to life.

Flashcards (6)

What is the BIOS, and where does it live?

What lives at sector 0, position 0 of a bootable disk?

Why can't a computer just run one program forever?

+3 more flashcards

BIOS and the OS as Middleman | Junior2Senior.dev | Junior2Senior.dev