Loading

Mount 隔离(挂载点隔离)


一、本节目标

上一节实现了 PID 隔离后,发现在容器内执行 ps aux 看到的结果仍然和宿主机一样,原因是 /proc 虚拟文件系统没有隔离。本节为 clone 新增系统调用参数 CLONE_NEWNS,实现 Mount 挂载点的隔离,并在容器内重新挂载 /proc,让 ps auxtop 只显示容器自己的进程。


二、先验证不隔离 Mount 的效果

退出所有终端,重新连接宿主机,开启两个终端。用上一节的 test4 进入容器:

  • 容器内执行 ps aux,会看到大量宿主机的进程,但容器里其实只启动了一个 /bin/bash 进程,不应该看到这么多
  • 容器内执行 top,看到的也和宿主机一模一样

原因:ps auxtop 读取的都是 /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 auxtop 只显示容器自己的进程,不再看到宿主机进程

截至目前已累计隔离的资源:

参数 隔离的资源
CLONE_NEWUTS 主机名 ✅
CLONE_NEWIPC IPC 通信工具 ✅
CLONE_NEWPID PID 进程ID ✅
CLONE_NEWNS Mount 挂载点 ✅

剩余的 Network(网络)User(用户) 两个名称空间的隔离作为小作业,感兴趣可以自行练习(对应参数为 CLONE_NEWNETCLONE_NEWUSER)。


七、阶段性总结

通过这一系列实验,我们用 C 语言一步步实现了容器引擎的核心逻辑。整个过程不是为了让你真的自己去实现容器引擎,而是为了更深入地理解后面要学的 Docker 等容器引擎的底层原理。当你后面在容器里看到 PID 号、ps aux 的结果、top 的结果、0 号进程等现象时,心里就应该非常清晰它们背后的原理是什么。这些知识为后面继续研究 1 号进程、0 号进程等内容打下了坚实基础。

posted @ 2026-06-19 09:42  知杏  阅读(7)  评论(0)    收藏  举报