感谢支持
我们一直在努力

Linux 根文件系统的挂载分析

在介绍根文件系统挂载之前先介绍一些基础知识


initramfs


当linux内核启动后,会找到并执行第一个用户程序,一般是init。这个程序存在于文件系统当中,文件系统存在于设备上,但不知道init存在哪个设备上,于是有了内核命令列选项root=,用来指定root文件系统存在于哪个设备上。


然后由于后来的设备类型越来越来多,比如可能在scsi,sata,flash这些设备,还有的存在于网络设备上,不可能把这些设备的驱动编译进内核,这样内核就会越来越来大。为了解决这些问题,出现了基于ram的文件系统,initramfs,这个文件系统可以包含多个目录和程序init,然后通过这个程序,内核再用这个程序去挂载真正的要文件系统。如果没有这个程序,内核可以来寻找和挂载一个根分区,接着执行一些/sbin/init的变种。


ramfs


ramf是一个小型的基于内存的文件系统,由于linux中页的数据被缓存在内存中,然后标识为可用,为防止别用,ramfs就是基于这种机制产生的。只是放在ramfs中的目录和页的缓存,不在写回。


rootfs


rootfs是一种特定的ramfs的实例,它一直存在于系统中,不能卸载。大部分其他的文件系统安装于rootfs之上。


initramfs和rootfs之间的关系


当内核启动的时候,会先注册和挂载一个虚拟的根文件系统,也就是rootfs,然后会把做好的initramfs(这个可以自己制作)中的文件解压到rootfs中。然后系统会挂载真的根文件系统,rootfs隐藏之后。


我的开发板上的u-boot传送的参数为noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M。


noinitrd的含义


(仅当内核配置了选项 CONFIG_BLK_DEV_RAM和CONFIG_BLK_DEV_INITRD)现在的内核都可以支持initrd了,引导进程首先装载内核和一个初始化的ramdisk,然后内核将initrd转换成普通的ramdisk,也就是读写模式的根文件系统设备。然后linuxrc执行,然后装载真正的根文件系统,之后ramdisk被卸载,最后执行启动序列,比如/sbin/init。


选项noinitrd告诉内核不执行上面的步骤,即使内核编译了initrd,而是把initrd的数据写到 /dev/initrd,只是这是一个一次性的设备。


01void __init vfs_caches_init(unsigned long mempages)


02{


03    unsigned long reserve;


04


05    /* Base hash sizes on available memory, with a reserve equal to


06           150% of current kernel size */


07


08    reserve = min((mempages – nr_free_pages()) * 3/2, mempages – 1);


09    mempages -= reserve;


10


11    names_cachep = kmem_cache_create(“names_cache”, PATH_MAX, 0,


12                  SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);


13


14    dcache_init();


15    inode_init();


16    files_init(mempages);


17    mnt_init();


18    bdev_cache_init();


19    chrdev_init();


20}


第14行为页目录缓存的初始化


第15行索引结点缓存的初始化


第16行文件的初始化


第17行虚拟文件系统挂载的初始化


第18行块设备缓存初始化。


第19行字符设备初始化


01void __init mnt_init(void)


02{


03    unsigned u;


04    int err;


05


06    init_rwsem(&namespace_sem);


07


08    mnt_cache = kmem_cache_create(“mnt_cache”, sizeof(struct vfsmount),


09                  0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);


10


11    mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);


12


13    if (!mount_hashtable)


14           panic(“Failed to allocate mount hash table\n”);


15


16    printk(“Mount-cache hash table entries: %lu\n”, HASH_SIZE);


17


18    for (u = 0; u < HASH_SIZE; u++)


19           INIT_LIST_HEAD(&mount_hashtable[u]);


20


21    err = sysfs_init();


22    if (err)


23           printk(KERN_WARNING “%s: sysfs_init error: %d\n”,


24                  __func__, err);


25    fs_kobj = kobject_create_and_add(“fs”, NULL);


26    if (!fs_kobj)


27           printk(KERN_WARNING “%s: kobj create error\n”, __func__);


28    init_rootfs();


29    init_mount_tree();


30}


第6行命明空间信号量的初始化


第8行分配空间


第11行挂载点哈希表分配空间


第18行初始化所有的挂载点哈希表。


第25行生成名为fs的kobject对象。


第28行初始化rootfs文件系统


第29行初始化mount树


第一部分 rootfs文件系统的注册


01int __init init_rootfs(void)


02{


03    int err;


04


05    err = bdi_init(&ramfs_backing_dev_info);


06    if (err)


07           return err;


08


09    err = register_filesystem(&rootfs_fs_type);


10    if (err)


11           bdi_destroy(&ramfs_backing_dev_info);


12


13    return err;


14}


第5行初始化


第9行注册rootfs文件系统


1static struct file_system_type rootfs_fs_type = {


2     .name             = “rootfs”,


3     .get_sb           = rootfs_get_sb,


4     .kill_sb    = kill_litter_super,


5};


第二部分挂载rootfs文件和创建根目录


01static void __init init_mount_tree(void)


02{


03    struct vfsmount *mnt;


04    struct mnt_namespace *ns;


05    struct path root;


06


07    mnt = do_kern_mount(“rootfs”, 0, “rootfs”, NULL);


08    if (IS_ERR(mnt))


09           panic(“Can’t create rootfs”);


10    ns = kmalloc(sizeof(*ns), GFP_KERNEL);


11    if (!ns)


12           panic(“Can’t allocate initial namespace”);


13    atomic_set(&ns->count, 1);


14    INIT_LIST_HEAD(&ns->list);


15    init_waitqueue_head(&ns->poll);


16    ns->event = 0;


17    list_add(&mnt->mnt_list, &ns->list);


18    ns->root = mnt;


19    mnt->mnt_ns = ns;


20


21    init_task.nsproxy->mnt_ns = ns;


22    get_mnt_ns(ns);


23


24    root.mnt = ns->root;


25    root.dentry = ns->root->mnt_root;


26    set_fs_pwd(current->fs, &root);


27    set_fs_root(current->fs, &root);


28}


这个函数的主要作用是是生成/目录的。


第3行定义一个挂载点


第4行定义一个命名空间


第5行定义一个根路径


第7行挂载rootfs文件系统,返回挂载点


第10行为命名空间分配空间


第13行设定命名空间的引用数为1


第14行初始化命名空间链表


第15行初始化等待对列


第18行命名空间的根结点指向挂载点


第19行挂载点指向命名空间


第21行第一个进程的命名空间第向刚才初始化的。


第24行路径的挂载点为命名空间的根结点


第25行路径的目录为命名空间所指向的挂载点的根目录


第26行设置/目录为当前的目录


第27行设置/目录为根目录


01struct vfsmount * do_kern_mount(const char *fstype, int flags, const char *name, void *data)


02{


03    struct file_system_type *type = get_fs_type(fstype);


04    struct vfsmount *mnt;


05


06    if (!type)


07           return ERR_PTR(-ENODEV);


08    mnt = vfs_kern_mount(type, flags, name, data);


09


10    if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&


11        !mnt->mnt_sb->s_subtype)


12           mnt = fs_set_subtype(mnt, fstype);


13    put_filesystem(type);


14    return mnt;


15}


do_kern_mount的参数介绍


fstype 要安装的文件系统的类型名


flag   安装的标志


name  存放文件系统的块设备的路径名


data    指向传递给文件系统中read_super方法的附加指针


第3行得到文件系统的类型,这里是rootfs,当然也会有其它的文件系统,比如proc,pipefs等


第8行返回挂载点


第13行增加对文件系统的引用


01struct vfsmount *


02vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)


03{


04    struct vfsmount *mnt;


05    char *secdata = NULL;


06    int error;


07


08    if (!type)


09           return ERR_PTR(-ENODEV);


10


11    error = -ENOMEM;


12    mnt = alloc_vfsmnt(name);


13    if (!mnt)


14           goto out;


15


16    if (data && !(type->fs_flags & FS_BINARY_MOUNTDATA)) {


17           secdata = alloc_secdata();


18           if (!secdata)


19                  goto out_mnt;


20


21           error = security_sb_copy_data(data, secdata);


22           if (error)


23                  goto out_free_secdata;


24    }


25


26    error = type->get_sb(type, flags, name, data, mnt);


27    if (error < 0)


28           goto out_free_secdata;


29    BUG_ON(!mnt->mnt_sb);


30


31   error = security_sb_kern_mount(mnt->mnt_sb, flags, secdata);


32   if (error)


33          goto out_sb;


34


35    mnt->mnt_mountpoint = mnt->mnt_root;


36    mnt->mnt_parent = mnt;


37    up_write(&mnt->mnt_sb->s_umount);


38    free_secdata(secdata);


39    return mnt;


40out_sb:


41    dput(mnt->mnt_root);


42    deactivate_locked_super(mnt->mnt_sb);


43out_free_secdata:


44    free_secdata(secdata);


45out_mnt:


46    free_vfsmnt(mnt);


47out:


48    return ERR_PTR(error);


49}


第4行定义挂载点


第12行分配一个新的已安装文件系统的描述符,存放在局部变量mnt中


第26行调用文件系统get_sb回调函数,这里是rootfs_get_sb,来初始化一个新的超级块,同时会创建/目录.后面会单独介绍


第35行挂载点根目录指向与文件系统根目录对应的目录项对象的地址


第36行挂载点父目录指向自己


第39行返回局部变量mnt

第三部分解压initramfs文件系统中的内容到rootfs


01static int __init populate_rootfs(void)


02{


03    char *err = unpack_to_rootfs(__initramfs_start,


04                  __initramfs_end – __initramfs_start);


05    if (err)


06           panic(err);      /* Failed to decompress INTERNAL initramfs */


07    if (initrd_start) {


08#ifdef CONFIG_BLK_DEV_RAM


09           int fd;


10           printk(KERN_INFO “Trying to unpack rootfs image as initramfs…\n”);


11           err = unpack_to_rootfs((char *)initrd_start,


12                  initrd_end – initrd_start);


13           if (!err) {


14                  free_initrd();


15                  return 0;


16           } else {


17                  clean_rootfs();


18                  unpack_to_rootfs(__initramfs_start,


19                         __initramfs_end – __initramfs_start);


20           }


21           printk(KERN_INFO “rootfs image is not initramfs (%s)”


22                         “; looks like an initrd\n”, err);


23           fd = sys_open(“/initrd.image”, O_WRONLY|O_CREAT, 0700);


24           if (fd >= 0) {


25                  sys_write(fd, (char *)initrd_start,


26                                initrd_end – initrd_start);


27                  sys_close(fd);


28                  free_initrd();


29           }


30#else


31           err = unpack_to_rootfs((char *)initrd_start,


32                  initrd_end – initrd_start);


33           if (err)


34                  printk(KERN_EMERG “Initramfs unpacking failed: %s\n”, err);


35           free_initrd();


36#endif


37    }


38    return 0;


39}


第3行解压initramfs文件到rootfs文件系统中,第一个参数为开始位置,第二个参数为长度。


第7行initrd_start值为0,下面不执行


01static int __init kernel_init(void * unused)


02{


03    …………


04    do_basic_setup();


05    /*


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


07    * the work


08    */


09


10    if (!ramdisk_execute_command)


11           ramdisk_execute_command = “/init”;


12


13    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {


14           ramdisk_execute_command = NULL;


15   


16    prepare_namespace();


17    }


18


19    /*


20    * Ok, we have completed the initial bootup, and


21    * we’re essentially up and running. Get rid of the


22    * initmem segments and start the user-mode stuff..


23    */


24


25    init_post();


26    return 0;


27}


第4行初始化init段里面的函数


第10行判断ramdisk_execute_command的值,如果为空,就给它赋于/init,这个也是启动是第一个进程。


第13行如果这个init个程序,访问它,其实也就是initramfs里面解压出来有这个程序的话,就不用再执行下面的函数,用这个init进程可以挂载真正的根文件系统。


第16行为进程0准备命名空间


第25行进行真正根文件系统中的init进程运行


第四部分准备命名空间,挂载flash上的根文件系统


01void __init prepare_namespace(void)


02{


03    int is_floppy;


04    if (root_delay) {


05           printk(KERN_INFO “Waiting %dsec before mounting root device…\n”,


06                  root_delay);


07           ssleep(root_delay);


08    }


09


10    /*


11    * wait for the known devices to complete their probing


12    *


13    * Note: this is a potential source of long boot delays.


14    * For example, it is not atypical to wait 5 seconds here


15    * for the touchpad of a laptop to initialize.


16    */


17    wait_for_device_probe();


18


19    md_run_setup();


20


21    if (saved_root_name[0]) {


22           root_device_name = saved_root_name;


23           if (!strncmp(root_device_name, “mtd”, 3) ||


24               !strncmp(root_device_name, “ubi”, 3)) {


25                  mount_block_root(root_device_name, root_mountflags);


26                  goto out;


27           }


28           ROOT_DEV = name_to_dev_t(root_device_name);


29           if (strncmp(root_device_name, “/dev/”, 5) == 0)


30                  root_device_name += 5;


31}


32


33    if (initrd_load())


34                  goto out;


35


36    /* wait for any asynchronous scanning to complete */


37    if ((ROOT_DEV == 0) && root_wait) {


38           printk(KERN_INFO “Waiting for root device %s…\n”,


39                  saved_root_name);


40           while (driver_probe_done() != 0 ||


41                  (ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)


42                  msleep(100);


43           async_synchronize_full();


44    }


45


46    is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;


47


48    if (is_floppy && rd_doload && rd_load_disk(0))


49           ROOT_DEV = Root_RAM0;


50


51    mount_root();


52out:


53    sys_mount(“.”, “/”, NULL, MS_MOVE, NULL);


54    sys_chroot(“.”);


55}


第21行检查saved_root_name是否为真,这里是有值的。


第22行把它赋给root_device_name,此时为/dev/mtdblock3


第23-27行不执行


第25行挂载根文件系统


第28行名字到设备号的转变,这个设备号是设备的设备号,下面会用到的。我这里是flash的第三个分区


第29行/dev/mtdblock3的前5个字符与/dev/比较,这里是相等的。


第30行root_device_name加5,所以此时root_device_name为mtdblock3.


第37-44行由于设备号不为0,所以这里面没有执行。


第53行移动根文件系统的根目录为/


01void __init mount_block_root(char *name, int flags)


02{


03    char *fs_names = __getname();


04    char *p;


05#ifdef CONFIG_BLOCK


06    char b[BDEVNAME_SIZE];


07#else


08    const char *b = name;


09#endif


10


11    get_fs_names(fs_names);


12retry:


13    for (p = fs_names; *p; p += strlen(p)+1) {


14           int err = do_mount_root(name, p, flags, root_mount_data);


15           switch (err) {


16                  case 0:


17                         goto out;


18                  case -EACCES:


19                         flags |= MS_RDONLY;


20                         goto retry;


21                  case -EINVAL:


22                         continue;


23           }


24            /*


25           * Allow the user to distinguish between failed sys_open


26           * and bad superblock on root device.


27           * and give them a list of the available devices


28           */


29#ifdef CONFIG_BLOCK


30           __bdevname(ROOT_DEV, b);


31#endif


32           printk(“VFS: Cannot open root device \”%s\” or %s\n”,


33                         root_device_name, b);


34           printk(“Please append a correct \”root=\” boot option; here are the available partitions:\n”);


35


36           printk_all_partitions();


37#ifdef CONFIG_DEBUG_BLOCK_EXT_DEVT


38           printk(“DEBUG_BLOCK_EXT_DEVT is enabled, you need to specify “


39                  “explicit textual name for \”root=\” boot option.\n”);


40#endif


41           panic(“VFS: Unable to mount root fs on %s”, b);


42    }


43


44    printk(“List of all partitions:\n”);


45    printk_all_partitions();


46    printk(“No filesystem could mount root, tried: “);


47    for (p = fs_names; *p; p += strlen(p)+1)


48           printk(” %s”, p);


49    printk(“\n”);


50#ifdef CONFIG_BLOCK


51    __bdevname(ROOT_DEV, b);


52#endif


53    panic(“VFS: Unable to mount root fs on %s”, b);


54out:


55    putname(fs_names);


56}


第3行申请空间


第11行fs_name指向内核里面编译文件系统的第一个


第13行循环把这些文件系统挂到根目录下,我的内核里面分别有,ext3,ext3,vfat等,这里用到的是yaffs文件系统


第14行调用do_mount_root函数进行挂载


第15行返回的结果0


第17行增加对文件系统的引用


01static int __init do_mount_root(char *name, char *fs, int flags, void *data)


02{


03    int err = sys_mount(name, “/root”, fs, flags, data);


04    if (err)


05           return err;


06


07    sys_chdir(“/root”);


08    ROOT_DEV = current->fs->pwd.mnt->mnt_sb->s_dev;


09    printk(“VFS: Mounted root (%s filesystem)%s on device %u:%u.\n”,


10           current->fs->pwd.mnt->mnt_sb->s_type->name,


11           current->fs->pwd.mnt->mnt_sb->s_flags & MS_RDONLY ?


12           ” readonly” : “”, MAJOR(ROOT_DEV), MINOR(ROOT_DEV));


13    return 0;


14}


第3行挂载文件系统,这里的name为/dev/root,fs为文件的类型,这里是yaffs2,文件类型当然还是ext3,ext2等。


第7行改变到/root目录下


第9行分别显示出挂载出根文件系统和主次设备号,这里是yaffs文件系统,主:次31:3


01void __init mount_root(void)


02{


03#ifdef CONFIG_ROOT_NFS


04    if (MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {


05           if (mount_nfs_root())


06                  return;


07


08           printk(KERN_ERR “VFS: Unable to mount root fs via NFS, trying floppy.\n”);


09           ROOT_DEV = Root_FD0;


10    }


11#endif


12#ifdef CONFIG_BLK_DEV_FD


13    if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {


14           /* rd_doload is 2 for a dual initrd/ramload setup */


15           if (rd_doload==2) {


16                  if (rd_load_disk(1)) {


17                         ROOT_DEV = Root_RAM1;


18                         root_device_name = NULL;


19                  }


20           } else


21                  change_floppy(“root floppy”);


22    }


23#endif


24#ifdef CONFIG_BLOCK


25    create_dev(“/dev/root”, ROOT_DEV);


26    mount_block_root(“/dev/root”, root_mountflags);


27#endif


28}


这里只执行了CONFI_BLOCK宏开关


第25行创建设备结点,这里我在ubutu上测试了一下,/dev/root是的连接是hda1


第26行挂载根文件系统


第五部分运行真正根目录中的init程序


01static noinline int init_post(void)


02    __releases(kernel_lock)


03{


04    /* need to finish all async __init code before freeing the memory */


05    async_synchronize_full();


06    free_initmem();


07    unlock_kernel();


08    mark_rodata_ro();


09    system_state = SYSTEM_RUNNING;


10    numa_default_policy();


11


12    if (sys_open((const char __user *) “/dev/console”, O_RDWR, 0) < 0)


13           printk(KERN_WARNING “Warning: unable to open an initial console.\n”);


14


15    (void) sys_dup(0);


16    (void) sys_dup(0);


17


18    current->signal->flags |= SIGNAL_UNKILLABLE;


19


20    if (ramdisk_execute_command) {


21           run_init_process(ramdisk_execute_command);


22           printk(KERN_WARNING “Failed to execute %s\n”,


23                         ramdisk_execute_command);


24    }


25


26    /*


27    * We try each of these until one succeeds.


28    *


29    * The Bourne shell can be used instead of init if we are


30    * trying to recover a really broken machine.


31    */


32    if (execute_command) {


33           run_init_process(execute_command);


34           printk(KERN_WARNING “Failed to execute %s.  Attempting “


35                                “defaults…\n”, execute_command);


36    }


37    run_init_process(“/sbin/init”);


38    run_init_process(“/etc/init”);


39    run_init_process(“/bin/init”);


40    run_init_process(“/bin/sh”);


41


42    panic(“No init found.  Try passing init= option to kernel.”);


43}


第12行打开/dev/console设备文件


第15-16行将文件描述符0复制给文件描述符1和2。


第20-24行ramdisk_execute_command变量中如果指定了要运行的程序,就开始运行这个程序,在u-boot的命令行参数中会指定rdinit=…,这个时候ramdisk_execute_command等于这个参数指定的程序。也可能/init程序存在,哪么ramdisk_execute_command就等于/init,或者为空。本程序没有指定,所以这里为空。


第37行执行/sbin/init程序,这个程序存在于根文件系统,如果存在,执行它,系统的控制权交给/sbin/init,不再返回init_post函数

赞(0) 打赏
转载请注明出处:服务器评测 » Linux 根文件系统的挂载分析
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏