前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >命名空间介绍之三:PID 命名空间

命名空间介绍之三:PID 命名空间

作者头像
谛听
修改2019-11-05 10:34:37
3.2K0
修改2019-11-05 10:34:37
举报
文章被收录于专栏:巫山跬步巫山跬步

接着前两篇命名空间文章,现在看一下 PID 命名空间。与 PID 命名空间相关的全局资源就是进程 ID 数字空间。这意味着在不同 PID 命名空间中的进程可以有相同的进程 ID。PID 命名空间实现的容器可在主机之间迁移,并保持容器内的进程 ID 不变。

对于传统 Linux (或 UNIX)系统,PID 命名空间内的进程 ID 是唯一的,从 1 开始依次分配,对于传统 Linux 系统,PID 1 是 init 进程,比较特殊:它是第一个在命名空间内创建的进程,在命名空间内执行特定的管理任务。

首探

clone() 带有 CLONE_NEWPID 标志时会创建一个新的 PID 命名空间。我们可通过一个简单的程序用 clone() 来创建一个新的 PID 命名空间,并理解 PID 命名空间中的基本概念。完成的程序 pidns_init_sleep.c 在这里

该程序忽略了错误检查。

主程序使用 clone() 创建了一个新的 PID 命名空间,并展示了子 PID:

代码语言:txt
复制
child_pid = clone(childFunc,
                child_stack + STACK_SIZE,   /* Points to start of
                                                downwardly growing stack */
                CLONE_NEWPID | SIGCHLD, argv[1]);

printf("PID returned by clone(): %ld\n", (long) child_pid);

新的子进程会运行函数 childFunc(),其参数为 clone() 的第二个参数。该参数的作用后面会看到。

childFunc() 展示了进程 ID 和父进程 ID,然后执行 sleep():

代码语言:txt
复制
printf("childFunc(): PID = %ld\n", (long) getpid());
printf("ChildFunc(): PPID = %ld\n", (long) getppid()); 
...
execlp("sleep", "sleep", "1000", (char *) NULL);

sleep() 的作用是方便区分父进程和子进程。

当运行该程序时,前几行的输出如下:

代码语言:txt
复制
$ su         # Need privilege to create a PID namespace
Password: 
# ./pidns_init_sleep /proc2
PID returned by clone(): 27656
childFunc(): PID  = 1
childFunc(): PPID = 0
Mounting procfs at /proc2

pidns_init_sleep 前两行的输出从两个不同的 PID 命名空间展示了子进程的 PID:一个是调用 clone() 的命名空间,另一个是子进程所在的命名空间。换言之,子进程有两个 PID,在父空间为 27656,在调用 clone() 后生成的新的 PID 命名空间中为 1。

接下来的一行输出是位于子进程所在的 PID 命名空间中的父进程 ID(getppid() 的返回值)。父进程 ID 为0,还挺奇怪的。如之前所述,PID 命名空间构成了一个层次体系:一个进程仅仅能“看到”那些位于其自己 PID 命名空间和其子命名空间内的进程。因为调用 clone() 的父进程是一个不同的命名空间,所以子进程不能“看到”其父进程;因此,getppid() 展示其父进程 PID 为 0。

看一下 pidns_init_sleep 的最后一行输出,需要看一下 childFunc() 的实现。

/proc/PID and PID namespaces

Linux 系统上的每个进程都有一个 /proc/PID 目录,包含了一些描述进程的伪文件。该模式可直接转换为 PID 命名空间模型。在 PID 命名空间内,/proc/PID 目录展示了关于位于当前 PID 命名空间或子命名空间的一些信息。

然而,为了创建一个关联到 PID 命名空间的 /proc/PID 目录,需在该 PID 命名空间中挂载 proc 文件系统(简称“procfs”)在。当一个 shell 运行在 PID 命名空间中时(也许是通过 system() 库函数产生的),可以使用如下形式的挂载命令:

代码语言:txt
复制
# mount -t proc proc /mount_point 

一个 procfs 也可被 mount() 系统调用挂载,正如 childFunc() 所述:

代码语言:txt
复制
mkdir(mount_point, 0555);       /* Create directory for mount point */
mount("proc", mount_point, "proc", 0, NULL);
printf("Mounting procfs at %s\n", mount_point);

mount_point 变量是在调用 pidns_init_sleep 时,从命令行中提供的参数初始化而来。

在在如上运行 pidns_init_sleep 的 shell 中,我们将一个新的 procfs 挂载在 /proc2。在真实的用法中,该 procfs(如果需要的话)通常被挂载在 /proc。然而,挂载在 /proc2 可避免对系统中剩余的进程造成麻烦:因为这些进程和我们的测试程序在同一个挂载空间,所以挂载在 /proc 会使得根 PID 命名空间不可见,从而与系统中剩余的进程混淆。

因此,在我们的 shell 中,挂载在 /proc 的 procfs 会显示父 PID 命名空间中可见进程的 PID 子目录,但挂载在 /proc2 的 porcfs 仅显示位于子 PID 命名空间中的进程的 PID 子目录。另外,值得一提的是,尽管子 PID 命名空间中的进程可以看到 /proc 挂载点暴露的 PID 目录,但那些 PID 对于子 PID 命名空间没有意义,因为这些进程所发出的系统调用是在其 PID 命名空间中解释的。

如果需要在子 PID 命名空间中运行类似于 ps 的工具,那么必须有一个挂载在传统挂载点 /proc 的 procfs,因为那些工具依赖于 /proc。有两种方式可在父 PID 命名空间中,在不影响 /proc 挂载点的情况下达到该目的。第一种,如果一个子进程通过 CLONE_NEWS 标志创建,那么该子进程将在与系统中其余进程位于不同的挂载点中。这种情况下,挂载在 /proc 的新 procfs 不会造成任何麻烦。另外一种,不用 CLONE_NEWNS 标志,子进程可通过 chroot() 改变其根目录并在 /proc 挂载一个 procfs。

让我们回到运行 pidns_init_sleep 的 shell 中。我们停止了程序,并在父命名空间中使用 ps 检查父进程和子进程的一些细节:

代码语言:txt
复制
^Z                          Stop the program, placing in background
[1]+  Stopped                 ./pidns_init_sleep /proc2
# ps -C sleep -C pidns_init_sleep -o "pid ppid stat cmd"
    PID  PPID STAT CMD
27655 27090 T    ./pidns_init_sleep /proc2
27656 27655 S    sleep 600

最后一行为 PPID 的值(27655),说明执行 sleep 的进程的父进程是执行 pidns_init_sleep 的进程。

readlink 命令展示了 /proc/PID/ns/pid 符号链接的(不同)内容(上周的文章中已经解释过了),我们可以看到两个进程在不同的 PID 命名空间。

代码语言:txt
复制
# readlink /proc/27655/ns/pid
pid:[4026531836]
# readlink /proc/27656/ns/pid
pid:[4026532412]

至此,从命名空间的角度来看,我们也可以使用我们新挂载的 procfs 来获取关于新 PID 命名空间中的进程的信息。我们可通过如下命令来获取命名空间中的一个 PID 列表:

代码语言:txt
复制
# ls -d /proc2/[1-9]*
/proc2/1

正如所见,该 PID 命名空间仅包含了一个进程,其 PID 为 1。也可以使用 /proc/PID/status 文件来获取相同的信息:

代码语言:txt
复制
# cat /proc2/1/status | egrep '^(Name|PP*id)'
Name:   sleep
Pid:    1
PPid:   0

PPid 为 0,与 getppid() 的结果相同。

嵌套的 PID 命名空间

如前所述,在 PID 命名空间内,可能会看到位于同一命名空间的其他进程,也可以看到后代命名空间中的进程。这里“看到”意味着可以通过在特定 PID 上调用系统调用(如通过 kill() 向某个进程发送信号)。但在子 PID 命名空间中看不到位于父命名空间中的进程(或被祖先命名空间移除的进程)。

一个进程在从根命名空间开始的每层 PID 命名空间中都有一个 PID。getpid() 返回一个当前进程进程所在的命名空间中的 PID。

可以使用 multi_pidns.c 展示一个在每层可见的命名空间用于不同 PID 的进程。接下来,我们将简单解释该程序做了什么,而不是浏览一遍代码。

该程序在嵌套的 PID 命名空间中递归创建了一系列子进程。命令行参数展示了当运行该程序时,会创建多少个 PID 命名空间:

代码语言:txt
复制
# ./multi_pidns 5

除了创建了一个子进程,每个递归步骤均在一个唯一的挂载点上挂载了一个 procfs 文件系统。在递归的最后,最后一个子进程执行 sleep(),输出如下:

代码语言:txt
复制
Mounting procfs at /proc4
Mounting procfs at /proc3
Mounting procfs at /proc2
Mounting procfs at /proc1
Mounting procfs at /proc0
Final child sleeping

看一下每个 procfs 中的 PID,可见后继 procfs“层”有更少的 PID,说明每个 PID 命名空间仅仅展示位于其命名空间及其后代命名空间中的成员。

代码语言:txt
复制
 ^Z                           Stop the program, placing in background
[1]+  Stopped            ./multi_pidns 5
# ls -d /proc4/[1-9]*        Topmost PID namespace created by program
/proc4/1  /proc4/2  /proc4/3  /proc4/4  /proc4/5
# ls -d /proc3/[1-9]*
/proc3/1  /proc3/2  /proc3/3  /proc3/4
# ls -d /proc2/[1-9]*
/proc2/1  /proc2/2  /proc2/3
# ls -d /proc1/[1-9]*
/proc1/1  /proc1/2
# ls -d /proc0/[1-9]*        Bottommost PID namespace
/proc0/1

grep 命令可让我们在递归的尾部看到进程的 PID(即在最深的嵌套层中执行 sleep() 的进程),在所有可见的命名空间中:

代码语言:txt
复制
# grep -H 'Name:.*sleep' /proc?/[1-9]*/status
/proc0/1/status:Name:       sleep
/proc1/2/status:Name:       sleep
/proc2/3/status:Name:       sleep
/proc3/4/status:Name:       sleep
/proc4/5/status:Name:       sleep

换言之,在最深的嵌套 PID 命名空间中(/porc0),执行 sleep() 进程的 PID 为 1,在最顶层 PID 命名空间中(/porc4),进程 PID 为 5。

如果运行文章中的程序,需注意它们会留下挂载点和挂载目录。在停止程序后,应运行如下 shell 命令进行清理:

代码语言:txt
复制
# umount /proc?
# rmdir /proc?

结束语

本文中,我们看了一些关于 PID 命名空间的细节。下篇文章中,我们将讨论 PID 命名空间中的 init 进程和一些其 API 细节。


原文:https://lwn.net/Articles/531419/

公众号:Geek乐园

博客:https://blog.csdn.net/u012319493/article/details/102789004

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 首探
  • /proc/PID and PID namespaces
  • 嵌套的 PID 命名空间
  • 结束语
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档