虚拟根文件系统与真实根文件系统

时间:2022-08-07 16:29:41

引言:根文件系统的noinitramfs已经分析,继续上文未完的initramfs和Android根文件系统分析,这两者有什么关系?

1.initramfs、initrd

对于initramfs,kernel 2.5开始引入,其实质是在内核镜像中附加一个cpio包(cpio一个用于备份、还原的工具,主要用于cpio和tar文件,其实质是文件、目录、节点的描述语言包),在该cpio包中包含了一个小型的文件系统。当内核启动时,会尝试解开这人 cpio包,并且将其中包含的文件系统安装到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,一般为用户空间的init进程。
initamfs的引入可以精简内核的初始化代码,同时是为了更方便地定制内核的初始化过程。这种方式的rootfs是包含在kernel image(即bootimage或secbootimage)之中的。

先看下initramfs的初始化代码:

static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
#ifdef CONFIG_BLK_DEV_RAM
if (initrd_start) {
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
if (!err) {
printk(" it is\n");
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
free_initrd();
return 0;
}
printk("it isn't (%s); looks like an initrd\n", err);
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
printk(KERN_INFO "Unpacking initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
if (err)
panic(err);
printk(" done\n");
free_initrd();
#endif
}
return 0;
}
rootfs_initcall(populate_rootfs);

根据前文分析,populate_rootfs将会在kernel_init中被调用,主要功能集中在static char * __init unpack_to_rootfs(char *buf, unsigned len, int check_only)函数中,该函数根据check_only这一参数来确定解压cpio包或者检查是否为cpio包。
其具体实现过程为:

static char * __init unpack_to_rootfs(char *buf, unsigned len, int check_only) {
dry_run = check_only;
state = Start;
written = write_buffer(buf, len);
}

static int __init write_buffer(char *buf, unsigned len) {
while (!actions[state]())
;
return len - count;
}

static __initdata int (*actions[])(void) = {
[Start] = do_start,
[GotHeader] = do_header,
};
static int __init do_start(void) {
read_into(header_buf, 110, GotHeader);
return 0;
}
static int __init do_header(void) {
if (dry_run) {
read_into(name_buf, N_ALIGN(name_len), GotName);
return 0;
}
}

以上函数可以看到如果dry_run为1,即check_only为1时,只做检查是否为cpio包。
继续分析populate_rootfs流程,这里根据宏CONFIG_BLK_DEV_RAM涉及到两种文件包:initramfs和initrd,两者的特点如下:
对于initramfs:

  • cpio-initramfs格式
  • ram filesystem支持
  • 编译时与内核链接成一个文件,链接地址为__initramfs_start
  • 由bootloader加载到内存中,解压后所占空间(__initramfs_end- __initramfs_start)会保留
  • 可以不依赖ram disk
  • 使用方便,不需要额外挂载文件系统,但体积稍大
  • 一般需要有/init可执行文件

对于initrd:

  • 可以是cpio-initrd格式,也可以是image-initrd格式
  • ram filesystem支持
  • 单独编译生成一个文件
  • 由bootloader单独加载到内存中,不在内核镜像地址空间
  • 通过”initrd=initns”命令,可以找到 initrd
  • initrd处理后的空间内存可以被释放
  • initrd是ramdisk镜像文件,必须配置CONFIG_BLK_DEV_RAM支持ram disk
  • 需要配置ram disk分区大小,默认4MB
  • 需要ex2等文件系统驱动支持
  • 需要传入根文件系统地址、大小(与ramdisk大小一致)、ramdisk设备节点

2.解压initramfs、initrd文件

对于这几种格式的文件,populate_rootfs进行了不同处理,主要逻辑为:
1)解压initramfs到根文件系统下,如果initramfs不存在,__initramfs_start和__initramfs_end的值相等,即参数 len=0,unpack_to_rootfs不会做任何处理。
2)对于cpio-initrd,直接将其解压到根目录。
3)对于image-initrd,将其解压成/initrd.image。

处理完成后回到kernel_init中:

static int __init kernel_init(void * unused)
{
do_basic_setup();
/*
* 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();
}
init_post();

对于initramfs和cpio-initrd的情况,如果虚拟文件系统中存在ramdisk_execute_command指定的文件:
(该文件由cmdline传递,如cmdline=”initrd=0x31000000,0x200000 root=/dev/ram rw init=/linuxrc console=ttySAC0 mem=64M”)
则直接转向init_post()来执行,否则执行函数prepare_namespace()。

在init_post()中,会执行传入的ramdisk_execute_command,所以该文件必须是一个可执行文件,在Android系统中为用户空间的init进程,也可以为init.sh shell脚本。此后,会切换到实际根目录文件系统,后边分析Android的init进程时也加以说明。
run_init_process(ramdisk_execute_command);

如果为image-initrd的情况,会调用 prepare_namespace

/*
* 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 %dsec 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:
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
}

这段代码有不少注释供理解,总体思路为:
等待/dev下的块设备驱动注册完毕,该设备由cmdline指定,然后提取image-initrd虚拟根文件系统VFS到根目录下,然后挂载根文件系统的设备到/root,并将真实根文件系统顶层目录移动到/目录下,最后将当前目录作为根目录,这样就从虚拟根文件系统切换到真实的根文件系统。
疑问1:那虚拟的根文件系统还存在吗?
疑问2:如何访问image-initrd这个文件内容?
对于问题2,需要查看代码rd_load_image("/initrd.image"),这里会将initrd.image的内容写入”/dev/ram”,如果/真实根文件系统配置为/dev/ram的话,则直接使用initrd作为真实根文件系统。
对于问题1,答案是存在,内核用/old和old_fd保存了虚拟根文件系统的切换环境。

好了,关于VRFS和RRFS就梳理到这儿,当然还有许多细节值得推敲,这里提到了关于Android根文件系统,也开始慢慢切入正题。