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

news/2024/9/23 1:14:25

一、基本概念介绍

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.ryyt.cn/news/63567.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

全网最适合入门的面向对象编程教程:51 Python 函数方法与接口-使用 Zope 实现接口

在 Python 中,Zope 提供了一种机制来定义和实现接口。Zope 的接口模块通常用于创建可重用的组件,并确保组件遵循特定的接口规范。全网最适合入门的面向对象编程教程:51 Python 函数方法与接口-使用 Zope 实现接口摘要: 在 Python 中,Zope 提供了一种机制来定义和实现接口。…

Java反序列化利用链篇 | CC1链的第二种方式-LazyMap版调用链【本系列文章的分析重点】

CC1链的第二种方式-LazyMap版调用链 目录LazyMap构造payloadCC1的调用链参考链接LazyMap 在之前的CC1链中分析,其实是其中一种方式(国内版本),还有另外一种方式,也是ysoserial中的CC1链的方式(国外版本)。 区别在于调用transform的类是不同的。 在寻找transform调用的时…

瑞云科技AIGC云平台:重塑电商设计流程!

在快节奏的电商市场中,商品更新换代的速度越来越快,而电商设计团队传统的设计流程和工作模式却难以满足当前行业对快速响应、高效发展和降低成本的实际需求.对此,瑞云科技针对电商设计行业的痛点,提供了全新的AIGC创作云平台.从2022年ChatGPT的发布到,AI正以惊人的速度席卷全球…

学习高校课程-软件工程-敏捷开发(ch5)

WHAT IS AGILITY 什么是敏捷性 An agile team is a nimble team able to appropriately respond to changes. Change is what software development is very much about. Changes in the software being built, changes to the team members, changes because of new technolog…

从零开始一个git操作实例,图文并茂

徒弟不懂git怎么用, 于是写了篇文章, 把本地git操作从头写了一遍, 自己去看吧!0、基本概念 •Git是一个免费、开源的、分布式版本控制系统 •它使用一个特殊的叫做仓库的数据库来记录文件的变化 •仓库中的每个文件都有一个完整的版本历史记录 1)安装 sudo apt-update sud…

Java反序列化利用链篇 | JdbcRowSetImpl利用链分析

JdbcRowSetImpl利用链 前言 首先说明一下:利用链都有自己的使用场景,要根据场景进行选择不同的利用链。 JdbcRowSetImpl利用链用于fastjson反序列化漏洞中。 为什么? 因为fastjson会在反序列化类时自动调用set开头的方法(不一定是setter方法),而JdbcRowSetImpl中存在一个…

torch.stack

看一下stack的直观解释,动词可以简单理解为:把……放成一堆、把……放成一摞。 torch.stack方法用于沿着一个新的维度 join(也可称为cat)一系列的张量(可以是2个张量或者是更多),它会插入一个新的维度,并让张量按照这个新的维度进行张量的cat操作。值得注意的是:张量序…

Java反序列化调用链分析系列 | URLDNS链

URLDNS链 URLDNS链是java通过反序列化发起dns请求的利用链。一般用于测试反序列化漏洞。 该链比较简单,利用链也比较短。 其中入口类为 HashMap,执行类为URLStreamHandler的hashCode()方法。 整个调用链如下: HashMap.readObject() HashMap.putVal() HashMap.hash()URL.hash…