浅谈 Linux namespace


Linux Namespace提供了一种内核级别隔离系统资源的方法,通过将系统的全局资源放在不同的 Namespace中,来实现资源隔离的目的。不同 Namespace的程序,可以享有一份独立的系统资源。目前Linux中提供了六类系统资源的隔离机制,分别是:

namespace系统调用参数隔离内容
UTSCLONE_NEWUTS主机名与域名
IPCCLONE_NEWIPC信号量、消息队列和共享内存
PIDCLONE_NEWPID进程编号
NetworkCLONE_NEWNET网络设备、网络栈、端口等等
MountCLONE_NEWNS挂载点(文件系统)
UserCLONE_NEWUSER用户和用户组
  • UTS: 隔离主机名和域名信息
  • IPC: 隔离进程间通信
  • PID: 隔离进程的ID
  • Network: 隔离网络资源
  • 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_NEWIPCCLONE_NEWPIDCLONE_NEWNETCLONE_NEWNSCLONE_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用于指明要分离的资源类别,它支持的flagsclone系统调用支持的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
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值