文章目录
Linux
Namespace
提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的
Namespace
中,来实现资源隔离的目的。不同
Namespace
的程序,可以享有一份独立的系统资源。目前Linux中提供了六类系统资源的隔离机制,分别是:
namespace | 系统调用参数 | 隔离内容 |
---|---|---|
UTS | CLONE_NEWUTS | 主机名与域名 |
IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
PID | CLONE_NEWPID | 进程编号 |
Network | CLONE_NEWNET | 网络设备、网络栈、端口等等 |
Mount | CLONE_NEWNS | 挂载点(文件系统) |
User | CLONE_NEWUSER | 用户和用户组 |
UTS
: 隔离主机名和域名信息IPC
: 隔离进程间通信PID
: 隔离进程的IDNetwork
: 隔离网络资源Mount
: 隔离文件系统挂载点User
: 隔离用户和用户组的ID
本文所讨论的 namespace 实现针对的均是 Linux 内核 3.8 及其以后的版本。
Namespace的使用
涉及到Namespace
的操作接口包括clone()
、setns()
、unshare()
以及还有/proc
下的部分文件。
为了使用特定的Namespace
,在使用这些接口的时候需要指定以下一个或多个参数:
CLONE_NEWUTS
: 用于指定UTS Namespace
CLONE_NEWIPC
: 用于指定IPC Namespace
CLONE_NEWPID
: 用于指定PID Namespace
CLONE_NEWNET
: 用于指定Network Namespace
CLONE_NEWNS
: 用于指定Mount Namespace
CLONE_NEWUSER
: 用于指定User Namespace
下面简单概述一下这几个接口的用法。
clone系统调用
可以通过clone
系统调用来创建一个独立Namespace
的进程,也是docker使用namespace最基本的方法,它的函数描述如下 :
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
参数说明:
-
child_func
传入子进程运行的程序主函数。 -
child_stack
传入子进程使用的栈空间。 -
flags
表示使用那些CLONE_*标志位,与namespce相关的包括CLONE_NEWUTS
、
CLONE_NEWIPC
、CLONE_NEWPID
、CLONE_NEWNET
、CLONE_NEWNS
、 CLONE_NEWUSER
。
args
则可用于传入用户参数。
通过/proc文件查看已存在的Namespace
在3.8内核开始,用户可以在/proc/$pid/ns
文件下看到本进程所属的Namespace
的文件信息。例如PID为12732进程的情况如下图所示:
其中 4026531839 表明是Namespace
的ID,如果两个进程的Namespace
ID相同表明两个进程同处于一个命名空间中。
这里需要注意的是:只/proc/$pid/ns/
对应的Namespace
文件被打开,并且该**文件描述符(fd)**存在,即使该PID所属的进程被销毁,这个Namespace
会依然存在。可以通过挂载的方式打开文件描述符:
touch ~/mnt
mount --bind /proc/2704/mnt ~/mnt
这样就可以保留住PID为2704的进程的Mount Namespace
了,即使2704进程被销毁或者退出,ID为4026531840的Mount Namespace
依然会存在。
文件描述符(fd)
linux中, 每一个进程在内核中,都对应有一个“打开文件”数组,存放指向文件对象的指针,fd 是这个数组的下标。
我们对文件进行操作时,系统调用,将fd传入内核,内核通过fd找到文件,对文件进行操作。
既然是数组下标,fd的类型为int, < 0 为非法值, >=0 为合法值。在linux中,一个进程默认可以打开的文件数为1024个,fd的范围为0~1023。可以通过设置,改变最大值。
在linux中,值为0、1、2的fd,分别代表标准输入、标准输出、标准错误输出。在上一篇文章中,使用重定向 2>/dev/null 就是把标准错误输出重定向到位桶中去,不显示出来。因为 0 1 2已经被linux使用了,通常在程序中打开的fd,是从3开始的。但我们在判断一个fd是否合法时,依然要使用>=0的判断标准。
fd的分配原则,是从小到大,找到第一个不用的进行分配。
除了open之外, socket编程的socket()/accept()等函数,也会返回一个fd值。
1)Linux系统下,所有进程允许打开的最大fd数量。查询语句:
cat /proc/sys/fs/file-max
2)Linux系统下,所有进程已经打开的fd数量及允许的最大数量。查询语句:
cat /proc/sys/fs/file-nr
3)单个进程允许打开的最大fd数量.查询语句:
ulimit -n
4)单个进程(例如进程id为5454)已经打开的fd.查询语句:
ls -l /proc/5454/fd/
setns加入已存在的Namepspace
setns()
函数可以把进程加入到指定的Namespace
中,它的函数描述如下:
int setns(int fd, int nstype);
它的参数描述如下:
fd
参数:表示文件描述符,前面提到可以通过打开/proc/$pid/ns/
的方式将指定的Namespace
保留下来,也就是说可以通过文件描述符的方式来索引到某个Namespace
。nstype
参数:用来检查fd
关联Namespace
是否与nstype
表明的Namespace
一致,如果填0的话表示不进行该项检查。
通过在程序中调用setns
来将进程加入到指定的Namespace
中。
unshare脱离到新的Namespace
unshare()
系统调用用于将当前进程和所在的Namespace
分离,并加入到一个新的Namespace
中,相对于setns()
系统调用来说,unshare()
不用关联之前存在的Namespace
,只需要指定需要分离的Namespace
就行,该调用会自动创建一个新的Namespace
。
unshare()
的函数描述如下:
int unshare(int flags);
其中flags
用于指明要分离的资源类别,它支持的flags
与clone
系统调用支持的flags
类似,这里简要的叙述一下几种标志:
CLONE_FILES
: 子进程一般会共享父进程的文件描述符,如果子进程不想共享父进程的文件描述符了,可以通过这个flag来取消共享。CLONE_FS
: 使当前进程不再与其他进程共享文件系统信息。CLONE_SYSVSEM
: 取消与其他进程共享SYS V信号量。CLONE_NEWIPC
: 创建新的IPC Namespace
,并将该进程加入进来。
注意事项
这里需要注意的是:unshare()
和setns()
系统调用对PID Namespace
的处理不太相同,当unshare PID namespace时,调用进程会为它的子进程分配一个新的PID Namespace
,但是调用进程本身不会被移到新的Namespace
中。而且调用进程第一个创建的子进程在新Namespace
中的PID为1,并成为新Namespace
中的init进程。
setns()
系统调用也是类似的,调用者进程并不会进入新的PID Namespace,而是随后创建的子进程会进入。
为什么创建其他的Namespace时unshare()
和setns()
会直接进入新的Namespace,而唯独PID Namespace不是如此呢?
因为调用getpid()
函数得到的PID是根据调用者所在的PID Namespace而决定返回哪个PID,进入新的PID namespace会导致PID产生变化。而对用户态的程序和库函数来说,他们都认为进程的PID是一个常量,PID的变化会引起这些进程奔溃。
换句话说,一旦程序进程创建以后,那么它的PID namespace的关系就确定下来了,进程不会变更他们对应的PID namespace。
小结
通过上面简单的概述,对于Namespace
的操作有以下方式:
- 1、可以在进程刚创建的时候通过
clone
系统调用为新进程分配一个或多个新的Namespace
。 - 2、通过
setns()
将进程加入到已有的Namespace
中。 - 3、通过
unshare()
为已存在的进程创建一个或多个新的Namespace
。