跳转至

为容器补全文件系统

在实验一中我们知道,仅有一个 init 程序的文件系统也可以启动一个完整的 Linux 系统,但是显然这样的 Linux 系统是没有什么实用价值的——它运行不了哪怕是稍微典型一些的 Linux 程序或 shell 命令,这是因为仅有一个 init 程序的文件系统缺乏大部分程序需要的目录结构,如 /proc/sys 等。

使用 mount 命令

mount(8) 是 Linux 中常用的挂载文件系统的命令,当什么参数都没有时,它列出全部已挂载的文件系统。

查看当前系统挂载点

在终端中运行 mount 命令,可以看到类似这样的输出:

sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
udev on /dev type devtmpfs (rw,nosuid,relatime,size=32852896k,nr_inodes=8213224,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=6576800k,mode=755)
none on /tmp type tmpfs (rw,nosuid,nodev,noatime)
/dev/nvme0n1p2 on / type ext4 (rw,relatime,errors=remount-ro)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)

mount 的每一行输出格式如下:

{源} on {挂载点} type {类型} ({挂载参数},{挂载参数},...)

例如,对于上面的第一行输出,

sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)

表示源 sysfssysfs 类型挂载到了 /sys 路径,参数为读写(rw)、无视 setuid 标志(nosuid)、不允许设备文件(nodev)、不允许执行程序(noexec)以及使用相对的访问时间(relatime)。

通常情况下,文件系统需要真实设备的支持(如 /dev/sda/dev/nvme0n1p2),但是对于一些类型的文件系统(如 tmpfs),挂载源是不重要的,甚至可以使用任意字符串。

挂载一个文件系统

使用 mount 命令挂载一个 tmpfs 到某个路径下的操作为:

mkdir -p /mnt/mytmpfs
mount -t tmpfs none /mnt/mytmpfs

mount 命令的详细用法可以在 mount(8) 的手册中查看。对于上面的第二行命令,对应的是下面这个格式:

mount [-fnrsvw] [-t fstype] [-o options] device dir

卸载文件系统

卸载文件系统可以使用 umount(8),例如

umount /mnt/mytmpfs

即可完成对刚才挂载的目录的卸载操作。

同样,umount(8) 也有一些参数,例如 -l(lazy)可以将挂载点从目录树结构中卸载,而不影响正在使用该文件系统结构的进程(正常情况下,卸载一个文件系统前不能有进程在占用这个文件系统)。

使用 mount 系统调用挂载文件系统

查看 mount(2) 的手册,可以发现其原型如下:

#include <sys/mount.h>

int mount(const char *source, const char *target,
          const char *filesystemtype, unsigned long mountflags,
          const void *data);

不难猜到,mount(2) 的前两个参数对应 mount(8) 的最后两个参数,而 mountflags 则对应部分挂载选项,如只读 / 可写,以及 nosuid, nodev, noexec 等选项。请自己查阅资料,了解最后一个参数 data 的意义(后面可能会用到)。

与 mount(2) 类似,umount(2) 及 umount2(2) 两个系统调用可以用于卸载文件系统。请自行查阅手册了解它们的用法。

隔离挂载点

请确保你在第一步完成了对挂载命名空间(CLONE_NEWNS)的隔离,再进行这一步操作。

隔离了挂载命名空间后,为了保证所有对现有挂载点的修改不会传播(propagate)到主机中,首先需要将 rootfs 递归地重新挂载为私有(提示:mount --make-rprivate /),然后再进行容器(新的挂载命名空间)内的挂载操作等。

实验要求

请使用 mount(2) 为你的容器在 /dev, /proc, /sys/tmp 位置各挂载一个合适的文件系统,并在 /sys/fs/cgroup 下挂载指定的三类 cgroup 控制器(见 cgroup 一节)。

注意/sys 挂载为只读(这是 systemd 的容器界面的一个要求,这里也作为本实验的要求)。

你可以自行决定是否为其他路径挂载恰当的文件系统(如 /run),或从主机系统以 bind 方式挂载一些目录。

挂载完成后,请在 /dev 下创建一些设备节点,例如 null, zero, urandom 等。建议将 /dev/tty 也创建,否则部分应用程序可能无法正常工作(如 less 和 Vim 等)。

下一节的 pivot_root 后你需要使用 umount(2) 从容器中卸载主机系统的根文件系统,以将主机从容器中“隐藏”。这一步操作将在下一节中详细讨论。

另外,在下一节 pivot_root 的过程中,你可能需要调整部分本节中已完成的 monut 操作的顺序。