Welcome! You are about to start on a journey to learn about booting process in different operating systems. For your reference, below is a list of the articles in this series:
- Part 1a: Linux - BIOS and Master Boot Record
- Part 1b: Linux - The Bootloader
- Part 1c: Linux - The Kernel and Initial Ramdisk (this article)
- Part 1d: Linux - Init Process
In this post, we continue Linux boot process with the kernel and Initial Ramdisk whose responsibility is to mount root filesystem and start Init process.
Kernel and Initial Ramdisk
Generally, the startup function of kernel perform these tasks: identify the CPU type, inspect and calculate RAM, sets up kernel stack, disable interrupts, enable the MMU, and call the C-language main procedure to discover devices and mount root filesystem.1 The startup function is written in assembly language and is machine dependent. The C code allocates a message buffer which messages of what is happening from kernel, and later from processes and initialization procedures that init starts, are written to. You can view these messages from kernel system log files, i.e. /var/log/kern.log or /var/log/messages or from dmesg command. The kernel data structures are also initialized. Most are of fixed size, but the page cache and certain page table structures depend on the amount of RAM available.
The kernel next tries to discover devices, it probes the devices from configuration files, those present are added to a table of attached devices by the kernel.
Device drivers are the software that handles or manages a hardware controller. The device drivers are included in kernel image as loadable modules and loaded dynamically while system is running to extend the functionality of the kernel, i.e. drivers, network protocols etc. because statically compiling drivers into one kernel can cause the kernel image to be so large to boot on computers with limited memory (it is practically impossible to include all storage controller drivers in the kernel). Moreover, this also allows drivers to link to different required library versions when compiling. You can dynamically try to load modules with modeprobe or insmod, but beware of potentially kernel panic.
There are a few issues with dynamically loaded drivers approach:
- One of them is security, as loading dynamically allows superusers to inject malicious code into the kernel.
- The more critical issue is to detect and load the modules necessary to mount the root filesystem at boot time. Loadable modules are essentially files residing in /lib/modules/($uname -r) directory but then Linux kernel could not access them as it does not have filesystem mounted in the first place. This is the famous Chicken and Egg problem in Linux booting.
The solution for the Chicken and Egg problem is to use a initial ramdisk method that load a temporary root filesystem into RAM. This root filesystem image is also stored in hard disk, i.e. /boot and is hence accessible and loaded by the bootloader into RAM together with kernel during 2nd step. There are 2 version of initial ramdisk: the outdated initrd and the modern initramfs.
The initial root filesystems contains configuration files, required kernel modules and user-space helpers that the kernel needs in order to find and mount the real root filesystem with read-only mode. Read-only mode ensures that fsck can check the root filesystem safely; after the check, the bootup process remounts the root filesystem in read-write mode.
The kernel knows where the root file system is as was passed as one of the boot arguments (root=) by GRUB. However, The real root filesystem cannot simply be mounted over / yet, since that would make the scripts and tools on the initial root file system inaccessible for any final cleanup tasks:
- With initrd: Kernel mounts the initial compressed filesystem image as /. The real root is mounted at a temporary mount point then rotated with pivot_root() to swap the root and temporary mount points. This leaves the initial root filesystem at a mount point (such as /initrd) the actual root filesystem on /. Boot scripts can later unmount initial root file system to free up memory held by the initrd.
- With initramfs: the image is a compressed archive and the kernel just need to extracts into RAM, mounts it at /. Then, user-space utility Udev included in the initramfs detects and loads the necessary driver modules for the real root filesystem. The initial root file system will then be emptied and the final root file system mounted over the top.
The kernel then executes the /sbin/init program to create init process (PID 1) which is a daemon that manages other daemons.
The classical init process is called “System V Init” but there are now more modern alternatives such as Systemd, Upstart or runit, etc.
- The classsical System V Init is defined by different system states known as run levels, i.e. runlevel 0 is to poweroff; runlevel 1 or single-user mode allows only a single user to log in to the system; runlevel 6 is reserved for rebooting the machine and runlevel 2-5 are for customization, normally for multi-user modes.
- Other modern init systems usually retained features from System V init such as runlevels and /etc/rc.d directories for backward compatibility. However, they try to overcome the shortcomings stemmed from the synchronous design of the classical System V, i.e. tasks are blocking until current one has completed. Upstart operates asynchronously; it handles starting of the tasks and services during boot and stopping them during shutdown, and also supervises the tasks and services while the system is running.
Andrew S. Tanenbaum. Modern Operating Systems. 4th Edition. Pearson PLC ↩