A small exploration of GRUB and initramfs

During the competition, I had some questions about the operating system startup process, so I briefly explored the related mechanisms of GRUB and initramfs in a problem-oriented manner. The relevant notes are placed here first.

Kernel startup process

In the traditional BIOS system, the specific startup process of the computer is as follows:

  1. Power on: When the computer’s power is turned on, the power supplies power to the computer’s hardware devices.
  2. BIOS self-test: The computer’s BIOS firmware will self-test hardware devices, including RAM, processor, hard disk, etc., to ensure that they are working properly.
  3. Boot device selection: BIOS will select a boot device based on a predefined boot sequence (usually hard disk, optical drive, USB, etc.).
  4. MBR (Master Boot Record) loading: If the selected boot device is a hard disk, the BIOS will load the MBR of the hard disk, which contains the boot loader.
  5. GRUB loading: The boot loader in an MBR is usually GRUB (or other boot loader). GRUB will be loaded into the computer’s memory and begin execution.
  6. GRUB Menu: GRUB displays a menu listing operating systems or kernels to choose from.
  7. Operating system loading: After the user selects the operating system, GRUB will load the corresponding operating system or kernel and hand over control to it.

In this kernel compilation and configuration process, the most important thing to explore is the loading process of the file system, which is the part between 6-7.

Overview

The development history of the file system in the startup process can be divided into the following three parts:

  1. GRUB file system

    Loaded by GRUB itself through services provided by the BIOS

  2. initramfs

    Loaded by GRUB for mounting real file systems

  3. true root file system

Below, the two processes 1 and 2 will be introduced.

GRUB

GRUB (GNU GRand Unified Bootloader) is a commonly used boot loader used to load the operating system when the computer starts.

The main function of GRUB is to provide a menu when the computer starts, allowing users to select the operating system or kernel to start. It supports multiple operating systems, including various versions of Linux, Windows, BSD, etc. With GRUB, users can easily switch between multiple operating systems.

In addition to operating system selection, GRUB also provides some advanced functions, such as boot parameter settings, memory detection, system recovery, etc. It also supports loading kernel modules and initializing RAM disk images (initrd or initramfs) during boot.

GRUB is highly configurable, allowing users to customize the boot menu, set default startup items, edit kernel parameters, etc. It also supports chained booting between boot loaders and can boot other boot loaders, such as Windows’ NTLDR.

The basic function flow of GRUB is:

  1. BIOS loads MBR, MBR loads GRUB, and starts executing the GRUB program.
  2. The GRUB program will read the grub.cfg configuration file
  3. The GRUB program performs operations such as loading the kernel and mounting the root file system based on the configuration file, and finally transfers control to the kernel.

grub.cfg

When the kernel starts, the GRUB program will read the GRUB configuration file grub.cfg in the /boot/grub/ directory, which records all the kernel options available for the GRUB menu. (menuentry) and its corresponding startup dependency parameters. Take the 6.4.0 kernel options as an example:

# menuentry identifies a kernel option in the GRUB menu
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-XXX' {<!-- -->
        recordfail #Record whether the last startup failed, used to handle the situation of startup failure
        load_video #Load the video driver module, used to display the graphical interface during the startup process
        gfxmode $linux_gfx_mode # Set graphics mode
        insmod gzio # Load the gzio module to provide support for GZIP compression and decompression functions
        # If it is on the Xen virtualization platform, load the xzio and lzopio modules
        if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
        
        insmod part_gpt # Load part_gpt module, support GUID partition table (GPT)
        insmod ext2 # Load the ext2 module and support the ext2 file system
        
        # Set the root partition of the file system
        set root='hd0,gpt3'
        if [ x$feature_platform_search_hint = xy ]; then
          search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt3 --hint-efi=hd0,gpt3 --hint-baremetal=ahci0,gpt3 XXX
        else
          search --no-floppy --fs-uuid --set=root XXX
        fi
        
        linux /boot/vmlinuz-6.4.0-rc3 + root=UUID=XXX ro text # Specify the path and startup parameters of the kernel image
        initrd /boot/initrd.img-6.4.0-rc3 + # Specify the path to the initramfs image
}

As you can see, grub.cfg mainly records some dependent modules required for the kernel startup, as well as paths to the kernel image and initramfs image.

In the menuentry code, there are the following points worth noting:

  1. insmod gzio

    Due to the loading of the gzio module, support for GZIP compression and decompression functions is provided.

    My first reaction when I saw this was that I felt a bit disconnected. Why does this seemingly insignificant decompression function need to be available before the kernel is started? Then I remembered that when configuring the kernel, there is an option like this:

    The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly

    In the configuration options, we selected support for initramfs and checked Support initial ramdisk/ramfs compressed using gzip , which means that the size of the initramfs is compressed by gzip during compilation to save space.

    Therefore, the initramfs we hold is in a compressed state before the kernel starts. Therefore, we naturally need to install the gzio module before the kernel starts to support subsequent decompression of initramfs.

  2. insmod ext2

    This code shows that GRUB’s temporary file system is of ext2 type. This code is actually the necessary dependency package for installing GRUB to create temporary files, so that the GRUB program can create its temporary file system and obtain it from /boot/initrd.img initramfs image.

  3. linux /boot/vmlinuz-6.4.0-rc3 + root=UUID=XXX ro text

    The startup parameters are specified, that is, the root file system is mounted on the block device corresponding to root=UUID=XXX in a read-only (ro) manner, and the default is < Start the kernel in code>text mode (that is, non-graphical Shell interface).

    The startup parameters here can be personalized in the grub file described in the next section.

Generation and modification of grub.cfg

In actual application, it is often necessary to make some modifications to the startup parameters. The following describes two methods of modifying grub.cfg.

/etc/default/grub

As you can see, grub.cfg actually has a relatively fixed format (that is, it consists of a series of menuentries with similar content). Therefore, we actually generate grub.cfg through grub.d (this is actually covered in the 6.S081 experiment), and /etc /default/grub is the configuration file generated by the GRUB program and grub.cfg. The following introduces the main configuration options of this file.

# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
# info -f grub -n 'Simple configuration'

# The duration of the GRUB interface when booting, set here to 30s
GRUB_TIMEOUT=30
GRUB_CMDLINE_LINUX=""

# Do not use graphical interface
#GRUB_TERMINAL=console
# Size of graphical interface
#GRUB_GFXMODE=640x480
# Do not use UUID
#GRUB_DISABLE_LINUX_UUID=true

# Hide recovery mode
#GRUB_DISABLE_RECOVERY="true"

Focus on these parameters:

  1. GRUB_CMDLINE_LINUX

    Indicates what parameters need to be appended to the linux line in each menuentry in the final generated grub.cfg.

    For example, if set to:

    # Indicates that initramfs needs to wait 120s before mounting the real root file system, which is used to prevent mounting failure caused by the disk not being ready.
    GRUB_CMDLINE_LINUX="rootdelay=120"
    

    Then, the final startup parameters in menuentry are:

    linux /boot/vmlinuz-6.4.0-rc3 + root=UUID=XXX ro rootdelay=120 text
    

    Some other common options:

    # Identify block devices directly by path instead of using UUID. This is an old option. It is recommended to use UUID as much as possible.
    GRUB_CMDLINE_LINUX="root=/dev/sda3"
    # Indicate the specific path of the init process (the first process after startup). Specified here as `/bin/sh`
    GRUB_CMDLINE_LINUX="init=/bin/sh"
    
  2. GRUB_DEFAULT

    Reference can be used to specify kernel options on reboot. For example, GRUB_DEFAULT="1> 0" means selecting the 2nd column (Advanced for Ubuntu) of the first menu interface and the 1st kernel of the second menu.

After modifying the grub file, we need to execute sudo update-grub to regenerate the grub.cfg file for next startup.

Modify directly in the GRUB interface

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly

We can select the required kernel in the GRUB interface and press the e key:

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly

Then you can modify the startup parameters and ^X exit.

It is worth noting that this modification is only effective for this launch. If long-term modification is required, it is recommended to modify it through the first method.

initramfs

The GRUB program will start initramfs through initrd.img to mount the real root file system.

initrd.img is an image file of the initial RAM disk in a Linux system. It is a compressed file system image that is usually loaded into memory during the boot process and provides a temporary root file system to provide necessary functionality and module.

We can use the unmkinitramfs /boot/initrd.img-6.4.0-rc3 + /tmp/initrd/ command to decompress initrd and explore what is inside.

├── bin -> usr/bin
├── conf
├── etc
├── init
├── lib -> usr/lib
├── lib32 -> usr/lib32
├── lib64 -> usr/lib64
├── libx32 -> usr/libx32
├── run
├── sbin -> usr/sbin
├── scripts
├── usr
└── var
init

As you can see, this is actually a small file system, namely initramfs. It has its own built-in Shell (BusyBox):

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly

There are a few shell commands (in the bin and sbin directories), and the code logic used to mount the real root file system (stored in the scripts directory). [I guess] Under normal circumstances, the system will execute the script code under scripts to mount the real file system. When an exception occurs during the mount, the system will hand over control to the Shell BusyBox built into the initramfs, and it is up to the user to figure out what went wrong.

We can next trace the file system mounting process in the script directory of initramfs.

The main function for mounting a real file system is local_mount_root:

# Only show the main process code
local_mount_root()
{<!-- -->
# Preprocessing, obtaining parameters, etc. (i.e. root=UUID configured in grub.cfg above)
local_top
if [ -z "${ROOT}" ]; then
panic "No root device specified. Boot arguments must include a root= parameter."
fi
\t
# Get the corresponding block device based on UUID
local_device_setup "${ROOT}" "root file system"
ROOT="${DEV}"

# Preprocessing before mounting
local_premount
\t
# mount
mount ${roflag} ${FSTYPE: + -t "${FSTYPE}"} ${ROOTFLAGS} "${ROOT}" "${rootmnt?}"
}

Since researching this is an error driver, I only looked at local_device_setup:

# $1=device ID to mount device ID
# $2=optionname (for root and etc) What is to be mounted? This should be the root file system
# $3=panic if device is missing (true or false, default: true)
# Sets $DEV to the resolved device node $DEV is the finally obtained block device
local_device_setup()
{<!-- -->
local dev_id="$1"
local name="$2"
local may_panic="${3:-true}"
local real_dev
local time_elapsed
local count

# Get the device waiting time of the rootdelay parameter of grub.cfg. Without this parameter, the default is 30 seconds
local slumber=30
if [ "${ROOTDELAY:-0}" -gt $slumber ]; then
slumber=$ROOTDELAY
fi

# Wait for device
case "$dev_id" in
UUID=*|LABEL=*|PARTUUID=*|/dev/*)
FSTYPE=$( wait-for-root "$dev_id" "$slumber" )
;;
*)
wait_for_udev 10
;;
esac

# The wait is over. If the condition is true, it means that the corresponding device still cannot be obtained, which can only mean that the device is dead.
# So we have to tell the user the problem, let the user solve it by themselves, and enter the BusyBox Shell
# We've given up, but we'll let the user fix matters if they can
while ! real_dev=$(resolve_device "${dev_id}") ||
! get_fstype "${real_dev}" >/dev/null; do
if ! $may_panic; then
echo "Gave up waiting for ${name}"
return 1
fi
echo "Gave up waiting for ${name} device. Common problems:"
echo "-Boot args (cat /proc/cmdline)"
echo "-Check rootdelay= (did the system wait long enough?)"
if [ "${name}" = root ]; then
echo "-Check root= (did the system wait for the right device?)"
fi
echo "-Missing modules (cat /proc/modules; ls /dev)"
panic "ALERT! ${dev_id} does not exist. Dropping to a shell!"
done

DEV="${real_dev}"
}

As you can see, if it enters an error state here, the final effect will be like this 2333:

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly

syntaxbug.com © 2021 All Rights Reserved.