Embercode
✕
How Computers WorkLesson 12 of 12

45 min read

Boot: from power button to login

Why this matters

A machine that will not start is easier to fix once you know the stages. "No bootable device" points at the firmware and the drive. A kernel panic points at the kernel or one of its drivers. A line that says a service "failed to start" points at one background program, not at the whole machine. Knowing the order lets you ask the right question before you start guessing.

The same chain explains things you see every day. Why does a service keep restarting? Why does one command say "could not be found" and another say "Access denied" for the same name? Why does a text login prompt appear before the desktop? Each answer comes from one of the stages below.

Lesson 4 gives a short version of this path. This lesson follows it in full, with real output. It does not explain how the scheduler shares the CPU between processes; lesson 5 covers that. Here a process only needs to be a running program, and you only need one process fact: the first process has number 1.

Start from an empty machine

When you press the power button, RAM is empty. RAM (random access memory) is the fast working memory of a computer. It loses everything when the power goes off. The operating system is stored on the drive, an SSD or a hard disk, which keeps data without power. The CPU cannot run a program straight from the drive, though. It runs programs from RAM, so the operating system must be copied there first.

So the first code the CPU runs must come from somewhere that does not need the drive. The motherboard holds a small flash chip with a program on it. The CPU is built to begin at a fixed place when it gets power, and on a PC that place leads to this chip.

The rest of the start is a chain of small steps. Each step is a small program that loads a larger one, then hands over control. The whole chain is called booting. The idea is that the machine lifts itself up one stage at a time, the way a small ladder lets you reach a higher shelf.

Stage 1: firmware

Firmware is a program stored in a chip on the motherboard. On modern PCs it is UEFI (Unified Extensible Firmware Interface), a standard that defines how the firmware finds and starts an operating system. Older PCs used BIOS (Basic Input/Output System), which does the same job in a simpler and older way.

The firmware does three things, in order:

  1. It checks the basic hardware: the CPU, the RAM and the links between devices. If something essential is broken, it may beep or show a message before any operating system is involved.
  2. It reads its settings. These are stored in a small memory that keeps its data while the machine is off. A small battery on the motherboard powers that memory. The settings include the boot order, the list of drives to try.
  3. It picks the first drive in the boot order that has a usable start program, and runs that program.

Here is an example. A PC has a USB stick plugged in and an SSD inside. The boot order says USB first, then SSD. The firmware looks at the USB stick. If the stick has no start program in the right place, the firmware moves on to the SSD. This is why a laptop sometimes starts from a USB stick you forgot was in a port.

On a UEFI machine, the firmware looks in a small section of the drive called the EFI System Partition. A partition is a labelled part of a drive. This one uses the FAT file format, a simple format that firmware can read without knowing anything about Linux or Windows. The partition holds the bootloader files in a fixed folder layout.

You cannot see the firmware from inside Linux, but you can check whether Linux was started through UEFI. On a UEFI PC running Linux, ls -d /sys/firmware/efi prints the path. The machine used for this lesson is a WSL2 installation. WSL2 runs Linux inside a lightweight virtual machine that Windows starts, so the firmware and bootloader steps happen on the Windows side. Here the check fails:

ls -d /sys/firmware/efi
ls: cannot access '/sys/firmware/efi': No such file or directory

So for stage 1 and stage 2 this lesson describes how a PC works, and it uses real output from this machine for stages 3 to 7, which Linux can see.

Stage 2: the bootloader

The bootloader is a small program stored in a file on the EFI System Partition. On a Linux PC it is often GRUB or systemd-boot. Its job is to load the kernel into RAM and start it. The kernel is the core of the operating system, the part that runs with full control of the hardware. Lesson 4 covers what the kernel does. Here you only need to know where it sits in the chain.

A bootloader usually does these things:

  • It shows a menu if more than one operating system or kernel version is installed. If you do nothing, it picks the default after a few seconds.
  • It reads its own settings file. That file names the kernel file and the options to pass to it.
  • It loads the kernel file into RAM.
  • It loads an initial RAM filesystem, called initramfs, next to the kernel. This is a small archive of files that the kernel unpacks into RAM at start. It holds the drivers the kernel needs in order to read the real disk. Without it, the kernel often could not read the drive where its own full set of files lives.
  • It hands control to the kernel and passes a command line, a list of options written as plain text.

What the bootloader passes on

The command line is easy to read on any Linux machine. This is the one on the machine used for this lesson:

cat /proc/cmdline
initrd=\initrd.img WSL_ROOT_INIT=1 panic=-1 nr_cpus=12 hv_utils.timesync_implicit=1 console=hvc0 debug pty.legacy_count=0 WSL_ENABLE_CRASH_DUMP=1

Each word is a setting the kernel reads when it starts:

  • initrd=\initrd.img names the initial RAM filesystem file.
  • console=hvc0 names the device where kernel messages are printed.
  • debug asks the kernel to print more messages.
  • nr_cpus=12 tells the kernel how many processor cores to prepare for.
  • The settings that start with WSL_ or hv_ belong to WSL2 and are not part of a standard PC. You can ignore them when you read a laptop's line.

A laptop's line looks similar but has its own root disk and its own options. The bootloader builds that line from its settings file, so change the option there and check this line after the next boot.

Stage 3: the kernel starts

The kernel sets up the things it cannot run without:

  • It finds the features of the CPU and turns on memory management, so that each program gets its own private memory (lesson 4 explains why this matters).
  • It loads the drivers it needs, using the files from the initial RAM filesystem.
  • It mounts the root filesystem. Mounting means attaching a disk partition to a folder, so that its files appear under that folder. The root filesystem is the disk partition that holds the operating system, and it is attached at /, the top folder of the system. Once that is done, the kernel can read the files in /sbin.
  • It starts the first process, the program at /sbin/init.

The kernel prints messages as it goes, and it keeps them in a buffer. The dmesg command prints that buffer. Each line starts with a time in square brackets, which is the number of seconds since the kernel started:

dmesg | head -3
[193801.070762] vethd8749f3: renamed from eth0
[193801.132278] br-4094d13bc4e9: port 3(veth5f0df1a) entered disabled state
[193801.132661] veth5f0df1a (unregistering): left allmulticast mode

These lines are from a little over two days after the kernel started, so the boot messages are not in the buffer any more. Your lines and timestamps will differ. The buffer has a fixed size, so on a machine that has run for days the earliest lines are usually gone. The timestamp is the most useful part of each line. When you read a boot problem, the timestamps tell you which messages came first.

Stage 4: the first process

The kernel starts process number 1, which has the PID (process ID) 1. A process is a running program with its own number. The kernel never stops process 1 on purpose. If it ever exits, the kernel panics (stops with an error), because nothing would be left to start and manage the other programs.

Two commands show what process 1 is on this machine:

cat /proc/1/comm
tr '\0' ' ' < /proc/1/cmdline
systemd
/sbin/init 

Process 1 is systemd, a program that starts and manages services. Its command line is /sbin/init, the path the kernel was told to run. On this machine, that path is a link to systemd: /sbin/init -> ../lib/systemd/systemd.

Every process has a parent, the process that started it. The ps command can show each process's parent in the PPID column:

ps -eo pid,ppid,comm --sort=pid | head -14
    PID    PPID COMMAND
      1       0 systemd
      2       1 init-systemd(Ub
      6       2 init
      9       2 sh
     22       1 cron
     63       1 systemd-journal
     85       1 systemd-resolve
    107       1 systemd-udevd
    180       1 chronyd-starter
    183       1 dbus-daemon
    187       1 networkd-dispat
    215       1 systemd-logind
    222       1 tinyproxy

Most of these background programs have PPID 1, which means systemd started them. Processes 2, 6 and 9 come from the way WSL starts Linux, so you can ignore them for this lesson. The rest of the list is what boot builds: cron, the journal, the network resolver, the device manager, the time service, the message bus and the login manager.

Stage 5: systemd starts the services

systemd does not run a single fixed list of steps. It reads unit files, which are small text files that each describe one thing it manages. A unit can be a service (a background program), a mount (a disk attached to a folder), a socket (a waiting point for a connection) or a target (a named group of units). The unit file for cron on this machine is at /usr/lib/systemd/system/cron.service. The status command shows that path when you ask about the service:

systemctl status cron.service

A unit file can say two things about ordering. It can say which units it needs to be running first, and which units must start before it. systemd uses these rules to start as many units at once as it can, while keeping the order that the rules require. Because of this, the start order is a graph of dependencies, not one simple list.

The target that the machine aims for is called the default target:

systemctl get-default
graphical.target

On this machine, the default target is graphical.target. The output of the critical-chain command below shows that graphical.target depends on multi-user.target, and that multi-user.target depends on services such as docker.service.

Reading the critical chain

The critical-chain command shows the path of dependencies that decided when the boot finished:

systemd-analyze critical-chain
The time when unit became active or started is printed after the "@" character.
The time the unit took to start is printed after the "+" character.
 
graphical.target @5.701s
└─multi-user.target @5.699s
  └─docker.service @1.494s +4.203s
    └─containerd.service @959ms +532ms
      └─dbus.service @702ms +250ms
        └─basic.target @693ms
          └─sockets.target @692ms
            └─snapd.socket @642ms +34ms
              └─sysinit.target @630ms
                └─systemd-udev-trigger.service @391ms +237ms
                  └─systemd-udevd-varlink.socket @366ms +26us
                    └─system.slice @332ms
                      └─-.slice @332ms

Read the chain from the top down to see what each unit waited for. Read it from the bottom up to see the order in which things happened. The @ number is the time the unit started. The + number is how long its start took, so adding the two gives the time it was ready.

So containerd.service started at 959 milliseconds and took 532 milliseconds, so it was ready at about 1.49 seconds. docker.service started at 1.494 seconds, just after that. It took 4.203 seconds, the longest step in this chain, and multi-user.target followed at 5.699 seconds.

The same command also prints a total:

systemd-analyze
Startup finished in 5.706s (userspace) 
graphical.target reached after 5.701s in userspace.

The word "userspace" means the time after the kernel handed control to systemd. The firmware and bootloader time are not in this number. On a UEFI PC, systemd-analyze can also show firmware and loader time next to the total. This machine does not have those stages, so it shows only the userspace line.

A small model of the order

The real systemd is large, so this lesson uses a small model of the same rule. Each stage lists the stages it needs first. The order function puts every stage after the stages it needs. The boot function walks that order. If a stage needs something that failed, the stage is skipped.

Python · editable

Save the program above as boot_lesson.py. Run it with no argument for a normal boot. Pass the name of a stage to make that stage fail:

python3 boot_lesson.py
order: firmware, bootloader, kernel, drivers, mount-root, network, cron, getty-tty1, login-prompt
ok       firmware
ok       bootloader
ok       kernel
ok       drivers
ok       mount-root
ok       network
ok       cron
ok       getty-tty1
ok       login-prompt
9 of 9 stages running
python3 boot_lesson.py network
order: firmware, bootloader, kernel, drivers, mount-root, network, cron, getty-tty1, login-prompt
ok       firmware
ok       bootloader
ok       kernel
ok       drivers
ok       mount-root
FAILED   network
SKIPPED  cron (needs network)
ok       getty-tty1
ok       login-prompt
7 of 9 stages running

When network fails, only cron is skipped, because only cron needs it. The text login still starts, because it needs mount-root and not the network. That matches a real machine, where you can still log in on a text console when the network is down.

A failure at a lower stage removes more. Here is the same model with drivers failing:

python3 boot_lesson.py drivers
order: firmware, bootloader, kernel, drivers, mount-root, network, cron, getty-tty1, login-prompt
ok       firmware
ok       bootloader
ok       kernel
FAILED   drivers
SKIPPED  mount-root (needs drivers)
SKIPPED  network (needs drivers, mount-root)
SKIPPED  cron (needs network, mount-root)
SKIPPED  getty-tty1 (needs mount-root)
SKIPPED  login-prompt (needs getty-tty1)
3 of 9 stages running

Three stages run, and the rest wait for the failed one. The model is simple, but it shows the rule that matters: a failure stops only what depends on it.

Stage 6: what the background services do

Once systemd has started the units, background services keep running with no window attached. This machine had 19 running services when this lesson was written. The first lines of the list are below:

systemctl list-units --type=service --state=running --no-legend --no-pager | head -12
  chrony.service              loaded active running chrony, an NTP client/server
  containerd.service          loaded active running containerd container runtime
  dbus.service                loaded active running D-Bus System Message Bus
  docker.service              loaded active running Docker Application Container Engine
  getty@tty1.service          loaded active running Getty on tty1
  networkd-dispatcher.service loaded active running Dispatcher daemon for systemd-networkd
  polkit.service              loaded active running Authorization Manager
  postgresql@18-main.service  loaded active running PostgreSQL Cluster 18-main
  rsyslog.service             loaded active running System Logging Service
  ssh.service                 loaded active running OpenBSD Secure Shell server
  systemd-journald.service    loaded active running Journal Service
  systemd-logind.service      loaded active running User Login Management

Some of these are part of a standard Linux system. chrony keeps the clock right by asking time servers on the network. dbus is a message bus that lets programs send each other short messages. rsyslog writes log messages into files. polkit decides which administrative actions a user may take. systemd-logind keeps track of who is logged in.

Some of these were installed for software development on this machine, not by Linux itself. docker, containerd and postgresql are examples. A fresh laptop shows a different list, so do not expect your output to match this one.

Starting a service and enabling it are different actions:

  • systemctl start starts a service now, and it runs until it is stopped or the machine restarts.
  • systemctl enable makes the service start at every boot. It does not start the service now.
  • systemctl enable --now does both.

Stage 7: the login

The login stage has two parts: a program that shows the prompt, and a check that the password is right.

The program is called getty (short for "get tty", where tty is the name for a terminal). It opens a text terminal and prints the "login:" prompt. On this machine, the getty for the first text terminal is set up like this:

systemctl show -p ActiveState,SubState,UnitFileState getty@tty1.service
ActiveState=active
SubState=running
UnitFileState=enabled

ActiveState=active means the unit is running now. UnitFileState=enabled means systemd starts it at every boot. The @tty1 part is a template. systemd creates one copy of the unit for each terminal name, so there is one for tty1, one for tty2 and so on.

On a desktop PC, a different program shows the graphical login screen. It is called a display manager. Common examples are GDM and SDDM. The graphical target pulls it in, and a desktop uses it as its default target.

What happens after you type the password

When you type a user name and password, the login program does these steps:

  1. It asks PAM (Pluggable Authentication Modules), a set of rules that the system uses to check a login. PAM decides which checks run and in which order.
  2. It turns the password you typed into a hash, a one-way scramble of the text. It then compares that hash with the hash stored in /etc/shadow. The system never stores your password itself, and only root can read that file.
  3. If the hashes match, the system starts a session for you. systemd-logind records the session.
  4. The login program starts your shell, the program that reads and runs your commands, such as bash or zsh.

You can list the sessions that systemd-logind knows about:

loginctl list-sessions --no-legend --no-pager
c1 1000 chiranjeet - 1937 user    pts/1 yes 2 days ago
c2 1000 chiranjeet - 2005 manager -     no  -

Each line is one session, and your list will differ. The first column is the session ID. The second and third columns are the numeric user ID (1000) and the user name. The pts/1 on the first line is a terminal, the kind that a terminal window uses. The second session has the class manager. That is the session for the user's own service manager, the program that starts that user's background services. The --no-legend option removes the header line, so you see only the data rows.

Second example: a boot where one service keeps failing

The next example is a real one from this machine. On the boot of 2026-11-02, cron (the program that runs jobs at set times) ended in a failed state. The first command to run is the one that lists failed units:

systemctl --failed --no-pager --no-legend
● cron.service loaded failed failed Regular background program processing daemon

The machine still reached graphical.target. A failed service does not stop the boot if nothing needs it. Only the stages that depend on cron would be skipped, as in the model above. Nothing in the chain depends on cron, so the boot finished.

Common mistakes

Thinking one failed service means the boot failed

A failed unit in systemctl --failed is a problem with that unit. It is not a verdict on the whole boot. In the example above, cron failed and the machine still reached the login stage. Read the failed unit's own log before you change anything else.

Reading "could not be found" and "Access denied" for the same name

These two commands use the same name, nosuch-demo.service, which does not exist on this machine:

systemctl status nosuch-demo.service
systemctl start nosuch-demo.service

The status command gives a different message from the start command:

Unit nosuch-demo.service could not be found.
Failed to start nosuch-demo.service: Access denied as the requested operation requires interactive authentication. However, interactive authentication has not been enabled by the calling program.
See system logs and 'systemctl status nosuch-demo.service' for details.

An exit code is the number a command returns when it ends. The status command exits with code 4, and the start command, run as a normal user, exits with code 1. On this machine, the start command seems to check permission before it looks up the name, so a normal user sees "Access denied" first. If you see "Access denied" on a service that does exist, the fix is to run the command with sudo, not to change the name.

Comparing the boot time with a stopwatch

systemd-analyze reported 5.706 seconds on this machine. That number starts when systemd starts. A stopwatch that starts at the power button includes firmware and bootloader time, so it will read more. Before you compare numbers, check what each one measures.

Expecting enable to start a service now

systemctl enable makes a service start at the next boot. It does not start the service in the current session. If you enable a service and expect it to run right away, run systemctl start as well, or use systemctl enable --now.

Writing a cycle into the dependencies

A cycle is a chain where each stage needs the next and the last one needs the first. Then no stage can start first. If kernel is changed to need login-prompt, order calls itself forever until Python stops it. The last line of the error message is:

RecursionError: maximum recursion depth exceeded

A cycle check would catch this earlier and name the stages in the loop. This message is from Python 3.14, and other versions may word it differently. systemd also reports ordering cycles, with its own wording, which this lesson did not reproduce. The idea is the same in both: a cycle leaves no first stage to start.

Debugging walkthrough: why cron keeps failing

This walkthrough follows the cron failure from the second example. The goal is to find the cause, not only the symptom. The machine was not changed during the investigation.

The first step is the status command. It shows the state and the last start attempt:

systemctl status cron.service --no-pager
× cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2026-11-02 18:07:31 UTC; 2 days ago
   Duration: 27ms
 Invocation: f616ab270c6c499889763976e7f07f81
       Docs: man:cron(8)
   Main PID: 441 (code=exited, status=1/FAILURE)
   Mem peak: 1.7M
        CPU: 8ms
 
Warning: some journal files were not opened due to insufficient permissions.

The status shows that the process exited with status 1, and that it ran for only 27 milliseconds. It does not say why. The warning means this user cannot read every journal file, which is why the next step uses sudo. Your IDs, times and memory numbers will differ. For that, read the log. The journalctl command reads systemd's log, and -u limits the output to one unit. Without sudo, journalctl prints a hint that it cannot see system messages, so this step uses sudo -n. The -n option makes sudo fail instead of asking for a password. On this machine no password is needed, so the command runs:

sudo -n journalctl -u cron.service -n 6 --no-pager
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Main process exited, code=exited, status=1/FAILURE
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Failed with result 'exit-code'.
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Scheduled restart job, restart counter is at 5.
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Start request repeated too quickly.
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Failed with result 'exit-code'.
Nov 02 18:07:31 LAPTOP-IJ0A1RKV systemd[1]: Failed to start cron.service - Regular background program processing daemon.

The line "Start request repeated too quickly" means systemd tried to restart cron several times in a short period, then stopped trying. The restart counter says the restart had reached 5. These lines describe systemd's retries, not the first error. The first error is further back, so the next step asks for more lines:

sudo -n journalctl -u cron.service --no-pager -n 40 | head -12
Oct 29 04:28:10 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Failed with result 'exit-code'.
Oct 29 04:28:10 LAPTOP-IJ0A1RKV systemd[1]: Failed to start cron.service - Regular background program processing daemon.
-- Boot 7d4512b47e0847f2808bb614b4650391 --
Nov 02 18:07:30 LAPTOP-IJ0A1RKV systemd[1]: Started cron.service - Regular background program processing daemon.
Nov 02 18:07:30 LAPTOP-IJ0A1RKV (cron)[182]: cron.service: Referenced but unset environment variable evaluates to an empty string: EXTRA_OPTS
Nov 02 18:07:30 LAPTOP-IJ0A1RKV cron[182]: cron: can't lock /var/run/crond.pid, otherpid may be 22: Resource temporarily unavailable
Nov 02 18:07:30 LAPTOP-IJ0A1RKV cron[182]: (CRON) DEATH (can't lock /var/run/crond.pid, otherpid may be 22: Resource temporarily unavailable)
Nov 02 18:07:30 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Main process exited, code=exited, status=1/FAILURE
Nov 02 18:07:30 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Failed with result 'exit-code'.
Nov 02 18:07:30 LAPTOP-IJ0A1RKV systemd[1]: cron.service: Scheduled restart job, restart counter is at 1.
Nov 02 18:07:30 LAPTOP-IJ0A1RKV systemd[1]: Started cron.service - Regular background program processing daemon.
Nov 02 18:07:30 LAPTOP-IJ0A1RKV (cron)[343]: cron.service: Referenced but unset environment variable evaluates to an empty string: EXTRA_OPTS

The lines above the -- Boot marker are from an earlier boot on October 29. The useful line is the cron[182]: cron: can't lock line. cron writes its process number into a file, /var/run/crond.pid, and it locks that file while it runs. The lock tells other copies that cron is already running. Here the new copy could not take the lock, and the message says the other copy may be process 22. In the process list earlier in this lesson, PID 22 is cron. That suggests a second cron copy started while the first one was still running. It refused to run and exited, systemd restarted it, and it failed again. The restart limit then stopped the cycle of retries.

The reading order matters. The last lines are the most recent, so the status and the last few log lines describe the end of the story. The cause sits earlier in the log. Use -n with a larger number, or read the log from the top, to find where the trouble started.

The fix is not in this lesson, because it depends on why a second copy of cron started. The next steps would be to check which copy holds the lock and which unit or script started the extra copy. Do not delete the pid file by hand while a copy is running, because that removes the lock that protects the first copy.

Practice

Exercise 1

Put these stages in the order a Linux boot runs them: login prompt, kernel, firmware, services, bootloader, first process.

Show the answer

firmware, bootloader, kernel, first process, services, login prompt

Exercise 2

Run python3 boot_lesson.py drivers. How many stages are running at the end, and which stages are skipped?

Show the answer

Three stages are running: firmware, bootloader and kernel. The stages mount-root, network, cron, getty-tty1 and login-prompt are skipped, because each one needs drivers or a stage that needs it. The last line reads "3 of 9 stages running".

Exercise 3

In the critical-chain output, which unit does multi-user.target wait for directly, and which unit does that unit wait for?

Show the answer

multi-user.target waits for docker.service, which started at 1.494 seconds and took 4.203 seconds to start. docker.service waits for containerd.service, which started at 959 milliseconds.

Exercise 4

Most of the processes in the ps output have PPID 1. What does that tell you about who started them?

Show the answer

Process 1, systemd, is the parent of those processes. So systemd started them.

Exercise 5

Cron keeps failing, and you want the messages that cron itself wrote, not systemd's summary of the restarts. Which command do you run, and why does it need sudo on this machine?

Show the answer

Run sudo -n journalctl -u cron.service. The -u option limits the log to the lines of that unit. The status command shows only a summary. Without sudo, the journal hides system messages from a normal user, and the output starts with a hint that says so.

Summary

  • Firmware on the motherboard tests the hardware, reads the boot order and starts a bootloader from the EFI System Partition.
  • The bootloader loads the kernel and the initial RAM filesystem into RAM, and passes a command line to the kernel.
  • The kernel mounts the root filesystem and starts process 1, which is systemd on this machine.
  • systemd starts units in dependency order, and a failed unit skips only the units that depend on it.
  • getty shows the text login prompt, PAM checks the password, and systemd-logind records the session.
  • Read a failed service with journalctl -u, not only with systemctl status, and read the earliest lines that show the first error.
Check yourself: Units, compression and boot (10 questions) →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Compression: lossless vs lossy