System Initialization

1. Administrative Announcements

1.1. Exam registration

  • Students who are eligible must register for the final exam in LSF/HISPOS.
  • There will probably not be an additional exam registration through CMS.
  • The instructor is not permitted to make exceptions for students who simply forget to register.
  • Students in programs that cannot register through LSF/HISPOS should contact the course staff and follow the procedure appropriate for their program.

1.2. Remaining lectures and review session

Three sessions remained after this lecture:

  1. A lecture about recent operating-systems research and the research performed by the instructor’s group.
  2. A guest lecture by a colleague working on topics related to computer architecture and machine-learning systems.
  3. A final exam review session on the following Wednesday.

The two research-oriented lectures were described as interesting but not exam-relevant.

For the review session, students should post questions or topics in the forum beforehand.

This allows the instructor to:

  • identify the topics that caused difficulties;
  • retrieve the relevant slides;
  • prepare additional explanations;
  • use the review time more effectively.

Questions could be specific, or they could simply name a topic such as nested virtual-memory virtualization.

1.3. Final exam scope

  • The final exam covers the entire course, including material taught before the midterm.
  • The second half of the course will receive more emphasis because it has not yet been examined.
  • Material from milestones 1 through 3 is part of the exam scope.
  • Milestone 3 remains exam-relevant even though its submission deadline is after the exam.
  • The optional bonus milestone 4 will not be examined.

1.4. Milestone feedback

  • The milestone 2 test results and design-document scores should be visible in CMS.
  • Students should contact the course staff if submitted work or scores appear to be missing.

2. Central Question: What Happens When a Computer Is Turned On?

The lecture studies how a computer moves from a powered-off state to a usable shell prompt.

At a high level, the sequence is:

  1. Power on.
  2. CPU reset.
  3. Firmware execution.
  4. Bootloader execution.
  5. Kernel entry and kernel initialization.
  6. Userspace initialization.

Kernel initialization itself contains several major stages:

  1. Basic CPU initialization.
  2. Memory discovery and memory-management initialization.
  3. Interrupt and timer initialization.
  4. Device discovery and driver initialization.
  5. Root-file-system initialization.
  6. Starting the first userspace process.

The important observation is that a large amount of complicated work happens before the operating-system kernel begins executing.

There is also no perfectly clean boundary after which firmware becomes completely irrelevant. Information and control may continue to pass between firmware, bootloader, kernel, and hardware throughout the initialization process and sometimes even during normal operation.

3. Power-On and CPU Reset

3.1. Stabilizing the hardware

Turning on a real computer is more complicated than merely supplying electricity to the CPU.

A modern board may contain hardware responsible for:

  • controlling different voltage rails;
  • waiting until power becomes stable;
  • sequencing the activation of different components;
  • monitoring the board;
  • deciding when the main processor may safely be released from reset.

Server-class boards may contain a separate management processor that participates in this process.

Different components often have to be powered on in a particular order.

For example, part of an interconnect may have to be initialized before the system can even reach another controller connected behind it.

3.2. Establishing a known processor state

Before reset, the internal contents of processor state elements cannot be treated as meaningful.

The hardware reset sequence therefore places the CPU into a known architectural state.

Among other things, reset determines:

  • the initial processor mode;
  • the initial values or defined states of important registers;
  • the address from which the CPU fetches its first instruction.

This starting address is commonly called the reset vector.

3.3. Where the first instruction comes from

Main memory is volatile.

Immediately after power-on, ordinary RAM does not yet contain a kernel or any other useful program.

The reset vector therefore points to some form of non-volatile memory, such as:

  • ROM;
  • flash memory;
  • internal processor ROM;
  • platform-specific non-volatile storage.

This memory contains the first firmware instructions.

The basic transition is therefore:

Power becomes stable
        |
        v
Hardware reset establishes a known CPU state
        |
        v
CPU starts executing at the reset vector
        |
        v
Instructions are fetched from ROM or flash

4. Firmware

4.1. Purpose of firmware

Firmware performs the early platform-specific initialization required before a normal operating system can run.

Its goal is not necessarily to provide the complete operating-system environment.

Its main goal is:

Leave the machine in a state in which an operating system or bootloader can start.

4.2. Why initialization is implemented in software

Some initialization could theoretically be hardwired into the hardware.

However, hardware state machines become difficult to design once initialization requires:

  • long sequences of operations;
  • conditional branches;
  • waiting for devices to become ready;
  • retries;
  • platform-specific configuration;
  • different procedures for different board revisions.

Consequently, hardware normally performs only enough work to begin executing firmware.

The more complicated sequencing is moved into software.

4.3. Typical firmware responsibilities

Depending on the platform, firmware may initialize or configure:

  • power-management components;
  • memory controllers;
  • main memory;
  • interrupt controllers;
  • I/O interconnects;
  • storage controllers;
  • processor-related platform features;
  • buses through which additional devices can be reached.

It may also determine which hardware is installed.

For example, on a configurable desktop or server, firmware may need to discover:

  • which processor is installed;
  • how many memory modules exist;
  • how much memory is available;
  • which PCI devices are connected;
  • which storage devices are present.

4.4. Power-on self-test

Firmware may perform basic hardware tests during startup.

This is traditionally called the power-on self-test or POST.

Possible checks include:

  • whether installed memory responds correctly;
  • whether expected controllers are present;
  • whether basic devices can be initialized;
  • whether a component reports an error.

Large servers with hundreds of gigabytes or terabytes of memory may spend a noticeable amount of time checking and initializing memory.

4.5. Platform-specific complexity

Firmware initialization is extremely specific to the concrete platform.

Even two x86 motherboards may require different internal initialization procedures.

On embedded SoCs, merely enabling a storage controller can require a complicated sequence involving:

  • clocks;
  • power domains;
  • reset lines;
  • interconnect configuration;
  • controller registers;
  • readiness checks.

The lecturer described an example in which researchers spent a very long time determining the correct sequence needed to initialize an SD-card controller, despite having thousands of pages of documentation.

Thus, firmware hides a large amount of low-level platform complexity from the operating system.

5. The Bootloader

5.1. Overall goal

The bootloader’s goal is to get the operating-system kernel running.

Conceptually, it must perform four tasks:

  1. Locate the kernel.
  2. Load the kernel into memory.
  3. Pass useful information to the kernel.
  4. Transfer control to the kernel’s entry point.

5.2. Locating the kernel

The kernel may come from many possible sources:

  • a hard disk;
  • an SSD;
  • an NVMe device;
  • a USB drive;
  • a floppy disk on older systems;
  • flash memory on an embedded device;
  • a network server;
  • another platform-specific storage device.

A general-purpose machine cannot assume that the kernel always exists in one fixed location.

The system may also be in different situations:

  • booting the normally installed operating system;
  • running an installer from USB;
  • booting a recovery environment;
  • booting remotely over the network;
  • selecting between multiple installed kernels.

Therefore, one of the bootloader’s first problems is discovering where the desired kernel is stored.

5.3. Loading the kernel

Once the kernel has been located, the bootloader must copy it into RAM.

This may require substantial support.

For example, the bootloader may need:

  • a storage-device driver;
  • a file-system reader;
  • a network stack;
  • a USB implementation;
  • an executable-file parser.

A kernel image is not necessarily a flat sequence of bytes.

If the kernel is stored in an executable format such as ELF, the bootloader may have to:

  • parse the executable headers;
  • place code and data segments at appropriate memory addresses;
  • clear regions corresponding to uninitialized data;
  • identify the executable entry point.

Some kernels are compressed.

In that case, either the bootloader or a small decompression wrapper around the kernel must decompress the kernel before normal kernel initialization begins.

5.4. Information passed to the kernel

Bootloaders normally pass metadata in addition to the kernel image.

Possible information includes:

  • the device from which the system was booted;
  • a physical-memory map;
  • the kernel command line;
  • the intended root-file-system device;
  • modules or initial RAM disks;
  • firmware data structures;
  • display or console information.

The exact interface is bootloader-specific.

For example, GRUB defines a protocol through which it passes structured information to the kernel.

5.5. Transferring control

After loading the kernel and preparing the required metadata, the bootloader jumps to the kernel’s entry point.

At this moment:

  • the kernel is laid out in memory;
  • the CPU is in some documented state;
  • certain metadata structures are available;
  • execution begins at the kernel’s first instruction.

From the operating system’s perspective, this is the beginning of kernel execution.

However, a great deal of platform initialization has already happened.

6. Legacy x86 Booting with BIOS

6.1. The BIOS model

BIOS stands for Basic Input/Output System.

In the traditional x86 BIOS model, firmware provides low-level services for operations such as:

  • reading disk sectors;
  • writing disk sectors;
  • displaying characters;
  • obtaining basic platform information.

Boot code invokes these services using software interrupts and register-based calling conventions.

Conceptually, this resembles a primitive system-call interface.

6.2. Selecting a boot device

The BIOS has a configurable boot order.

For example, it might try:

  1. a USB device;
  2. the first hard disk;
  3. a network interface;
  4. another storage device.

For each candidate device, the BIOS reads its first sector and checks for a special two-byte boot signature.

If the signature is valid, the BIOS treats that sector as bootable, loads it into memory, and begins executing it.

6.3. Boot sector and Master Boot Record

A traditional disk sector is 512 bytes.

Two bytes are needed for the BIOS boot signature, leaving only 510 bytes for boot code and other data.

This first sector is often called:

  • the boot sector;
  • the Master Boot Record or MBR in the traditional partitioned-disk setting;
  • stage 1 of the bootloader.

A modern compiler can generate far more than 510 bytes for even a trivial program.

Therefore, the first-stage bootloader cannot contain a complete storage stack, file-system implementation, user interface, and kernel loader.

Its principal job is usually to load a larger second-stage bootloader.

6.4. Loading stage 2

Stage 1 can use BIOS disk services to read additional sectors into memory.

However, it still needs to know which sectors contain stage 2.

A traditional solution is to record a list of disk-block locations in the first-stage bootloader.

When a bootloader such as GRUB is installed, an installation utility can:

  1. place stage 2 in a file system or reserved disk area;
  2. determine the physical blocks containing it;
  3. store enough block-location information in stage 1;
  4. install stage 1 in the boot sector.

During boot, stage 1 follows this block list and loads stage 2 without understanding the complete file system.

There is an important weakness in this design.

If the stage-2 file is moved or its disk blocks change, the block list in stage 1 becomes stale.

Unless the bootloader installation data is updated, the machine may no longer boot.

6.5. Stage-2 functionality

The second stage is large enough to contain much richer functionality.

A general bootloader such as GRUB may include:

  • read-only implementations of file systems such as ext-family file systems, FAT, or XFS;
  • drivers for different storage mechanisms;
  • support for loading kernels from files;
  • a menu for selecting operating systems or kernel versions;
  • network-boot functionality;
  • command-line processing;
  • support for loading additional modules or an initial RAM disk.

The bootloader usually needs only to read the file system.

It does not need the full allocation and update machinery required for writable file-system operation.

Nevertheless, supporting many file systems and devices still requires a large amount of code.

7. Legacy x86 Quirks

7.1. Starting in 16-bit mode

Modern x86 processors preserve backward compatibility with very old processors.

Consequently, an x86 CPU begins booting in 16-bit real mode.

Traditional BIOS services are also designed to be called from this mode.

A bootloader that wants to use BIOS services may therefore need to execute 16-bit code.

7.2. The A20 gate

Early x86 software sometimes depended on addresses wrapping around at the one-megabyte boundary.

When later processors gained additional address lines, preserving this behavior required the A20 address line to remain disabled during early boot.

The bootloader must explicitly enable A20 before accessing memory beyond the old real-mode boundary reliably.

Historically, one way of controlling A20 involved communicating with the keyboard controller, even though the purpose was memory addressing rather than keyboard input.

This is an example of historical compatibility producing an unintuitive modern interface.

7.3. Address-space limitations

The different early x86 modes impose severe address-space limitations.

  • Real mode is effectively based on the old approximately one-megabyte address space.
  • Later 16-bit protected-mode designs could address a larger physical space, but still far less than modern kernels need.
  • Modern kernels therefore transition to 32-bit or 64-bit operation.

Furthermore, not every physical address corresponds to usable RAM.

Some physical address ranges may contain:

  • ROM;
  • firmware;
  • device registers;
  • reserved regions;
  • holes without any backing hardware.

7.4. Switching processor modes

A sophisticated BIOS bootloader may need to switch between processor modes.

For example:

  • 32-bit mode is useful for addressing more memory and running more convenient code.
  • 16-bit mode may still be required when invoking legacy BIOS services.

The bootloader may therefore repeatedly transition between 16-bit and 32-bit modes.

This adds additional complexity.

7.5. Practical conclusion

Writing a robust bootloader for general x86 hardware requires dealing with:

  • strict size limits;
  • BIOS calling conventions;
  • multiple processor modes;
  • the A20 gate;
  • disk layout details;
  • many storage devices;
  • multiple file systems;
  • memory-map discovery;
  • numerous compatibility quirks.

The lecturer’s practical recommendation was:

For a real x86 operating system, use an established bootloader such as GRUB rather than writing the entire boot path yourself.

A simple teaching operating system such as Pintos can use its own bootloader because it assumes a highly constrained and predictable machine configuration.

8. UEFI

8.1. Motivation

UEFI is the modern replacement for the traditional BIOS boot interface.

It provides a more structured and significantly richer environment.

Although UEFI is often associated with x86, it is also used on other architectures, including server-class ARM systems.

8.2. EFI System Partition

A UEFI system normally contains an EFI System Partition.

This partition uses a standardized file system, normally FAT.

Rather than loading an unstructured 512-byte boot sector, UEFI firmware can:

  • understand the partition’s file system;
  • locate a named executable file;
  • load it as a normal file;
  • execute it.

UEFI boot executables normally use the PE executable format.

They are not necessarily Windows programs, even though Windows also uses PE.

The firmware stores boot entries that identify paths to particular executables on the EFI System Partition.

Operating-system installers normally configure these entries automatically.

8.3. Richer execution environment

UEFI provides a set of services that resembles a small runtime environment.

These services can include:

  • file-system access;
  • memory allocation;
  • console input and output;
  • graphics or display support;
  • access to firmware configuration;
  • retrieval of memory maps;
  • debugging and logging facilities.

This is much richer than the traditional BIOS interface, which mostly exposes low-level sector and display operations.

8.4. Is a separate bootloader still necessary?

In principle, UEFI can directly load a sufficiently prepared kernel executable.

Therefore, a separate bootloader is not always strictly required.

In practice, systems frequently still use a bootloader such as a UEFI version of GRUB.

Reasons include:

  • presenting a boot menu;
  • choosing between multiple kernels;
  • loading an initial RAM disk;
  • processing kernel parameters;
  • supporting both legacy BIOS and UEFI through a common bootloader design;
  • providing a consistent environment to the kernel.

9. Embedded Booting and U-Boot

9.1. More static hardware configurations

Embedded platforms are often much more predictable than general-purpose PCs or servers.

The developer may know in advance:

  • the exact processor;
  • the amount and location of memory;
  • the storage controller;
  • the location of the kernel;
  • which peripherals are connected;
  • the intended boot procedure.

A bootloader can therefore be compiled for one concrete board and one expected boot method.

9.2. U-Boot

U-Boot is a widely used open-source bootloader for embedded systems.

A U-Boot image is normally built with a platform-specific configuration.

It may be configured to load a kernel from:

  • a known flash offset;
  • an SD card;
  • an embedded file system;
  • a serial connection;
  • a network server.

Although embedded booting is more static, U-Boot can still provide advanced features such as:

  • an interactive serial console;
  • configurable boot commands;
  • network booting;
  • HTTP or other network-based loading;
  • debugging and recovery facilities.

The difference is not that embedded bootloaders have no features.

The difference is that their expected hardware environment and normal boot path are usually known beforehand.

10. Why Separate Firmware and Bootloader?

On a simple embedded machine, it may appear that firmware and bootloader could be merged into one program.

The lecture gave several reasons to keep them separate.

10.1. Firmware should be small and stable

Firmware is stored in a location that may be difficult or risky to update.

If power is lost during an unsafe firmware update, the device may become unable to boot at all.

Therefore, it is useful to keep the fundamental firmware component:

  • small;
  • simple;
  • stable;
  • changed only rarely.

10.2. Bootloaders are easier to update

The bootloader can be stored as ordinary software in flash or another storage device.

It can therefore be updated more easily.

More complicated and changeable functionality can be placed there, including:

  • additional device support;
  • file-system support;
  • networking protocols;
  • recovery interfaces;
  • alternative boot policies.

10.3. Reducing firmware size

Keeping sophisticated functionality outside the initial firmware also reduces the amount of non-volatile memory required for the immutable early-boot component.

Even when memory cost is not a major concern, a smaller trusted component is easier to validate and less likely to fail.

11. State After the Bootloader

When the bootloader finishes, the system is typically in the following state:

  • the kernel image has been loaded into RAM;
  • executable segments have been placed at their intended locations;
  • the CPU is executing at the kernel entry point;
  • the bootloader has provided some metadata;
  • the system may already be in 32-bit or another useful processor mode;
  • some early hardware initialization has already been performed.

The supplied metadata may include:

  • memory-layout information;
  • the boot device;
  • the root-device specification;
  • a command line;
  • locations of additional modules;
  • references to firmware tables;
  • display information.

Exactly what has been initialized depends on the boot protocol.

The kernel must not assume that every subsystem is already fully usable.

12. The Main Kernel Challenge: Hardware Discovery

Once the kernel begins running, it must determine what resources exist in the machine and how to control them.

This is one of the central difficulties of operating-system initialization.

12.1. Resources that must be discovered

The kernel may need to determine:

  • which physical-address ranges contain usable RAM;
  • which ranges are reserved;
  • how many processors exist;
  • what identifiers the processors have;
  • how secondary processors are started;
  • which interrupt controllers exist;
  • how interrupts are routed;
  • which timers are available;
  • which buses exist;
  • which devices are connected;
  • which drivers match those devices;
  • which memory belongs to which NUMA node.

12.2. Why the physical address space is not simply RAM

A 64-bit CPU can express an enormous number of physical addresses, but only a small fraction are backed by installed memory.

Other address ranges may represent:

  • memory-mapped device registers;
  • firmware ROM;
  • platform-reserved memory;
  • nonexistent addresses;
  • apertures for PCI devices;
  • regions hidden by hardware;
  • memory already occupied by the kernel or boot data.

Therefore, the kernel must receive or discover an accurate description of the physical address space before it begins general memory allocation.

12.3. Two broad platform models

The lecture distinguished two broad approaches:

  1. Externally described platforms
  2. Self-describing platforms

Externally described platforms are common in embedded systems.

Self-describing platforms are common in configurable desktops, servers, and other systems whose hardware may vary dynamically.

13. Externally Described Platforms: Device Trees

13.1. Basic idea

A device tree is a structured description of the platform’s hardware.

Rather than requiring the kernel to probe every possible address, the platform provides a data structure that tells the kernel what exists.

The human-readable Device Tree Source format is compiled into a compact binary Device Tree Blob.

The binary blob can be:

  • built directly into the kernel;
  • stored separately;
  • selected and passed by the bootloader;
  • generated from a template and modified by the bootloader.

The slides’ example on pages 22–24 describes memory, CPUs, an interrupt controller, architectural timers, and a serial port.

13.2. Root-level platform description

A device tree may begin with a platform compatibility string:

compatible = "vendor,board-x";

This allows the kernel to determine which board or platform description it is processing.

Properties such as #address-cells and #size-cells define how addresses and sizes are represented in child nodes.

13.3. The chosen node

The chosen node carries runtime boot information rather than a description of one particular hardware device.

For example:

chosen {
    bootargs = "console=ttyAMA0,115200 root=/dev/mmcblk0p2 rw";
    stdout-path = "serial0:115200n8";
};

This tells the kernel information such as:

  • the kernel command line;
  • which device should be used as the root file system;
  • which serial device should be used for early console output;
  • the serial communication settings.

The bootloader may modify these properties before passing the device tree to the kernel.

13.4. Describing memory

A memory node might contain:

memory@80000000 {
    device_type = "memory";
    reg = <0x80000000 0x40000000>;
};

This means that one gigabyte of memory begins at physical address 0x80000000.

This tells the kernel where RAM exists from the hardware’s perspective.

The kernel must still avoid allocating regions already occupied by:

  • its own image;
  • the device-tree blob;
  • bootloader data;
  • reserved firmware structures.

13.5. Describing CPUs

The slide example contains separate nodes such as:

cpu@0 {
    device_type = "cpu";
    compatible = "arm,cortex-a53";
    reg = <0x0>;
};

A CPU node can specify:

  • the existence of a core;
  • the core type;
  • an architectural or platform-specific identifier;
  • information required to start or address the core.

Device trees can also describe heterogeneous systems in which different cores have different capabilities.

This is relevant to designs such as ARM big.LITTLE systems.

13.6. Describing an interrupt controller

An interrupt-controller node identifies both the controller type and the addresses of its control registers.

For example:

gic: interrupt-controller@2c000000 {
    compatible = "arm,gic-400";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x2c000000 0x1000>,
          <0x2c001000 0x2000>;
};

The compatible string allows the kernel to match the hardware to the appropriate driver.

The reg property gives the physical register ranges through which the controller is programmed.

13.7. Describing timers and interrupts

The timer node identifies an architectural timer and its interrupt connections.

An interrupt description may encode several pieces of information, such as:

  • interrupt type;
  • interrupt number;
  • trigger mode or other flags.

The exact interpretation is defined by the interrupt controller’s device-tree binding.

This information allows the kernel to connect the timer driver to the correct interrupt inputs.

13.8. Describing a serial port

A UART node may specify:

uart0: serial@9000000 {
    compatible = "arm,pl011";
    reg = <0x09000000 0x1000>;
    interrupts = <0 33 4>;
};

This tells the kernel:

  • which UART implementation is present;
  • where its memory-mapped registers are located;
  • which interrupt it raises.

The kernel matches "arm,pl011" with its PL011 serial-driver implementation.

13.9. Advantages and limitations

Device trees are convenient when the platform configuration is known and mostly static.

They provide one explicit structure describing the system.

However, they are less natural for highly dynamic hardware in which devices can be added, removed, or rearranged without replacing the platform description.

14. Self-Describing and Dynamically Discovered Platforms

14.1. In-memory descriptions

On more dynamic systems, hardware or firmware prepares data structures in memory that the kernel can inspect.

Examples include:

  • BIOS memory maps;
  • bootloader-provided memory maps;
  • UEFI memory-map services;
  • ACPI tables;
  • PCI configuration structures.

The kernel combines information from several sources to construct a complete model of the machine.

14.2. Memory-map sources

On older BIOS systems, firmware placed information at conventional locations or made it available through BIOS interfaces.

A bootloader such as GRUB could parse this information and pass it to the kernel in a more convenient format.

Under UEFI, the boot program can request a structured memory map using UEFI services.

The map classifies regions as, for example:

  • usable memory;
  • reserved memory;
  • firmware code or data;
  • device-related regions;
  • memory needed by UEFI runtime services.

14.3. Information is spread across many structures

A simple memory map is insufficient for a modern server.

The kernel may also need to know:

  • which memory belongs to which NUMA node;
  • which CPUs are close to which memory;
  • which interrupt-controller ID belongs to each CPU;
  • how PCI host bridges are arranged;
  • how interrupts are routed;
  • how devices can be powered down;
  • how processors can change power or frequency states.

This information may be distributed across many ACPI tables.

Consequently, machine discovery becomes a process of locating, validating, and combining multiple data structures.

15. ACPI

15.1. General role

ACPI stands for Advanced Configuration and Power Interface.

It is used to communicate a large amount of platform information and control logic from firmware to the operating system.

ACPI can describe or support:

  • processor discovery;
  • NUMA topology;
  • interrupt-controller information;
  • power management;
  • device power states;
  • hot-plug operations;
  • shutdown and sleep procedures;
  • PCI-related platform configuration.

15.2. Finding the tables

The kernel must first locate the ACPI root structures.

Historically, this may involve:

  • checking particular memory ranges;
  • looking for a signature;
  • verifying a checksum;
  • following pointers to other tables.

The root tables then reference additional tables identified by short signatures.

A real operating system may need information from many different ACPI tables before it understands the complete system topology.

15.3. ACPI Machine Language

ACPI is not limited to static tables.

Firmware can provide bytecode known as ACPI Machine Language, or AML.

The operating system runs this bytecode using an interpreter.

AML may implement platform-specific operations such as:

  • powering a device on or off;
  • transitioning a device between power states;
  • changing processor power settings;
  • performing a system shutdown;
  • handling certain hot-plug events.

Even an operation that appears simple, such as turning off the computer, may require executing firmware-provided ACPI methods.

15.4. Requirements placed on the kernel

Running ACPI bytecode requires substantial kernel infrastructure.

An ACPI implementation may expect the kernel to provide:

  • memory allocation and deallocation;
  • synchronization primitives;
  • threads or asynchronous execution;
  • access to I/O ports;
  • access to memory-mapped registers;
  • interrupt handling;
  • timer and delay functions.

Operating systems commonly use a reusable ACPI implementation such as ACPICA and then provide an operating-system-specific wrapper layer.

That wrapper connects the generic ACPI interpreter to the kernel’s own services.

15.5. Firmware may continue to participate

Firmware does not necessarily disappear after the kernel begins running.

Some firmware code or data remains active or callable.

This is especially relevant for:

  • power-management operations;
  • system-management events;
  • device hot plug;
  • memory hot plug;
  • UEFI runtime services;
  • ACPI methods.

Modern systems therefore retain a complicated long-term interface between firmware and the operating system.

15.6. ACPI beyond x86

ACPI is not restricted to traditional x86 PCs.

Some server-class ARM systems also use UEFI together with ACPI.

The lecturer mentioned AWS Graviton systems as an example of ARM machines using this style of platform description.

16. Kernel Entry

Once control reaches the kernel entry point, the kernel performs a staged initialization.

The major stages are:

  1. Basic CPU initialization.
  2. Memory discovery and allocator initialization.
  3. Interrupt and timer initialization.
  4. Device discovery and driver initialization.
  5. Root-file-system mounting.
  6. Starting the initial userspace process.

The exact order contains dependencies.

For example:

  • page tables require memory;
  • the full memory allocator may require mapped memory;
  • drivers may require interrupts and timers;
  • the root file system requires a storage driver;
  • userspace programs require the root file system.

17. Basic CPU Initialization

17.1. Enabling architectural features

The CPU may initially have many modern features disabled for compatibility.

The kernel must configure control registers and other architectural state to enable the features it intends to use.

Examples include:

  • protected execution modes;
  • 32-bit or 64-bit operation;
  • virtual memory;
  • specific instruction-set extensions;
  • floating-point and SIMD state;
  • protection mechanisms;
  • interrupt handling.

On x86, instruction sets such as SSE historically require explicit enabling before the kernel can safely use them.

17.2. Setting up execution-mode state

The kernel may have to prepare:

  • descriptor tables;
  • exception vectors;
  • interrupt tables;
  • privilege-level state;
  • per-CPU state;
  • stacks for exceptions and kernel execution.

The details depend heavily on the architecture.

17.3. Initial virtual memory

The kernel normally establishes its own page tables early in initialization.

This provides:

  • the kernel’s preferred virtual-address layout;
  • mappings for physical memory;
  • mappings for device registers;
  • protection between regions;
  • the foundation for later process address spaces.

On x86-64, entering full 64-bit long mode is tied to enabling paging.

Therefore, some page-table setup is required before the kernel can execute in its final 64-bit environment.

18. Memory Discovery

18.1. Determining usable memory

The kernel gathers memory information from sources such as:

  • the device tree;
  • the bootloader;
  • BIOS memory maps;
  • UEFI services;
  • ACPI topology tables.

It must distinguish between:

  • normal usable RAM;
  • memory occupied by the kernel;
  • firmware-reserved memory;
  • memory occupied by boot metadata;
  • memory-mapped I/O;
  • unavailable address-space holes;
  • memory belonging to particular NUMA nodes.

18.2. Excluding already occupied regions

A memory map may report that a large range is physically backed by RAM.

That does not mean the entire range is immediately free.

The kernel image itself has already been placed somewhere in that RAM.

Other occupied regions may include:

  • bootloader structures;
  • the kernel command line;
  • an initial RAM disk;
  • device-tree data;
  • ACPI tables;
  • early page tables.

The memory allocator must mark these regions as reserved before making the remaining pages available.

19. Early Memory Allocation: The Chicken-and-Egg Problem

19.1. The circular dependency

To initialize a general-purpose memory allocator, the kernel needs metadata.

That metadata consumes memory.

For a very large machine, the metadata may itself be large because it may need to track millions or billions of physical pages.

However, before the allocator has been initialized, the kernel does not yet have a normal way to allocate the memory needed for this metadata.

There is a similar dependency involving virtual memory:

  1. The kernel needs memory for page tables.
  2. Page tables are needed to map memory.
  3. Mapped memory is needed to store allocator data structures.
  4. Those data structures are needed to manage memory.

19.2. Why one fixed static array is insufficient

The kernel could reserve a fixed amount of memory at compile time.

However, one kernel may run on machines ranging from very small systems to servers with terabytes of RAM.

If the static array is sized for the largest possible machine, it wastes substantial memory on small systems.

If it is sized for a small machine, it cannot describe a large one.

19.3. Staged allocator design

The common solution is staged initialization.

A simplified sequence is:

  1. Begin with a small preallocated memory region.
  2. Use a very simple early allocator, often similar to a bump allocator.
  3. Allocate the first page tables and essential metadata.
  4. Map additional physical memory.
  5. Construct the complete physical-page allocator.
  6. Transfer the remaining free memory to the full allocator.
  7. Stop using or reclaim the temporary early-allocation structures where possible.

The early allocator does not need sophisticated features.

It may simply return consecutive regions of memory and never free them.

Its purpose is to provide enough temporary allocation capability to construct the real memory-management system.

20. Interrupt and Timer Initialization

20.1. Why interrupts are needed early

Interrupts are required for:

  • receiving device-completion notifications;
  • handling asynchronous hardware events;
  • communicating between processors;
  • implementing preemptive scheduling;
  • detecting timer expirations.

The kernel must locate and initialize the platform’s interrupt controllers.

Modern systems may contain:

  • multiple interrupt controllers;
  • several different kinds of controllers;
  • hierarchical interrupt-routing structures.

This is much more complicated than the simplified Pintos environment, where interrupt numbers appear fixed and globally known.

20.2. Timers

Timers are needed early for several reasons.

They support:

  • scheduler preemption;
  • timekeeping;
  • delays;
  • driver timeouts;
  • recovery when hardware fails to respond.

Timeouts are particularly important during device initialization.

Without a timer, a driver waiting for broken hardware could block forever.

20.3. Starting additional processors

Most multiprocessor systems begin booting with only one CPU actively running the kernel.

This first CPU is often called the boot processor or bootstrap processor.

After basic initialization, it starts the remaining processors.

Depending on the architecture, this may involve:

  • sending special interprocessor interrupts;
  • placing startup code at a known physical address;
  • telling firmware which address a secondary processor should execute;
  • releasing secondary processors from a firmware waiting loop.

Each secondary processor must then initialize its own per-CPU state and join the kernel’s normal scheduling environment.

21. Device Discovery and Driver Initialization

21.1. Discovering buses

On modern general-purpose systems, many devices are connected through PCI or PCI Express.

Before enumerating devices, the kernel may first need firmware information identifying the system’s PCI host bridges or root buses.

On a complicated machine, there may be several independent PCI hierarchies.

ACPI or another platform description may identify their locations and address ranges.

21.2. Enumerating PCI devices

PCI devices are self-identifying.

The kernel can read configuration information containing values such as:

  • vendor ID;
  • device ID;
  • class code;
  • configuration registers;
  • memory-region requirements;
  • interrupt capabilities.

The kernel walks the PCI hierarchy, discovers devices, and records their identities.

21.3. Matching devices to drivers

The kernel compares a device’s identifiers with tables supplied by its drivers.

The basic process is:

  1. Discover a device.
  2. Read its identity and capabilities.
  3. Find a matching driver.
  4. invoke the driver’s initialization routine.
  5. Let the driver configure the device.

A driver may then:

  • map device registers;
  • allocate kernel memory;
  • allocate DMA buffers;
  • configure interrupt delivery;
  • reset the device;
  • load device firmware;
  • wait for the device to become ready.

Device initialization can involve substantial communication and may take noticeable time.

21.4. Interrupt routing

The device’s interrupt is not necessarily associated with one permanently fixed global number.

The kernel may need to:

  • allocate an interrupt vector;
  • configure an interrupt controller;
  • configure PCI MSI or MSI-X;
  • choose a destination CPU;
  • connect the driver’s handler to the resulting interrupt.

This is one reason discovery and initialization require information from several platform subsystems.

22. File-System Initialization

22.1. Requirement for storage drivers

The kernel cannot mount a disk-based file system until the corresponding storage device is operational.

Therefore, file-system initialization depends on successful driver initialization for devices such as:

  • SATA controllers;
  • NVMe devices;
  • virtual disks;
  • embedded flash controllers;
  • network interfaces for network-root systems.

22.2. Mounting the root file system

A Unix-like system needs a root file system from which it can load programs and configuration files.

The root-device specification may come from:

  • the kernel command line;
  • a device-tree bootargs property;
  • bootloader metadata;
  • an initial RAM disk;
  • a built-in default.

After identifying the device, the kernel:

  1. opens or initializes the block device;
  2. identifies the file-system type;
  3. invokes the corresponding file-system implementation;
  4. mounts it as the root of the directory hierarchy.

Once the root file system is mounted, the kernel can access ordinary userspace programs.

23. Starting Init

23.1. The first userspace process

After completing enough kernel initialization, the kernel creates the first userspace process.

Traditionally this process is called init.

On many modern Linux systems, it is systemd.

This process is special because it becomes the ancestor of much of the remaining userspace process tree.

23.2. Boundary between kernel and userspace initialization

The kernel does not directly perform every operation needed to make the system usable.

It establishes the basic mechanisms:

  • processes;
  • virtual memory;
  • drivers;
  • file systems;
  • networking primitives;
  • system calls.

It then starts an ordinary userspace program that applies higher-level system policy.

This keeps many complex and changeable operations outside the kernel.

24. Userspace Initialization

The initial userspace process starts system services and completes the boot procedure.

Typical userspace tasks include:

  • mounting additional file systems;
  • checking or repairing file systems;
  • starting device-management services;
  • configuring network interfaces;
  • running a DHCP client;
  • assigning IP addresses;
  • starting logging services;
  • starting background daemons;
  • creating terminal sessions;
  • presenting a login prompt;
  • authenticating users;
  • starting a shell or graphical session.

For example, obtaining a network address with DHCP is normally performed by a userspace program rather than directly by the kernel.

Similarly, the program that displays:

Login:
Password:

is a userspace program.

After successful authentication, it starts the user’s shell.

The full sequence therefore ends approximately as follows:

Kernel mounts the root file system
        |
        v
Kernel starts init/systemd
        |
        v
Userspace services are started
        |
        v
Terminals and login services are created
        |
        v
User logs in
        |
        v
Shell prompt appears

25. Complete Initialization Sequence

The complete process can be summarized as follows:

Power button pressed
        |
        v
Power rails stabilize
        |
        v
Hardware reset
        |
        v
CPU starts at reset vector
        |
        v
Firmware executes from ROM or flash
        |
        +--> initializes platform hardware
        +--> tests and discovers components
        |
        v
Firmware selects and starts a bootloader
        |
        +--> locates the kernel
        +--> reads the kernel from storage or network
        +--> places it in memory
        +--> prepares boot metadata
        |
        v
Bootloader jumps to the kernel entry point
        |
        +--> basic CPU initialization
        +--> virtual-memory setup
        +--> physical-memory discovery
        +--> allocator initialization
        +--> interrupt and timer initialization
        +--> secondary-CPU startup
        +--> bus and device discovery
        +--> driver initialization
        +--> root-file-system mounting
        |
        v
Kernel starts init/systemd
        |
        v
Userspace configures the rest of the system
        |
        v
Login service or graphical interface starts
        |
        v
Usable system

26. Main Design Lessons

26.1. Initialization is necessarily staged

Later subsystems depend on earlier ones.

Examples include:

  • drivers depend on memory allocation;
  • the full allocator depends on early memory allocation;
  • virtual memory depends on page-table memory;
  • scheduling depends on timers;
  • device I/O depends on interrupts;
  • the root file system depends on storage drivers;
  • userspace depends on the root file system.

The kernel therefore cannot initialize everything independently or in an arbitrary order.

26.2. Discovery is a fundamental problem

A general-purpose kernel must not assume that every machine has the same:

  • memory layout;
  • CPU count;
  • interrupt topology;
  • storage devices;
  • PCI hierarchy;
  • NUMA structure.

Some description must come from firmware, bootloader metadata, device trees, ACPI, or self-identifying buses.

26.3. Backward compatibility creates complexity

Legacy x86 booting illustrates how decades of compatibility requirements accumulate:

  • 16-bit startup;
  • BIOS interrupt interfaces;
  • the A20 gate;
  • MBR size limitations;
  • repeated processor-mode changes.

Each individual mechanism has a historical explanation, but together they make the boot path difficult to implement correctly.

26.4. Firmware is part of the operating-system environment

The operating system does not begin on a completely blank machine.

Firmware has already:

  • initialized hardware;
  • constructed metadata;
  • selected a boot path;
  • potentially left callable services or bytecode.

The kernel must understand and cooperate with these interfaces.

26.5. Embedded and general-purpose systems favor different strategies

Embedded systems often prefer:

  • static descriptions;
  • device trees;
  • board-specific bootloader configurations;
  • known hardware layouts.

General-purpose systems often require:

  • dynamic discovery;
  • UEFI;
  • ACPI;
  • PCI enumeration;
  • support for many configurations.

Neither approach is universally simpler.

The appropriate design depends on how variable the target hardware is.

26.6. The kernel stops booting earlier than users may expect

A large amount of visible system setup is performed in userspace rather than the kernel.

The kernel provides mechanisms and starts the first process.

Programs such as init or systemd then establish the high-level system environment.

Thus, reaching the kernel entry point is only the middle of the complete path from power-on to an interactive shell.

Author: Lowtroo

Created on: 2026-07-11 Sat 15:36

Powered by Emacs 29.3 (Org mode 9.6.15)