Skip to main content
  1. Posts/

The Linux Boot Process Explained

··8 mins·
Maciej Opaliński
Linux

Introduction
#

If you are reading this, you probably used Linux before. No matter if you are running some Linux distribution as a daily driver or not, you might use an Android phone that is using the Linux kernel. You also requested this website content from a server running Linux. But did you ever wonder how Linux works? In this article, I am going to explain one of the most fundamental aspects of Linux - its boot process.

BIOS/UEFI Firmware
#

When you power on your computer, the first thing that happens is the BIOS or UEFI firmware (depending on your system) initializes the hardware and performs a Power-On Self-Test to ensure that all components are functioning correctly. The BIOS/UEFI firmware then looks for a boot device, typically a HDD or SDD, where the operating system is installed.

BIOS vs UEFI
#

I found a great comparison of BIOS and UEFI posted by u/AiwendilH on r/linux. Below is an excerpt from the comment explaining how BIOS and UEFI work:

BIOS (Basic Input/Output System)
#

“Let’s look at the first sector of the first hard disk and boot the kernel saved in it.”

“We are not in the 80s anymore… no kernel fits in the boot sector nowadays.”

“Then, let’s try booting a tiny kernel that loads the real kernel… we can call it a boot manager.”

“Nice, this works… but what if we want to boot another OS?”

“We already have the boot manager, let’s just add a menu to it to choose what kernel it should boot.”

“Mhhh, but those kernels are on different filesystems like ext2 or FAT.”

“Well… let’s add filesystem drivers to our boot manager.”

“But now the boot manager is too big to fit in the boot sector… we are back at square one.”

“Let’s use the empty sectors before the first partition as well… we assume that those are unused and only needed for partition alignment… that gives us some more space.”

u/AiwendilH on r/linux

This is not a good solution. The boot manager falls apart in certain conditions. For more information, read the entire comment thread.

Also I’m pretty sure that no kernel ever fit in the 512 byte boot sector, so a true “BIOS boot” has not been around for years on any modern machines. So we might as well already move to UEFI.

UEFI (Unified Extensible Firmware Interface)
#

“Okay, the BIOS was bad, let’s make it better.”

“We support loading kernels from partitions right away… let’s go with something simple for the filesystem format… like FAT32.”

u/AiwendilH on r/linux

Basically, UEFI is more modern, faster and less problematic to setup. Just use UEFI.

There is much more going into UEFI, for example Secure Boot that was quite controversial. The original comment has some factual errors explaining how Secure Boot was introduced and how it’s used. If you want to know more, please read the entire comment thread.

Bootloader
#

The bootloader is responsible for loading the kernel into memory. The most common bootloader is GRUB (GRand Unified Bootloader). It provides a menu of boot options if multiple operating systems or kernels are installed. The bootloader also loads any necessary drivers for hardware components that are required to access the boot device.

You can use GRUB to choose which operating system to boot (Linux, Windows, etc.) and which kernel to load into memory. When you upgrade the kernel, most Linux distributions don’t actually remove the old kernel. This is done in case the newly upgraded kernel fails to boot, then you can just go into advanced options in GRUB and choose a different kernel that works.

Linux Kernel
#

Once the bootloader has loaded, it locates the Linux kernel and loads it into memory. The kernel is the core component of the operating system. It is responsible for managing system resources such as CPU, memory, input/output (I/O) devices and many more. The kernel also initializes system services and device drivers.

Initramfs
#

After loading the kernel, the system then loads the initramfs (initial RAM filesystem). The initramfs is a temporary filesystem used to initialize and load other device drivers, optionally handle decryption and finally mount the actual root file system. The first program that is executed by the Linux kernel after loading initramfs is the /init executable.

We can explore the initramfs of our Linux installation. It is usually found in the /boot directory.

In my Arch Linux install, I have the following files in the /boot directory:

$ tree /boot
/boot
├── efi
│   ├── EFI
│   │   ├── Boot
│   │   │   ├── bootx64.efi
│   │   │   ├── fbx64.efi
│   │   │   └── mmx64.efi
│   │   ├── grub_uefi
│   │   │   └── grubx64.efi
│   │   └── Microsoft
│   │       ├── Boot
│   │           (...)
├── grub
│   (...)
│   ├── grub.cfg
│   (...)
├── initramfs-linux-fallback.img
├── initramfs-linux.img
(...)
└── vmlinuz-linux

I replaced some unimportant files with (...).

Let’s explain what every file and directory is for:

  • efi/ is the directory to which my EFI partition mounts. I’m running UEFI, so GRUB bootloader lives there along with the default Windows Boot Manager;
  • grub/ is the directory in which GRUB keeps its config files, like grub.cfg;
  • initramfs-linux.img and initramfs-linux-fallback.img are the initramfs images themselves;
  • vmlinuz-linux is the compressed Linux kernel, there instead could be vmlinux-linux which is uncompressed Linux kernel.

Let’s explore the initramfs image!

Exploring initramfs
#

The simplest method of extracting the initramfs image is to use the lsinitcpio tool from mkinitcpio package.

mkdir initramfs/
cd initramfs/
sudo cp /boot/initramfs-linux.img ./ # working on a copy
sudo chown $USER:$USER ./initramfs-linux.img # to avoid extracting with sudo
lsinitcpio -x initramfs-linux.img # extract to the current directory, run without the -x flag to just list the files

First thing that the Linux kernel executes is the /init script, so let’s have a look at it.

#!/usr/bin/ash
# SPDX-License-Identifier: GPL-2.0-only

export PATH='/usr/local/sbin:/usr/local/bin:/usr/bin'

# !!! redacted !!!

mount_handler=default_mount_handler
init=/sbin/init

# !!! redacted !!!

. /init_functions

mount_setup

# parse the kernel command line
parse_cmdline </proc/cmdline

# !!! redacted !!!

# Mount root at /new_root
"$mount_handler" /new_root

# !!! redacted !!!

exec env -i \
    "TERM=$TERM" \
    /usr/bin/switch_root /new_root "$init" "$@"

# vim: set ft=sh ts=4 sw=4 et:

I redacted some of the less important parts of the script. Let’s analyze the script line by line.

First, we are exporting a PATH environmental variable with default locations of all executables. We need it for our shell to locate the executables without typing out their entire paths by ourselves.

Then, we are defining some other variables that will be used later in the script.

Next, we evaluate the /init_functions script. The script is much longer and contains many functions used in the /init script, such as: default_mount_handler, parse_cmdline, etc.

After that, we call mount_setup from the mentioned /init_functions script. Here is how the function looks:

mount_setup() {
    mount -t proc proc /proc -o nosuid,noexec,nodev
    mount -t sysfs sys /sys -o nosuid,noexec,nodev
    mount -t devtmpfs dev /dev -o mode=0755,nosuid
    mount -t tmpfs run /run -o nosuid,nodev,mode=0755
    mkdir -m755 /run/initramfs

    if [ -e /sys/firmware/efi ]; then
        mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec
    fi

    # Setup /dev symlinks
    if [ -e /proc/kcore ]; then
        ln -sfT /proc/kcore /dev/core
    fi
    ln -sfT /proc/self/fd /dev/fd
    ln -sfT /proc/self/fd/0 /dev/stdin
    ln -sfT /proc/self/fd/1 /dev/stdout
    ln -sfT /proc/self/fd/2 /dev/stderr
}

It basically mount all required system directories like /proc, /sys, etc.

Next step is quite interesting, because we are calling parse_cmdline function. The function makes sure that the Linux boot params are easily accessible as shell variables from inside the script. This is done for convenience purposes. I won’t show the entire function, as it is rather complicated. After verifying that the shell variable is valid it just calls eval in the following way:

eval "$key"='${value:-y}'

Finally, we can mount our real root filesystem, where all our persistent files are stored. We call $mount_handler, which was previously defined as default_mount_handler. Here is the function definition:

default_mount_handler() {
    msg ":: mounting '$root' on real root"
    if ! mount -t "${rootfstype:-auto}" -o "${rwopt:-ro}${rootflags:+,$rootflags}" "$root" "$1"; then
        echo "You are now being dropped into an emergency shell."
        # shellcheck disable=SC2119
        launch_interactive_shell
        msg "Trying to continue (this will most likely fail) ..."
    fi
}

The function tries to mount the root filesystem to /new_root and will drop th euser into an emergency shell if something goes wrong.

Where does the $root variable come from? It comes from the previous step when we called parse_cmdline.

The final step in the /init script is to execute /usr/bin/switch_root to obviously chroot into the /new_root directory that contains our actual files and programs. The next argument is the path to the executable to be launched next ($init, previously defined as /sbin/init). This is the path from our actual root filesystem.

$ ls -la /sbin/init
lrwxrwxrwx 1 root root 22 Mar 29 20:41 /sbin/init -> ../lib/systemd/systemd

As we can see, in my case, the /sbin/init is linked to /lib/systemd/systemd as I am running systemd as my init system.

This was just an example /init script from my Arch Linux install. Yours might be different. You can even write your own /init! More on that in an upcoming article!

Init System
#

Once the Linux kernel executes /sbin/init, the init system takes over. The init system is responsible for starting system services, such as networking, logging as well as running any startup scripts required by the system. The most popular init systems used in Linux are: systemd, OpenRC, runit.

Conclusion
#

In conclusion, the Linux boot process is a complex sequence of steps that starts with the BIOS/UEFI firmware initializing the hardware and performing a Power-On Self-Test (POST), followed by the bootloader loading the Linux kernel into memory. The kernel initializes system resources, loads necessary device drivers, and hands over control to the init system, which starts system services and user applications. Understanding the Linux boot process is essential for troubleshooting and diagnosing problems with the operating system.