当前位置: 首页 > 科技观察

Linux操作系统中的Namespace是个什么鬼

时间:2023-03-17 22:39:29 科技观察

Linux操作系统中的Namespace是什么?和基本用法。namespace命名空间的概念是Linux内核用来隔离内核资源的方式。通过命名空间,一些进程只能看到与自己相关的部分资源,而另一些进程只能看到与自己相关的资源。这两组进程根本感觉不到对方的存在。具体实现方法是在同一个命名空间中指定一个或多个进程的相关资源。Linux命名空间是一种对全局系统资源的封装和隔离,使得不同命名空间中的进程拥有独立的全局系统资源。更改命名空间中的系统资源只会影响当前命名空间中的进程,而不会影响其他命名空间中的进程。.namespace的用处可能是绝大多数用户和我一样,都是在使用docker之后才开始了解linux的namespace技术的。事实上,Linux内核实现命名空间的主要目的之一就是实现轻量级的虚拟化(容器)服务。同一命名空间下的进程可以感知彼此的变化,但对外部进程一无所知。这样容器中的进程就可以有一种自己在一个独立系统中的错觉,从而达到隔离的目的。也就是说,Linux内核提供的命名空间技术为docker等容器技术的出现和发展提供了基础条件。我们可以从docker实现者的角度来考虑如何实现一个资源隔离的容器。比如能否通过chroot命令切换根目录的挂载点,隔离文件系统。为了在分布式环境中进行通信和定位,容器必须有独立的IP、端口和路由,这就需要对网络进行隔离。同时,容器还需要一个独立的主机名来在网络中标识自己。接下来需要隔离进程间通信、用户权限等。运行在容器中的应用需要一个进程ID(PID),自然需要和宿主机中的PID隔离开来。也就是说,这六种隔离能力是实现一个容器的基础。让我们看看Linux内核的命名空间特性为我们提供了什么样的隔离能力:上表中的前六个命名空间是实现容器所必需的隔离技术,至于新提供的Cgroup命名空间,目前还没有被采用码头工人。相信在不久的将来,各种容器也会加入对Cgroup命名空间的支持。命名空间的发展历史Linux在很早的版本中就实现了部分命名空间,比如2.4内核就实现了mount命名空间。大多数名称空间支持在内核2.6中完成,例如IPC、Network、PID和UTS。还有一些特殊的命名空间,比如User,从2.6内核开始实现,但在3.8内核中宣布完成。同时,随着Linux自身的发展以及容器技术不断发展带来的需求,也会支持新的命名空间。例如,Cgroup命名空间是在4.6内核中添加的。Linux提供了多种API来操作命名空间,分别是clone()、setns()和unshare()函数。为了确定哪个命名空间被隔离,在使用这些API时,通常需要指定一些调用参数:CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS和CLONE_NEWCGROUP。如果想同时隔离多个命名空间,可以使用|(按位或)组合这些参数。同时我们也可以通过/proc下的一些文件来操作namespace。下面我们就来看看这些接口的简单用法。查看进程所属的命名空间从内核版本号3.8开始,/proc/[pid]/ns目录下会包含进程所属的命名空间信息。使用如下命令查看当前进程所属的命名空间信息:$ll/proc/$$/ns首先,这些命名空间文件是链接文件。链接文件的内容格式为xxx:[inodenumber]。其中xxx是namespace的类型,inodenumber用来标识一个namespace。我们也可以理解为命名空间的ID。如果两个进程的命名空间文件指向同一个链接文件,则意味着它们相关的资源在同一个命名空间中。其次,将这些链接文件放在/proc/[pid]/ns中的另一个作用是,一旦这些链接文件被打开,只要打开的文件描述符(fd)存在,那么即使该命名空间下的所有进程都关闭了最终这个命名空间会一直存在,后续进程可以再次加入它。除了打开文件的方式,我们还可以通过挂载文件的方式来防止命名空间被删除。比如我们可以将当前进程中的uts挂载到~/uts文件中:$touch~/uts$sudomount--bind/proc/$$/ns/uts~/uts使用stat命令查看结果:很神奇,~/uts中的inode和链接文件中的inode号是一样的,是同一个文件。克隆函数我们可以在创建新进程时使用clone()来创建命名空间。C语言库中clone()的声明如下:);其实上面,clone()是C语言库中定义的一个包装函数,负责构建新进程的栈,调用对程序员隐藏的clone()系统调用。clone()其实是Linux系统调用fork()的更通用的实现,可以通过flags来控制使用多少个函数。以CLONE_开头的falg(标志位)参数有20多种,控制clone进程的方方面面(比如是否与父进程共享虚拟内存等),下面我们只介绍4个namespace相关的参数:fn:指定一个要被新进程执行的Function。当此函数返回时,子进程终止。该函数返回一个整数,表示子进程的退出代码。child_stack:传入子进程使用的栈空间,即把用户态栈指针赋给子进程的esp寄存器。调用进程(调用clone()的进程)应始终为子进程分配一个新堆栈。flags:表示哪些标志以CLONE_开头,与命名空间相关的有CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS和CLONE_NEWCGROUP。arg:指向传递给fn()函数的参数。在后续文章中,我们主要使用clone()函数来创建和演示各种类型的命名空间。setns函数可以通过setns()函数将当前进程添加到已有的命名空间中。C语言库中setns()的声明如下:#define_GNU_SOURCE#includeintsetns(intfd,intnstype);和clone()函数一样,C语言库中的setns()函数也是对setns()系统调用的封装:fd:表示要加入命名空间的文件描述符。是指向/proc/[pid]/ns目录下文件的文件描述符,可以通过直接打开该目录下的链接文件或者打开挂载该目录下链接文件的文件获得。nstype:参数nstype允许调用者检查fd指向的命名空间类型是否符合实际要求。如果此参数设置为0,则表示不检查。前面我们提到,命名空间可以通过挂载来保留。保留命名空间的目的是为以后将进程添加到这个命名空间做准备。在docker中,使用dockerexec命令在已经运行的容器中执行新命令需要使用setns()函数。为了利用新添加的命名空间,还需要引入execve()系列函数(作者在《Linux 创建子进程执行任务》一文中介绍了execve()系列函数,有兴趣的同学可以去了解一下),这个function可以执行用户的Command,更常见的用法是调用/bin/bash并接受参数来运行一个shell。unshare函数和命令可以通过unshare函数对原进程进行命名空间隔离。即创建并加入一个新的命名空间。C语言库中unshare()的声明如下:#define_GNU_SOURCE#includeintunshare(intflags);与前面两个函数一样,C语言库中的unshare()函数也调用了unshare()系统封装。调用unshare()的主要作用是在不启动新进程的情况下达到资源隔离的效果,相当于跳出原来的命名空间进行操作。系统默认还提供了一个叫unshare的命令,其实就是调用unshare()系统调用。下面的demo使用unshare命令将当前进程的用户命名空间设置为root:总结命名空间是Linux内核提供的特性,为虚拟化而生。随着docker的诞生,引爆了容器技术,长期以来一直在后台默默奉献的namespace技术也被推到了大家的面前。笔者试图通过对命名空间技术的学习和理解来加深对容器技术的理解,所以接下来会通过文章记录学习命名空间的点点滴滴,希望能和同学们一起进步。