2.4.2 Namespace的接口和使用
对Namespace的操作,主要是通过clone、setns和unshare这3个系统调用来完成的。
clone可以用来创建新的Namespace。它接受一个叫flags的参数,这些flag包括CLONE_NEWNS、CLONE_NEWIPC、CLONE_NEWUTS、CLONE_NEWNET、CLONE_NEWPID和CLONE_NEWUSER,我们通过传入这些CLONE_NEW*来创建新的Namespace。这些flag对应的Namespace都可以从字面上看出来,除了CLONE_NEWNS,这是用来创建Mount Namespace的。指定了这些flag后,由clone创建出来的新进程,就位于全新的Namespace里了,并且很自然地这个新进程以后创建出来的进程,也都在这个Namespace中。
提示
Mount Namespace是第一个实现的Namespace,当初实现时并不是为了实现Linux容器,因此也就没有预料到会有新的Namespace出现,因此用了CLONE_NEWNS而不是CLONE_NEWMNT之类的名字。
那么,能不能为已有的进程创建新的Namespace呢?答案是可以,unshare就是用来达到这个目的的。调用这个系统调用的进程,会被放进新创建的Namespace里,要创建什么Namespace由flags参数指定,可以使用的flag也就是上面提到的那些。
以上两个系统调用都是用来创建新的Namespace的,而setns则可以将进程放到已有的Namespace里,问题是如何指定已有的Namespace?答案在procfs里。每个进程在procfs下都有一个目录,在那里面就有Namespace相关的信息,如下。
# ls –l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Jun 16 14:39 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 net -> net:[4026531957]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jun 16 14:39 uts -> uts:[4026531838]
这里每个虚拟文件都对应了这个进程所处的Namespace。因此,如果另一个进程要进入这个进程的Namespace,可以通过open系统调用打开这里面的虚拟文件并得到一个文件描述符,然后把文件描述符传给setns,调用返回成功的话,就进入这个进程的Namespace了。
提示
docker exec命令的实现原理就是setns。
以下是一个简单的程序,在Linux终端调用这个程序就会进入新的Namespace,同时也可以打开另一个终端,这个终端是在host的Namespace里,这样就可以对比两个Namespace的区别了。
#define _GNU_SOURCE #include <sys/wait.h> #include <sys/utsname.h> #include <sched.h> #include <stdio.h> #define STACK_SIZE (1024 * 1024) static char stack[STACK_SIZE]; static char* const child_args[] = { "/bin/bash", NULL }; static int child(void *arg) { execv("/bin/bash", child_args); return 0; } int main(int argc, char *argv[]) { pid_t pid; pid = clone(child, stack+STACK_SIZE, SIGCHLD|CLONE_NEWUTS, NULL); waitpid(pid, NULL, 0); }
这个程序创建了UTS Namespace,可以通过修改flag,创建其他Namespace,也可以创建几个Namespace的组合。这个程序将会用来为下面的内容做演示。