As soon as we turn on our systems, it's very crucial for us to understand the processes that get triggered. Hence, it is really important for anyone to go through the concepts of booting to understand how an operating system behaves and also to resolve any booting error. Since we're talking about Linux systems here, let's begin with understanding the basics of booting in Linux.
Booting a Linux system involves several stages and software components, including firmware initialization, execution of a boot loader, initiation and launch of a Linux kernel image, and finally execution of various startup scripts and daemons. Here, we'll briefly be discussing all the booting processes in Linux.
The high-level stages of a typical Linux boot process are:
- 1. BIOS / UEFI
- 2. MBR
- 3. GRUB
- 4. Kernel
- 5. Init
- 6. Run Levels
Now, let's explore more about these stages -
1. BIOS / UEFI
BIOS stands for Basics Input/Output System. The first step in the Linux boot process is the BIOS, which performs system integrity checks. The BIOS is a firmware most commonly found in IBM PC-compatible computers, the dominant type of computers in the modern world. The main objective of the BIOS is to find the system bootloader.
So once the BIOS boots up the hard drive, it searches for the boot block to figure out how to boot up the system. Depending on how the disk partition is created, it will look at the MBR (Master Boot Record) and GPT (GUID Partition Table).
Conversely, a UEFI (Unified Extensible Firmware Interface) system stores all startup data in an .efi file. The file is on the EFI System Partition, which contains the boot loader. UEFI comes with many improvements from the traditional BIOS firmware. However, since we are using Linux, the majority of us are using BIOS.
We can check the partitions in our Linux machine by using the 'fdisk' command.
root@ubuntu:~$ sudo fdisk -l Disk /dev/sda: 92 GiB, 98784247808 bytes, 192937984 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: gpt Disk identifier: DA785EC6-AB02-4D8F-9623-B1977C5CCCD5 Device Start End Sectors Size Type /dev/sda1 2048 4095 2048 1M BIOS boot /dev/sda2 4096 3149823 3145728 1.5G Linux filesystem /dev/sda3 3149824 67106815 63956992 30.5G Linux filesystem Disk /dev/sdb: 84 GiB, 90194313216 bytes, 176160768 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 30.5 GiB, 32744931328 bytes, 63954944 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes
In summary, the bootloader is a small program that loads the operating system. It basically performs three main actions with the Linux kernel:
- Locate on the disk
- Insert into memory and
- Execute with the supplied options.
2. Master Boot Record (MBR)
The MBR is situated in the first sector of the hard drive, the first 512 bytes. It contains the code to load another program somewhere on the disk, which in turn actually loads up our bootloader. It is further divided into three components - primary boot loader info in the first 446 bytes, partition table info in the next 64 bytes, and MBR validation check in the last 2 bytes.
It contains information about GRUB (or LILO in previous generation systems). So, in simpler terms MBR loads and executes the GRUB boot loader.
Note: LILO or Linux Loader was the default boot loader for most Linux distributions in the past. Today, many distributions use GRUB as the default boot loader, but LILO and its variant ELILO are still widely used.
Since we're talking about modern Linux distros here, let us understand what a GRUB boot loader is.
3. GRUB
GRUB stands for Grand Unified Bootloader.
Now that we know the primary goal of the bootloader is to load up the kernel, it is equally important for us to understand how it finds the kernel. To find it, we will need to look at our kernel or boot parameters. Now, let's look at these parameters:
- initrd - (initial ramdisk) is a scheme to load a temporary root file system into memory for use as part of the Linux startup process.
- BOOT_IMAGE - This is where the kernel image is located.
- root - The location of the root filesystem; the kernel searches inside this location to find init. It is often represented by its UUID or the device name, such as /dev/sda1.
- ro - It's a standard parameter that mounts the filesystem as read-only mode.
- quiet - This parameter is added so that we don't see display messages that are going on in the background during boot.
- splash - This parameter displays the splash screen.
GRUB displays a splash screen, waits for a few seconds, and loads the default kernel image (or the one that is chosen manually), as specified in the grub configuration file.
One big difference between GRUB and LILO systems is that GRUB supports booting multiple operating systems, whereas LILO can boot only one. GRUB also has knowledge of file systems, unlike the older LILO.
The main GRUB configuration file can be found in '/boot/grub/grub.cfg'. A simple configuration file could look something like this:
root@ubuntu:~$ sudo nano /boot/grub/grub.cfg # /boot/grub/grub.cfg # # GRUB configuration file # Timeout for menu set timeout=5 # Default boot entry set default=0 # Entry titles menuentry "Ubuntu" { linux /vmlinuz root=/dev/sda1 ro quiet splash initrd /initrd.img }
From the above output, we can see that the menuentry section represents a specific boot option. In this case, the entry is titled "Ubuntu." The Linux line specifies the kernel image file (vmlinuz) and the root device (/dev/sda1). The initrd line specifies the initial RAM disk file (initrd.img), which is used for loading additional drivers and modules during the boot process.
So, in simple terms, GRUB just loads and executes Kernel and initrd images.
You can check the grub version on your system with the following command. On my Ubuntu 23.04 system the output looks like this:
$ grub-install --version grub-install (GRUB) 2.06-2ubuntu16 $
The commonly used versions of grub are either 0.97 (legacy) or grub 2, with grub 2 being the most recent and widely adopted.
4. Kernel
Now that our bootloader has passed on the necessary parameters, we must understand how the rest of the booting actions take place:
Initrd vs Initramfs
Initrd (initial RAM disk) and initramfs (initial RAM file system) are both used as temporary file systems loaded into memory during the boot process. Initrd is an older method that uses a compressed file system image, while initramfs is a newer approach that utilizes a cpio archive for greater flexibility and efficiency in managing the boot process.
Mounting the root filesystem
Mounting the root file system is a crucial step in the boot process to make the main file system accessible to the operating system. Now the kernel has all the modules it needs to create a root device and mount the root partition.
Before we proceed any further, the root partition is actually mounted in read-only mode so that 'fsck' can run safely and check for system integrity. Afterward, it remounts the root filesystem in read-write mode. Then the kernel locates the init program and executes it.
Kernel executes the /sbin/init file or program. Since init is the first program to be executed by kernel, it has the process id (PID/ Process ID) of 1 always. We can see the PID of the init file by executing the following command - ps -ef | grep init
.
root@ubuntu:/sbin$ ps -ef | grep init root 1 0 0 May07 ? 00:01:43 /sbin/init maybe-ubiquity
5. Init
Init checks and identifies the default init level from file "/etc/inittab" and loads all appropriate programs required for the run level. Listing down the run levels below:
- 0 – halt
- 1 – single-user mode
- 2 – Multiuser, without NFS
- 3 – Full multiuser mode
- 4 – unused
- 5 – X11
- 6 – reboot
We usually set the default run level between 0 and 6 for a smooth operation.
The init system is launched via the program /sbin/init
. It might be a symlink to the actual init system binary. On my ubuntu system it points to the systemd binary
$ ls -la /sbin/init lrwxrwxrwx 1 root root 20 Mar 20 19:47 /sbin/init -> /lib/systemd/systemd $
There are actually three different init systems in Linux:
SysV init (System V)
It starts and stops processes in a systematic order based on startup scripts. The state of the machine is denoted by runlevels, and each runlevel behaves differently. It has now been replaced by Systemd.
Upstart
Upstart uses the idea of jobs and events and works by starting jobs that perform certain actions in response to events. This unit is found on older Ubuntu installations.
Systemd
This is the new standard for init and a more popular one. The command used to interact with systemd is systemctl that can be used to manage the system and all the services running under systemd. Inside Systemd services are identified as units, and besides services there are many other type of units like mount points, devices and sockets etc.
Systemd uses the /etc/systemd/system/default.target
or /lib/systemd/system/default.target
file to decide the state or target the Linux system boots into.
To check what init system is running on your linux system use the following command:
$ sudo stat /proc/1/exe File: /proc/1/exe -> /usr/lib/systemd/systemd Size: 0 Blocks: 0 IO Block: 1024 symbolic link Device: 0,22 Inode: 16337 Links: 1 Access: (0777/lrwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2023-06-24 16:30:01.644109024 +0530 Modify: 2023-06-06 12:56:01.788704998 +0530 Change: 2023-06-06 12:56:01.788704998 +0530 Birth: - $
6. Run Levels
Run levels, in the Linux booting process, define different operating modes or configurations that the system can be in.
Depending on the default init level setting, the system will execute the programs from one of the following directories:
- Run level 0 – /etc/rc.d/rc0.d/
- Run level 1 – /etc/rc.d/rc1.d/
- Run level 2 – /etc/rc.d/rc2.d/
- Run level 3 – /etc/rc.d/rc3.d/
- Run level 4 – /etc/rc.d/rc4.d/
- Run level 5 – /etc/rc.d/rc5.d/
- Run level 6 – /etc/rc.d/rc6.d/
There are several notations to be considered under run levels:
- There are symbolic links available for these directories under /etc directly. For example, /etc/rc0.d is linked to /etc/rc.d/rc0.d.
- Inside /etc/rc.d/rc*.d/ directories, we'll see programs that start with S and K.
- Programs starting with 'S' are used for startup.
- Programs starting with 'K' are used to kill the process or shut it down.
- We can see a number right after the letter 'S' or 'K'. This is actually the sequence number in which a program is executed (killed or started).
There we have it. This is exactly what goes on behind the Linux booting process.
How do Dual-Boot Setups Work
In a typical dual-boot setup, the system is configured to have multiple operating systems installed on the same computer, allowing us to choose between them during startup.
On modern systems, UEFI (Unified Extensible Firmware Interface) and ESP (EFI System Partition) play important roles in this process. Now, let's see how the dual-boot setup works:
As previously discussed, UEFI is the modern firmware interface that replaces the traditional BIOS. It provides a standard method for booting the operating system and managing system settings. UEFI firmware initializes the hardware, performs the necessary tests, and then looks for the bootloader.
ESP (EFI System Partition) : The ESP is a partition on the storage device that holds the necessary files for booting the operating systems. It is typically formatted with the FAT32 file system, which is compatible with UEFI firmware. The ESP contains various directories, with the "EFI" directory being the most significant.
Separate boot-loaders : The EFI directory within the ESP stores the bootloaders and related files for each installed operating system. For example, if we have Linux and Windows installed, the ESP will contain separate directories for each. Within these directories, the EFI bootloaders for Linux and Windows reside.
During startup, the UEFI firmware detects the ESP and looks for the bootloader in the EFI directory. The bootloader, such as GRUB for Linux or the Windows Boot Manager for Windows, is responsible for loading the selected operating system.
GRUB, the default bootloader in many dual-boot configurations, resides in the EFI directory within the ESP. It provides a boot menu with options for different operating systems. The GRUB configuration files, such as "grub.cfg," are typically stored in the '/boot/grub' directory (Linux).
When we select an operating system from the boot menu, the corresponding EFI bootloader is loaded from the ESP. For Linux, GRUB loads the Linux kernel and initiates the Linux operating system. For Windows, the Windows Boot Manager is invoked, starting the Windows operating system.
In summary, in a dual-boot setup, the UEFI firmware looks for the bootloader in the EFI directory within the ESP. The ESP contains separate directories for each installed operating system, where the EFI bootloaders are stored. The bootloader, such as GRUB or Windows Boot Manager, loads the selected operating system based on the user's choice from the boot menu.
Links and Resources
https://www.gnu.org/software/grub/manual/grub/html_node/Images.html
https://opensource.com/article/17/2/linux-boot-and-startup