PID namespace

PID namespace用来隔离进程的PID空间,使得不同PID namespace中的进程号可以重复且互不影响。Linux下的每个进程都有一个对应的/proc/pid目录,该目录包含了进程相关的信息。对于一个PID namespace,/proc目录只包含当前namespace和它所有子孙后代namespace里的进程信息。创建一个新的PID namespace后,需要挂载/proc文件系统才能让子进程中top、ps等依赖/proc文件系统的命令正常工作。
1.命令行使用
下面通过例子演示挂载/proc文件系统的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ echo $$
9501
$ readlink /proc/$$/ns/pid
pid:[4026531836]
$ sudo unshare --pid --mount --fork /bin/bash
# readlink /proc/$$/ns/pid
pid:[4026531836]
# ps
PID TTY TIME CMD
11178 pts/0 00:00:00 sudo
11180 pts/0 00:00:00 unshare
11183 pts/0 00:00:00 bash
11211 pts/0 00:00:00 ps
# echo $$
1
# ps 1
PID TTY STAT TIME COMMAND
1 ? Ss 0:02 /sbin/init splash

在创建新PID namespace后,当前bash进程的pid变成了1,但是通过ps命令可以看出进程信息使用的还是旧PID namespace中的,因此readlink读取的PID namespace信息是/sbin/init进程的,所以PID namespace没有变。–mount新建的mount namespace的挂载信息是从旧mount namespace中拷贝过来的,因此需要挂载新的/proc文件系统:

1
2
3
4
5
6
7
# mount -t proc proc /proc
# readlink /proc/$$/ns/pid
pid:[4026532726]
# ps
PID TTY TIME CMD
1 pts/0 00:00:00 bash
26 pts/0 00:00:00 ps

现在看到的进程信息和PID namespace就是正确的了。其实unshare命令有一个专门的–mount-proc选项来配合PID namespace创建:

1
$ sudo unshare --pid --mount-proc --fork /bin/bash

这样就会在创建PID和Mount namespace之后自动挂载/proc文件系统了。
2.代码实现
Mount namespace需要特权用户来执行,可以在运行时加sudo,或者可以先创建User namespace映射到root用户再运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <string>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/types.h>

int main()
{
int flags;
int ret;
flags = 0;
flags |= CLONE_NEWNS; // Mount namespace
flags |= CLONE_NEWPID; // PID namespace
if (unshare(flags) == -1) {
perror("unshare");
exit(-1);
}

pid_t cpid = fork();
if (cpid == -1) {
perror("fork");
exit(-1);
}
if (cpid == 0) {
if (mount("proc", "/proc", "proc", 0, NULL) == -1) {
perror("mount");
exit(-1);
}
std::string cmd = "bash";
const char *argv[2] = {NULL};
argv[0] = cmd.c_str();
execvp("/bin/bash", (char* const*)argv);
}
int state;
wait(&state);
printf("child exit\n");
return 0;
}

要用fork创建新进程,因为一个进程创建时所属的PID namespace就确定不变了,如果只用execvp创建的shell还是原进程,不能切换到新的PID namespace。

reference
https://www.cnblogs.com/sparkdev/p/9442208.html