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

引言:根文件系统的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根文件系统,也开始慢慢切入正题。

你可能感兴趣的:(android基础,linux基础)