4.7.2 切换到根文件系统

真正的根文件系统已经被挂载了,我们接下来就要切换到真正的根文件系统。

initramfs中的这个init脚本也完成了它的历史使命,该退出舞台了。系统的第一个进程应该使用根文件系统中的一个程序了。无论是SystemV还是systemd,都会提供一个init程序,通常这个程序都是使用二进制格式的。但是这里,我们为了不把事情搞得太复杂,真正的用户空间的init程序依然使用shell脚本。一般而言,init存储在/sbin目录下,所以需要在根文件系统中建立/sbin目录。

4.7.2 切换到根文件系统 - 图1

init脚本简单的执行一个交互式的bash。

4.7.2 切换到根文件系统 - 图2

最后注意将该程序加上可执行权限

4.7.2 切换到根文件系统 - 图3

根文件系统准备好后,接下来开始向根文件系统切换,步骤如下:

1)删除rootfs文件系统中不再需要的内容,释放内存空间。

现在挂载在“/”下的rootfs中的内容是initramfs解压来的,在我们准备把磁盘文件系统挂载到“/”前,需要删除rootfs中的内容,以释放其占用的内存空间。但是,在删除rootfs前,我们需要:

❑停止正在运行的进程,这里就是udevd;

❑将/dev、/run、/proc和/sys目录移动到真正的文件系统上。因此,需要在根文件系统上建立如下目录:

4.7.2 切换到根文件系统 - 图4

2)将根文件系统从"/root"移动“/”下。

3)更改进程的文件系统namespace,使其指向真正的根文件系统。因为当前进程就是进程1,而后续进程都是从进程1复制的,所以后续进程的文件系统的namespace自然就是使用的真正的根文件系统。

4)运行真正的文件系统中的"init"程序。

这里提一个问题,前面的几个动作还可以用脚本继续实现吗?单个动作本身没有问题,但是不知道读者留意到没有,一旦步骤1)执行了,rootfs中就没有内容了,因此后面步骤中使用的命令已经不在了,被删除了,何谈步骤2)和步骤3)?因此,这里我们使用一个小技巧,将上面的步骤都封装到一个二进制程序中,将这个程序加载进内存后,我们再删除rootfs中的内容,如此一来,步骤2)和步骤3)都得以顺利完成。这个程序源码如下:

4.7.2 切换到根文件系统 - 图5

4.7.2 切换到根文件系统 - 图6

4.7.2 切换到根文件系统 - 图7

Makefile文件如下:

4.7.2 切换到根文件系统 - 图8

switch_root本身的逻辑比较简单,基本就是执行我们上面的步骤1)~步骤4),首先切换到新的文件系统,因此后面的“.”就是在新文件系统中,然后使用chroot命令,将“.”作为新的根文件系统,完成进程的文件系统namespace的切换,并重定向了stdio、stdout和stderr。其中有一处需要特别注意,就是在执行删除前,需要做一个小的判断,以免把挂载在${ROOTMNT}下的真正的文件系统也删除了。

编译后,将switch_root复制到initramfs:

4.7.2 切换到根文件系统 - 图9

修改initramfs中的init脚本如下:

4.7.2 切换到根文件系统 - 图10

4.7.2 切换到根文件系统 - 图11

最后,为了验证是否已经切换到根文件系统了,我们在rootfs中安装两个程序cat和ls:

4.7.2 切换到根文件系统 - 图12

用修改过的根文件系统和initramfs更新vita系统,然后重新启动vita系统。使用命令cat打印文件/proc/mounts的内容,如图4-29所示。

4.7.2 切换到根文件系统 - 图13

图 4-29 成功切换到根文件系统

根据/proc/mounts的输出可见,存放根文件系统的分区"/dev/sda2"已经挂载到了“/”下。在这里,我们也看到,rootfs还是作为整个虚拟文件系统的根存在的。