背景
一个文件系统,只有挂载到内存中目录树下的一个目录,进程才能访问这个文件系统。
启动流程
我们都知道,系统的启动流程中
先加载uboot,负责硬件的基本初始化;
然后设置env,这里的env是全局env,在kernel/rootfs加载过程中都能使用;
加载内核到内存中,跳转到内核的入口
最后挂载根文件系统
mount命令需要的参数
mount -t fstype [-o optation] dev_name filedir
所以,mount 需要知道挂载的文件系统类型,挂载参数,挂载目录,我们的目标是去获取mount所需要的所有参数。
这里会引出一个问题,挂载,必须要有目录才能进行,加载内核后,没挂载根文件系统之前,哪里来的目录去挂载根文件系统呢?
这里引出一个新概念,隐藏的根文件系统,即,内核启动时,自行创建/使用ramdiskfs的文件系统。
ramdisk
目录
以t3代码为例,ramdisk目录如下
目录已经在上一步解决了,接下来看挂载参数包括对元数据的处理, fstype怎么来?
fstype
常用的嵌入式 文件系统类型包括ext4、ubifs、yaffs2等,每种文件系统类型都各有优缺点,ext4介绍请查看培训内容《存储介质和文件系统.pptx》
制作文件系统时,使用特定的制作工具,将做好的文件系统目录,打包制作成对应的格式,这是编译阶段就做好的。其实在加载的时候,内核会一个个文件系统类型尝试挂载,直到遍历完毕或成功挂载,后面追踪源码的时候会说明。
dev_name
dev_name是文件系统镜像所在的分区,在挂载的时候怎么获取,总不能也一个个分区去遍历把。 这里其实是可以通过env分区获取,前面介绍env是整个开机流程都可以获取的。
到这一步,挂载点有了,挂载参数有了,挂载真实的根文件系统,任务下放给1号进程,开机结束。
开始源码分析
总结上面,挂载分为三个大步骤
参数解析
在加载内核的时候,执行了bootcmd。在bootcmd中,会将一些变量设置为环境变量。(详情查看《uboot环境变量》《uboot启动流程》两个章节)
挂载隐藏根文件系统
开始介绍挂载隐藏根文件系统
虚拟文件系统
上图,包括7个函数,其中四个函数为虚拟文件系统的四大数据对象(详情查看虚拟文件系统章节),分别是初始化目录项、索引节点、文件、超级块。除了这四个之外还有初始化最大文件句柄数,也就是每个进程可以打开的最多的文件数。一般默认是1024,可以通过ulimit指令查看
初始化挂载点和初始化块设备缓存
这里详细追踪一下初始化挂载点,其实就是构建一个挂载树
第4行,创建高速缓存,用于存储struct mount结构体类型的对象。struct mount描述挂载点,包含了挂载点的路径、挂载的文件系统类型、挂载的文件系统对象等。
第5行,指向哈希表的指针,用于存储挂载点的信息。
第6行,指向哈希表的指针,用于存储挂载点路径饿对应的struct mount对象的指针。
第8行,初始化kernfs文件系统。kernfs用于向内核提供对象的信息和控制接口,通常挂载在/sys目录下的kernfs子目录中,可以通过此目录下的文件来获取或修改内核对象的信息和属性。
第9行,初始化sysfs文件系统,用于向用户空间提供内核对象的信息和控制接口。通过挂载在/sys目录下。在rootfs之前,说明此时sysfs可以使用
第10行,初始化共享内存系统(shmemfs),用于向用户空间提供共享内存。通常挂载在/dev/shm目录下。
第11行,初始化隐藏的的rootfs。
第12行,初始化文件系统的挂载点和超级块。
这里举例 sysfs 向内核注册一个sysfs文件系统类型
在继续深入追踪,
init_rootfs
我们一共做了哪些操作,初始化虚拟文件系统(查看2 虚拟文件系统的数据结构章节)的四个对象,超级块、索引节点、目录项、文件;然后注册并挂载了与内核交互的sysfs、kernelfs、shemfs,并且初始化了rootfs。到了这一步,我们所需要的挂载树基本构建完成,。
总结:
注册并挂载sysfs tmpfs rootfs,构建出mount tree,为rootfs创建super_block,root dentry,root inode并填充super_block,为后续其他文件系统的挂载提供基础
还差最后一步,挂载点,需要把这个隐藏文件系统挂载起来
初始化挂载点
挂载,需要一个挂载点还需要配置工作所需要的环境,比如命名空间,会话,工作目录,最顶级的父进程
1、挂载一个名为rootfs的文件系统
2、创建一个命名空间(命名空间,用来隔离资源)
3、根据挂载点获取到真实挂载点(真是挂载点,是指整个系统中的挂载点,而不是当前挂载的文件系统中的挂载点)
4、配置真实挂载点的命名空间
5、设置0号线程的命名空间
6、设置文件系统路径
7、把0号线程的当前工作目录,设置为rootfs文件系统的根目录
8、把0号线程的根目录,设置为roofs文件系统的根目录
到这一步,虚拟文件系统其实已经构建完毕了,接下来是真实存在的,镜像中rootfs分区中的文件系统的挂载
继续
挂载真实文件系统
根文件系统挂载流程如下图所示,拆分亮哥步骤,第一个是挂载,第二个是启动1号进程
挂载又分为两种,一种是启用randisk,一种是不启用ramdisk
rest_init函数启用了一个内核线程kernel_thread(kernel_init, NULL, CLONE_FS);
内核线程负责初始化内存管理、进程调度、文件系统,在初始文件系统时,会自动调用rootfs_initcall函数
先介绍启用ramdisk的流程,rootfs_initcall(populate_rootfs)会将cpio包给解压到对应的内存上。
如果没启用ramdisk,rootfs_initcall(default_rootfs);这里只是创建了/dev/ /root两个目录和/dev/console 一个文件。
接下来为console创建标准输入、标准输出、标准错误文件描述符,也就是0 1 2。到这里,就可以通过console口对设备进行交互了
第三步,准备根文件系统,如果是ramdisk,它已经具备了事前准备的所有工作,开始执行根文件系统中的init进程,也就是ramdisk中的init进程
到此,ramdisk方式的根文件系统挂载结束
对于非ramdisk方式,
1、探测块设备是否准备完毕(确保设备已经注册到设备模型中,并可以进行后续的操作)
2、如果存在节点是/dev/mtdxxx,说明存储设备是flash,执行mount_block_root函数,函数中会查找系统中支持的所有格式的文件系统,并一个个尝试挂载,直到挂载成功或遍历完成。
3、如果是emmc,则执行mount_root,这里创建块设备文件/dev/root,然后执行mount_block_root把根文件系统挂载到目录/root下
4、挂载devtmpfs文件系统
5、把当前工作目录移动到文件系统的根目录
6、跟文件系统重新挂载到根目录下
7、把1号线程的根目录设置为根文件系统的根目录
到此,根文件系统挂载完成
最后一步,执行根文件系统的1号进程,init。这里可能是systemd sysvinit upstart(查看章节3 systemd)
结束
转载请标明出处 源码追踪—-内核挂载文件系统的流程-优快云博客