Skip to content

From power button to shell

A few seconds pass between pressing the power button and the login prompt, and in that time control passes through four owners in turn. As long as everything works, you will never need to know this chain.

The need comes when something breaks. The system won’t boot after a kernel update. You get emergency mode instead of a desktop. A service starts fine by hand but not at boot. Each of these maps to a specific link in the chain, so half of the diagnosis is figuring out which link the system reached at all.

Prerequisites. Execution modes (module 2), PID 1 and the process tree (module 6).

Boot chain from UEFI firmware to systemd, showing who performs each stepfirmwarebootloaderkerneluser space1UEFI firmware: self-test, memory and bus initialization2finding the bootloader on the ESP partition3bootloader: kernel choice, command-line parameters4the kernel decompresses and takes over the hardware5initramfs: the drivers needed to reach the root6switch to the real root partition7start PID 1, the first user-mode process8systemd starts targets and services, the login prompt appears
Each link knows exactly enough to find and start the next one. A problem at any step stops everything after it, so diagnosis starts with the question of which step the system reached.

It all starts with the firmware. The CPU fetches its first instruction from a fixed address, and that is where the firmware sits. It checks and initializes memory, buses and controllers, then looks for what to boot next.

Modern firmware is UEFI. It understands the FAT32 file system and reads the bootloader as an ordinary file from a separate partition called the ESP (EFI System Partition). The list of boot entries lives in the motherboard’s non-volatile memory, and you can edit it from the running system with efibootmgr.

The old BIOS + MBR scheme works completely differently: the BIOS reads the first 512 bytes of the disk and executes them. Those 512 bytes also hold the partition table, which leaves so little room for code that the bootloader had to be split into stages. The limits of MBR come from the same place: four primary partitions and 2 TiB, both of which GPT removes.

The bootloader, GRUB or systemd-boot, shows a menu, finds the kernel image and the initramfs, and passes the kernel its command-line parameters. That command line decides what the kernel does next: where to look for the root, whether to turn on debug output, which drivers to disable.

The kernel decompresses itself, initializes its subsystems, discovers the CPUs and memory, starts drivers and mounts the initramfs.

The last thing the kernel does is start the first user-mode process, PID 1. It is special in several ways at once: an ordinary signal can’t kill it, it adopts every orphan process (module 6), and if it exits, the kernel halts the system.

The classic init ran scripts one after another by number. Simple, but slow: every service waited for the previous one, even when the two had nothing to do with each other.

systemd instead describes the system as a dependency graph and starts in parallel everything that can run in parallel.

The thing it manages is called a unit. There are several types: .service for a process, .socket for a socket that starts a service when someone connects to it, .timer in place of cron, .mount for a mount point and .target for a group of units.

Think of a target not as a runlevel but as a point in the graph. multi-user.target means the system is ready to work at the console, and graphical.target adds a graphical environment on top.

Socket activation deserves its own mention. systemd opens the socket right away and starts the service only on the first connection. The client sees no difference, because the connection is accepted and waits, but boot is no longer held up by services nobody uses.

Terminal window
[ -d /sys/firmware/efi ] && echo 'UEFI' || echo 'BIOS (legacy)'

Whether the system booted through UEFI. The directory exists only in UEFI mode.

Terminal window
efibootmgr -v 2>/dev/null | head; lsblk -o NAME,PARTTYPENAME,MOUNTPOINT | head

The boot entries in the motherboard’s memory and the ESP partition, usually mounted at /boot/efi.

Terminal window
cat /proc/cmdline

The parameters the running kernel was booted with, that is, what the bootloader passed. This shows where the root came from and which options apply.

Terminal window
lsinitramfs /boot/initrd.img 2>/dev/null | head -20 || lsinitrd 2>/dev/null | head -20

The contents of the initramfs: the set of modules needed to reach the root.

This deliberately uses /boot/initrd.img, a symbolic link to the image of the default kernel. If you use /boot/initrd.img-$(uname -r), the command fails exactly when things get interesting: after a kernel update, when the new image already exists but the system is still running the old kernel, which may have no image. It’s worth comparing both.

Terminal window
systemd-analyze; systemd-analyze blame | head -10

How long boot took, broken down into firmware, bootloader, kernel and user space, and which units took the most time.

Terminal window
systemd-analyze critical-chain

The critical chain: the sequence that actually determined boot time. Only what is in it is worth optimizing.

Terminal window
systemctl list-units --type=target --state=active
systemctl get-default

Active targets and the default target.

Terminal window
journalctl -b -p err --no-pager | tail -10

Errors from the current boot. -b -1 shows the previous one, which is invaluable when the system crashed and managed to reboot.

“BIOS and UEFI are the same thing, just a new name.” They are different schemes. The BIOS executes the first 512 bytes of the disk, while UEFI reads an .efi file from a FAT32 partition and keeps its list of entries in the motherboard’s memory.

“initramfs is there for speed.” It is there to break a circle: mounting the root needs drivers that live on the root itself.

“systemd is just init.” It is a service manager with a dependency graph, socket activation, timers, mount points and a journal. The sheer amount it took on is exactly why it is controversial.

“A target is a runlevel.” Targets aren’t ordered by number and don’t exclude each other. They are just synchronization points in the graph.

“The service works by hand, so the unit is correct.” The unit has a different environment: none of your variables, a different working directory, stdout going to journald. The mismatch is usually there.

“The system won’t boot, I’ll reinstall.” First look at journalctl -b -1 -p err and the parameters in /proc/cmdline. Two things are to blame most often: an initramfs not rebuilt after a kernel update and a wrong root UUID in /etc/fstab.

Check yourself

1. Why is initramfs needed?
2. What is the fundamental difference between UEFI and BIOS?
3. What happens if the process with PID 1 exits?
4. A service starts by hand but fails when started by systemd. Where do you start?
5. What does socket activation give you in systemd?
6. Where can you see the parameters the running kernel was booted with?

B1: installing the system. GPT partitioning, an ESP partition, booting through UEFI and your own .service unit with dependencies. The mandatory part is to break the boot on purpose and fix it from emergency mode.

  • Nemeth et al., UNIX and Linux System Administration Handbook, chapter 2
  • man 7 boot, man 1 systemd, man 5 systemd.unit, man 5 systemd.service
  • systemd for Administrators: a series by the author
  • UEFI Specification: if you need the format details