注:本文绝大部分内容来自 LinuxNamespaces实践part4 ,原文 系列文章详细描述了
Linux Namespace
相关内容,英语过关的建议阅读原文,本文内容主要用来记录学习内容,如有不当之处还请评论区指正。
PID Namespace
一个新创建的PID Namespace中的第一个进程的ID为1,这个进程同时也是该Namespace中的初始化(init)进程,需要承担回收孤儿进程等任务。
一个例子
ns_child_exec.c
在新的Namespace中执行simple_init.c
simple_init.c
新Namespace中的init
进程,回收孤儿进程orphan.c
一个孤儿进程示例
上述三个程序组成了这个示例,在ns_child_exec.c
程序中调用clone
函数创建子进程并设置Namespace标志。子进程运行simple_init.c
程序,该程序会模拟一个终端,调用execvp
命令执行输入的程序。在这个“终端”中执行orphan
程序,可以看到orphan
的父进程结束后,子进程成为孤儿进程并且被simple_init
回收。代码ns_child_exec.c, simple_init.c, orphan.c,代码中的英文注释解释了处理SIGTTOU信号、设置终端前台进程组等的原因。
上述程序的执行结果如下:
root@jeffrey: /Coding/namespace# ./ns_child_exec -pmv ./simple_init -v
./ns_child_exec: PID of child created by clone() is 545
init: my PID is 1
init$ ./orphan
init: created child 2
Parent (PID=2) created child with PID 3
Parent (PID=2; PPID=1) terminating
init: SIGCHLD handler: PID 2 terminated
init$
Child (PID=3) now an orphan (parent PID=1)
Child (PID=3) terminating
init: SIGCHLD handler: PID 3 terminated
init$ ps
init: created child 4
PID TTY TIME CMD
240 tty2 00:00:00 init
526 tty2 00:00:00 sudo
527 tty2 00:00:00 su
528 tty2 00:00:00 bash
544 tty2 00:00:00 ns_child_exec
545 tty2 00:00:00 simple_init
548 tty2 00:00:00 ps
init: SIGCHLD handler: PID 4 terminated
init$ mount -t proc proc /proc
init: created child 5
init: SIGCHLD handler: PID 5 terminated
init$ ps
init: created child 6
PID TTY TIME CMD
1 tty2 00:00:00 simple_init
6 tty2 00:00:00 ps
init: SIGCHLD handler: PID 6 terminated
init$ ^C./ns_child_exec: terminating
root@jeffrey: /Coding/namespace#
上述shell命令及输出结果展示了该程序的使用
-pmv
参数表示创建新的PID Namespace
与Mount Namespace
,并打印日志信息- 日志信息
init: my PID is 1
表明simple_init
程序成为了新PID Namespace
的init
进程 - 执行
orphan
程序后simple_init
打印了两条SIGCHLD
信号处理日志,表明孤儿进程也被它回收了 ps
命令依赖/proc
中的信息,故若要在执行ps
显示当前PID Namespace
的程序,需要显式挂载它- 创建
Namespace
时需指定创建新Mount Namespace
及PID Namespace
,如-pmv
参数 - 可以通过
mount -t proc proc /proc
命令挂载 - 或通过
mount("proc", "/proc", "proc", 0, NULL)
函数挂载
- 创建
init进程与信号
linux中的init
进程:
- 只处理设置了处理程序的信号
- 不会被其它程序或用户(包括superuser)kill
PID Namespace
中的init
进程:
- 类似的,在该
Namespace
中只会收到设置了处理程序的信号 - 内核产生的信号依然会发送给
init
进程,如硬件异常等 - 对于来自祖先
PID Namespace
的信号,init
进程会收到设置了处理程序的信号+SIGKILL
+SIGSTOP
- 被
SIGKILL
(来自祖先)kill后,所有子进程都会被kill
一般来讲,一个PID Namespace
的所有进程都被终止后,这个PID Namespace
也会被销毁,然而有一个特例:若/proc/PID/ns/pid
文件处于bind mounted
或打开状态,则该PID Namespace
不会被销毁。不过,这种情况下无法再在该PID Namespace
中创建新的进程(通过setns()+fork()
),实际上当执行fork
时会失败并产生ENOMEM
错误。
unshare() 与 setns()
unshare(CLONE_NEWPID);
fd = open("/proc/PID/ns/pid", O_RDONLY);
setns(fd, 0);
通过在unshare(int flags)
的参数中指定CLONE_NEWPID
可以创建一个新PID Namespace
,调用unshare
的进程本身不会加入到新PID Namespace
中,但由该进程创建的子进程都会被加入其中。其原因在于,几乎Linux程序包括GNU等都认为进程标识PID
是不变的,若改变当前进程的PID
,则许多依赖于此的函数都将无法使用了。
setns(int fd, int nstype)
同样支持PID Namespace
,fd
参数为一个PID Namespace
的文件描述符,该PID Namespace
应为调用进程的PID Namespace
的后代。相应文件描述符fd
可以通过打开/proc/PID/ns/pid
文件获得。与unshare
类似,setns
同样不会讲调用进程移动到新的PID Namespace
,而是将其子进程放入其中。
又一个例子
本示例展示了setns
的使用,使用上一个例子创建一个新的PID Namespace
并运行simple_init
模拟终端,之后我们在另一个终端中运行ns_run
程序,设置子进程的PID Namespace
与simple_init
的相同,然后创建一个子进程。示例程序,如下为终端的运行结果。
terminal a:
root@jeffrey: /Coding/namespace# clear
root@jeffrey: /Coding/namespace# ./ns_child_exec -p ./simple_init -v
init: my PID is 1
init$ init: SIGCHLD handler: PID 4 terminated
terminal b:
root@jeffrey: /Coding/namespace# ps -C simple_init
PID TTY TIME CMD
601 tty3 00:00:00 simple_init
root@jeffrey: /Coding/namespace# ./ns_run -f -n /proc/601/ns/pid ./orphan
Parent (PID=3) created child with PID 4
Parent (PID=3; PPID=0) terminating
root@jeffrey: /Coding/namespace#
Child (PID=4) now an orphan (parent PID=1)
Child (PID=4) terminating
root@jeffrey: /Coding/namespace#
首先在终端a中执行./ns_child_exec -p ./simple_init -v
,之后再终端b中执行./ns_run -f -n /proc/601/ns/pid ./orphan
,其中601为simple_init
程序的PID
。可以看到,orphan
程序创建了孤儿进程PID=4
,该进程被simple_init
回收了,同时orphan
的输出Parent (PID=3; PPID=0)
也表明它在与simple_init
相同的Namespace
中执行了,因而无法看到位于ns_run
的Namespace
中的父进程表示PPID=0
。下图表明了上述代码的关系:
小结
本文主要讲述了PID Namespace
中init
进程的角色、unshare
和setns
等内容,感兴趣的同学可以阅读原文获得更多相关内容。