当前位置: 首页 > news >正文

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

一、基本概念介绍

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-startlinux,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,即:在编译内核时,要选中如下配置;

Device Drivers  ---> [*] Block devices  ---><*>   RAM block device support(1)     Default number of RAM disks(131072) Default RAM disk size (kbytes)

配置完成,会在.config生成如下配置:

CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_COUNT=1
CONFIG_BLK_DEV_RAM_SIZE=131072

同时为了让内核有能力在内核加载阶段就能装入ramdisk,并运行其中的内容,要选中:

General setup  ---> [*] 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加入如下配置:

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/ram0bootloader - >kernel -> image-initrd(加载访问real rootfs的必备驱动) -> /linuxrc 脚本(加载real rootfs),内核卸载/dev/ram0,释放initrd内存,最后内核启动init进程(/sbin/init);
  • root = /dev/ram0bootloader -> kernel -> image initrd直接将/dev/ram0作为根文件系统, 内核启动init进程/sbin/init
1.2.2 cpio-initrd

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

1.2.2.1 内核配置

需要在make menuconfig中配置以下选项就可以了:

General setup  ---> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
1.2.2.2 启动参数

通过备树dts里的chosen节点的中的linux,initrd-startlinux,initrd-end属性指定其地址范围;

chosen {linux,initrd-start=xxxxxxlinux,initrd-end=xxxxxx
}
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中配置以下选项就可以了:

General setup  ---> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/opt/filesystem) Initramfs source file(s)

其中/opt/filesystem就是根目录,这里可以使一个现成的gzip压缩的cpio,也可以使一个目录。

1.3.2 挂载方式

initramfscpio-initrd的区别, initramfs是将cpio rootfs编译进内核,而cpio-initrdcpio rootfs是不编译入内核,是外部的。其挂载方式和cpio-initrd是一致的。

当系统启动的时候,内核将initramfs释放到rootfs,结束内核对initramfs的操作。

bootloader -> kernel -> initramfs(加载访问real rootfs的必备驱动等)-> /init脚本(加载real rootfs,并启动了init进程/sbin/init)。

二、源码分析

内核有关根文件系统挂载的调用链路如下:

start_kernelvfs_caches_init()mnt_init()// 创建虚拟根文件系统init_rootfs()register_filesystem(&rootfs_fs_type)init_ramfs_fsregister_filesystem(&ramfs_fs_type)// 注册根文件系统init_mount_tree()rest_initkernel_initkernel_init_freeableif(!ramdisk_execute_command)ramdisk_execute_command="/"do_basic_setupdo_initcallspopulate_rootfsunpack_to_rootfsrun_init_process(ramdisk_execute_command)
2.1 VFS的注册

首先不得不从linux系统的函数start_kernel说起。函数start_kernel中会去调用vfs_caches_init来初始化VFS,函数位于fs/dcache.c

void __init vfs_caches_init(void)
{names_cachep = kmem_cache_create_usercopy("names_cache", PATH_MAX, 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, 0, PATH_MAX, NULL);dcache_init();inode_init();files_init();files_maxfiles_init();mnt_init();bdev_cache_init();chrdev_init();
}

函数mnt_init会创建一个rootfs,这是个虚拟的rootfs,即内存文件系统,后面还会指向真实的文件系统。

2.1.1 mnt_init

mnt_init函数位于fs/namespace.c

void __init mnt_init(void)
{int err;mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct mount),0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);mount_hashtable = alloc_large_system_hash("Mount-cache",sizeof(struct hlist_head),mhash_entries, 19,HASH_ZERO,&m_hash_shift, &m_hash_mask, 0, 0);mountpoint_hashtable = alloc_large_system_hash("Mountpoint-cache",sizeof(struct hlist_head),mphash_entries, 19,HASH_ZERO,&mp_hash_shift, &mp_hash_mask, 0, 0);if (!mount_hashtable || !mountpoint_hashtable)panic("Failed to allocate mount hash table\n");kernfs_init();err = sysfs_init();if (err)printk(KERN_WARNING "%s: sysfs_init error: %d\n",__func__, err);fs_kobj = kobject_create_and_add("fs", NULL);if (!fs_kobj)printk(KERN_WARNING "%s: kobj create error\n", __func__);// 创建虚拟根文件系统init_rootfs();// 注册根文件系统init_mount_tree();
}
2.1.2 init_rootfs

init_rootfs定义在init/do_mounts.c

static struct file_system_type rootfs_fs_type = {.name           = "rootfs",.mount          = rootfs_mount,.kill_sb        = kill_litter_super,
};int __init init_rootfs(void)
{int err = register_filesystem(&rootfs_fs_type);if (err)return err;if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&(!root_fs_names || strstr(root_fs_names, "tmpfs"))) {err = shmem_init();is_tmpfs = true;} else {err = init_ramfs_fs();}if (err)unregister_filesystem(&rootfs_fs_type);return err;
}
2.1.3 init_mount_tree

init_mount_tree函数位于fs/namespace.c

static void __init init_mount_tree(void)
{struct vfsmount *mnt;struct mnt_namespace *ns;struct path root;struct file_system_type *type;type = get_fs_type("rootfs");if (!type)panic("Can't find rootfs type");// 创建虚拟文件系统mnt = vfs_kern_mount(type, 0, "rootfs", NULL);put_filesystem(type);if (IS_ERR(mnt))panic("Can't create rootfs");ns = create_mnt_ns(mnt);if (IS_ERR(ns))panic("Can't allocate initial namespace");init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);root.mnt = mnt;root.dentry = mnt->mnt_root;mnt->mnt_flags |= MNT_LOCKED;set_fs_pwd(current->fs, &root);// 将当前的文件系统配置为根文件系统set_fs_root(current->fs, &root);
}

可能有人会问,为什么不直接把真实的文件系统配置为根文件系统?

答案很简单,内核中没有根文件系统的设备驱动,如usbeMMC等存放根文件系统的设备驱动,而且即便你将根文件系统的设备驱动编译到内核中,此时它们还尚未加载,其实所有的驱动是由在后面的kernel_init线程进行加载,所以需要initrd/initramfs

2.2 VFS的挂载

参考文章

[1] linux内核启动initramfsinitrd及其挂载

[2] 嵌入式软件开发之------浅析linux根文件系统挂载(九)

[3] 根文件系统的含义和相关重要概念以及加载代码分析


http://www.mrgr.cn/news/33844.html

相关文章:

  • Codeforces Round 972 (Div. 2) E2. Subtangle Game (Hard Version)(博弈+双指针 sg函数思想)
  • 一文 学透 力扣—N数之和
  • hql杂谈一
  • Delphi 12.2 新增的 WebStencils 尝鲜
  • 【变化检测】基于Superpoint+Lightglue+TinyCD建筑物(LEVIR-CD)变化检测实战及ONNX推理
  • AtCoder Regular Contest 156 C. Tree and LCS(思维题 构造 数学归纳法)
  • Java 入门基础篇08 - Java的变量与数据类型的认识
  • 解决RabbitMQ设置x-max-length队列最大长度后不进入死信队列
  • 机器学习查漏补缺(5)
  • 2024年中国科技核心期刊目录(自然科学卷)科技统计源核心(续)
  • MySQL FLOAT 不准问题解析
  • nginx网站服务
  • iOS V2签名网站系统源码,开源免授权(含视频教程)
  • GNU编译器(GCC):编译的4个过程及.elf、.list、.map文件功能说明
  • 【Android】BottomSheet基本用法总结(BottomSheetDialog,BottomSheetDialogFragment)
  • 聚簇索引和非聚簇索引的定义和区别
  • Codeforces Round 974 (Div. 3) G. Milky Days
  • 布草洗涤-酒店分楼层统计报表--———未来之窗行业应用跨平台架构
  • 中小企业体系技术抽象沉淀-异地灾备篇
  • Linux:环境变量