Mount 隔离(挂载点隔离)
一、本节目标
上一节实现了 PID 隔离后,发现在容器内执行 ps aux 看到的结果仍然和宿主机一样,原因是 /proc 虚拟文件系统没有隔离。本节为 clone 新增系统调用参数 CLONE_NEWNS,实现 Mount 挂载点的隔离,并在容器内重新挂载 /proc,让 ps aux 和 top 只显示容器自己的进程。
二、先验证不隔离 Mount 的效果
退出所有终端,重新连接宿主机,开启两个终端。用上一节的 test4 进入容器:
- 容器内执行
ps aux,会看到大量宿主机的进程,但容器里其实只启动了一个/bin/bash进程,不应该看到这么多 - 容器内执行
top,看到的也和宿主机一模一样
原因:ps aux 和 top 读取的都是 /proc 目录里的信息,而 /proc 这个挂载点在容器和宿主机之间没有隔离,大家看到的是同一份内容。
三、代码修改:两处关键变更
3.1 修改一:clone 新增 CLONE_NEWNS 参数
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
3.2 修改二:容器启动后重新挂载 /proc
光加 CLONE_NEWNS 还不够,还需要在容器启动后的函数里重新挂载 /proc:
system("mount -t proc proc /proc");
因为有了 Mount 隔离,容器内的挂载操作不会影响宿主机,容器里的 /proc 就是自己独立的了。
3.3 附加修改:打印 PID 方便观察
顺便在打印信息中加上 getpid(),启动时直接能看到容器和容器引擎各自的 PID:
// container_main 中
printf("Container - inside the container! [%5d]\n", getpid());
// main 中
printf("Parent - start a container! [%5d]\n", getpid());
四、完整代码
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container - inside the container! [%5d]\n", getpid());
/* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
system("mount -t proc proc /proc");
sethostname("container",10);
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent - start a container! [%5d]\n", getpid());
/* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
/* 等待子进程结束 */
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
五、编译与运行验证
5.1 编译
[root@test04 container_test]# gcc -o test5 test.c
5.2 运行
[root@test04 container_test]# ./test5
Parent - start a container! [ 46052]
Container - inside the container! [ 1]
启动时直接能看到:
- 容器引擎(
test5)的 PID 是 46052 - 容器第一个进程的 PID 是 1(PID 隔离生效)
5.3 验证 Mount 隔离 —— ps aux
[root@container container_test]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 116480 2964 pts/1 S 16:31 0:00 /bin/bash
root 21 0.0 0.0 155448 1848 pts/1 R+ 16:32 0:00 ps aux
现在容器内 ps aux 只能看到容器自己的进程了!只有两个:
- PID 1:
/bin/bash—— 容器的第一个进程 - PID 21:
ps aux—— 当前命令本身临时产生的进程
不再像之前那样看到宿主机上一大堆进程了,说明 /proc 挂载点已经隔离。top 命令同理,也只会显示容器自己的进程。
5.4 验证 PPID —— 0 号进程的含义
[root@container container_test]# ps -elf
F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
4 S root 1 0 0 80 0 - 29120 do_wai 16:31 pts/1 00:00:00 /bin/bash
0 R root 22 1 0 80 0 - 38862 - 16:32 pts/1 00:00:00 ps -elf
容器第一个进程 /bin/bash 的 PID 是 1,PPID 是 0。这个 0 号进程是谁?其实就是宿主机上的容器引擎 test5(PID 46052)。之所以在容器内显示为 0,是因为 PID 隔离的特点 —— 容器引擎的真实 PID(46052)属于容器外部的 PID 编号体系,在容器内的隔离空间里看不到,所以显示为 0。
六、本节小结
| 要素 | 内容 |
|---|---|
| 隔离的资源 | Mount(挂载点) |
| 系统调用参数 | CLONE_NEWNS |
| 额外操作 | 容器启动后需执行 mount -t proc proc /proc 重新挂载 |
| 验证方式 | ps aux 和 top 只显示容器自己的进程,不再看到宿主机进程 |
截至目前已累计隔离的资源:
| 参数 | 隔离的资源 |
|---|---|
CLONE_NEWUTS |
主机名 ✅ |
CLONE_NEWIPC |
IPC 通信工具 ✅ |
CLONE_NEWPID |
PID 进程ID ✅ |
CLONE_NEWNS |
Mount 挂载点 ✅ |
剩余的 Network(网络) 和 User(用户) 两个名称空间的隔离作为小作业,感兴趣可以自行练习(对应参数为 CLONE_NEWNET 和 CLONE_NEWUSER)。
七、阶段性总结
通过这一系列实验,我们用 C 语言一步步实现了容器引擎的核心逻辑。整个过程不是为了让你真的自己去实现容器引擎,而是为了更深入地理解后面要学的 Docker 等容器引擎的底层原理。当你后面在容器里看到 PID 号、ps aux 的结果、top 的结果、0 号进程等现象时,心里就应该非常清晰它们背后的原理是什么。这些知识为后面继续研究 1 号进程、0 号进程等内容打下了坚实基础。

浙公网安备 33010602011771号