How initrd is mounted by Linux kernel ?

Lets first try to understand what are the ways to mount the root file system, Altough the documentation from Documentation/early-userspace/README inside kernel source code is bit outdated, it is still has valid information as below,

The kernel has currently 3 ways to mount the root filesystem:

a) all required device and filesystem drivers compiled into the kernel, no initrd. init/main.c:init() will call prepare_namespace() to mount the final root filesystem, based on the root= option and optional init= to run some other init binary than listed at the end of init/main.c:init().

b) some device and filesystem drivers built as modules and stored in an initrd. The initrd must contain a binary ‘/linuxrc’ which is supposed to load these driver modules. It is also possible to mount the final root filesystem via linuxrc and use the pivot_root syscall. The initrd is mounted and executed via prepare_namespace().

c) using initramfs. The call to prepare_namespace() must be skipped.
This means that a binary must do all the work. Said binary can be stored into initramfs either via modifying usr/gen_init_cpio.c or via the new initrd format, an cpio archive. It must be called “/init”. This binary is responsible to do all the things prepare_namespace() would do.

To maintain backwards compatibility, the /init binary will only run if it comes via an initramfs cpio archive. If this is not the case,
init/main.c:init() will run prepare_namespace() to mount the final root and exec one of the predefined init binaries.

Now, lets check the code from kernel init/main.c file,

static char *ramdisk_execute_command;

static int __init rdinit_setup(char *str)
{
unsigned int i;

ramdisk_execute_command = str;
/* See “auto” comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup(“rdinit=”, rdinit_setup);

static int __ref kernel_init(void *unused)
{

if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err(“Failed to execute %s (error %d)\n”,
ramdisk_execute_command, ret);
}

}

static noinline void __init kernel_init_freeable(void)
{

/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/

if (!ramdisk_execute_command)
ramdisk_execute_command = “/init”;

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

}

The above call from “prapare_namespace” goes to init/do_mounts.c as below,

/*
* Prepare the namespace – decide what/where to mount, load ramdisks, etc.
*/
void __init prepare_namespace(void)
{
int is_floppy;

if (root_delay) {
printk(KERN_INFO “Waiting %d sec before mounting root device…\n”,
root_delay);
ssleep(root_delay);
}

/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();

md_run_setup();

if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, “mtd”, 3) ||
!strncmp(root_device_name, “ubi”, 3)) {

mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, “/dev/”, 5) == 0)
root_device_name += 5;
}

if (initrd_load())
goto out;

/* wait for any asynchronous scanning to complete */

if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO “Waiting for root device %s…\n”,
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}

is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;

mount_root();
out:
devtmpfs_mount(“dev”);

sys_mount(“.”, “/”, NULL, MS_MOVE, NULL);
sys_chroot(“.”);
}

Now, we will try to understand the first three ways mentioned,

c ) for using initramfs :

The related source code inside kernel is located inside usr/ directory and in file gen_init_cpio.c , refer source for more details. Once you compile the kernel, you will get the gen_init_cpio binary compiled for the host, which you can use with input file file_list which may contain details of device nodes, directories etc.

Leave a Comment