本文介绍Linux虚拟文件系统的初始化过程。如下图:
+—–> start_kernel()
|
|
+—–> vfs_caches_init_early()
|
|
+—–> vfs_caches_init()
| +—>new thread(kernel_init)
| | +
| | |
+—–> rest_init()+—— v
kernel_init_freeable
+
|
v
do_basic_setup
+
|
v
prepare_namespace
在Linux kernel的入口函数start_kernel中,系统调用vfs_caches_init_early和vfs_caches_init进行必要的初始化。
vfs_caches_init_early
Dentry和inode cache是linux为了加速对虚拟文件系统管理,在内存中分配的的hash Cache,用来存放最近访问的inode或dentry节点。 一般而言,最近访问的节点也是最容易被再次访问的。当要访问inode或dentry节点时,先到内存中的cache查找(Hash链表)。如果可以找到,便可以直接使用内存中的节点,否则才到文件系统中去找。
1 | static void __init dcache_init_early(void) |
+———————-+
| Create MNT cache and |
| MNT cache Hash table |
+———–+———-+
|
+———–v———-+ +———————+
| Create Mount Point | | init mount tree |
| Hash Table | | |
+———–+———-+ +———-^———-+
| |
+———–v———-+ +———-+———-+
| kernfs_init() | | init_rootfs() |
| | | |
+———–+———-+ +———-^———-+
| |
+———–v———-+ +———-+———-+
| sysfs_init() +–> | Register FS folder |
| | | to sysfs |
+———————-+ +———————+
1 | 首先,创建struct mount cache及哈希链表,之后创建文件系统加载点(struct mountpoint)Cache Hash。 其中前者为系统中已经Mount的VFS相关信息(包含不同VFS Mount的相互关系),后者主要存放的是系统加载点Dentry。 _kernfs_init_:主要创建kernfs_node的cache。kernfs_node主要为创建kernfs结构的结构体。kernfs的作用引用wiki如下: |
之后的文章会介绍shmem的mount,shmem_mount init_mount_tree(): 如其名,初始化文件系统挂载树。代码如下:
1 | static void __init init_mount_tree(void) |
至此,vfs_caches_init初始化流程结束
do_basic_setup
在rest_init函数中,kernel创建kernel_init线程,该线程会执行do_basic_setup。其执行do_initcalls–>rootfs_initcall(populate_rootfs);–>rootfs_initcall( default_rootfs); populate_rootfs主要是将打包在initramfs中的cpio档填充到rootfs,而default_rootfs主要创建/dev,/dev/console和/root
prepare_namespace
当/sbin/init 或者 init不存在时,便执行prepare_namespace。其主要作用也能够便是挂载MTD device或UBI device到root。这里引用IBM developworkers的说明对prepare_namespace作介绍如下:
在内核和 initrd 映像被解压并拷贝到内存中之后,内核就会被调用了。它会执行不同的初始化操作,最终您会发现自己到了
init/main.c:init()
(subdir/file:function)函数中。这个函数执行了大量的子系统初始化操作。此处会执行一个对init/do_mounts.c:prepare_namespace()
的调用,这个函数用来准备名称空间(挂载 dev 文件系统、RAID 或 md、设备以及最后的 initrd)。加载 initrd 是通过调用init/do_mounts_initrd.c:initrd_load()
实现的。initrd_load()
函数调用了init/do_mounts_rd.c:rd_load_image()
,它通过调用init/do_mounts_rd.c:identify_ramdisk_image()
来确定要加载哪个 RAM 磁盘。这个函数会检查映像文件的 magic 号来确定它是 minux、etc2、romfs、cramfs 或 gzip 格式。在返回到initrd_load_image
之前,它还会调用init/do_mounts_rd:crd_load()
。这个函数负责为 RAM 磁盘分配空间,并计算循环冗余校验码(CRC),然后对 RAM 磁盘映像进行解压,并将其加载到内存中。现在,我们在一个适合挂载的块设备中就有了这个 initrd 映像。 现在使用一个init/do_mounts.c:mount_root()
调用将这个块设备挂载到根文件系统上。它会创建根设备,并调用init/do_mounts.c:mount_block_root()
。在这里调用init/do_mounts.c:do_mount_root()
,后者又会调用fs/namespace.c:sys_mount()
来真正挂载根文件系统,然后chdir
到这个文件系统中。这就是我们在清单 6 中所看到的熟悉消息VFS: Mounted root (ext2 file system).
的地方。 最后,返回到init
函数中,并调用init/main.c:run_init_process
。这会导致调用execve
来启动 init 进程(在本例中是/linuxrc
)。linuxrc 可以是一个可执行程序,也可以是一个脚本(条件是它有脚本解释器可用)。这些函数的调用层次结构如下边清单所示。尽管此处并没有列出拷贝和挂载初始 RAM 磁盘所涉及的所有函数,但是这足以为我们提供一个整体流程的粗略框架init/main.c:init()
init/do_mounts.c:prepare_namespace()
init/do_mounts_initrd.c:initrd_load()
init/do_mounts_rd.c:rd_load_image()
init/do_mounts_rd.c:identify_ramdisk_image()
init/do_mounts_rd.c:crd_load()
lib/inflate.c:gunzip()
init/do_mounts.c:mount_root()
init/do_mounts.c:mount_block_root()
init/do_mounts.c:do_mount_root()
fs/namespace.c:sys_mount()
init/main.c:run_init_process()
execve
引用Kernel中的README The kernel has currently 3 ways to mount the root filesystem: a) all required device and filesystem drivers compiled into the kernel, no initrd. init/main.c:init() will call prepare_namespace() to mount the final root filesystem, based on the root= option and optional init= to run some other init binary than listed at the end of init/main.c:init(). b) some device and filesystem drivers built as modules and stored in an initrd. The initrd must contain a binary ‘/linuxrc’ which is supposed to load these driver modules. It is also possible to mount the final root filesystem via linuxrc and use the pivot_root syscall. The initrd is mounted and executed via prepare_namespace(). c) using initramfs. The call to prepare_namespace() must be skipped. This means that a binary must do all the work. Said binary can be stored into initramfs either via modifying usr/gen_init_cpio.c or via the new initrd format, an cpio archive. It must be called “/init”. This binary is responsible to do all the things prepare_namespace() would do. To maintain backwards compatibility, the /init binary will only run if it comes via an initramfs cpio archive. If this is not the case, init/main.c:init() will run prepare_namespace() to mount the final root and exec one of the predefined init binaries.