Linux内核启动之根文件系统挂载

打印 上一主题 下一主题

主题 1720|帖子 1720|积分 5160

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
一、基本概念介绍

  1.1 rootfs

  什么是根文件系统?理论上说一个嵌入式设备如果内核能运行起来,且不必要用户进程的话(估计这种情况很少),是不必要文件系统的。文件系统简单的说就是一种目录结构,由于linux操纵系统的设备在系统中是以文件的形式存在,将这些文件分类管理以及提供和内核交互的接口,就形成了一定的目录结构也就是文件系统。文件系统是为用户反映系统的一种形式,为用户提供一个检测控制系统的接口。
  而根文件系统,就是一种特殊的文件系统。那么根文件系统和普通的文件系统有什么区别呢?
  借用书上的话说就是,根文件系统就是内核启动时挂载的第一个文件系统。由于根文件系统是启动时挂载的第一个文件系统,那么根文件系统就要包括linux启动时所必须的目录和关键性的文件,例如linux启动时都必要有效户进程init对应的文件,在linux挂载分区时一定要会找/etc/fstab这个挂载文件等,根文件系统中还包括了许多的应用程序,如 /bin目录下的下令等。任何linux启动时所必须的文件的文件系统都可以称为根文件系统。
  根文件系统,对应/目录节点,分为虚拟rootfs和真实rootfs。
  1.1.1 虚拟rootfs

  虚拟rootfs由内核自己创建和加载,仅仅存在于内存之中,其文件系统是tmpfs类型或者ramfs类型。
  1.1.2 真实rootfs

  真实rootfs则是指根文件系统存在于存储设备上,内核在启动过程中会在虚拟rootfs上挂载这个存储设备,然后将/目录节点切换到这个存储设备上,这样存储设备上的文件系统就会被作为根文件系统使用。
  1.2 initrd

  initrd总的来说现在有两种格式:image格式和cpio格式。
  当系统启动的时候,bootloader会把initrd文件读到内存中,然后把initrd文件在内存中的起始地点和大小通报给内核;
  

  • 可以通过bootargs参数initrd指定其地点范围;
  • 也可以通过备树dts里的chosen节点的中的linux,initrd-start和linux,initrd-end属性指定其地点范围;
  内核在启动初始化过程中会解压缩initrd文件,然后将解压后的initrd挂载为根目录,然后实行根目录中的/linuxrc脚本;
  

  • cpio格式的initrd为/init;
  • image格式的initrd为/initrc;,
  我们可以在这个脚本中加载真实文件系统。这样,就可以mount真正的根目录,并切换到这个根目录中来。
  1.2.1 image-initrd

  image-initrd是将一块内存看成物理磁盘,然后在上面载入文件系统,好比我们在《Rockchip RK3399 - busybox 1.36.0制作根文件系统》制作的ramdisk文件系统就是就属于这一种。
  1.2.1.1 内核ramdisk设置

  为了能够使用ramdisk,内核必须要支持ramdisk,即:在编译内核时,要选中如下设置;
  1. Device Drivers  --->
  2.      [*] Block devices  --->
  3.           <*>   RAM block device support
  4.           (1)     Default number of RAM disks
  5.         (131072) Default RAM disk size (kbytes)
复制代码
设置完成,会在.config天生如下设置:
  1. CONFIG_BLK_DEV_RAM=y
  2. CONFIG_BLK_DEV_RAM_COUNT=1
  3. CONFIG_BLK_DEV_RAM_SIZE=131072
复制代码
同时为了让内核有本领在内核加载阶段就能装入ramdisk,并运行其中的内容,要选中:
  1. General setup  --->
  2.     [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
复制代码
会在设置文件中界说CONFIG_BLK_DEV_INITRD。
  1.2.1.2 启动参数

  在uboot启动内核时指定根文件系统的位置;修改uboot启动参数bootargs中的root属性为root=/dev/ram0,表示根目录挂载点为/dev/ram0块设备;
  假设ramdisk.gz文件被加载到内存指定位置0x42000000。修改bootargs加入如下设置:
  1. initrd=0x42000000,0x14000000
复制代码
initrd参数格式为:地点,长度,这里设备RAM地点为0x42000000起始,只要是在内核RAM物理地点空间内。长度这里只要比ramdisk.gz压缩包大小大就可以了。
  1.2.1.3 挂载方式

  当系统启动的时候,bootloader会把image-initrd文件读到内存中,内核将image-initrd生存在rootfs下的initrd.image中, 并将其读入/dev/ram0中,根据root是否等于/dev/ram0做不同的处理;
  

  • root != /dev/ram0:bootloader - >kernel -> image-initrd(加载访问real rootfs的必备驱动) -> /linuxrc 脚本(加载real rootfs),内核卸载/dev/ram0,开释initrd内存,末了内核启动init进程(/sbin/init);
  • root = /dev/ram0:bootloader -> kernel -> image initrd直接将/dev/ram0作为根文件系统, 内核启动init进程/sbin/init。
  1.2.2 cpio-initrd

  特教唆用cpio格式创建的initrd映像,和编译进内核的initramfs格式是一样的,只不过它是独立存在的,也被称为外部initramfs,
  1.2.2.1 内核设置

  必要在make menuconfig中设置以下选项就可以了:
  1. General setup  --->
  2.     [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
复制代码
1.2.2.2 启动参数

  通过备树dts里的chosen节点的中的linux,initrd-start和linux,initrd-end属性指定其地点范围;
  1. chosen {
  2.         linux,initrd-start=xxxxxx
  3.         linux,initrd-end=xxxxxx
  4. }
复制代码
1.2.2.3 挂载方式

  当系统启动的时候,bootloader会把cpio-initrd文件读到内存中,内核将cpio-initrd开释到rootfs,结束内核对cpio-initrd的操纵。
  bootloader -> kernel -> cpio-initrd(加载访问real rootfs的必备驱动等)-> /init脚本(加载real rootfs,并启动了init进程/sbin/init)。
  1.3 initramfs

  在linux 2.5内核开始引入initramfs技能,是一个基于ram的文件系统,只支持cpio格式。
  它的作用和cpio-initrd类似,initramfs和内核一起编译到了一个新的镜像文件。
  initramfs被链接进了内核中特殊的数据段.init.ramfs上,其中全局变量__initramfs_start和__initramfs_end分别指向这个数据段的起始地点和结束地点。内核启动时会对.init.ramfs段中的数据举行解压,然后使用它作为暂时的根文件系统。
  1.3.1 内核设置

  要制作这样的内核,我们只必要在make menuconfig中设置以下选项就可以了:
  1. General setup  --->
  2.     [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support(/opt/filesystem) Initramfs source file(s)
复制代码
其中/opt/filesystem就是根目录,这里可以使一个现成的gzip压缩的cpio,也可以使一个目录。
  1.3.2 挂载方式

  initramfs和cpio-initrd的区别, initramfs是将cpio rootfs编译进内核,而cpio-initrd中cpio rootfs是不编译入内核,是外部的。其挂载方式和cpio-initrd是一致的。
  当系统启动的时候,内核将initramfs开释到rootfs,结束内核对initramfs的操纵。
  bootloader -> kernel -> initramfs(加载访问real rootfs的必备驱动等)-> /init脚本(加载real rootfs,并启动了init进程/sbin/init)。
  二、源码分析

  内核有关根文件系统挂载的调用链路如下:
  1. start_kernel
  2.         vfs_caches_init()
  3.                 mnt_init()
  4.                     // 创建虚拟根文件系统
  5.                         init_rootfs()
  6.                                 register_filesystem(&rootfs_fs_type)
  7.                                 init_ramfs_fs
  8.                                         register_filesystem(&ramfs_fs_type)
  9.                     // 注册根文件系统
  10.                         init_mount_tree()
  11.         rest_init
  12.                 kernel_init
  13.                         kernel_init_freeable
  14.                                 if(!ramdisk_execute_command)
  15.                                         ramdisk_execute_command="/"
  16.                                 do_basic_setup
  17.                                         do_initcalls
  18.                                                 populate_rootfs
  19.                                                         unpack_to_rootfs
  20.                 run_init_process(ramdisk_execute_command)
复制代码
2.1 VFS的注册

  首先不得不从linux系统的函数start_kernel说起。函数start_kernel中会去调用vfs_caches_init来初始化VFS,函数位于fs/dcache.c;
  1. void __init vfs_caches_init(void)
  2. {
  3.         names_cachep = kmem_cache_create_usercopy("names_cache", PATH_MAX, 0,
  4.                         SLAB_HWCACHE_ALIGN|SLAB_PANIC, 0, PATH_MAX, NULL);
  5.         dcache_init();
  6.         inode_init();
  7.         files_init();
  8.         files_maxfiles_init();
  9.         mnt_init();
  10.         bdev_cache_init();
  11.         chrdev_init();
  12. }
复制代码
函数mnt_init会创建一个rootfs,这是个虚拟的rootfs,即内存文件系统,后面还会指向真实的文件系统。
  2.1.1 mnt_init

  mnt_init函数位于fs/namespace.c:
  1. void __init mnt_init(void)
  2. {
  3.         int err;
  4.         mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct mount),
  5.                         0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
  6.         mount_hashtable = alloc_large_system_hash("Mount-cache",
  7.                                 sizeof(struct hlist_head),
  8.                                 mhash_entries, 19,
  9.                                 HASH_ZERO,
  10.                                 &m_hash_shift, &m_hash_mask, 0, 0);
  11.         mountpoint_hashtable = alloc_large_system_hash("Mountpoint-cache",
  12.                                 sizeof(struct hlist_head),
  13.                                 mphash_entries, 19,
  14.                                 HASH_ZERO,
  15.                                 &mp_hash_shift, &mp_hash_mask, 0, 0);
  16.         if (!mount_hashtable || !mountpoint_hashtable)
  17.                 panic("Failed to allocate mount hash table\n");
  18.         kernfs_init();
  19.         err = sysfs_init();
  20.         if (err)
  21.                 printk(KERN_WARNING "%s: sysfs_init error: %d\n",
  22.                         __func__, err);
  23.         fs_kobj = kobject_create_and_add("fs", NULL);
  24.         if (!fs_kobj)
  25.                 printk(KERN_WARNING "%s: kobj create error\n", __func__);
  26.             // 创建虚拟根文件系统
  27.         init_rootfs();
  28.             // 注册根文件系统
  29.         init_mount_tree();
  30. }
复制代码
2.1.2 init_rootfs

  init_rootfs界说在init/do_mounts.c;
  1. static struct file_system_type rootfs_fs_type = {
  2.         .name           = "rootfs",
  3.         .mount          = rootfs_mount,
  4.         .kill_sb        = kill_litter_super,
  5. };
  6. int __init init_rootfs(void)
  7. {
  8.         int err = register_filesystem(&rootfs_fs_type);
  9.         if (err)
  10.                 return err;
  11.         if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
  12.                 (!root_fs_names || strstr(root_fs_names, "tmpfs"))) {
  13.                 err = shmem_init();
  14.                 is_tmpfs = true;
  15.         } else {
  16.                 err = init_ramfs_fs();
  17.         }
  18.         if (err)
  19.                 unregister_filesystem(&rootfs_fs_type);
  20.         return err;
  21. }
复制代码
2.1.3 init_mount_tree

  init_mount_tree函数位于fs/namespace.c:
  1. static void __init init_mount_tree(void)
  2. {
  3.         struct vfsmount *mnt;
  4.         struct mnt_namespace *ns;
  5.         struct path root;
  6.         struct file_system_type *type;
  7.         type = get_fs_type("rootfs");
  8.         if (!type)
  9.                 panic("Can't find rootfs type");
  10.             // 创建虚拟文件系统
  11.         mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
  12.         put_filesystem(type);
  13.         if (IS_ERR(mnt))
  14.                 panic("Can't create rootfs");
  15.         ns = create_mnt_ns(mnt);
  16.         if (IS_ERR(ns))
  17.                 panic("Can't allocate initial namespace");
  18.         init_task.nsproxy->mnt_ns = ns;
  19.         get_mnt_ns(ns);
  20.         root.mnt = mnt;
  21.         root.dentry = mnt->mnt_root;
  22.         mnt->mnt_flags |= MNT_LOCKED;
  23.         set_fs_pwd(current->fs, &root);
  24.             // 将当前的文件系统配置为根文件系统
  25.         set_fs_root(current->fs, &root);
  26. }
复制代码
大概有人会问,为什么不直接把真实的文件系统设置为根文件系统?
  答案很简单,内核中没有根文件系统的设备驱动,如usb、eMMC等存放根文件系统的设备驱动,而且即便你将根文件系统的设备驱动编译到内核中,此时它们还尚未加载,其实所有的驱动是由在后面的kernel_init线程举行加载,所以必要initrd/initramfs。
  2.2 VFS的挂载

  参考文章
  [1] linux内核启动initramfs与initrd及其挂载
  [2] 嵌入式软件开辟之------浅析linux根文件系统挂载(九)
  [3] 根文件系统的含义和相干重要概念以及加载代码分析

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表