博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux Namespace系列(05):pid namespace (CLONE_NEWPID)
阅读量:7055 次
发布时间:2019-06-28

本文共 10409 字,大约阅读时间需要 34 分钟。

PID namespaces用来隔离进程的ID空间,使得不同pid namespace里的进程ID可以重复且相互之间不影响。

PID namespace可以嵌套,也就是说有父子关系,在当前namespace里面创建的所有新的namespace都是当前namespace的子namespace。父namespace里面可以看到所有子孙后代namespace里的进程信息,而子namespace里看不到祖先或者兄弟namespace里的进程信息。

目前PID namespace最多可以嵌套32层,由内核中的宏MAX_PID_NS_LEVEL来定义

Linux下的每个进程都有一个对应的/proc/PID目录,该目录包含了大量的有关当前进程的信息。 对一个PID namespace而言,/proc目录只包含当前namespace和它所有子孙后代namespace里的进程的信息。

在Linux系统中,进程ID从1开始往后不断增加,并且不能重复(当然进程退出后,ID会被回收再利用),进程ID为1的进程是内核启动的第一个应用层进程,一般是init进程(现在采用systemd的系统第一个进程是systemd),具有特殊意义,当系统中一个进程的父进程退出时,内核会指定init进程成为这个进程的新父进程,而当init进程退出时,系统也将退出。

除了在init进程里指定了handler的信号外,内核会帮init进程屏蔽掉其他任何信号,这样可以防止其他进程不小心kill掉init进程导致系统挂掉。不过有了PID namespace后,可以通过在父namespace中发送SIGKILL或者SIGSTOP信号来终止子namespace中的ID为1的进程。

由于ID为1的进程的特殊性,所以每个PID namespace的第一个进程的ID都是1。当这个进程运行停止后,内核将会给这个namespace里的所有其他进程发送SIGKILL信号,致使其他所有进程都停止,于是namespace被销毁掉。

本篇所有例子都在ubuntu-server-x86_64 16.04下执行通过

简单示例

#查看当前pid namespace的IDdev@ubuntu:~$ readlink /proc/self/ns/pidpid:[4026531836]#启动新的pid namespace#这里同时也启动了新的uts和mount namespace#新的uts是为了设置一个新的hostname,便于和老的namespace区分#新的mount namespace是为了方便我们修改新namespace里面的mount信息,#因为这样不会对老namespace造成影响#这里--fork是为了让unshare进程fork一个新的进程出来,然后再用bash替换掉新的进程#这是pid namespace本身的限制,进程所属的pid namespace在它创建的时候就确定了,不能更改,#所以调用unshare和nsenter后,原来的进程还是属于老的namespace,#而新fork出来的进程才属于新的namespacedev@ubuntu:~$ sudo unshare --uts --pid --mount --fork /bin/bashroot@ubuntu:~# hostname container001root@ubuntu:~# exec bashroot@container001:~##查看进程间关系,当前bash(31646)确实是unshare的子进程root@container001:~# pstree -pl├─sshd(955)─┬─sshd(17810)───sshd(17891)───bash(17892)───sudo(31644)───unshare(31645)───bash(31646)───pstree(31677)#他们属于不同的pid namespaceroot@container001:~# readlink /proc/31645/ns/pidpid:[4026531836]root@container001:~# readlink /proc/31646/ns/pidpid:[4026532469]#但为什么通过这种方式查看到的namespace还是老的呢?root@container001:~# readlink /proc/$$/ns/pidpid:[4026531836]#由于我们实际上已经是在新的namespace里了,并且当前bash是当前namespace的第一个进程#所以在新的namespace里看到的他的进程ID是1root@container001:~# echo $$1#但由于我们新的namespace的挂载信息是从老的namespace拷贝过来的,#所以这里看到的还是老namespace里面的进程号为1的信息root@container001:~# readlink /proc/1/ns/pidpid:[4026531836]#ps命令依赖/proc目录,所以ps的输出还是老namespace的视图root@container001:~# ps efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 7月07 ?       00:00:06 /sbin/initroot         2     0  0 7月07 ?       00:00:00 [kthreadd] ...root     31644 17892  0 7月14 pts/0   00:00:00 sudo unshare --uts --pid --mount --fork /bin/bashroot     31645 31644  0 7月14 pts/0   00:00:00 unshare --uts --pid --mount --fork /bin/bash#所以我们需要重新挂载我们的/proc目录root@container001:~# mount -t proc proc /proc#重新挂载后,能看到我们新的pid namespace ID了root@container001:~# readlink /proc/$$/ns/pidpid:[4026532469]#ps的输出也正常了root@container001:~# ps -efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 7月14 pts/0   00:00:00 bashroot        44     1  0 00:06 pts/0    00:00:00 ps -ef

PID namespace嵌套

  • 调用unshare或者setns函数后,当前进程的namespace不会发生变化,不会加入到新的namespace,而它的子进程会加入到新的namespace。也就是说进程属于哪个namespace是在进程创建的时候决定的,并且以后再也无法更改。

  • 在一个PID namespace里的进程,它的父进程可能不在当前namespace中,而是在外面的namespace里面(这里外面的namespace指当前namespace的祖先namespace),这类进程的ppid都是0。比如新namespace里面的第一个进程,他的父进程就在外面的namespace里。通过setns的方式加入到新namespace中的进程的父进程也在外面的namespace中。

  • 可以在祖先namespace中看到子namespace的所有进程信息,且可以发信号给子namespace的进程,但进程在不同namespace中的PID是不一样的。

嵌套示例

#--------------------------第一个shell窗口----------------------#记下最外层的namespace IDdev@ubuntu:~$ readlink /proc/$$/ns/pidpid:[4026531836]#创建新的pid namespace, 这里--mount-proc参数是让unshare自动重新mount /proc目录dev@ubuntu:~$ sudo unshare --uts --pid --mount --fork --mount-proc /bin/bashroot@ubuntu:~# hostname container001root@ubuntu:~# exec bashroot@container001:~# readlink /proc/$$/ns/pidpid:[4026532469]#再创建新的pid namespaceroot@container001:~# unshare --uts --pid --mount --fork --mount-proc /bin/bashroot@container001:~# hostname container002root@container001:~# exec bashroot@container002:~# readlink /proc/$$/ns/pidpid:[4026532472]#再创建新的pid namespaceroot@container002:~# unshare --uts --pid --mount --fork --mount-proc /bin/bashroot@container002:~# hostname container003root@container002:~# exec bashroot@container003:~# readlink /proc/$$/ns/pidpid:[4026532475]#目前namespace container003里面就一个bash进程root@container003:~# pstree -pbash(1)───pstree(22)#这样我们就有了三层pid namespace,#他们的父子关系为container001->container002->container003#--------------------------第二个shell窗口----------------------#在最外层的namespace中查看上面新创建的三个namespace中的bash进程#从这里可以看出,这里显示的bash进程的PID和上面container003里看到的bash(1)不一样dev@ubuntu:~$ pstree -pl|grep bash|grep unshare|-sshd(955)-+-sshd(17810)---sshd(17891)---bash(17892)---sudo(31814)---unshare(31815)---bash(31816)---unshare(31842)---bash(31843)---unshare(31864)---bash(31865)#各个unshare进程的子bash进程分别属于上面的三个pid namespacedev@ubuntu:~$ sudo readlink /proc/31816/ns/pidpid:[4026532469]dev@ubuntu:~$ sudo readlink /proc/31843/ns/pidpid:[4026532472]dev@ubuntu:~$ sudo readlink /proc/31865/ns/pidpid:[4026532475]#PID在各个namespace里的映射关系可以通过/proc/[pid]/status查看到#这里31865是在最外面namespace中看到的pid#45,23,1分别是在container001,container002和container003中的piddev@ubuntu:~$ grep pid /proc/31865/statusNSpid:  31865   45     23      1#创建一个新的bash并加入container002dev@ubuntu:~$ sudo nsenter --uts --mount --pid -t 31843 /bin/bashroot@container002:/##这里bash(23)就是container003里面的pid 1对应的bashroot@container002:/# pstree -pbash(1)───unshare(22)───bash(23)#unshare(22)属于container002root@container002:/# readlink /proc/22/ns/pidpid:[4026532472]#bash(23)属于container003root@container002:/# readlink /proc/23/ns/pidpid:[4026532475]#为什么上面pstree的结果里面没看到nsenter加进来的bash呢?#通过ps命令我们发现,我们新加进来的那个/bin/bash的ppid是0,难怪pstree里面显示不出来#从这里可以看出,跟最外层namespace不一样的地方就是,这里可以有多个进程的ppid为0#从这里的TTY也可以看出哪些命令是在哪些窗口执行的,#pts/0对应第一个shell窗口,pts/1对应第二个shell窗口root@container002:/# ps -efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 04:39 pts/0    00:00:00 bashroot        22     1  0 04:39 pts/0    00:00:00 unshare --uts --pid --mount --fork --mount-proc /bin/bashroot        23    22  0 04:39 pts/0    00:00:00 bashroot        46     0  0 04:52 pts/1    00:00:00 /bin/bashroot        59    46  0 04:53 pts/1    00:00:00 ps -ef#--------------------------第三个shell窗口----------------------#创建一个新的bash并加入container001dev@ubuntu:~$ sudo nsenter --uts --mount --pid -t 31816 /bin/bashroot@container001:/##通过pstree和ps -ef我们可看到所有三个namespace中的进程及他们的关系#bash(1)───unshare(22)属于container001#bash(23)───unshare(44)属于container002#bash(45)属于container003,而68和84两个进程分别是上面两次通过nsenter加进来的bash#同上面ps的结果比较我们可以看出,同样的进程在不同的namespace里面拥有不同的PIDroot@container001:/# pstree -plbash(1)───unshare(22)───bash(23)───unshare(44)───bash(45)root@container001:/# ps -efUID        PID  PPID  C STIME TTY          TIME CMDroot         1     0  0 04:37 pts/0    00:00:00 bashroot        22     1  0 04:39 pts/0    00:00:00 unshare --uts --pid --mount --fork --mount-proc /bin/bashroot        23    22  0 04:39 pts/0    00:00:00 bashroot        44    23  0 04:39 pts/0    00:00:00 unshare --uts --pid --mount --fork --mount-proc /bin/bashroot        45    44  0 04:39 pts/0    00:00:00 bashroot        68     0  0 04:52 pts/1    00:00:00 /bin/bashroot        84     0  0 05:00 pts/2    00:00:00 /bin/bashroot        95    84  0 05:00 pts/2    00:00:00 ps -ef#发送信号给contain002中的bashroot@container001:/# kill 68#--------------------------第二个shell窗口----------------------#回到第二个窗口,发现bash已经被kill掉了,说明父namespace是可以发信号给子namespace中的进程的root@container002:/# exitdev@ubuntu:~$

“init”示例

当一个进程的父进程被kill掉后,该进程将会被当前namespace中pid为1的进程接管,而不是被最外层的系统级别的init进程接管。

当pid为1的进程停止运行后,内核将会给这个namespace及其子孙namespace里的所有其他进程发送SIGKILL信号,致使其他所有进程都停止,于是当前namespace及其子孙后代的namespace都被销毁掉。

#还是继续以上面三个namespace为例#--------------------------第一个shell窗口----------------------#在003里面启动两个新的bash,使他们的继承关系如下root@container003:~# bashroot@container003:~# bashroot@container003:~# pstreebash───bash───bash───pstree#利用unshare、nohup和sleep的组合,模拟出我们想要的父子进程#unshare --fork会使unshare创建一个子进程#nohup sleep sleep 3600&会让这个子进程在后台运行并且sleep一小时root@container003:~# unshare --fork nohup sleep 3600&[1] 77#于是我们得到了我们想要的进程间关系结构root@container003:~# pstree -pbash(1)───bash(26)───bash(36)─┬─pstree(80)                              └─unshare(77)───sleep(78)#如我们所期望的,kill掉unshare(77)后, sleep就被当前pid namespace的bash(1)接管了root@container003:~# kill 77root@container003:~# pstree -pbash(1)─┬─bash(26)───bash(36)───pstree(82)        └─sleep(78)#重新回到刚才的状态,后面将尝试在第三个窗口中kill掉这里的unshare进程root@container003:~# kill 78root@container003:~# unshare --fork nohup sleep 3600&root@container003:~# pstree -pbash(1)───bash(26)───bash(36)─┬─pstree(85)                              └─unshare(83)───sleep(84)#--------------------------第三个shell窗口----------------------#来到第三个窗口root@container001:/# pstree -pbash(1)───unshare(22)───bash(23)───unshare(44)───bash(45)───bash(113)───bash(123)───unshare(170)───sleep(171)#kill掉sleep(171)的父近程unshare(170),root@container001:/# kill 170#结果显示sleep(171)被bash(45)接管了,而不是bash(1),#进一步说明container003里的进程只会被container003里的pid 1进程接管,#而不会被外面container001的pid 1进程接管root@container001:/# pstree -pbash(1)───unshare(22)───bash(23)───unshare(44)───bash(45)─┬─bash(113)───bash(123)          └─sleep(171)#kill掉container002中pid 1的bash进程,在container001中,对应的是bash(23)root@container001:/# kill 23#根本没反应,说明bash不接收TERM信号(kill默认发送SIGTERM信号)root@container001:/# pstree -pbash(1)───unshare(22)───bash(23)───unshare(44)───bash(45)─┬─bash(113)───bash(123)          └─sleep(171)#试试SIGSTOP,貌似也不行      root@container001:/# kill -SIGSTOP 23root@container001:/# pstree -pbash(1)───unshare(22)───bash(23)───unshare(44)───bash(45)─┬─bash(113)───bash(123)          └─sleep(171)#最后试试杀手锏SIGKILL,马到成功root@container001:/# kill -SIGKILL 23root@container001:/# pstree -pbash(1)#--------------------------第一个shell窗口----------------------#container003和container002的bash退出了,#第一个shell窗口直接退到了container001的bashroot@container003:~# Killedroot@container001:~##--------------------------第二个shell窗口----------------------#通过nsenter方式加入到container002的bash也被kill掉了root@container002:/# Killeddev@ubuntu:~$#从结果可以看出,container002的“init”进程被杀死后,#内核将会发送SIGKILL给container002里的所有进程,#这样导致container002及它所有子孙namespace里的进程都杀死,#同时container002和container003也被销毁

里面说SIGSTOP也可以kill掉子namespace里的“init”进程,但我在上面试了下,没效果,具体原因未知。

其他

  • 通常情况下,如果PID namespace中的进程都退出了,这个namespace将会被销毁,但就如在前面里介绍的,有两种情况会导致就算进程都退出了,这个namespace还会存在。但对于PID namespace来说,就算namespace还在,由于里面没有“init”进程,Kernel不允许其它进程加入到这个namespace,所以这个存在的namespace没有意义

  • 当一个PID通过UNIX domain socket在不同的PID namespace中传输时(请参考里面的SCM_CREDENTIALS),PID将会自动转换成目的namespace中的PID.

参考

转载地址:http://nglol.baihongyu.com/

你可能感兴趣的文章
接入高防后为什么有一些网站,APP等会出现延迟,打开速度慢等问题? ...
查看>>
Vue-cli3 简qian易yi教程
查看>>
Linux服务器---DansGuardian
查看>>
Intel处理器供应紧张最晚4季度缓解,俄勒冈州新工厂6月底前开建 ...
查看>>
Confluence 6 计划你的升级
查看>>
网站常见问题1分钟定位 - 如何使用阿里云ARMS轻松重现用户浏览器问题 ...
查看>>
【机器学习PAI实战】—— 玩转人工智能之综述
查看>>
基于HBase和Spark构建企业级数据处理平台
查看>>
MyBatis的配置方式
查看>>
5分钟,关于Python 解包,你需要知道的一切
查看>>
实验吧--隐写术
查看>>
NLP领域中更有效的迁移学习方法
查看>>
【全面解禁!真正的Expression Blend实战开发技巧】第六章 认识ListBox
查看>>
蜗牛爬行日记——记Python语法基础与C语言的异同(二)
查看>>
云栖大会首设“科技脱贫”专场 ,20张会场门票等你来拿!
查看>>
Redis字符串类型内部编码剖析
查看>>
TensorFlow实战(一)-人工智能基础知识
查看>>
Mysql数据库学习笔记
查看>>
疯狂ios讲义之使用CoreLocation定位(5)
查看>>
memcached 在生产环境下安装脚本
查看>>