soenderskov.xyz
← back to blog

My Understanding of the Linux Boot Sequence

Understanding the flow fo booting up a linux operating system

#Bootloader #Linux

Introduction

In 2025, I began working on an embedded Linux system for Scurid. Initially, the task felt tedious because I did not understand it and did not invest time to learn. Over time, I realized that to produce a deployable embedded Linux system, I needed to comprehend the Linux boot sequence. I’ve therefore spent the past couple of months reading about this exact topic and have decided to write this article to help others standing in the same scenario as myself.

The boot sequence of a Linux system can seem very complicated, but if you take it piece by piece, things will make sense rather quickly. The important thing is not to know every process that the init process starts, but rather to have a fundamental understanding of everything up to the point where this initial process starts.

The best way to learn is by building and exploring the Linux system. Therefore, I provide examples and exercises I used to understand the components of the Linux boot sequence. I want to thank Greg O’Keefe for this awesome quote “verum ipsum factum”, which means “we only know what we make” and was coined by the 18th-century philosopher Giambattista Vico.

The Boot Sequence

Prerequisites

Before you start reading, I suggest you review the following glossary explaining key concepts you should know.

  • ROM: Read-Only Memory is a type of non-volatile memory (NVM) that retains its data even when the device is powered off. In practice, modern devices use flash memory (a form of EEPROM) rather than true ROM, but the term “ROM” is commonly used to refer to this persistent storage. It stores essential data such as bootloader code and firmware required for a computer to boot.
  • RAM: Random Access Memory is a computer’s high-speed, short-term memory that stores data for active applications and processes. Unlike ROM and disks, RAM is volatile, meaning it loses all its data when power is removed. The CPU reads and writes to RAM during operation.
  • Disk: A disk is a persistent storage medium used to store an operating system, applications, and user data long-term. Unlike RAM, disks retain data after power is removed. In embedded systems, storage commonly takes the form of eMMC, SD cards, or SSDs.
  • Partition: A partition is a distinct division of a storage medium into separate, isolated sections. Each partition can hold its own filesystem and is managed independently. Partitioning allows a single disk to serve multiple purposes, such as separating the OS from user data.
  • Partition Table: A partition table is a data structure that lists all the partitions on a disk, recording where each partition starts and ends. Think of it as a map of key-value pairs pointing to the different partitions and their respective addresses. The most common partition schemes are MBR (Master Boot Record) and GPT (GUID Partition Table).
  • Filesystem: A filesystem is the structure and logic an operating system uses to organize, store, and retrieve data on a storage medium. It defines how data is written to and read from a disk, how files are named, and how directories are arranged. Common filesystems include ext4 (Linux), NTFS (Windows), and FAT32 (cross-platform/embedded).
  • Firmware: Firmware is the software that makes hardware work, stored in non-volatile memory so it persists when the device is powered off. It comes in two levels: low-level firmware controls booting the device, while high-level firmware controls specific components like a GPU or network card. Both levels can generally be updated via a process called “flashing”, though low-level firmware updates are less frequent and typically only performed by manufacturers or advanced users.
  • BIOS / UEFI: On x86 architecture, BIOS and UEFI are the two standard firmware interfaces used to initialize hardware and load the bootloader. UEFI is the modern replacement for BIOS, offering features like secure boot and support for larger disks. On ARM architecture, there is no such standard—each SoC manufacturer defines their own low-level firmware, such as Raspberry Pi’s proprietary Broadcom GPU firmware.
  • SoC: A System on Chip is a single chip that contains all the core parts of a computer—CPU, GPU, memory controller, and more—packed into one piece of silicon. SoCs are common in phones and embedded devices where saving space and power is important. Examples include Apple’s M-series chips and Qualcomm Snapdragon.
  • SBC: A Single-Board Computer is a compact computer with the CPU, RAM, storage, and other components integrated onto a single circuit board. Unlike an SoC, which packs everything onto one chip, an SBC is an entire board with discrete components. Examples include Raspberry Pi, LattePanda, and NVIDIA Jetson Nano.

Firmware Initialization

Firmware initialization is the first thing that happens in the boot sequence. It is the part I especially had a hard time comprehending at first, simply because it is “far away” from what you usually work with. Over time it has become easier to understand and reason about. It is important to note that there are two different initialization sequences between ARM and x86, because of the different firmware designs being used.

Firmware Initialization on ARM

When a reset or power-on is triggered, the ARM CPU restarts and immediately runs a small piece of code built directly into the SoC during manufacturing. This is called ROM code or Boot ROM. This small piece of code is permanently embedded into the chip itself and cannot be replaced or updated.

The main job of the ROM code is simple: find and load the SPL (Secondary Program Loader). It does this by checking several places in order. First, the ROM code reads the bootstrap pins to determine the configured boot source, and runs a basic POST (Power-On Self-Test) to ensure core hardware is working correctly. It then looks for code on a flash memory chip connected via SPI (Serial Peripheral Interface). Next, it checks the first sectors of an MMC device—such as an eMMC chip or SD card—or looks for a file named MLO (Memory Loader) on the first partition of that MMC device. If all of these fail, it falls back to other sources like a USB device, reading its code as a byte stream.

Once the ROM code successfully finds and loads the SPL into SRAM, control is handed off to the SPL. The SPL then initializes the hardware fully with help from device trees, and loads the full bootloader into DRAM—which is the next topic.

ARM: Power-on → ROM code → SPL → Bootloader (U-Boot) → Kernel

Firmware Initialization on x86

When a reset or power-on is triggered, the x86 CPU restarts and immediately runs code stored on a flash memory chip on the motherboard. This is called UEFI—or the legacy BIOS—and unlike ARM’s Boot ROM, it is not permanently embedded into the chip itself and can be updated via a process called flashing.

The main job of UEFI is simple: initialize the hardware and find the bootloader. It does this through four phases. First, the SEC (Security Phase) runs as the very first code after power-on. Since DRAM is not yet available, the CPU’s internal cache is used as temporary RAM—a technique called Cache-as-RAM (CAR). SEC performs basic CPU validation and establishes a minimal trusted execution environment. Next, the PEI (Pre-EFI Initialization) phase takes over and initializes DRAM. It runs a POST (Power-On Self-Test) to verify the CPU and core hardware are functioning, then brings up the memory controller and DRAM. Once DRAM is available, UEFI copies itself from the flash chip into DRAM to run faster—a process called shadowing. Then the DXE (Driver Execution Environment) phase loads drivers for all detected hardware components—storage controllers, network interfaces, graphics adapters, and more—building up a complete runtime environment. Finally, the BDS (Boot Device Selection) phase reads the boot order from NVRAM—a prioritized list of devices configured in UEFI settings, such as an SSD, USB drive, or network interface. It scans each device in order, looking for a bootloader on a dedicated EFI System Partition (ESP). Once found, control is handed off to the bootloader.

x86: Power-on → SEC → PEI → DXE → BDS (UEFI) → GRUB → Kernel

Notes, Distinctions, and Glossary Additions

  • Boot ROM: Boot ROM is the specific ROM chip on the SoC that contains the ROM code. When referring to Boot ROM, we are in turn referring to the memory that holds the ROM code.
  • SRAM vs DRAM: SRAM (Static Random Access Memory) has a more complex internal structure, but this means it does not need to be constantly refreshed and can read and write data very quickly—making it ideal for small, speed-critical tasks like running ROM code during boot. DRAM (Dynamic Random Access Memory) has a simpler internal structure but needs to be refreshed constantly while in use, or it loses its data. Because its structure is simpler and requires fewer transistors per bit, DRAM can store much more data—making it suitable for loading larger software like a bootloader.
  • SPI vs SPL: SPI (Serial Peripheral Interface) is a communication protocol used to send and receive data between components—for example, between a microcontroller and a flash memory chip. SPL (Secondary Program Loader) is a small, stripped-down bootloader whose only job is to set up just enough hardware to initialize DRAM and load the full bootloader—and in some cases also load the TPL (Tertiary Program Loader).
  • NVRAM: Non-Volatile Random Access Memory (NVRAM) is a small memory chip on x86 motherboards that stores UEFI settings such as the boot order. Unlike regular RAM, it retains its data when the power is off. It is a type of the former mentioned NVM.
  • CPU Reset: When the CPU restarts, it resets its registers and sets the program counter to a fixed, hardcoded memory address—this address points directly to where the ROM code (on ARM) or UEFI (on x86) lives, and is where execution begins immediately after every reset or power-on.
  • Device Tree: A device tree is a data structure used on ARM systems to describe what hardware is connected to the SoC—such as which peripherals are attached and how they are configured. It is used by the SPL and bootloader to know what hardware exists before the kernel is loaded.
  • Cache-as-RAM (CAR): A technique used during the SEC phase on x86 where the CPU’s internal cache is configured to act as temporary RAM before DRAM is initialized. This gives the firmware a small, fast scratch space to execute code and store state during the earliest stages of boot.
  • SEC Phase: The first UEFI boot phase. Runs entirely in Cache-as-RAM, validates the CPU, and establishes a minimal trusted environment before handing off to PEI.
  • PEI Phase: The second UEFI boot phase. Responsible for initializing DRAM, running POST, and producing a memory map for DXE.
  • DXE Phase: The third UEFI boot phase. Loads hardware drivers and builds the full runtime environment needed for booting.
  • BDS Phase: The fourth UEFI boot phase. Reads the NVRAM boot order and locates the bootloader on the appropriate device.
  • Partition: A logical division of a physical storage device into distinct, independent sections that the operating system treats as separate spaces.

Exercises

  1. View your partitions There are a couple of different utility packages that are nice to know of. But because this is an exercise, I will just use one: fdisk. Run these commands and try to make sense of what goes on.
# Shows all disks
sudo fdisk -l

# Open a specific disk to see its partition table (replace sdx with your actual disk, e.g. sda)
sudo fdisk /dev/sdx

Note that sudo fdisk /dev/sdx opens an interactive shell — type p to print the partition table and q to quit. There is a lot more to fdisk and also parted, which is an alternative, but it is out of this article’s scope.

  1. Figure out which firmware you use This is an essential exercise for making sense of your current operating system. If your system uses UEFI, the directory /sys/firmware/efi will exist. If it does not exist, your system is likely using BIOS.
# Check if the EFI firmware directory exists
ls /sys/firmware/efi

If the directory is present, you are running UEFI. You can inspect it further with ls -la /sys/firmware/efi to see variables and other EFI-specific entries.

  1. Locate the EFI System Partition (ESP) The bootloader resides either on a specialized EFI System Partition (ESP) or, if the system uses BIOS, in the first 512 bytes of the first sector of the bootable drive. If your system uses BIOS you should skip this exercise.
# View the partition scheme of your boot disk (replace sdx with your actual disk, e.g. sda)
sudo fdisk -l /dev/sdx

# Navigate to the ESP mount point and list its contents
cd /boot/efi && ls -la

The ESP is commonly mounted at /boot/efi or /boot depending on your distribution. Look for a partition of type “EFI System” in the fdisk output to confirm.

Bootloader

The bootloader is the second stage in the boot sequence. Its job is to initialize the system to a degree where the kernel can be loaded into memory, and it is therefore the bridge between firmware and kernel. As covered in the previous section, the SPL (and in some cases the TPL, or Tertiary Program Loader, which adds an optional extra initialization stage) prepares just enough hardware to hand control to the full bootloader. The full bootloader then takes over, finishes initializing the hardware, loads the kernel into DRAM, and passes control to it.

In this article we’re primarily looking at GRUB and U-Boot—the standard and widely used bootloaders for x86 and ARM respectively, which is the scope of this article.

Loading the bootloader on ARM

U-Boot is the most commonly used bootloader for ARM architecture.

To start where we left off in the previous chapter, the SPL uses a device tree to understand which hardware is present and how it is configured. It reads this file and initializes the hardware accordingly. The most critical step is initializing DRAM, since without it there is nowhere to load the bootloader. Once DRAM is initialized, the SPL searches for and loads the full bootloader (U-Boot) into DRAM and hands control to it.

It is worth noting that ARM can also use UEFI. In that case, the boot flow on ARM looks much closer to x86: UEFI handles hardware initialization and loads the bootloader directly, bypassing the SPL stage entirely. This is becoming more common on higher-end ARM platforms.

Loading the bootloader on x86

GRUB is the most commonly used bootloader for x86 architecture.

On x86 the story is a bit different. The low-level firmware—most likely UEFI, but possibly the legacy BIOS—has already run POST and initialized all hardware, including DRAM. It has also scanned the boot order, located a bootloader on the EFI System Partition of the bootable device, and loaded it into DRAM. By this point, the firmware has done the heavy lifting that SPL does on ARM, and control is handed directly to the bootloader—without any intermediate stage.

Running the bootloader

Once the bootloader has control—whether handed off by the SPL on ARM or by UEFI on x86—it performs a few key tasks. First, it locates the kernel image on the boot disk. It then loads the kernel into DRAM, and in some cases also loads an initial RAM disk (initramfs), which is a temporary filesystem the kernel uses during early startup. The bootloader also passes a set of boot parameters to the kernel via the kernel command line—specifying things like where the root filesystem is located, which filesystem type it uses, and any additional kernel options. Once everything is in place, the bootloader hands control to the kernel, and its job is done.

Notes, Distinctions, and Glossary Additions

  • Device Tree: A device tree is a data structure used on ARM systems to describe what hardware is connected to the SoC—such as which peripherals are attached and how they are configured. It is used by the SPL and bootloader to know what hardware exists before the kernel is loaded.
  • Kernel Command Line: The kernel command line is a set of parameters the bootloader passes to the kernel at boot time. These parameters tell the kernel essential information such as where to find the root filesystem, which filesystem type to use, and any additional runtime options. On GRUB, these parameters are visible and editable in the GRUB menu by pressing e.
  • initramfs: A temporary, in-memory filesystem loaded by the bootloader alongside the kernel. It contains a minimal set of tools and scripts the kernel uses to mount the real root filesystem before handing off to the init system.

Exercises

  1. Locate the bootloader on your system It’s always good to know where the bootloader is located. On UEFI systems, the bootloader resides on the EFI System Partition (ESP), which is typically mounted at /boot/efi.
# List the contents of the ESP to find the bootloader
ls -la /boot/efi/EFI

# On many Linux systems, GRUB lives here specifically
ls -la /boot/efi/EFI/ubuntu   # or fedora, debian, etc. depending on your distro
  1. Open the boot manager and command line Understanding the boot manager is where things really begin to make sense. Restart or power on your computer and immediately press Esc, F2, or F12 repeatedly until the boot manager appears — the exact key varies by manufacturer, so check your system’s documentation if none of these work.

Here you can see the bootable images available on your system. To explore the bootloader command line directly:

  • GRUB: At the GRUB menu, press c to open the GRUB command line, or press e to edit the boot parameters for the selected entry.
  • U-Boot: During the autoboot countdown, press Space (or any key, depending on configuration) to stop autoboot and drop into the U-Boot command line.
  1. Locate device tree files If you are running Linux on an ARM system, device tree files are present on your filesystem. They come in two forms: the raw source format (.dts) and the compiled binary format (.dtb) that the bootloader and kernel actually use. The compiled device tree blobs are typically located in /boot or alongside the kernel image.
# Find compiled device tree blobs on your system
find /boot -name "*.dtb"

# If you have the device tree compiler installed, you can decompile a .dtb back to human-readable source
dtc -I dtb -O dts -o output.dts /boot/your-file.dtb

# You can also find the device tree the running kernel is currently using
ls /sys/firmware/devicetree/base

The /sys/firmware/devicetree/base directory exposes the active device tree as a filesystem, where each node is a directory and each property is a file. This is a useful way to inspect what hardware the kernel currently sees without needing any additional tools.

Kernel

The kernel is the third stage of the boot sequence. When talking about Linux, what most people do not realize is that Linux itself is only a kernel—not a full operating system. The kernel sits between the hardware and user space, acting as the bridge that manages how software interacts with the physical hardware beneath it. The way software interacts with the kernel is through something called system calls, or syscalls.

One of the most famous principles of Linux is that everything is a file. This means that hardware devices, processes, and system information are all represented as files. For example, your mouse is represented as a file at /dev/input/mice, and your storage devices appear as files like /dev/sda. This design makes it possible to interact with hardware using the same tools and calls you would use to read or write a regular file—something that becomes very clear when working with file descriptors in C.

From Bootloader to Kernel

At this point the bootloader has done its job and the kernel is loaded into DRAM and running. This is an important distinction—the kernel does not run from a storage device or a filesystem, it runs entirely from DRAM, just like the bootloader did before it.

The first thing the kernel does once it takes control is decompress itself—since kernel images are typically stored in a compressed format to save space. It then begins initializing its core subsystems, which includes setting up memory management, starting the process scheduler, and initiating device drivers. On ARM, the kernel also reads the device tree to understand what hardware is present on the system, in the same way the SPL did earlier in the boot process. On x86, the kernel instead discovers hardware through ACPI (Advanced Configuration and Power Interface).

Once the core subsystems are initialized, the kernel mounts the root filesystem. The very last thing the kernel does is start the first process, typically called Init, which is assigned Process ID 1. From this point on, Init takes over and is responsible for starting all other processes on the system—and the kernel’s boot sequence is complete. All subsequent processes can only interact with hardware via the kernel through syscalls, and the kernel itself never directly manages any process after Init is started.

Kernel Command Line

When the bootloader hands control to the kernel, it also passes a set of arguments known as the kernel command line. This was touched on in the previous chapter, but it is worth revisiting here since it directly affects how the kernel behaves at boot.

root=/dev/mmcblk0p2 console=ttyS0,115200 ro quiet splash

Each parameter is either a key-value pair or a standalone flag. In the example above, root=/dev/mmcblk0p2 tells the kernel where to find the root filesystem, and console=ttyS0,115200 tells it to output boot messages to a serial console at a baud rate of 115200. ro instructs the kernel to mount the root filesystem as read-only initially—this is standard practice, as it allows the system to run a filesystem check (fsck) before remounting it as read-write. quiet suppresses kernel log output during boot, and splash tells the kernel to display a splash screen instead. These parameters can be configured in the bootloader—for example in U-Boot on ARM or in GRUB on x86.

Glossary Additions

  • Subsystems: A subsystem is a self-contained component within the kernel that is responsible for a specific area of functionality. For example, the memory management subsystem handles how RAM is allocated and freed, the process scheduler subsystem decides which processes run and when, and the networking subsystem manages network communication. Each subsystem operates largely independently but can interact with others through well-defined interfaces.
  • Compression: Compression is the process of reducing the size of data by encoding it more efficiently. In the context of the Linux boot process, the kernel image is stored in a compressed format to save storage space. When the kernel takes control from the bootloader, one of its first tasks is to decompress itself into DRAM before it can start executing. Common compression formats used for kernel images include gzip and lz4.
  • ACPI: Advanced Configuration and Power Interface (ACPI) is a standard used on x86 architecture that allows the firmware and operating system to discover and manage hardware. It serves a similar purpose to device trees on ARM—providing the kernel with a description of what hardware is present and how it is configured. ACPI also handles power management, such as sleep states and CPU frequency scaling.
  • Process ID: A Process ID (PID) is a unique number assigned by the kernel to each process running on the system. Every process is started by a parent process—except for Init, which is started directly by the kernel and is always assigned PID 1. Init is therefore the parent of all other processes on the system.
  • syscall: A system call (syscall) is the mechanism through which a process in user space requests a service from the kernel—such as reading a file, allocating memory, or sending data over a network. Syscalls are the only sanctioned way for user space software to interact with hardware, ensuring that all hardware access goes through the kernel’s controlled interfaces.

Exercises

  1. View the kernel command line Your running system exposes the exact kernel command line that was passed at boot. This is useful for verifying boot parameters and understanding how your system was configured.
cat /proc/cmdline
  1. Show the first 20 processes Since Init is PID 1 and is the parent of all other processes, inspecting the process list from the top gives a clear picture of the boot hierarchy.
ps aux | head -n 20
  1. Inspect the process tree Rather than a flat list, the process tree shows the parent-child relationships between processes, making it easy to see how everything traces back to PID 1.
pstree -p

Root Filesystem

The root filesystem is the last part of the Linux boot sequence, and it is the part that you, as a Linux user, interact with directly. The root filesystem lives in user space and is mounted by the kernel as the final step of the boot process—once Init has started, the root filesystem is fully operational and the system is ready to use.

Everything in Linux is organized under a single directory tree that starts at the root, denoted by /. Unlike Windows, which uses separate drive letters (C:, D:), Linux places everything—files, devices, configuration, and even hardware—under this single root. Below is an overview of the most important directories and what they contain.

  • / (Root): The top of the entire filesystem hierarchy. Every file and directory on the system lives under here. Only the root user has write access to this directory.
  • /boot: Contains the files needed to boot the system—most importantly the kernel image and, on x86 architecture systems, the GRUB bootloader configuration. On ARM systems this directory may also contain device tree files used during boot.
  • /bin: Contains essential binary executables—programs that must be available to all users even in single-user or recovery mode. Common examples include ls, cp, mv, and bash. On modern Linux systems /bin is often a symbolic link to /usr/bin.
  • /sbin: Contains system binaries intended for system administration tasks rather than regular users. Examples include fdisk, fsck, and reboot. Like /bin, it is often a symbolic link to /usr/sbin on modern systems.
  • /etc: Contains system-wide configuration files. This is where you find configuration for services, network settings, user accounts, and more. Examples include /etc/fstab (filesystem mount configuration) and /etc/hostname (the system’s hostname).
  • /lib & /lib64: Contains shared libraries needed by the binaries in /bin and /sbin. Think of these as the equivalent of .dll files on Windows—code that multiple programs share rather than each bundling their own copy. /lib64 contains the same but for 64-bit libraries.
  • /tmp: A temporary directory where programs can store files that only need to exist for the duration of a session or process. The contents of /tmp are typically cleared on every reboot.
  • /usr: Contains the majority of user-facing programs, libraries, and documentation. It is essentially a secondary hierarchy that mirrors the root—containing its own /usr/bin, /usr/sbin, and /usr/lib. On modern Linux systems most binaries live here rather than directly in /bin or /sbin.
  • /home: Contains the personal directories of all regular users on the system. For example, a user named anton would have their files stored at /home/anton. This is where user-specific configuration files, documents, and data live.
  • /var: Contains variable data—files that are expected to grow or change continuously during normal system operation. Common examples include log files in /var/log, mail spools, and package manager databases.
  • /dev: Contains device files—one of the clearest examples of the “everything is a file” principle in Linux. Every hardware device on the system is represented here as a file. For example, your first storage device appears as /dev/sda, and your mouse as /dev/input/mice.
  • /mnt & /media: Both directories are used for mounting external storage devices and filesystems. /mnt is traditionally used for temporary manual mounts. /media is typically used by the system to automatically mount removable devices like USB drives and SD cards when they are plugged in.

Exercises

  1. Relax I don’t really feel like making more exercises and frankly speaking, the most important is just to know and understand that there is such a thing as a root filesystem, and that everything sits ontop of that.

I am working on making an article on how to build a full Linux operating system, but it has yet to come out.

How Linux Works by Brian WardU-Boot DocumenationArch Documenation on PartitioningMastering Embedded Linux DevelopmentGetting to Know the Linux Kernel: A Beginner’s Guide - Kelsey Steele & Nischala YelchuriThe Linux Kernel: What it is, and how it works!Linux File System/Structure Explained!