现在的位置: 首页 > 综合 > 正文

根文件系统挂载过程分析

2017年12月03日 ⁄ 综合 ⁄ 共 14071字 ⁄ 字号 评论关闭
一、内存盘INITRD(INITial Ram Disk)技术
在Linux操作系统中,有一项特殊的功能——初始化内存盘INITRD(INITial Ram Disk)技术,而且内核支持压缩的文件系统映像。有了这两项功能,我们可以让Linux系统从小的初始化内存盘启动,并把系统内存的一部分作为根文件系统挂载,而且不使用交换分区(如果不运行X Windows这是完全可以的),即把Linux系统完全嵌入到内存中,而不依赖于任何其他硬盘。现在PC机内存至少128M,而根文件系统所用的只有 30MB,因此不仅不会使整机性能下降,反而有很大的提高。

由于系统不工作在硬盘上,所以系统消除了由于机械驱动而导致的问题;因为系统运行于内存中,根文件系统和操作完全在CPU/RAM环境下,系统性能在速度和可靠性方面非常好;它不会由于非法关机而破坏文件系统,因为我们每一次启动是把压缩的文件系统解压至内存盘中作为根文件系统挂载。

1 硬件要求
对于这样一个系统,硬件不需要特别的设计,只是通过普通的PC机上的组件实现。值得一提是系统的内存的大小,它至少应该有64M。因为30M作为 Ramdisk使用,剩下30多兆作为系统运行,才能保证系统的正常工作,我们现在的计算机内存一般为128M,这个条件都能满足。唯一特别的是一个 flash盘 ,它相当于一个IDE接口的硬盘,大小为20M,主要用它作为启动LILO和放置根文件系统压缩包。

2 Ramdisk的使用
ramdisk 是一种基于内存的虚拟磁盘技术,采用ext2文件系统。Ramdisk就是将内存的一部分分配为一个分区并作为硬盘来使用。对于系统运行时不断使用的程序,将它们放在Ramdisk中将加快计算机的操作,如大数据量的网络服务器、无盘工作站等。为了能够使用Ramdisk,我们在编译内核时须将block device中的Ramdisk支持选上,它下面还有两个选项,一个是设定Ramdisk的大小,默认是4096k;另一个是initrd的支持。它既可以直接编译进内核,也可以编译成模块,在需要的时候加载。我们由于在启动时就用它,所以必须将它直接编译进内核。

如果对Ramdisk的支持已经编译进内核,我们就可以使用它了。首先在/mnt目录下创建目录ram,运行mkdir /mnt/ram;然后对/dev/ram0创建文件系统,运行mke2fs /dev/ram;最后挂载上/dev/ram,运行mount /dev/ram /mnt/ram,就可以象对普通硬盘一样对它进行操作了。值得注意的是,在创建文件系统的时候,在屏幕上输出1024 inodes ,4096 blocks,即ramdisk大小为4M=4096个块,但是我们挂载上之后,用命令df –k /dev/ram查看时,显示出来ramdisk大小只有3963K,这是由于文件系统本身占用了一些空间。

3.利用initrd内核在启动阶段可以顺利的加载设备驱动程序,然而initrd存在以下缺点:
(1)initrd大小是固定的,例如上面的压缩之前的initrd大小是4M(4k*1024),假设您的根目录(上例中的miniroot/)总大小仅仅是 1M,它仍然要占用4M的空间。如果您在dd阶段指定大小为1M,后来发现不够用的时候,必须按照上面的步骤重新来一次。

(2)initrd是一个虚拟的块设备,在上面的例子中,您可是使用fdisk对这个虚拟块设备进行分区。在内核中,对块设备的读写还要经过缓冲区管理模块,也 就是说,当内核读取initrd中的文件内容时,缓冲区管理层会认为下层的块设备速度比较慢,因此会启用预读和缓存功能。这样initrd本身就在内存 中,同时块设备缓冲区管理层还会保存一部分内容。 为了避免上述缺点,于是出现了initramfs,它的作用和initrd类似,

4.linux2.4内核对initrd的处理流程如下:
(1). boot loader把内核以及/dev/initrd的内容加载到内存,/dev/initrd是由boot loader初始化的设备,存储着initrd。

(2). 在内核初始化过程中,内核把 /dev/initrd 设备的内容解压缩并拷贝到 /dev/ram0 设备上。

(3). 内核以可读写的方式把 /dev/ram0 设备挂载为原始的根文件系统。

(4). 如果 /dev/ram0 被指定为真正的根文件系统,那么内核跳至最后一步正常启动。

(5). 执行 initrd 上的 /linuxrc 文件,linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。

(6). /linuxrc 执行完毕,真正的根文件系统被挂载。

(7). 如果真正的根文件系统存在 /initrd 目录,那么 /dev/ram0 将从 / 移动到 /initrd。否则如果 /initrd 目录不存在, /dev/ram0 将被卸载。

我来说说自己的理解:这里ram0是一个虚拟的内存设备,是被自动创建来存储ininrd的。在内核初始化过程中,内核把 /dev/initrd 设备的内容解压缩并拷贝到 /dev/ram0 设备上。并把 /dev/ram0 设备挂载为原始的根文件系统。如果像这样指定/dev/ram0,那么/dev/ram0 被指定为真正的根文件系统。如果没有指定,而是直接用/dev/hda8这样直接指定真正的根文件系统,那么将用默认的ram0存储initrd,并以一定的方式做转换,将根文件系统挂载到hda8,这两种方式ram0都是要用的,不同的是,第一种直接把ram0做为根文件系统了,第二种用完了ram0,还要把根转换到hda8.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

二、mount 根文件系统有方式
1.linux启动时,经过一系列初始化之后,需要mount 根文件系统,为最后运行init进程等做准备,mount 根文件系统有这么几种方式:
(1)文件系统已经存在于硬盘(或者类似的设备)的某个分区上了,kernel根据启动的命令行参数(root=/dev/xxx),直接进行mount。这里有一个问题,在root文件系统本身还不存在的情况下,kernel如何根据/dev/xxx来找到对应的设备呢?原来kernel通过直接解析设备的名称来获得设备的主、从设备号,然后就可以访问对应的设备驱动了。所以在init/main.c中有很长一串的root_dev_names,通过这个表就可以根据设备名称得到设备号。

(2)从软驱等比较慢的设备上装载根文件系统,如果kernel支持ramdisk,在装载root文件系统时,内核判断到需要从软盘(fdx)mount,就会自动把文件系统映象复制到ramdisk,一般对应设备ram0,然后在ram0上mount 根文件系统。 从源码看,如果kernel编译时没有支持ramdisk,而启动参数又root=/dev/fd0, 系统将直接在软盘上mount,除了速度比较慢,理论上是可行的(这个我没试过,不知道是不是样?)

(3)启动时用到initrd来mount根文件系统。一开始我被ramdisk和initrd这两个东西弄胡涂了,其实ramdisk只是在ram上实现的块设备,initrd可以说是启动过程中用到的一种机制。就是在装载linux之前,bootloader可以把一个比较小的根文件系统的映象装载在内存的某个指定位置,姑且把这段内存称为initrd,然后通过传递参数的方式告诉内核initrd的起始地址和大小(也可以把这些参数编译在内核中),在启动阶段就可以暂时的用initrd来mount根文件系统。initrd的最初的目的是为了把kernel的启动分成两个阶段:在kernel中保留最少最基本的启动代码,然后把对各种各样硬件设备的支持以模块的方式放在initrd中,这样就在启动过程中可以从initrd所mount的根文件系统中装载需要的模块。这样的一个好处就是在保持kernel不变的情况下,通过修改initrd中的内容就可以灵活的支持不同的硬件。在启动完成的最后阶段,根文件系统可以重新mount到其他设备上,但是也可以不再重新mount(很多嵌入式系统就是这样)。 initrd的具体实现过程是这样的:bootloader把根文件系统映象装载到内存指定位置,把相关参数传递给内核,内核启动时把initrd中的内容复制到ramdisk中(ram0),把initrd占用的内存释放掉,在ram0上mount根文件系统。从这个过程可以看出,内核需要对同时对ramdisk和initrd的支持。

2.嵌入式系统根文件系统的一种实现方法。对于kernel和根文件系统都存储在flash中的系统,一般可以利用linux启动的initrd的机制。具体的过程前面已经比较清楚了,还有一点就是在启动参数中传递root=/dev/ram0,这样使得用initrd进行mount的根文件系统不再切换,因为这个时候实际的设备就是ram0。还有就是initrd的起始地址参数为虚拟地址,需要和bootloader中用的物理地址对应。
即:root=/dev/ram0 initrd=0xc2000000,20M mem=128M 

三:rootfs的种类
总的来说,rootfs分为两种:虚拟rootfs和真实rootfs.现在kernel的发展趋势是将更多的功能放到用户空间完成。以保持内核的精简。虚拟rootfs也是各linux发行厂商普遍采用的一种方式。可以将一部份的初始化工作放在虚拟的rootfs里完成。然后切换到真实的文件系统.在虚拟rootfs的发展过程中。又有以下几个版本:
1.initramfs:Initramfs是在 kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。这种这种方式的rootfs是包含在kernel image之中的.
2.cpio-initrd: cpio格式的rootfs
3.image-initrd:传统格式的rootfs

四:文件系统的挂载过程 
1.首选在内核启动过程,会初始化rootfs文件系统,rootfs和tmpfs都是内存中的文件系统,其类型为ramfs. 然后会把这个rootf挂载到根目录。
[start_kernel() -> vfs_caches_init() -> mnt_init()] 
void __init mnt_init(void) {
    ......
    init_rootfs();//临时rootfs文件系统的注册
    init_mount_tree();//临时rootfs文件系统的挂载
}

//init_rootfs()注册rootfs文件系统,代码如下:
static struct file_system_type rootfs_fs_type = {
    .name = "rootfs",
    .get_sb = rootfs_get_sb,
    .kill_sb = kill_litter_super,
};

int __init init_rootfs(void)
{
    err = register_filesystem(&rootfs_fs_type);
    ......
    return err;
}

2.init_mount_tree会把rootfs挂载到/目录。
//将rootfs文件系统挂载。它的挂载点默认为”/”.最后切换进程的根目录和当前目录为”/”.这也就是根目录的由来。不过这里只是初始化。等挂载完具体的文件系统之后,一般都会将根目录切换到具体的文件系统。所以在系统启动之后,用mount命令是看不到rootfs的挂载信息的.
static void __init init_mount_tree(void)
{
    struct vfsmount *mnt;
    struct mnt_namespace *ns;
    
    mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
    ......
    set_fs_pwd(current->fs, ns->root, ns->root->mnt_root);
    set_fs_root(current->fs, ns->root, ns->root->mnt_root);//将rootfs设为根文件系统
}

大家可能会会说为什么要这样一个过程了,为什么不直接将我们设置的root=/dev/mtdblock2设备做为根文件系统挂载啊。
首先有两方面的原因:
(1).可能内核中没有根文件系统设备的驱动(usb,sata硬盘的驱动),需要像initrd,initramdisk,cpio-initrd安装驱动,然后再去根文件系统设备取数据然后挂载,而这些往往需要以文件系统格式访问,故首先需要建立文件系统。
(2).因为我们的root设备往往以设备文件的形式给出,如果没有文件系统,怎么会有设备文件之说呢,内核怎么知道如何访问根文件系统设备,这就是鸡蛋和鸡的问题,也许有人又说哪这个虚拟的根文件系统的设备文件在哪。其实由于其是虚拟的,叫内存文件系统,也就是人为的给它一个设备号(0,255),人为的创建内存根目录。

//do_kern_mount()会调用前面注册的rootfs文件系统对象的rootfs_get_sb()函数,
//[rootfs_get_sb() -> ramfs_fill_super() -> d_alloc_root()]
struct dentry * d_alloc_root(struct inode * root_inode)
{
    struct dentry *res = NULL;
    
    if (root_inode) {
        static const struct qstr name = { .name = "/", .len = 1 };
    
        res = d_alloc(NULL, &name);//根目录
        if (res) {
            res->d_sb = root_inode->i_sb;
            res->d_parent = res;
            d_instantiate(res, root_inode);
        }
    }
    return res;
}
//从上面的代码中的可以看出,这个rootfs的dentry对象的名字为"/",也就是根目录了。 

3.在start_kernel()的最后,调用rest_init(),rest_init()会建立一个新的内核进程,并在这个内核进程中执行 kernel_init()函数,
    kernel_init()会调用populate_rootfs()来探测和解压initrd文件。这个函数需要处理的几种initrd的情况。
static void noinline rest_init(void)__releases(kernel_lock)
{
    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);//大名鼎鼎的init进程
    numa_default_policy();
    unlock_kernel();
    preempt_enable_no_resched();
    
    /*
    * The boot idle thread must execute schedule()
    * at least one to get things moving:
    */
    schedule();
    
    cpu_idle();
}
//下面我们就来Init函数
static int init(void * unused)
{
    //..........    
    populate_rootfs();//检测initrd,initramdisk,cpio-initrd等
    
    do_basic_setup();//主要是初始化设备驱动,完成其他驱动程序(直接编译进内核的模块)的初始化。内核中大部分的启动数据输出(都是各设备的驱动模块输出)都是这里产生的
    
    if (sys_access((const char __user *) "/init", 0) == 0)//
     execute_command = "/init";
    else
     prepare_namespace();//挂载真正的根文件系统
    
    //这之后真正的根文件系统已经建立
    
    /* Ok, we have completed the initial bootup, and
    * we're essentially up and running. Get rid of the
    * initmem segments and start the user-mode stuff..
    */
    free_initmem();
    unlock_kernel();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();
    
    //这里需要打开 /dev/console,如果没有这个节点,系统就出错。这个错误信息也是经常碰到的。可能的原因是:
    //1、制作文件系统的时候忘记创建/dev/console节点
    //2、文件系统挂载问题,挂载上的文件系统不是什么都没有,就是挂错了节点。
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");
    /*
    复制两次标准输入(0)的文件描述符(它是上面打开的/dev/console,也就是系统控制台):
    一个作为标准输出(1)
    一个作为标准出错(2)
    现在标准输入、标准输出、标准出错都是/dev/console了。
    这个console在内核启动参数中可以配置为某个串口(ttySn、ttyOn等等),也可以是虚拟控制台(tty0)。所以我们就在串口或者显示器上看到了之后的系统登录提示。
    */
    (void) sys_dup(0);
    (void) sys_dup(0);
    
    /*
    * We try each of these until one succeeds.
    */
    if (execute_command)
        run_init_process(execute_command);
    
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");
    
    panic("No init found. Try passing init= option to kernel.");                
}
 
//populate_rootfs负责检测initrd。
/*unpack_to_rootfs:顾名思义就是解压包,并将其释放至rootfs。它实际上有两个功能,一个是释放包,一个是查看包,看其是否属于cpio结构的包。功能选择是根据最后的一个参数来区分的.
在这个函数里,对应我们之前分析的三种虚拟根文件系统的情况。
(1)一种是跟kernel融为一体的initramfs.在编译kernel的时候,通过链接脚本将其存放在__initramfs_start至__initramfs_end的区域。这种情况下,直接调用unpack_to_rootfs将其释放到根目录.如果不是属于这种形式的。也就是__initramfs_start和__initramfs_end的值相等,长度为零。不会做任何处理。退出.
(2)对应后两种情况。从代码中看到,必须要配制CONFIG_BLK_DEV_RAM才会支持image-initrd。否则全当成cpio-initrd的形式处理。对于是cpio-initrd的情况。直接将其释放到根目录。
(3)对于是image-initrd的情况。将其释放到/initrd.image.最后将initrd内存区域归入伙伴系统。这段内存就可以由操作系统来做其它的用途了。
*/
void __init populate_rootfs(void)
{
    /* 如果__initramfs_end - __initramfs_start不为0,就说明这是和内核文件集成在一起的cpio的intrd。*/
    char *err = unpack_to_rootfs(__initramfs_start,__initramfs_end - __initramfs_start, 0);
    if (err)
        panic(err);
    
#ifdef CONFIG_BLK_DEV_INITRD
    /* 如果initrd_start不为0,说明这是由bootloader加载的initrd,那么需要进一步判断是cpio格式的initrd,还是老式块设备的initrd。*/ 
    if (initrd_start) {//initrd检测
        int fd;
        printk(KERN_INFO "checking if image is initramfs...");
        /* 这里unpack_to_rootfs()的最后一个参数为1,表示check only,不会执行解压缩。*/
        err = unpack_to_rootfs((char *)initrd_start,initrd_end - initrd_start, 1);
        if (!err) {
        /* 如果是cpio格式的initrd,把它解压到前面挂载的根文件系统上,然后释放initrd占用的内存。*/
            printk(" it is\n");
            unpack_to_rootfs((char *)initrd_start,initrd_end - initrd_start, 0);//解压
            free_initrd_mem(initrd_start, initrd_end);//释放initrd占用的内存
            return;
        }
        /* 如果执行到这里,说明这是旧的块设备格式的initrd。
        * 那么首先在前面挂载的根目录上创建一个initrd.image文件,
        * 再把initrd_start到initrd_end的内容写入到/initrd.image中,
        * 最后释放initrd占用的内存空间(它的副本已经保存到/initrd.image中了。)。
        */
        printk("it isn't (%s); looks like an initrd\n", err);
        fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700);//创建并打开文件/initrd.image
        if (fd >= 0) {
            sys_write(fd, (char *)initrd_start,initrd_end - initrd_start);//将内存中的initrd(通常由bootload加载到内存中)赋值到initrd.image中,以释放其占用的内存资源。    
            sys_close(fd);
            free_initrd_mem(initrd_start, initrd_end);//释放内存资源
        }
    }
#endif
}
//经过populate_rootfs()函数的处理之后,如果是cpio格式的initrd,那么unpack_to_rootfs()函数已经把目录解压缩到之前mount的根目录上面了。但是如果是旧的块设备的initrd,unpack_to_rootfs()函数解压缩后得到的是一个块虚拟的设备 镜像文件/initrd.image,对于这种情况,还需要进一步处理才能使用。接下来,kernel_init()就要处理这种情况。 
 
//然后就是prepare_namespace(),它负责具体根文件系统挂载。
/*首先用户可以用root=来指定根文件系统。它的值保存在saved_root_name中。如果用户指定了以mtd开始的字串做为它的根文件系统。就会直接去挂载。这个文件是mtdblock的设备文件。
否则将设备结点文件转换为ROOT_DEV即设备节点号,然后,转向initrd_load()执行initrd预处理后,再将具体的根文件系统挂载。
注意到,在这个函数末尾。会调用sys_mount()来移动当前文件系统挂载点到”/”目录下。然后将根目录切换到当前目录。这样,根文件系统的挂载点就成为了我们在用户空间所看到的”/”了.
*/
void __init prepare_namespace(void)
{
    int is_floppy;
    mount_devfs();//挂载devfs文件系统到/dev目录。这个是必须的,因为initrd要放到/dev/ram0里
    
    if (root_delay) {
        printk(KERN_INFO "Waiting %dsec before mounting root device...\n",root_delay);
        ssleep(root_delay);
    }
    
    md_run_setup();//mtd的处理
    
    if (saved_root_name[0]) {//用户指定的根文件系统/dev/ram0
        root_device_name = saved_root_name;
        //获得设备号
        ROOT_DEV = name_to_dev_t(root_device_name);//返回Root_RAM0,Root_RAM0 = MKDEV(RAMDISK_MAJOR, 0),#define RAMDISK_MAJOR 1,即Root_RAM0=(1<<20 | 0)=1048576
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }
    
    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
    
    if (initrd_load())//把之前保存的/initrd.image 加载到内存盘中
        goto out;
    
    if (is_floppy && rd_doload && rd_load_disk(0))
        ROOT_DEV = Root_RAM0;//如果我们在bootoption哟参数root=/dev/mtdblock2,ROOT_DEV就为/dev/mtdblock设备号。
        
    mount_root();//加载Root_RAM0根文件系统到rootfs文件系统的/root目录下,并且将当前目录设为/root目录
    
out:
    umount_devfs("/dev"); //devfs从虚拟的根文件系统的/dev umount
    
    //由于之前已经切换到了新的根文件系统(Root_RAM0根文件系统)的根目录中去,所以这两步的作用是用新的根文件系统的根目录替换 rootfs ,使其成为 Linux VFS 的根目录。    
    sys_mount(".", "/", NULL, MS_MOVE, NULL);//将挂载点从当前目录/root(在mount_root函数中设置的)移到根目录    
    sys_chroot("."); //将当前目录即【/root】(真正文件系统挂载的目录)做为系统根目录。
    //至此虚拟系统根目录文件系统切换到了实际的根目录文件系统。
    
    mount_devfs_fs ();//将devfs挂到真正根文件系统的/dev
}

/*建立一个ROOT_RAM的设备节点,并将/initrd/.image释放到这个节点中,/initrd.image的内容,就是我们之前分析的image-initrd。
如果根文件设备号是ROOT_RAM0,rd_load_image加载后,也是通过mount_root加载Root_RAM0根文件系统
如果根文件设备号不是ROOT_RAM0(用户指定的根文件系统不是/dev/ram0),就会转入到handle_initrd()如果当前根文件系统是/dev/ram0.将其直接挂载就好了。*/
int __init initrd_load(void)
{
    if (mount_initrd) {
        create_dev("/dev/ram", Root_RAM0);//建立一个ram0设备/dev/ram,其实这也是一个ramfs文件系统。
        
        //接着为了把 initrd 释放到内存盘中,先需要创建设备文件,然后通过 rd_load_image 把之前保存的 /initrd.image 加载到内存盘中。
        //之后判断如果内核启动参数中指定的最终的根文件系统不是内存盘的话,那就先要执行initrd 中的 linuxrc;如果最终的根文件系统就是刚加载到内存盘的 initrd 的话,那就先不处理它,留到之后当真正的根文件系统处理。
        //要没有用到 cpio 类型的 initrd,那内核都会执行到 rd_load_image("/initrd.image"),无论是否真的提供了 initrd 。如果没有提供 initrd,那 /initrd.image 自然不会存在,rd_load_image() 也会提早结束。另外 /dev/ram 这个设备节点文件在 rd_load_image() 中用完之后总会被删除(但相应的内存盘中的内容还在)。
        //内容就是后来的/dev/root设备下的内容
        //调用rd_load_image()把/initrd.image加载到/dev/ram中。
        if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {//相等,下边不会执行
            sys_unlink("/initrd.image");
            handle_initrd();
            return 1;
        }
    }
    sys_unlink("/initrd.image");
    return 0;
}

//加载Root_RAM0根文件系统
void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
       if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {
              if (mount_nfs_root())
                     return;
 
              printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
              ROOT_DEV = Root_FD0;
       }
#endif
#ifdef CONFIG_BLK_DEV_FD
       if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
              /* rd_doload is 2 for a dual initrd/ramload setup */
              if (rd_doload==2) {
                     if (rd_load_disk(1)) {
                            ROOT_DEV = Root_RAM1;
                            root_device_name = NULL;
                     }
              } else
                     change_floppy("root floppy");
       }
#endif
            //这里在rootfs 中新建了一个 /dev/root 设备文件,这个设备文件一般就是指内核启动参数指定的包含根文件系统的设备。在 rootfs 中,这个设备文件被命名为 /dev/root。
       create_dev("/dev/root", ROOT_DEV, root_device_name);//创建ROOT_DEV设备文件即为根文件系统设备文件。
       mount_block_root("/dev/root", root_mountflags);//挂载根文件系统,root_mountflags为文件系统类型。
}
 
void __init mount_block_root(char *name, int flags)
{
    char *fs_names = __getname();//分配空间
    char *p;
    char b[BDEVNAME_SIZE];
    get_fs_names(fs_names);//获取已注册的文件系统 ,即rootfs文件系统。如果在bootoption里有,则就为这个文件系统类型,如果没有指定,则返回filesytem链上所有类型,下面再对每个进行尝试.
retry:
    for (p = fs_names; *p; p += strlen(p)+1) {
        int err = do_mount_root(name, p, flags, root_mount_data);//将Root_RAM0根文件系统挂载到/root目录,p为文件系统类型,由get_fs_names得到
        switch (err) {
            case 0:
                goto out;
            case -EACCES:
                flags |= MS_RDONLY;
                goto retry;
            case -EINVAL:
                continue;
        }
        __bdevname(ROOT_DEV, b);
        printk("VFS: Cannot open root device \"%s\" or %s\n",root_device_name, b);
        printk("Please append a correct \"root=\" boot option\n");    
        panic("VFS: Unable to mount root fs on %s", b);
    }
    panic("VFS: Unable to mount root fs on %s", __bdevname(ROOT_DEV, b));
out:
    putname(fs_names);
}
 
static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
    int err = sys_mount(name, "/root", fs, flags, data);//将当前的Root_RAM0根文件系统挂载到rootfs问价系统下的/root目录
    if (err)
     return err;
    
    sys_chdir("/root");//将当前目录设为/root目录,即cd到新的根文件系统(即Root_RAM0根文件系统)的根目录中去
    ROOT_DEV = current->fs->pwdmnt->mnt_sb->s_dev;
    printk("VFS: Mounted root (%s filesystem)%s.\n",current->fs->pwdmnt->mnt_sb->s_type->name,current->fs->pwdmnt->mnt_sb->s_flags & MS_RDONLY ?" readonly" : "");
    return 0;
}
 
到此根文件系统挂载完成。
整个函数调用路径如下
Start_kernel->rest_init->init-> prepare_namespace-> mount_root-> mount_block_root do_mount_root-> sys_mount(name, "/root", fs, flags, data)-> sys_chroot(".");

抱歉!评论已关闭.