精华内容
下载资源
问答
  • 命名空间的三种使用方法 #include "stdafx.h" #include using namespace std; //标准库的命名空间 解包了所有此空间下的内容 //命名空间的关键字(key word) namespace + 空间名 namespace Space { int x; int ...

    命名空间的使用方法

    #include "stdafx.h"
    #include <iostream>
    using namespace std; //标准库的命名空间 解包了所有此空间下的内容
    
    //命名空间的关键字(key word) namespace + 空间名
    
    namespace Space
    {
    	int x;
    	int y;
    }
    
    namespace Other
    {
    	int x;
    	int y;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	Space::x = 200; //直接带前坠使用
    	cout << Space::x << endl;
    
    	using Space::x; //相当于将Space中的x解包到此作用域下 
    	//之后可以直接对x进行操作 但是不能与本作用域中原有变量冲突
    	x = 20;
    	cout << x << endl;
    
    	//using namespace Space;  将此命名空间中所有内容解包到此作用域
    
    	return 0;
    }
     

    命名空间的嵌套

    #include "stdafx.h"
    #include <iostream>
    using namespace std; 
    
    //命名空间的关键字(key word) namespace + 空间名
    
    namespace Space
    {
    	int x; 
    	int y;
    	namespace Other //命名空间的嵌套
    	{
    		int m;
    		int n;
    	}
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	using namespace Space::Other; 
    	//只有解开里面嵌套的命名空间才能用里面的值
    
    	m = 20;
    	cout << m << endl;
    
    	return 0;
    }
     

    命名空间的作用、用途

    作用:命名空间是程序设计者命名的内存区域,程序设计者根据需指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开,通俗的说,每个名字空间都是一个名字空间域,存放在名字空间域中的全局实体只在本空间域内有效,名字空间对全局实体加以域的限制,从而合理的解决命名冲突,命名空间就是为了避免可能的名字冲突,保持代码的局域性。

    协同开发:
    因为同名的命名空间可以融合,所以当多人开发时,可以将自己的内容都裹上同一个名字的命名空间,具体操作是 例如你开发一个类 将它的头文件、和cpp文件中都用命名空间包装好,当多个文件合一起后 include相应头文件 在解开统一的命名空间,就可以使用里面的内容。
     

    无名命名空间

    定义无名命名空间后,外部不能得知无名命名空间的成员名字,即不让外部知道我的成员名字及其调用,由于没有名字所以其他文件无法引用,它只能在本文件的作用域内有效,它的作用域:从无名命名空间声明开始到本文件结束,在本文件使用无名命名空间成员时不必用命名空间限定,其实无名命名空间和static是同样的道理,都是只在本文件内有效,无法被其他文件引用

    namespace
    {
    	int a = 100;
    	void foo() { cout << "foo" << endl; }
    }
    
    int main()
    {
    	cout << a << endl;
    	foo();
    }

    note:

    • 无名命名空间允许无限定的使用其成员函数,并且为它提供了内部连接(只有在定义的文件内可以使用
    • 命名空间不需要命名,它的成员不需要限定就可以使用。
    • 如果在一个文件中包含了两个相同成员的无名命名空间,其含义是不明确的,会导致重复定义的错误。
    • 无名命名空间可以替代全局作用域的static数据成员
    展开全文
  • linux中的网络命名空间使用

    千次阅读 2018-09-03 22:29:24
    命名空间使用,类似虚拟化技术,在同一台物理机上,创建的多个命名空间相互独立,各个空间的进程独立运行,互不干扰。 在此作一总结,学习加深理解。 linux命名空间 命名空间(Linux namespace)是linux内核...

    背景

    项目中使用了网络命名空间,用来隔离不同空间中的应用。

    命名空间的使用,类似虚拟化技术,在同一台物理机上,创建的多个命名空间相互独立,各个空间的进程独立运行,互不干扰。

    在此作一总结,学习加深理解。

    linux命名空间

    命名空间(Linux namespace)是linux内核针对实现容器虚拟化映入的一个特性。

    我们创建的每个容器都有自己的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样,命名空间保证了容器之间互不影响。

    命名空间和cgroups是软件集装箱化(Docker)的大部分新趋势的主要内核技术之一。 简单来说,cgroups是一种计量和限制机制,它们控制您可以使用多少系统资源(CPU,内存)。 另一方面,命名空间限制了您可以看到的内容。 由于命名空间进程有自己的系统资源视图。

    系统中的所有进程是通过PID标识的,这意味着内核必须管理一个全局的PID列表。而且,所有调用者通过uname系统调用返回的系统相关信息(包括系统名称和有关内核的一些信息)都是相同的。用户ID的管理方式类似,即各个用户是通过一个全局唯一的UID号标识。

    全局ID使得内核可以有选择地允许或拒绝某些特权。虽然UID为0的root用户基本上允许做任何事,但其他用户ID则会受到限制。例如UID为n 的用户,不允许杀死属于用户m的进程(m≠ n)。但这不能防止用户看到彼此,即用户n可以看到另一个用户m也在计算机上活动。只要用户只能操纵他们自己的进程,这就没什么问题,因为没有理由不允许用户看到其他用户的进程。

    但有些情况下,当厂商需要向用户提供root权限时,理论上他需要向每个用户提供一台计算机。显然这代价太高,使用KVM或者VMWare提供的虚拟化环境是一种解决问题的方法,但资源分配做得不是非常好。

    命名空间提供了一种不同的解决方案,只使用一个内核在一台物理计算机上运作,所有全局资源都通过命名空间抽象起来。这使得可以将一组进程放置到容器中,各个容器彼此隔离。隔离可以使容器的成员与其他容器毫无关系。但也可以通过允许容器进行一定的共享,来降低容器之间的分隔。例如,容器可以设置为使用自身的PID集合,但仍然与其他容器共享部分文件系统。

    Linux内核提供了6种命名空间:

    命名空间描述作用备注
    进程命名空间隔离进程IDinux通过命名空间管理进程号,同一个进程,在不同的命名空间进程号不同进程命名空间是一个父子结构,子空间对于父空间可见
    网络命名空间隔离网络设备、协议栈、端口等通过网络命名空间,实现网络隔离docker采用虚拟网络设备,将不同命名空间的网络设备连接到一起
    IPC命名空间隔离进程间通信进程间交互方法PID命名空间和IPC命名空间可以组合起来用,同一个IPC名字空间内的进程可以彼此看见,允许进行交互,不同空间进程无法交互
    挂载命名空间隔离挂载点隔离文件目录进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高
    UTS命名空间隔离Hostname和NIS域名让容器拥有独立的主机名和域名,从而让容器看起来像个独立的主机要目的是独立出主机名和网络信息服务(NIS)
    用户命名空间隔离用户和group ID每个容器内上的用户跟宿主主机上不在一个命名空间同进程 ID 一样,用户 ID 和组 ID 在命名空间内外是不一样的,并且在不同命名空间内可以存在相同的 ID

    下面主要介绍Linux网络命名空间。

    网络命名空间

    在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡、路由转发表、iptables)的环境。网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名字空间的设备,才能看到彼此。

    从逻辑上说,网络命名空间是网络栈的副本,有自己的网络设备、路由选择表、邻接表、Netfilter表、网络套接字、网络procfs条目、网络sysfs条目和其他网络资源。

    从系统的角度来看,当通过clone()系统调用创建新进程时,传递标志CLONE_NEWNET将在新进程中创建一个全新的网络命名空间。

    从用户的角度来看,我们只需使用工具ip(package is iproute2)来创建一个新的持久网络命名空间。

    iproute2 is usually shipped in a package called iproute or iproute2 and consists of several tools, of which the most important are ip and tc. ip controls IPv4 and IPv6 configuration and tc stands for traffic control. Both tools print detailed usage messages and are accompanied by a set of manpages.

    创建netns

    • 查看是否存在命名空间:ip netns list

      • 若报错,需要更新系统内核,以及 ip 工具程序。
    • 添加命名空间:ip netns add ns1

    添加网卡

    • 列出网卡: ip link list

    • 创建新的虚拟网卡:ip link add veth0 type veth peer name veth1

      • 同时创建veth0和veth1两个虚拟网卡
      • 这个时候这两个网卡还都属于“default”或“global”命名空间,和物理网卡一样。
    • 把其中一个网卡转移到命名空间ns1中:ip link set veth1 netns ns1

    • 验证:ip netns exec ns1 ip link list
    • 删除:ip netns exec ns1 ip link del veth1

    • 配置命名空间中的端口:

    $ ip netns exec ns1 ip addr add 10.1.1.1/24 dev veth1
    $ ip netns exec ns1 ip link set veth1 up
    $ ip netns exec ns1 ip link set lo up
    • 配置主机上网卡ip:
    $ ip addr add 10.1.1.2/24 dev veth0
    $ ip link set veth0 up
    • 此时在ns1中ping自己10.1.1.1能通:
    ip netns exec ns1 ping 10.1.1.1
    PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
    64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.058 ms
    • 但还不能ping通10.1.1.2,因为ns1中未设置路由。

    设置命名空间路由

    • 设置默认路由
    $ ip netns exec ns1 ip route add default via 10.1.1.1
    $ ip netns exec ns1 ip route show
    default via 10.1.1.1 dev veth1
    • 此时能够ping通主机:
    $ip netns exec ns1 ping 10.1.1.2
    PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
    64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.086 ms

    使能IP转发

    但是到了这里依旧还不够,ns1命名空间中已经可以联通主机上的网络,但是依旧连不同主机以外的外部网络。这个时候必须在host主机上启动转发,并且在iptables中设置伪装。

    Linux系统的IP转发的意思是,当Linux主机存在多个网卡的时候,允许一个网卡的数据包转发到另外一张网卡;在linux系统中默认禁止IP转发功能,可以打开如下文件查看,如果值为0说明禁止进行IP转发:

    cat /proc/sys/net/ipv4/ip_forward

    运行如下命令:

    #使能ip转发
    
    echo 1 > /proc/sys/net/ipv4/ip_forward
    
    #刷新forward规则
    iptables -F FORWARD
    iptables -t nat -F
    
    #刷新nat规则
    iptables -t nat -L -n
    
    #使能IP伪装
    iptables -t nat -A POSTROUTING -s 10.1.1.0/255.255.255.0 -o eth8 -j MASQUERADE # eth8连接外网
    
    #允许veth0和ens160之间的转发
    iptables -A FORWARD -i eth8 -o veth0 -j ACCEPT
    iptables -A FORWARD -o eth8 -i veth0 -j ACCEPT
    • 使能IP伪装这条语句,添加了一条规则到NAT表的POSTROUTING链中,对于源IP地址为10.1.1.0网段的数据包,用eth8网口的IP地址替换并发送。
    • iptables -A FORWARD这两条语句使能物理网口eth8和veth0之间的数据转发

    总结

    Linux网络命名空间提供了强大的虚拟化功能,使用得当能够达到事半功倍的效果。

    本文简要介绍了Linux提供的6种命名空间,对命名空间的作用和使用场景作了详细的介绍。

    着重对Linux网络命名空间进行了讲解,包括命名空间的创建、使用,与主机通信以及与外部网络的连接等。

    对于一般的应用而言,掌握网络命名空间的创建、使用、ip命令等就能顺当使用命名空间了。

    展开全文
  • 命名空间和using使用

    千次阅读 2016-01-10 08:48:23
    为什么使用命名空间(1)  C#采用的是单一的全局变量命名空间,若有两个变量或函数的名称完全相同,就会出现冲突.  如定义了一个用户名变量userName,可能在调用某个库文件或别的程序代码中也定义了相同的变量名...

    什么使用命名空间(1)

        C#采用的是单一的全局变量命名空间,若有两个变量或函数的名称完全相同,就会出现冲突.

         如定义了一个用户名变量userName,可能在调用某个库文件或别的程序代码中也定义了相同的变量名,此时便出现了冲突。

         命名空间就是为解决C#中变量、函数的命名冲突而服务的。可以将同一名称的变量定义在不同的命名空间中。

        如:张家有电视机,李家也有电视机,王家也有电视机,但因为它们属于不同的家庭,使用时只需将它们所属的家庭作为前缀名即可。

    namespaceTcl

    {

        class Monitor

        {

            public void ListModels()

            {

                Console.WriteLine("供应Tcl以下型号的显示器:");

                Console.WriteLine("14\",15\" \n");

            }

        }

    }

    namespaceCH

    {

        public class Monitor

        {

            public void ListModelStocks()

            {

                Console.WriteLine("以下是 长虹 显示器的规格及其库存量:");

                Console.WriteLine("14\"=1000,15\"=2000, 17\"=3000");

            }

            static void Main(string[] args)

            {

                Tcl.MonitorobjTcl =new Tcl.Monitor();

                MonitorobjCh= new Monitor();//不指定命名空间,使用CH.Monitor类

                objTcl.ListModels();

                objCh.ListModelStocks();

            }

        }

    }

    为什么使用using语句(2)

         创建了命名空间后,如果命名空间中嵌套比较深,在外部引用它们时是十分不方便的,书写冗长.可以使用using语句来简化对它们的访问,

    using语句的意思就好比是“我们需要对这个命名空间中的变量,所以不要每次总是要求对它们分类”。

    什么情况下使用命名空间的别名

           使用using语句可以简化对命名空间的引用,然而某些命名空间中很可能包含相同名称的类,在某些情况下却恰恰需要用到这些同名的类。

    例如在.NET Framework类库中存在3个Timer类:System.Timer.Timer、System.Threading.TimerSystem.Windows.Forms.Timer,我们需要使

    用System.Timer.Timer在后台以固定的时间间隔检查所有应用程序或系统的状态,而使用System.Windows.Forms.Timer来在用户界面中显示一个

    简单的动画,这时,开发人员就必须在类前面加上命名空间。

    别名 解决代码冗长问题

    using Class1 = Namespace1.Test;  //别名到类

    using Ns2=Namespace2.NameSpace3.NameSpace4;  //别名到命名空间

    UsingClass2=Namespace2.Namespace3.Namespce4.Test

    namespaceNamespace1                                   

    {

        class Test

        {

            public Test()

            {

                System.Console.WriteLine("Hello from Namespace1.Test");

            }

        }

    }

    classMainClass

    {

        public static void Main()

      {

      Class1 obj1=new Class1();

      Ns2.Test obj2=new Ns2.Test();

      System.Console.Read();

      }

    namespace Namespace2

    {

       …

       namespace Namespace3

       {

      …

              namespace Namespace4

              {

      …

                    class Test

                    {

                           public Test()

                           {

                System.Console.WriteLine

    ("Hello from Namespace2.Test");

                           }

                   }

             }

       }

    }

    别名解决问题冲突

    namespaceConsoleApplication10

    {

          

        namespace CH

        {

            public class Monitor

            {

                public void ListModelStocks()

                {

                    Console.WriteLine("以下是 长虹 显示器的规格及其库存量:");

                    Console.WriteLine("14\"=1000,15\"=2000, 17\"=3000");

                }          

            }

        }

        namespace Tcl

        {

            class Monitor

            {

                public void ListModels()

                {

                    Console.WriteLine("供应Tcl以下型号的显示器:");

                    Console.WriteLine("14\",15\" \n");

                }           

            }

        }

    }

    namespacetest

    {

        using ConsoleApplication10;

       

        class Test

        {

            public void Func()

            {

               

                 Monitor m = new Monitor();

           }

        }

    }



    展开全文
  • namespace命名空间-之使用

    千次阅读 2017-05-31 21:12:09
    由于各种原因,用户空间命名空间的实现算是一个里程碑了,首先是历时五年最复杂命名空间之一的user命名空间开发完结,其次是内核绝大多数命名空间已经实现了,当前的命名空间可以说是一个稳定版本了,但这并不意味着...

    文章下载地址    http://download.csdn.net/detail/shichaog/8185583

    由于各种原因,用户空间命名空间的实现算是一个里程碑了,首先是历时五年最复杂命名空间之一的user命名空间开发完结,其次是内核绝大多数命名空间已经实现了,当前的命名空间可以说是一个稳定版本了,但这并不意味着命名空间开发工作的完结,新的命名空间可能被添加进内核,也可能需要对当前的命名空间做一些扩展,例如内核日志的隔离。最后,就当前user命名空间的不同使用方法而言,可以说是“游戏规则”的改变:从3.8版本开始,非特权进程可以创建namespace,并且其拥有特权权限,下面将会展示编程中如何使用命名空间的API。

    命名空间

    目前Linux实现了六种类型的namespace,每一个namespace是包装了一些全局系统资源的抽象集合,这一抽象集合使得在进程的命名空间中可以看到全局系统资源。命名空间的一个总体目标是支持轻量级虚拟化工具container的实现,container机制本身对外提供一组进程,这组进程自己会认为它们就是系统唯一存在的进程。

    在下面的讨论中,按命名空间实现的版本先后依次对其介绍,当提到命名空间的API(clone(),ushare(),setns())时括号内的CLONE_NEW*用于标识命名空间的类型。

    mount命名空间 (CLONE_NEWS, Linux2.4.19)用于隔离一组进程看到的文件系统挂载点集合,即处于不同mount 命名空间的进程看到的文件系统层次很可能是不一样的。mount()和umount()系统调用的影响不再是全局的而只影响其调用进程指向的命名空间。

    mount命名空间的一个应用类似chroot,然而和chroot()系统调用相比,mount 命名空间在安全性和扩展性上更好。其它一些更复杂的应用如:不同的mount命名空间可以建立主从关系,这样可以让一个命名空间的事件自动传递到另一个命名空间。

    mount命名空间是Linux内核最早实现的命名空间,于2002年就开始了;这就是CLONE_NEWS的由来,当时没人想到其它不同的命名空间会被添加到内核。

    UTS命名空间(CLONE_NEWUTS, Linux2.6.19)隔离了两个系统变量,系统节点名和域名;uname()系统调用返回UTS,名字使用setnodename()和setdomainname()系统调用设置。从容器的上下文看,UTS赋予了每个容器各自的主机名和 网络信息服务名(NIS) (Network Information Service),这使得初始化和配置脚本能够根据不同的名字进行裁剪。UTS源于传递给uname()系统调用的参数:struct utsname。该结构体的名字源于"UNIX Time-sharing System"

    IPC namespaces (CLONE_NEWIPC, Linux 2.6.19)隔离进程间通信资源,具体来说就是System V IPC objects and (since Linux2.6.30) POSIX message queues;这些机制的共同特点是由其特点而非文件系统路径名标识。每一个IPC命名空间尤其自己的System V IPC标识符和POSIX 消息队列文件系统。

    PID namespaces (CLONE_NEWPID, Linux 2.6.24)隔离进程ID号命名空间,话句话说就是位于不同进程ID命名空间的进程可以有相同的进程ID号,PID命名空间的最大的好处是在主机之间移植container时,可以保留container内的ID号,PID命名空间允许每个container拥有自己的init进程(ID=1),init进程是所有进程的祖先,负责系统启动时的初始化和作为孤儿进程的父进程。

    从特殊的角度来看PID命名空间,就是一个进程有两个ID,一个ID号属于PID命名空间,一个ID号属于PID命名空间之外的主机系统,此外,PID命名空间能够被嵌套。

    Network namespaces (CLONE_NEWNET, Linux2.6.24开始结束于 Linux 2.6.29)用于隔离和网络有关的资源,这就使得每个网络命名空间有其自己的网络设备、IP地址、IP路由表、/proc/net目录、端口号等等。

    从网络命名空间的角度看,每个container拥有其自己的网络设备(虚拟的)和用于绑定自己网络端口号的应用程序。主机上合适的路由规则可以将网络数据包和特定container相关的网络设备关联。例如,可以有多个web 服务器,分别存在不同的container中,这就使得这些web 服务器可以在其命名空间中绑定80端口号。

    User namespaces (CLONE_NEWUSER, 起始于 Linux2.6.23 完成于 Linux 3.8) 隔离用户和组ID空间,换句话说,一个进程的用户和组ID在用户命名空间之外可以不同于命名空间之内的ID,最有趣的是一个用户ID在命名空间之外非特权,而在命名空间内却可以是具有特权的。这就意味着在命名空间内拥有全部的特权权限,在命名空间之外则不是这样。

    自Linux3.8开始,非特权进程可以创建用户命名空间,由于非特权进程在user命名空间内具有root权限,命名空间内非特权应用程序可以使用以前只有root能够使用的一些功能。

    总结

    自从第一个命名空间的实现到现在已有十年之久,命名空间的概念也发展为更通用的框架-隔离先前系统级的全局资源。结果使能命名空间能够提供完整的轻量级虚拟化系统,呈现的形式就是container。随着命名空间概念的扩展,与之相关的clone()系统调用和一两个/proc下的文件发展成许多其它的系统调用和/proc下更多的文件。这些扩展后的API成为本文接下来讨论的主题。

    系列博文索引

    下面的列表给出了后续的系列博文。

    Part 2: the namespaces API

    demo_uts_s.c:示例了UTS命名空间的使用方法。
    ns_exec.c: 使用setns()关联一个命名空间并且执行该命令。

    unshare.c: 停止命名空间的共享并执行命令。

     

    Part 3: PID namespaces

    pidns_init_sleep.c:证明PID命名空间
    multi_pidns.c:嵌套的PID命名空间中创建一系列的子进程。

     

    Part 4: more on PID namespaces

    ns_child_exec.c: 创建一个子进程在新的命名空间中执行命令
    simple_init.c: 简单的init类型的程序,用于在PID命名空间中使用。

    orphan.c: 证明一个子进程变为孤儿进程后,将被init进程收留

    ns_run.c: 使用setns()关联一个或多个命名空间,并且在这些命名空间中执行一个命令,使用场景很可能是在子进程中。

     

    Part 5: user namespaces

    demo_userns.c: 创建命名空间的简单程序,并且显示了程序的权限和能力
    userns_child_exec.c: 创建一个在一个新的命名空间执行shell命令的子进程,类似于ns_child_exec.c,但是提供了用户命名空间的额外选项。

     

    Part 6: more on user namespaces

    userns_setns_test.c:从连个不同的user命名空间测试setns()参数。

    Part 7: network namespaces

    命名空间 API

    一个命名空间包含一些抽象化的全局系统资源,这些隔离的全局资源在命名空间将呈现给进程。命名空间被用于很多目的,最突出的就是轻量级虚拟化容器(container)了。

    命名空间API包括三个系统调用:clone()、unshare()和setns(),此外,还包括/proc目录下的许多文件。本文将讨论上述系统调用以及/proc目录下的一些文件。为了明确使用的命名空间的类型,三个系统调用使用先前提到的CLONE_NEW*常量: 

    CLONE_NEWIPC, CLONE_NEWNS, CLONE_NEWNET, CLONE_NEWPID, CLONE_NEWUSER,and CLONE_NEWUTS。

    创建一个新的命名空间: clone()

    创建一个命名空间的方法是使用clone()系统调用,其会创建一个新的进程。为了说明创建的过程,给出clone()的原型如下:

    int clone(int(*child_func)(void *), void *child_stack, int flags, void*arg);

    本质上,clone()是一个通用的fork()版本,fork()的功能由flags参数控制。总的来说,约有超过20个不同的CLONE_*标志控制clone()提供不同的功能,包括父子进程是否共享如虚拟内存、打开的文件描述符、子进程等一些资源。如调用clone时设置了一个CLONE_NEW*标志,一个与之对应的新的命名空间将被创建,新的进程属于该命名空间。可以使用多个CLONE_NEW*标志的组合。

    我们的例子(demo_uts_namespace.c)调用clone()时设置了CLONE_NEWUTS标志以创建一个UTS命名空间。完整的demo_uts_namespaces.c

     


    #define _GNU_SOURCE
    #include <sys/wait.h>
    #include <sys/utsname.h>
    #include <sched.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    static int  childFunc(void *arg){
    
       struct utsname uts;
    
       if (sethostname(arg, strlen(arg)) == -1)
    
           exit(0);
    
       if (uname(&uts) == -1)
    
           exit(0);
    
       printf("uts.nodename in child: %s\n", uts.nodename);
     
       sleep(100);
    
       return 0;           /* Terminateschild */
    
    }
    
    
    #define STACK_SIZE (1024 * 1024)    /* Stack size for cloned child */
     
    static char child_stack[STACK_SIZE];
    
    int main(int argc, char *argv[]){
    
       pid_t child_pid;
       struct utsname uts;
       if (argc < 2) {
    
           fprintf(stderr, "Usage: %s <child-hostname>\n",argv[0]);
       }
     
       child_pid = clone(childFunc, child_stack +STACK_SIZE,CLONE_NEWUTS | SIGCHLD,argv[1]);
    
       if (child_pid == -1){
          
          exit(0);
       }
    
       printf("PID of child created by clone() is %ld\n", (long)child_pid);
    
       sleep(1);           
    
       if (uname(&uts) == -1){
        
           exit(0);
    
       }
    
       printf("uts.nodename in parent: %s\n", uts.nodename);
    
       if (waitpid(child_pid, NULL, 0) == -1){
       
           exit(0);
       }
          
       printf("child has terminated\n");
    
       exit(0);
    }
    


    例程需要一个命令行参数,当运行时,其创建一个在新的UTS命名空间执行的子进程。在新的命名空间中,子进程使用获得的命令行参数更改主机名。

    main函数第一个比较重要的部分是创建子进程的clone()调用。

       child_pid = clone(childFunc,

                        child_stack +STACK_SIZE,   /* Points to start of

                                                       downwardly growing stack */

                        CLONE_NEWUTS | SIGCHLD,argv[1]);

       printf("PID of child created by clone() is %ld\n", (long)child_pid);

    新的子进程将会执行用户定义的函数childFunc();该函数会接收clone()传递的最后一个参数argv[1],由于创建时使用了CLONE_NEWUTS标识,新的UTS命名空间会被创建。

    主程序然后休眠一段时间。这是一个粗暴的方式以让子进程修改UTS命名空间内的主机名。那个程序然后使用uname()获得命名空间内的主机名并且显示主机名。

       sleep(1);           /* Give childtime to change its hostname */

       if (uname(&uts) == -1)

           errExit("uname");

       printf("uts.nodename in child: %s\n", uts.nodename);

    与此同时,子进程执行的函数childFunc()首先更改主机名为命令行输入的参数,然后显示修改后的主机名。

       if (uname(&uts) == -1)

           errExit("uname");

       printf("uts.nodename in child: %s\n", uts.nodename);

    在结束之前,子进程休眠一会。效果就是让子进程的UTS命名空间存在一段时间,这段时间使得我们能够做一些后面我们给出的实验。

    执行如下命令:

       $ su                  # Need privilege to create a UTS namespace

        Password:

        # uname -n

        antero

        # ./demo_uts_namespacesbizarro

        PID of child created byclone() is 27514

        uts.nodename inchild:  bizarro

        uts.nodename in parent:antero

    正如其它命名空间(user namespace除外),创建一个UTS命名空间需要特权权限(CAP_SYS_ADMIN)。这可以避免set-user-ID类的应用程序因系统主机问题被误导而执行错误的事。

    另外一个可能性是set-user-ID类应用可能使用主机名作为锁文件的一部分。如果一个非特权用户能够在一个具有任何主机名的UTS命名空间运行程序,这可能导致应用受到各种攻击。最简单的,这将使锁无效,在不同的UTS命名空间中引发应用实例被运行。另外,一个恶意用户可以在一个UTS命名空间运行一个set-user-ID应用程序来覆盖一个重要文件的锁。

    The /proc/PID/ns 文件

    每一个进程有一个/proc/PID/ns目录,该目录下每一个命名空间对应一个文件。从3.8版本起,每一个这类文件都是一个特殊的符号链接。该符号链接提供在其命名空间上执行某个操作的某种方法。

    $ ls -l/proc/

    /ns         #
    is replaced byshell's PID

        total 0

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 ipc -> ipc:[4026531839]

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 mnt -> mnt:[4026531840]

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 net -> net:[4026531956]

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 pid -> pid:[4026531836]

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 user -> user:[4026531837]

        lrwxrwxrwx. 1 mtk mtk 0Jan  8 04:12 uts -> uts:[4026531838]

    这些符号链接可以用来判断两个命名空间是否在同一个命名空间。如果两个进程在同一个命名空间,内核会保证由/proc/PID/ns导出的inode号将会是一样的。inode号可以通过stat()系统调用获得。

    然而,内核同样为每个 /proc/PID/ns构建了符号链接,以使其指向包含标识命名空间类型字符串(字符串以inode号结尾)的名字。我们可以通过ls -l或者readlink命令查看名字。

    让我们回到上面 demo_uts_namespaces运行的shell会话,通过查看父子进程的/proc/PID/ns符号链接信息可以知道它们是否位于同一个命名空间。

       ^Z                               # Stop parent and child

        [1]+ Stopped         ./demo_uts_namespaces bizarro

        # jobs-l                        # Show PID of parent process

        [1]+ 27513Stopped         ./demo_uts_namespacesbizarro

        # readlink/proc/27513/ns/uts     # Show parent UTS namespace

        uts:[4026531838]

        # readlink/proc/27514/ns/uts     # Show child UTS namespace

        uts:[4026532338]

    正如我们看到的, /proc/PID/ns/uts 符号链接并不一样,表明它们位于不同的命名空间中。

    /proc/PID/ns同样服务于其它目的,如果我们随便打开一个文件,那么只要文件描述打开状态,那么命名空间将会保持存在而不论命名空间中的进程是否全部退出,相同的效果可以通过绑定其中一个符号链接到文件系统的其它地方获得。

     # touch~/uts                           # Create mount point

        # mount --bind/proc/27514/ns/uts ~/uts

    在3.8之前, /proc/PID/ns 下的文件是硬链接,并且只有ipc、net和uts文件是存在的。

    关联一个存在命名空间:setns()

    当一个命名空间没有进程时还保持其打开,这么做是为了后续添加进程到该命名空间。而添加这个功能这就是使用setns()系统调用来完成了,这使得调用的进程能够和命名空间关联:

     intsetns(int fd, int nstype);

    准确来说,setns()将调用的进程和一个特定的命名空间解除关系并将该进程和一个同类型的命名空间相关联。

    fd参数指明了关联的命名空间,其是指向了 /proc/PID/ns 目录下一个符号链接的文件描述符,可以通过打开这些符号链接指向的文件或者打开一个绑定到符号链接的文件来获得文件描述符(所谓的获得指的是引用计数加1)。

    nstype参数运行调用者检查fd指向的命名空间的类型,如果这个参数等于零,将不会检查。当调用者已经知道命名空间的类型时这会很有用。我们的示例程序(ns_exec.c)的nstype参数等于零,其适用于任何命名空间。当nstype被赋值为CLONE_NEW*的常量时,内核会检查fd指向的命名空间的类型。 

    使用setns()和execve()(或者其它的exec()函数)使得我们能够构建一个简单但是有用的工具,一个和特定命名空间关联的程序并且在命名空间中可以执行一个命令。

    /* ns_exec.c

     

      Copyright 2013, Michael Kerrisk

      Licensed under GNU General Public License v2 or later

     

      Join a namespace and execute a command in the namespace

    */

    #define _GNU_SOURCE

    #include <fcntl.h>

    #include <sched.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

     

    /* A simple error-handling function:print an error message based

      on the value in 'errno' and terminate the calling process */

     

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \

                            } while (0)

     

    int

    main(int argc, char *argv[])

    {

       int fd;

     

       if (argc < 3) {

           fprintf(stderr, "%s /proc/PID/ns/FILE cmd [arg...]\n",argv[0]);

           exit(EXIT_FAILURE);

       }

     

       fd = open(argv[1], O_RDONLY);   /*Get descriptor for namespace */

       if (fd == -1)

           errExit("open");

     

       if (setns(fd, 0) == -1)         /*Join that namespace */

           errExit("setns");

     

       execvp(argv[2], &argv[2]);     /* Execute a command in namespace */

       errExit("execvp");

    }

    我们的应用程序接收两个命令行参数。第一个参数是/proc/PID/ns/* 符号链接的路径,剩下的参数是在和第一参数关联的命名空间中将要运行的程序名,程序中的关键步骤如下:

       fd = open(argv[1], O_RDONLY);   /* Get descriptor for namespace */

        setns(fd,0);                  /* Join thatnamespace */

       execvp(argv[2], &argv[2]);      /* Execute acommand in namespace */

    一个在命名空间中执行的有意思的程序当然是shell程序。我么可以使用先前创建的UTS命名空间以及ns_exec程序来在demo_uts_namespaces.c创建的新的UTS命名空间中执行一个shell。

       # ./ns_exec ~/uts /bin/bash     # ~/uts is bound to/proc/27514/ns/uts

       My PID is: 28788

    可以证明新的UTS命名空间创建的shell是emo_uts_namespaces的子进程,可以通过查看主机名或者比较 /proc/PID/ns/uts 的inode节点得到该结论。

       # hostname

       bizarro

       # readlink /proc/27514/ns/uts

       uts:[4026532338]

       # readlink /proc/

    /ns/uts      #
    is replaced byshell's PID

       uts:[4026532338]

    早期的内核版本,使用setns()关联mount、PID和user 命名空间是不可能的,但是从3.8开始支持所有的命名空间类型。

    Leaving a namespace: unshare()

    最后一种命名空间的系统调用是unshare():

       int unshare(int flags);

    unshare()系统调用提供类似clone()的功能,但是作用于调用的进程。其会创建由flags参数中制定的CLONE_NEW*命名空间,并且将调用者作为命名空间的一部分。 unshare()的主要目的是消除命名空间的副作用而不需要创建新的进程或线程。

    撇开clone系统调用的影响,调用的形式是:

     clone(...,CLONE_NEWXXX, ....);

    就命名空间术语来说,等价于下列顺序:

       if (fork() == 0)

           unshare(CLONE_NEWXXX);      /* Executed in the childprocess */

    unshare的系统调用的一个例子是在命令行下使用unshare命令,其允许用户使用shell执行另一个命名空间的命令。该命令的通常形式如下:

       unshare [options] program [arguments]

    参数[arguments] 是传递给命令program的,options传递给unshare指向的命名空间。

    实现unshare命令的关键步骤很直接:

        /* Code to initialize 'flags' according to command-line options

           omitted */

        unshare(flags);

         /* Now execute 'program' with 'arguments'; 'optind' is the index

           of the next command-line argument after options */

         execvp(argv[optind], &argv[optind]);

    一个简单的unshare命令的实现代码如下:

    /* unshare.c 

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

       A simple implementation of the unshare(1)command: unshare

       namespaces and execute a command.

    */

     

    #define_GNU_SOURCE

    #include<sched.h>

    #include<unistd.h>

    #include<stdlib.h>

    #include<stdio.h>

     

    /* A simpleerror-handling function: print an error message based

       on the value in 'errno' and terminate thecalling process */

     

    #defineerrExit(msg)    do { perror(msg);exit(EXIT_FAILURE); \

                            } while (0)

     

    static void

    usage(char *pname)

    {

        fprintf(stderr, "Usage: %s [options]program [arg...]\n", pname);

        fprintf(stderr, "Options canbe:\n");

        fprintf(stderr, "    -i  unshare IPC namespace\n");

        fprintf(stderr, "    -m  unshare mount namespace\n");

        fprintf(stderr, "    -n  unshare network namespace\n");

        fprintf(stderr, "    -p  unshare PID namespace\n");

        fprintf(stderr, "    -u  unshare UTS namespace\n");

        fprintf(stderr, "    -U  unshare user namespace\n");

        exit(EXIT_FAILURE);

    }

     

    int

    main(int argc,char *argv[])

    {

        int flags, opt;

        flags = 0;

     

        while ((opt = getopt(argc, argv,"imnpuU")) != -1) {

            switch (opt) {

            case 'i': flags |= CLONE_NEWIPC;        break;

            case 'm': flags |= CLONE_NEWNS;         break;

            case 'n': flags |= CLONE_NEWNET;        break;

            case 'p': flags |= CLONE_NEWPID;        break;

            case 'u': flags |= CLONE_NEWUTS;        break;

            case 'U': flags |= CLONE_NEWUSER;       break;

            default:  usage(argv[0]);

            }

        }

        if (optind >= argc)

            usage(argv[0]);

        if (unshare(flags) == -1)

            errExit("unshare");

        execvp(argv[optind],&argv[optind]); 

        errExit("execvp");

    }

    在下面的shell会话中,我们使用unshare.c程序在另外一个mount命名空间中执行一个shell。

       # echo$$                            # Show PID of shell

       8490

       # cat /proc/8490/mounts | grep mq     # Show one of themounts in namespace

       mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0

       # readlink/proc/8490/ns/mnt          # Showmount namespace ID

        mnt:[4026531840]

       # ./unshare -m/bin/bash             # Start new shell in separate mount namespace

       # readlink/proc/$$/ns/mnt           # Show mount namespace ID

        mnt:[4026532325]

    从上述readlink输出可以看到两个shell属于不同的命名空间,改变一个命名空间的挂载点,然后查看另外一个命名空间对上述改变是否可见即可分辨它们是否位于同一个命名空间。

       # umount /dev/mqueue                 # Remove a mount point in this shell

       # cat /proc/$$/mounts | grep mq       # Verifythat mount point is gone

       # cat /proc/8490/mounts | grep mq     # Is it still presentin the other namespace?

     mqueue/dev/mqueue mqueuerw,seclabel,relatime 0 0

    从输出的最后两个参数看到, /dev/mqueue挂载点一个命名空间可以看到而另一个命名空间看不到。

    总结

    本文我们查看了命名空间API以及它们的使用。接下来的文章,我们将更深入查看命名空间的,特别会深入查看PID和user 命名空间。

    PID命名空间

    被PID命名空间隔离的全局资源是进程ID号空间,这就意味着位于不同命名空间的进程的ID号可以相同,PID命名空间被用来实现container。

    和传统Linux系统一样,在PID命名空间内的进程ID号是各不相等的,它们被从1开始分配进程ID号。同样的,ID号等于1的init进程是一个特殊进程,它是命名空间中的第一个进程,它也命名空间提供一些管理工作。

    初探

    一个新的PID命名空间调用clone(...CLONE_NEWPID...)创建,我们将展示一个简单的使用clone创建PID命名空间的例子,并且使用该例子阐释PID命名空间的基本概念,完整的pidns_init_sleep.c的源码如下:

    /*pidns_init_sleep.c

     

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

     

       A simple demonstration of PID namespaces.

    */

    #define_GNU_SOURCE

    #include<sched.h>

    #include<unistd.h>

    #include<stdlib.h>

    #include<sys/wait.h>

    #include<sys/mount.h>

    #include<sys/types.h>

    #include<sys/stat.h>

    #include<string.h>

    #include<signal.h>

    #include<stdio.h>

     

    /* A simpleerror-handling function: print an error message based

       on the value in 'errno' and terminate thecalling process */

     

    #defineerrExit(msg)    do { perror(msg);exit(EXIT_FAILURE); \

                            } while (0)

     

    static int              /* Start function for clonedchild */

    childFunc(void*arg)

    {

        printf("childFunc(): PID  = %ld\n", (long) getpid());

        printf("childFunc(): PPID =%ld\n", (long) getppid());

     

        char *mount_point = arg;

     

        if (mount_point != NULL) {

            mkdir(mount_point, 0555);       /* Create directory for mount point */

            if (mount("proc",mount_point, "proc", 0, NULL) == -1)

                errExit("mount");

            printf("Mounting procfs at %s\n",mount_point);

        }

     

        execlp("sleep","sleep", "600", (char *) NULL);

        errExit("execlp");  /* Only reached if execlp() fails */

    }

     

    #define STACK_SIZE(1024 * 1024)

     

    static charchild_stack[STACK_SIZE];    /* Space forchild's stack */

     

    int

    main(int argc,char *argv[])

    {

        pid_t child_pid;

     

        child_pid = clone(childFunc,

                        child_stack +STACK_SIZE,   /* Points to start of

                                                      downwardly growing stack */

                        CLONE_NEWPID | SIGCHLD,argv[1]);

     

        if (child_pid == -1)

            errExit("clone");

     

        printf("PID returned by clone():%ld\n", (long) child_pid);

     

        if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */

            errExit("waitpid");

     

        exit(EXIT_SUCCESS);

    }

    主程序使用clone()创建一个PID命名空间,并且显示了返回的PID号。

     child_pid =clone(childFunc,

                       child_stack +STACK_SIZE,   /* Points to start of

                                                      downwardly growing stack */

                       CLONE_NEWPID | SIGCHLD, argv[1]);

        printf("PID returned by clone(): %ld\n", (long) child_pid);

    新的子进程开始执行childFunc()函数,该函数接收clone()调用的最后一个参数argv[1],该参数的作用后续讲解。 childFunc()函数显示进程和其父进程ID,并且使用sleep函数结束。

       printf("childFunc(): PID = %ld\n", (long) getpid());

       printf("ChildFunc(): PPID = %ld\n", (long) getppid());

        ...

       execlp("sleep", "sleep", "1000", (char *) NULL);

    使用sleep的主要价值在于其让我们区分子进程和父进程更简单。当我们允许该程序, 第一行的输出如下:

       $ su         # Need privilege to createa PID namespace

       Password:

        #./pidns_init_sleep /proc2

       PID returned by clone(): 27656

       childFunc(): PID  = 1

       childFunc(): PPID = 0

       Mounting procfs at /proc2

     pidns_init_sleep 前两行输出了从两个不同的PID命名空间查看子进程的ID号:clone()调用所在的PID命名空间和子进程存在的PID命名空间。即子进程有两个PID:父进程命名空间中的为27656,新创建的命名空间中的ID是1。输出的下一行展示了子进程的父进程ID。父进程ID等于0显示了命名空间实现的一点怪异之处。正如下面我们讨论的,命名空间形成一个层次结构:一个进程仅仅能够"看见"在其命名空间内和子命名空间内的进程ID(子进程的子进程...均能看见),子进程看不见父进程命名空间的进程。由于由clone()创建的子进程同父进程在不同的命名空间中,所有子进程“看”不到父进程;因此,getppid()返回的父进程ID为0.

    为了解释最后一行,我们需要重新查看childFunc() 中跳过的一些代码段。

    /proc/PID和PID命名空间

    Linux系统上的每一个进程都有一个/proc/PID目录,该目录包括了描述进程的伪文件。这一机制可以直接得到PID的命名空间。在一个命名空间内部,/proc/PID目录仅包含在该命名空间内和子命名空间。

    然而,为了使和一个PID命名空间相关的/proc/PID目录可见,proc文件系统("procfs")需要在PID命名空间内被挂载。在一个命名空间内运行的shell上,我们可以使用mount命令完成:

     #mount -tproc proc/mount_point

    也可以使用mount()系统调用完成,这里在childFunc()里调用如下:

       mkdir(mount_point, 0555);       /* Createdirectory for mount point */

       mount("proc", mount_point, "proc", 0, NULL);

       printf("Mounting procfs at %s\n", mount_point);

    mount_pioint参数在运行pidns_init_sleep时通过命令行参数给出。

    在我们的例子中,shell中运行pidns_init_sleep,我们在/proc2目录下挂着procfs。在现实世界中,procfs通常被挂载在/proc目录下。然而,我们的例子在/proc2目录下挂载procfs,这避免了会给系统上其它进程带来问题:因为他们位于同样的挂载点,更改挂载在/proc目录下的文件系统将使得root PID 命名空间“看”不到/proc/PID目录。

    所以,在我们的shell会话中,/proc目录下挂载的procfs将显示从父PID命名空间能够看到的进程的PID子目录,/proc2则用于子进程命名空间。需要提醒的是虽然子进程PID命名空间的进程能够看见由/proc挂载点导出的PID目录,但是这些PID目录是对于子进程PID命名空间的进程而言是无意义的,因为这些进程的系统调用只能看到它们所在命名空间的PID。

    如果我们想让像ps这样的工具在一个子进程中能够正确运行,那么在/proc挂载点挂载一个procfs文件系统还是必要的。因为这些工具的信息源于/proc目录。有两种方法在不影响父进程使用的PID命名空间前提下达到这个目标。其一,如果子进程使用CLONE_NEWNS标志创建,那么子进程将和系统的其它部分在不同的mount 命名空间,在这种情况下,在/proc目录下挂载procfs不会产生任何问题。另外,不采用CLONE_NEWNS的方法,子进程可以使用chroot()并在/proc目录下挂载procfs。

    让我们回到运行pidns_init_sleep程序的shell上,我们停止该程序并在fu2命名空间中使用ps检查父子进程的一些信息。

       ^Z                         Stopthe program, placing in background

       [1]+ Stopped                ./pidns_init_sleep /proc2

       # ps -C sleep -C pidns_init_sleep -o "pid ppid stat cmd"

         PID  PPID STAT CMD

       27655 27090 T    ./pidns_init_sleep /proc2

       27656 27655 S    sleep 600

    PPID的值为27655,最后一行系显示了sleep是在父进程中执行的。

    通过使用readlink命令查看父子进程的不同/proc/PID/ns/pid符号链接信息,我们可以看到两个进程在不同的PID命名空间中:

       # readlink /proc/27655/ns/pid

       pid:[4026531836]

       # readlink /proc/27656/ns/pid

       pid:[4026532412]

    到此,我们可以使用新挂载的procfs获得新PID命名空间的进程信息。我们可以使用下面的命令获得PID的列表:

       # ls -d /proc2/[1-9]*

       /proc2/1

    正如我们看到的,PID命名空间仅仅包括一个进程,它的进程ID号是1。同样也可以使用/proc/PID/status文件作为一个获得一个进程信息的不同方法。

       # cat /proc2/1/status | egrep '^(Name|PP*id)'

       Name:   sleep

       Pid:    1

       PPid:   0

    PPid是0,符合前面getppid()系统调用的返回的父进程的ID号。

    嵌套的 PID 命名空间

    如前面提到的,PID命名空间是以父子关系层次嵌套的。在一个PID命名空间内,可以看同一个命名空间中的所有其它进程以及后裔进程。这里,“看见”意思是能够在特定的进程ID号上使用系统调用,一个子PID命名空间不能看见父PPID的命名空间。

    一个进程在PID命名空间的每一层都有一个进程ID,存在的范围是该进程的PID命名空间一直到root命名空间。getpid()总是返回PID命名空间内的进程ID。

    我们可以使用multi_pidns.c来展示一个进程在不同的命名空间中拥有不同的进程ID号。为了简洁,我们简单阐述该程序都做了什么。

    /* multi_pidns.c

     

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

     

       Create a series of child processes in nestedPID namespaces.

    */

    #define_GNU_SOURCE

    #include<sched.h>

    #include<unistd.h>

    #include<stdlib.h>

    #include<sys/wait.h>

    #include<string.h>

    #include<signal.h>

    #include<stdio.h>

    #include<limits.h>

    #include<sys/mount.h>

    #include<sys/types.h>

    #include<sys/stat.h>

     

    /* A simpleerror-handling function: print an error message based

       on the value in 'errno' and terminate thecalling process */

     

    #defineerrExit(msg)    do { perror(msg);exit(EXIT_FAILURE); \

                            } while (0)

     

    #define STACK_SIZE(1024 * 1024)

     

    static charchild_stack[STACK_SIZE];    /* Space forchild's stack */

                    /* Since each child gets a copyof virtual memory, this

                       buffer can be reused as eachchild creates its child */

     

    /* Recursivelycreate a series of child process in nested PID namespaces.

       'arg' is an integer that counts down to 0during the recursion.

       When the counter reaches 0, recursion stopsand the tail child

       executes the sleep(1) program. */

     

    static int

    childFunc(void*arg)

    {

        static int first_call = 1;

        long level = (long) arg;

     

        if (!first_call) {

     

            /* Unless this is the first recursivecall to childFunc()

               (i.e., we were invoked from main()),mount a procfs

               for the current PID namespace */

     

            char mount_point[PATH_MAX];

     

            snprintf(mount_point, PATH_MAX,"/proc%c", (char) ('0' + level));

     

            mkdir(mount_point, 0555);       /* Create directory for mount point */

            if (mount("proc",mount_point, "proc", 0, NULL) == -1)

                errExit("mount");

            printf("Mounting procfs at %s\n",mount_point);

        }

     

        first_call = 0;

     

        if (level > 0) {

     

            /* Recursively invoke childFunc() tocreate another child in a

               nested PID namespace */

     

            level--;

            pid_t child_pid;

     

            child_pid = clone(childFunc,

                        child_stack +STACK_SIZE,   /* Points to start of

                                                      downwardly growing stack */

                        CLONE_NEWPID | SIGCHLD,(void *) level);

     

            if (child_pid == -1)

                errExit("clone");

     

            if (waitpid(child_pid, NULL, 0) ==-1)  /* Wait for child */

                errExit("waitpid");

     

        } else {

     

            /* Tail end of recursion: executesleep(1) */

     

            printf("Final childsleeping\n");

            execlp("sleep","sleep", "1000", (char *) NULL);

            errExit("execlp");

        }

     

        return 0;

    }

     

    int

    main(int argc,char *argv[])

    {

        long levels;

     

        levels = (argc > 1) ? atoi(argv[1]) : 5;

        childFunc((void *) levels);

     

        exit(EXIT_SUCCESS);

    }

    该程序递归创建一系列的子进程命名空间,命令行参数指明了子进程和PID命名空间的创建次数:

    #./multi_pidns5

    除了递归创建子进程,每一个递归步骤还会在一个独一无二的挂载点挂载procfs文件系统。递归最后的子进程执行sleep()系统调用。上面的命令行产生如下的输出:

       Mounting procfs at /proc4

       Mounting procfs at /proc3

       Mounting procfs at /proc2

       Mounting procfs at /proc1

       Mounting procfs at /proc0

       Final child sleeping

    在每一个procfs下查看PID,我们看到越是后创建的procfs包含的PID越少,表明每一个PID命名空间只显示其命名空间自身以及其后创建的命名空间的信息。

       ^Z                          Stop the program, placing in background

       [1]+ Stopped            ./multi_pidns5

       # ls -d /proc4/[1-9]*        Topmost PIDnamespace created by program

       /proc4/1  /proc4/2  /proc4/3  /proc4/4  /proc4/5

       # ls -d /proc3/[1-9]*

       /proc3/1  /proc3/2  /proc3/3  /proc3/4

       # ls -d /proc2/[1-9]*

       /proc2/1  /proc2/2  /proc2/3

       # ls -d /proc1/[1-9]*

       /proc1/1  /proc1/2

       # ls -d /proc0/[1-9]*        Bottommost PIDnamespace

       /proc0/1

    一个grep命令使得我们能够看见递归最后的PID。

       # grep -H 'Name:.*sleep' /proc?/[1-9]*/status

       /proc0/1/status:Name:       sleep

       /proc1/2/status:Name:       sleep

       /proc2/3/status:Name:       sleep

       /proc3/4/status:Name:       sleep

       /proc4/5/status:Name:       sleep

    换句话说,嵌套最深的PID命名空间(/proc0),该进程执行sleep并且进程ID是1,在最上面创建的PID命名空间是/proc4,进程的PID是5。

    如果你运行本文的例子,需要说明的是它们将残留下挂载点和挂载目录。在结束程序时,如下的shell命令行完成做够的清理工作:

       # umount /proc?

       # rmdir /proc?

    总结

    在本文,我们了解了一些PID命名空间的操作。在下文中,我们将讨论PID命名空间的init进程和一些其它的PID命名空间的API。

     

    深入PID 命名空间

    本文是对PID命名空间的更深入探讨。PID命名空间的一个应用是打包一组进程(container),使打包的进程组自身就像一个操作系统。传统操作系统和这里的container一样的一个关键点是init进程。所以我们来看看init进程以及两种情况下都有哪些不同。按惯例,我们将看看适用于PID命名空间的其它一些细节。

    PID命名空间的init进程

    在PID命名空间中创建的第一个进程的进程ID是1,该进程的角色和传统操作系统的init进程一样;特别地,init进程能够完成PID命名空间需要的初始化工作(这些工作很可能包括启动其它进程),其同样会是孤儿进程的父进程。

    为了解释PID命名空间,我们将使用一些服务于目的的例程。第一个例程是ns_child_exec.c,命令行运行的语法如下:

    ns_child_exec[options]command [arguments]

    /* ns_child_exec.c

     

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

     

       Create a child process that executes a shellcommand in new namespace(s).

    */

    #define_GNU_SOURCE

    #include <sched.h>

    #include<unistd.h>

    #include<stdlib.h>

    #include<sys/wait.h>

    #include<signal.h>

    #include<stdio.h>

     

    /* A simpleerror-handling function: print an error message based

       on the value in 'errno' and terminate thecalling process */

     

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \

                            } while (0)

     

    static void

    usage(char *pname)

    {

        fprintf(stderr, "Usage: %s [options]cmd [arg...]\n", pname);

        fprintf(stderr, "Options canbe:\n");

        fprintf(stderr, "    -i  new IPC namespace\n");

        fprintf(stderr, "    -m  new mount namespace\n");

        fprintf(stderr, "    -n  new network namespace\n");

        fprintf(stderr, "    -p  new PID namespace\n");

        fprintf(stderr, "    -u  new UTS namespace\n");

        fprintf(stderr, "    -U  new user namespace\n");

        fprintf(stderr, "    -v  Display verbose messages\n");

        exit(EXIT_FAILURE);

    }

     

    static int              /* Start function for clonedchild */

    childFunc(void*arg)

    {

        char **argv = arg;

     

        execvp(argv[0], &argv[0]);

        errExit("execvp");

    }

     

    #define STACK_SIZE(1024 * 1024)

     

    static charchild_stack[STACK_SIZE];    /* Space forchild's stack */

     

    int

    main(int argc,char *argv[])

    {

        int flags, opt, verbose;

        pid_t child_pid;

     

        flags = 0;

        verbose = 0;

     

        /* Parse command-line options. The initial'+' character in

           the final getopt() argument preventsGNU-style permutation

           of command-line options. That's useful,since sometimes

           the 'command' to be executed by thisprogram itself

           has command-line options. We don't wantgetopt() to treat

           those as options to this program. */

     

        while ((opt = getopt(argc, argv,"+imnpuUv")) != -1) {

            switch (opt) {

            case 'i': flags |= CLONE_NEWIPC;        break;

            case 'm': flags |= CLONE_NEWNS;         break;

            case 'n': flags |= CLONE_NEWNET;        break;

            case 'p': flags |= CLONE_NEWPID;        break;

            case 'u': flags |= CLONE_NEWUTS;        break;

            case 'U': flags |= CLONE_NEWUSER;       break;

            case 'v': verbose = 1;                  break;

            default:  usage(argv[0]);

            }

        }

     

        child_pid = clone(childFunc,

                        child_stack + STACK_SIZE,

                        flags | SIGCHLD,&argv[optind]);

        if (child_pid == -1)

            errExit("clone");

     

        if (verbose)

            printf("%s: PID of child createdby clone() is %ld\n",

                    argv[0], (long) child_pid);

     

        /* Parent falls through to here */

     

        if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */

            errExit("waitpid");

     

        if (verbose)

            printf("%s: terminating\n",argv[0]);

        exit(EXIT_SUCCESS);

    }

    ns_child_exec程序使用clone()系统调用来创建子进程;子进程然后执行命令行中的command命令,命令的参数是命令行中的argument参数。命令行中的option参数用于指定要创建的命名空间类型,该参数将传递给clone()系统调用。例如 -p选项将指导子进程创建新的PID命名空间,如下所示:

       $ su                 # Need privilege to create aPID namespace

       Password:

       # ./ns_child_exec -p sh -c 'echo $$'

       1

    上面的命令行在新的PID命名空间中创建了一个子进程,该子进程执行一个显示shell进程ID的echo命令。该shell进程ID是1,当shell运行时其将是PID命名空间的init进程。

    我们的下一个例程,simple_init.c是一个我们要将其作为一个PID命名空间的init进程的程序。该程序用于证实PID命名空间的init进程的一些特性。

    /* simple_init.c

     

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

     

       A simple init(1)-style program to be used asthe init program in

       a PID namespace. The program reaps thestatus of its children and

       provides a simple shell facility forexecuting commands.

    */

    #define_GNU_SOURCE

    #include<unistd.h>

    #include<stdio.h>

    #include<stdlib.h>

    #include<string.h>

    #include<signal.h>

    #include<wordexp.h>

    #include<errno.h>

    #include<sys/wait.h>

     

    #defineerrExit(msg)    do { perror(msg);exit(EXIT_FAILURE); \

                            } while (0)

     

    static int verbose= 0;

     

    /* Display waitstatus (from waitpid() or similar) given in 'status' */

     

    /* SIGCHLDhandler: reap child processes as they change state */

     

    static void

    child_handler(intsig)

    {

        pid_t pid;

        int status;

     

        /* WUNTRACED and WCONTINUED allow waitpid()to catch stopped and

           continued children (in addition toterminated children) */

     

        while ((pid = waitpid(-1, &status,

                              WNOHANG | WUNTRACED |WCONTINUED)) != 0) {

            if (pid == -1) {

                if (errno == ECHILD)        /* No more children */

                    break;

                else

                   perror("waitpid");     /* Unexpected error */

            }

     

            if (verbose)

                printf("\tinit: SIGCHLDhandler: PID %ld terminated\n",

                        (long) pid);

        }

    }

     

    /* Perform wordexpansion on string in 'cmd', allocating and

       returning a vector of words on success orNULL on failure */

     

    static char **

    expand_words(char*cmd)

    {

        char **arg_vec;

        int s;

        wordexp_t pwordexp;

     

        s = wordexp(cmd, &pwordexp, 0);

        if (s != 0) {

            fprintf(stderr, "Word expansionfailed\n");

            return NULL;

        }

     

        arg_vec = calloc(pwordexp.we_wordc + 1,sizeof(char *));

        if (arg_vec == NULL)

            errExit("calloc");

     

        for (s = 0; s < pwordexp.we_wordc; s++)

            arg_vec[s] = pwordexp.we_wordv[s];

     

        arg_vec[pwordexp.we_wordc] = NULL;

     

        return arg_vec;

    }

     

    static void

    usage(char *pname)

    {

        fprintf(stderr, "Usage: %s[-q]\n", pname);

        fprintf(stderr, "\t-v\tProvide verboselogging\n");

     

        exit(EXIT_FAILURE);

    }

     

    int

    main(int argc,char *argv[])

    {

        struct sigaction sa;

    #define CMD_SIZE10000

        char cmd[CMD_SIZE];

        pid_t pid;

        int opt;

     

        while ((opt = getopt(argc, argv,"v")) != -1) {

            switch (opt) {

            case 'v': verbose = 1;          break;

            default:  usage(argv[0]);

            }

        }

     

        sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;

        sigemptyset(&sa.sa_mask);

        sa.sa_handler = child_handler;

        if (sigaction(SIGCHLD, &sa, NULL) ==-1)

            errExit("sigaction");

     

        if (verbose)

            printf("\tinit: my PID is%ld\n", (long) getpid());

     

        /* Performing terminal operations while notbeing the foreground

           process group for the terminal generatesa SIGTTOU that stops the

           process. However our init "shell" needs to be able to perform

           such operations (just like a normalshell), so we ignore that

           signal, which allows the operations toproceed successfully. */

     

        signal(SIGTTOU, SIG_IGN);

     

        /* Become leader of a new process group andmake that process

           group the foreground process group forthe terminal */

     

        if (setpgid(0, 0) == -1)

            errExit("setpgid");;

        if (tcsetpgrp(STDIN_FILENO, getpgrp()) ==-1)

            errExit("tcsetpgrp-child");

     

        while (1) {

     

            /* Read a shell command; exit on end offile */

     

            printf("init$ ");

            if (fgets(cmd, CMD_SIZE, stdin) ==NULL) {

                if (verbose)

                    printf("\tinit:exiting");

                printf("\n");

                exit(EXIT_SUCCESS);

            }

     

            if (cmd[strlen(cmd) - 1] == '\n')

                cmd[strlen(cmd) - 1] = '\0';        /* Strip trailing '\n' */

     

            if (strlen(cmd) == 0)

                continue;           /* Ignore empty commands */

     

            pid = fork();           /* Create child process */

            if (pid == -1)

                errExit("fork");

     

            if (pid == 0) {         /* Child */

                char **arg_vec;

     

                arg_vec = expand_words(cmd);

                if (arg_vec == NULL)        /* Word expansion failed */

                    continue;

     

                /* Make child the leader of a newprocess group and

                   make that process group theforeground process

                   group for the terminal */

     

                if (setpgid(0, 0) == -1)

                    errExit("setpgid");;

                if (tcsetpgrp(STDIN_FILENO,getpgrp()) == -1)

                   errExit("tcsetpgrp-child");

     

                /* Child executes shell command andterminates */

     

                execvp(arg_vec[0], arg_vec);

                errExit("execvp");          /* Only reached if execvp() fails */

            }

     

            /* Parent falls through to here */

     

            if (verbose)

                printf("\tinit: created child%ld\n", (long) pid);

     

            pause();                /* Will be interrupted by signalhandler */

     

            /* After child changes state, ensurethat the 'init' program

               is the foreground process group forthe terminal */

     

            if (tcsetpgrp(STDIN_FILENO, getpgrp())== -1)

                errExit("tcsetpgrp-parent");

        }

    }

    simple_init程序实现init进程的两个主要功能,一个是系统初始化,大多数init程序是复杂的且多半是基于表的方法来初始化系统。我们的simple_init程序提供一个简单的shell工具,该工具使我们能够手动执行需要初始化命名空间的任何命令;该方法使我们能够随意执行shell命令以对命名空间做一些测试。另一个simple_init实现的功能是使用waitpid()获得其子进程的退出状态。

    所以可以联合使用ns_child_exec和simple_init来在一个新的PID命名空间中启动init进程:

       # ./ns_child_exec -p ./simple_init

       init$

    init$提示符表明simple_init程序能够读取和执行shell命令。

    我们将使用上面的两个以及下面的orphan.c示例来证实在PID命名空间中变为孤儿进程将被PID命名空间中的init进程收留,而不是系统的init进程收留。

    /* orphan.c

     

       Copyright 2013, Michael Kerrisk

       Licensed under GNU General Public License v2or later

     

       Demonstrate that a child becomes orphaned(and is adopted by init(1),

       whose PID is 1) when its parent exits.

    */

    #include<stdio.h>

    #include<stdlib.h>

    #include<unistd.h>

     

    int

    main(int argc,char *argv[])

    {

        pid_t pid;

     

        pid = fork();

        if (pid == -1) {

            perror("fork");

            exit(EXIT_FAILURE);

        }

     

        if (pid != 0) {             /* Parent */

            printf("Parent (PID=%ld) createdchild with PID %ld\n",

                    (long) getpid(), (long) pid);

            printf("Parent (PID=%ld; PPID=%ld)terminating\n",

                    (long) getpid(), (long)getppid());

            exit(EXIT_SUCCESS);

        }

     

        /* Child falls through to here */

     

        do {

            usleep(100000);

        }while (getppid() != 1);           /* Am Ian orphan yet? */

     

        printf("\nChild  (PID=%ld) now an orphan (parentPID=%ld)\n",

                (long) getpid(), (long) getppid());

     

        sleep(1);

     

        printf("Child  (PID=%ld) terminating\n", (long) getpid());

        _exit(EXIT_SUCCESS);

    }

    orphan程序执行一个fork()命令创建子进程。在进程继续执行时父进程会退出;当父进程退出时子进程变成孤儿进程。子进程执行一个循环直到其变为一个孤儿进程(getppid()的返回值是1);一旦子进程变成孤儿进程,它将退出。父子进程打印的信息使我们能够看见当两个子进程退出时刻以及何时子进程变成孤儿进程。

    为了更清楚simple_init程序接受孤儿进程的哪些信息,我们使用-v选项,该选项将产生子进程自己产生的各种信息。

        # ./ns_child_exec-p ./simple_init -v

               init: my PID is 1

       init$ ./orphan

               init: created child 2

       Parent (PID=2) created child with PID 3

       Parent (PID=2; PPID=1) terminating

               init: SIGCHLD handler: PID 2terminated

       init$                   #simple_init promptinterleaved with output from child

       Child (PID=3) now an orphan (parent PID=1)

       Child (PID=3) terminating

               init: SIGCHLD handler: PID 3terminated

    上面的输出中,以init:开始的信息由simple_init在启动verbose选项时打印出的。其它所有的信息(除了init$提示符)由orphan程序打印。从输出来看,子进程(PID是3)在父进程(PID是2)退出时变成孤儿进程。变成孤儿进程的子进程(PID是3)被命名空间的init进程(PID是1)收留。

    信号和init进程

    传统的init进程对信号的处理有些特殊。能够发送给init进程的信号是那些已经有信号处理函数的信号。其它所有信号将被忽略。这阻止了init进程被任何用户意外的kill掉,init进程的存在对于系统的稳定是至关重要的。

    PID命名空间使命名空间内的init进程实现了和传统init类似的一些行为。命名空间内的其它进程(即使特权进程)仅能够发送init进程建立了处理函数的信号。这防止了命名空间内的其它进程不经意地杀死了命名空间中具有特殊作用的init进程。然而,如同传统的init进程一样,在通常的环境中内核仍然能够为PID命名空间内的init进程产生信号(例如,硬件中断,终端产生的SIGTOU等信号,定时器超时)。

    PID命名空间内的祖先进程仍然能够发送信号给子PID命名空间内的Init进程。同样的,只有init设置了对应处理函数的信号才可以被发送给它,除了SIGKILL和SIGSTOP这两个特例。当一个位于祖先PID命名空间的进程发送上述两个特殊信号给init进程时,它们被强行发送。SIGSTOP停止init进程;SIGKILL终结init进程。由于init进程对于PID命名空间如此重要,以至于如果init进程被SIGKILL信号终结掉,内核将对该PID命名空间内的其它所有进程发送SIGKILL信号来终结所有进程。

    通常,PID命名空间在init进程终结时将被摧毁,然而,特例是:只要该命名空间内对应的/proc/PID/ns/pid文件被打开或者绑定挂载点,命名空间将不会被摧毁。然而,不太可能使用setns()和fork()在新的命名空间中创建进程:在fork()调用时会检测到缺少init进程,这将会返回ENOMEM错误码(就是PID不能够被分配)。换句话说,PID命名空间仍然存在,但是变得不可用。

    挂载一个procfs文件系统

    在先前这个系列的文章中,PID命名空间的/proc文件系统procfs被挂载在不同的挂载点(而不是在/proc挂载点),这使得我们可以使用shell命令行载对应的/proc/PID目录下查看每一个新的PID命名空间,同时也可以使用ps命令查看在rootPID命名空间可以看见的进程。

    然而像ps之类内容依赖于挂载在/proc目录下的procfs文件以获取它们需要的信息。因此,如果我们想让ps在命名空间中也运行正确,我们需要为命名空间挂载一个procfs。由于simple_init程序允许我们执行shell命令行,我们可以再命令行下使用如下mount命令:

       # ./ns_child_exec -p -m ./simple_init

       init$ mount -t proc proc /proc

       init$ ps a

         PID TTY      STAT  TIME COMMAND

           1 pts/8    S     0:00 ./simple_init

           3 pts/8    R+    0:00 ps a

    ps命令列出了通过/proc命令能够存取的所有进程。在这种情况下,我们仅看见两个进程,表明命名空间只有两个进程在运行。

    当运行上面的ns_child_exec命令,我们使用了了-m参数,该参数用于将创建的子进程放在一个独立的mount命名空间。这使得mount命令并不会影响命名空间之外的进程看到的/proc挂载点。

    unshare()和setns()

    自从3.8版本,上面两个系统调用能够被PID命名空间使用,但是有些特殊的地方。

    指定CLONE_NEWPID标志使用unshare()创建新的PID命名空间,但是并没有将unshare的调用者放入命名空间中。而调用者创建的任何子进程将被放在新的命名空间中;第一个子进程将是该命名空间的init进程。

    setns()系统调用现在支持PID命名空间:

       setns(fd, 0);   /* Second argument can be CLONE_NEWPIDtoforce a

                          check that 'fd' refersto a PID namespace */

    fd参数是一个指明PID命名空间的文件描述符,该描述符是PID命名空间调用者的后裔;通过打开目标命名空间中一个进程的/proc/PID/ns/pid文件来获得该文件描述符。unshare()、setns()不将调用者放在创建的命名空间中,而调用者后续创建的子进程将被放入命名空间中。

    我们可以使用一个增强型ns_exec.c版本来验证setns()的一些特性。

    /* ns_exec.c

     

      Copyright 2013, Michael Kerrisk

      Licensed under GNU General Public License v2 or later

     

      Join a namespace and execute a command in the namespace

    */

    #define _GNU_SOURCE

    #include <fcntl.h>

    #include <sched.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

     

    /* A simple error-handling function: printan error message based

      on the value in 'errno' and terminate the calling process */

     

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \

                            } while (0)

     

    int

    main(int argc, char *argv[])

    {

       int fd;

     

       if (argc < 3) {

           fprintf(stderr, "%s /proc/PID/ns/FILE cmd [arg...]\n",argv[0]);

           exit(EXIT_FAILURE);

       }

     

       fd = open(argv[1], O_RDONLY);   /*Get descriptor for namespace */

       if (fd == -1)

           errExit("open");

     

       if (setns(fd, 0) == -1)         /*Join that namespace */

           errExit("setns");

     

       execvp(argv[2], &argv[2]);     /* Execute a command in namespace */

       errExit("execvp");

    }

    新的ns_run.c程序如下:

    /* ns_run.c

     

      Copyright 2013, Michael Kerrisk

      Licensed under GNU General Public License v2 or later

     

      Join one or more namespaces using setns() and execute a command in

      those namespaces, possibly inside a child process.

     

      This program is similar in concept to nsenter(1), but has a

      different command-line interface.

    */

    #define _GNU_SOURCE

    #include <fcntl.h>

    #include <sched.h>

    #include <unistd.h>

    #include <stdlib.h>

    #include <stdio.h>

    #include <sys/wait.h>

     

    /* A simple error-handling function: printan error message based

      on the value in 'errno' and terminate the calling process */

     

    #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \

                            } while (0)

     

    static void

    usage(char *pname)

    {

       fprintf(stderr, "Usage: %s [-f] [-n /proc/PID/ns/FILE] cmd[arg...]\n",

               pname);

       fprintf(stderr, "\t-f    Execute command in child process\n");

       fprintf(stderr, "\t-n    Join specified namespace\n");

     

       exit(EXIT_FAILURE);

    }

     

    int

    main(int argc, char *argv[])

    {

       int fd, opt, do_fork;

       pid_t pid;

     

       /* Parse command-line options. The initial '+' character in

          the final getopt() argument prevents GNU-style permutation

          of command-line options. That's useful, since sometimes

          the 'command' to be executed by this program itself

          has command-line options. We don't want getopt() to treat

          those as options to this program. */

     

       do_fork = 0;

       while ((opt = getopt(argc, argv, "+fn:")) != -1) {

           switch (opt) {

     

           case 'n':       /* Join anamespace */

               fd = open(optarg, O_RDONLY); /* Get descriptor for namespace */

               if (fd == -1)

                    errExit("open");

     

               if (setns(fd, 0) == -1)      /* Join that namespace */

                    errExit("setns");

               break;

     

           case 'f':

               do_fork = 1;

               break;

     

           default:

               usage(argv[0]);

           }

       }

     

       if (argc <= optind)

           usage(argv[0]);

     

       /* If the "-f" option was specified, execute the suppliedcommand

          in a child process. This is mainly useful when working with PID

          namespaces, since setns() to a PID namespace only places

          (subsequently created) child processes in the names, and

          does not affect the PID namespace membership of the caller. */

     

       if (do_fork) {

           pid = fork();

           if (pid == -1)

               errExit("fork");

     

           if (pid != 0) {                 /*Parent */

               if (waitpid(-1, NULL, 0) == -1)    /* Wait for child */

                    errExit("waitpid");

               exit(EXIT_SUCCESS);

           }

     

           /* Child falls through to code below */

       }

     

       execvp(argv[optind], &argv[optind]);

        errExit("execvp");

    }

    ns_run [-f][-n/proc/PID/ns/FILE]... command [arguments]

    该程序使用setns()关联由/proc/PID/ns(-n选项指定)文件指定的命名空间。然后其执行给定的command命令并将参数arguments传递给它,如果指定了-f参数,它使用fork()创建的子进程执行上面的命令。

    考虑一个场景,在一个终端窗口,我们在一个新的PID命名空间中启动simple_init程序,启动verbose选项记录日志以方便我们知道何时子进程被收留:

       # ./ns_child_exec -p ./simple_init -v

               init: my PID is 1

       init$ 

    然后我们切到第二个终端,这里我们使用ns_run程序来执行我们的orphan程序。这个效果就是在simple_init管理的PID命名空间中创建两个子进程。

       # ps -C sleep -C simple_init

         PID TTY          TIME CMD

        9147 pts/8    00:00:00 simple_init

        # ./ns_run -f -n /proc/9147/ns/pid./orphan

        Parent (PID=2) created child with PID 3

        Parent (PID=2; PPID=0) terminating

        #

        Child (PID=3) now an orphan (parent PID=1)

        Child (PID=3) terminating

    来看“父”进程(PID=2)的输出,可以知道其父进程ID是0。这揭示了和启动orphan进程位于不同命名空间的事实。

    下图揭示了再Oprah的"父"进程终结之前的进程关系。箭头表明了进程间的父子关系。


    回到创建simple_init 程序的窗口,可以看到如下的输出:

      init: SIGCHLD handler: PID 3 terminated

    由orphan创建的子进程(PID等于3)由simple_init 收留,但是其“父”进程(PID=2)并不是这样,这是因为“父”进程由在另一个命名空间中它自己的父进程ns_run收留。下图这展示了orphan“父进程”结束但是“子”进程还未结束时的进程关系:


    值得强调的是setns()和unshare()处理PID命名空间的方式不同。对其它类型的命名空间,这些系统调用确实改变了调用这些函数的进程的命名空间。不改变这些调用者的PID命名空间的原因是:成为其它PID命名空间的进程将导致进程自己的PID改变,这是由于getpid()返回的是进程所在PID命名空间的进程PID。许多用户空间的程序和库依赖一个进程的PID是确定的这一假设;如果进程的PID改变可能导致这些程序失败。换句话说,进程的PID命名空间在进程创建时确定,且后续不会改变。

    总结

    本文,我们查看了PID命名空间init进程,展示了为了让ps之类的工具可用而如何为一个PID命名空间挂载一个procfs。此外,还查看了unshare和setns对PID命名空间特别的地方。

    网络 namespaces

    网络命名空间,正如从名字可以联想到的,网络命名空间分割了网络资源-设备、地址、端口、路由、防火墙规则等。网络命名空间出现在2.6.24内核版本,那几乎是五年前的事了;在一年前开发好了从那时起也被很多开发这忽略了。

    网络命名空间的基本管理

    正如其它命名空间,网络命名空间通过传递CLONE_NEWNET标志给clone()系统调用来创建。通过命令行工作ip很容易对网络命名空间进行相关的操作,例如:

       # ip netns add netns1

    上面的命令创建一个叫netns1的网络命名空间。当ip工具创建一个网络命名空间,其将在/var/run/netns目录下创建一个绑定的mount点;这样即使命名空间中没有进程运行命名空间仍然存在,并且也简化的命名空间自身的操作。由于网络命名空间在可用前需要进行相当数量的配置工作,系统管理这真该感谢该特性。

    “ip netns exec”命令能被用于在一个命名空间中执行网络管理命令:

       # ip netns exec netns1 ip link list

       1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT

            link/loopback00:00:00:00:00:00 brd 00:00:00:00:00:00

    这个命令列出了命名空间可以看见的接口。一个网络命名空间的移除使用如下命令:

       # ip netns delete netns1

    该命令删除命名空间对应的挂载点。然而,只要命名空间内有进程在运行命名空间自身将持续存在。

    网络命名空间配置

    新的网络命名空间将仅有一个回环设备。除了回环设备,每一个网络设备(物理和虚拟接口,桥等)仅仅只能存在于一个网络命名空间。此外,除了root不能够将物理设备(没有连接到真实硬件)分配到命名空间。相反,虚拟网络设备(虚拟以太网或者veth)能够被创建并分配到命名空间中。这些虚拟设备使得命名空间内的进程能够通过网络进行通信;如何通信则依赖于配置、路由等。

    当第一次被创建时,在命名空间中的lo回环设备是down的状态,所以即使一个ping命令也会失败:

       # ip netns exec netns1 ping 127.0.0.1

       connect: Network is unreachable

    启动该接口将允许ping回环设备的地址:

       # ip netns exec netns1 ip link set dev lo up

       # ip netns exec netns1 ping 127.0.0.1

       PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.

       64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.051 ms

       ...

    但这仍然不能是netns1命名空间和root命名空间通信。为了实现它们间通信的目的,虚拟网络设备需要被创建和配置:

       # ip link add veth0 type veth peer name veth1

       # ip link set veth1 netns netns1

    第一个命令设置了连接的一对虚拟网络设备。发送给veth0数据包将会被veth1收到,反之亦然。第二个命令将veth1的网络命名空间设为netns1。

       # ip netns exec netns1 ifconfig veth1 10.1.1.1/24 up

       # ifconfig veth0 10.1.1.2/24 up

    然后,这两个命令设置两个设备的IP地址。

       # ping 10.1.1.1

       PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.

       64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.087 ms

       ...

       

        #ip netns exec netns1 ping 10.1.1.2

       PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.

       64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.054 ms

       ...

    两个方向的通信现在变得可行了,上述的ping命令已经验证了。

    正如先前提到的,命名空间并不共享路由表或者防火墙规则,如在netns1中运行route和iptables命令。

        # ipnetns exec netns1 route

       # ip netns exec netns1 iptables -L

    第一个命令显示了数据包路由到10.1.1子网的路由,第二个命令则表明没有防火墙配置。所有的那些意味着从netns1送到网络上的数据包过大将会产生“网络不可达”错误。如果需要还有几个方法将命名空间连接上网。可以再在root命名空间和netns1命名空间为veth设备创建网桥。另外,IP转发和网络地址转换(NAT)可以再root命名空间中被配置。这两种方法将允许从网络接收数据包,也运行netns1发送数据包到网络。

    被分配到命名空间的非root进程(通过clone、unshare、setns)仅能访问命名空间内已经设置的网络设备。当然,root能够添加并配置新的网络设备。使用 ip netns 前缀的命令,有两种方法来寻址一个命名空间:通过名字,如netns1,或者通过进程ID。因为init通常存在于root命名空间,可以使用如下的命令:

       # ip link set vethX netns 1

    这将在root命名空间中创建一个新的veth设备,并且该设备可以为任何其他命名空间root所用。在并不希望root拥有操作某个命名空间网络设备的权限时,PID和mount命名空间可以用来完成此目的。

    网络命名空间使用

    正如我们看到的,一个命名空间的网络可以从不能进行网络通信到使用所有的的网络带宽进行通信。这导致了网络命名空间的许多不同的使用案例。

    通过关闭一个命名空间的网络,管理员可以确保命名空间中运行的进程不能和命名空间之外的程序进行通信。即使一个进程牺牲一些安全性,其也不能够执行一些像关联僵尸网络或者发送垃圾邮件之类的动作。

    即使处理网络流量的进程(web服务器工作者进程或者web浏览渲染进程等)可以被放置于一个特定的命名空间。一旦和远端的连接建立,连接的文件描述符可以被由clone()创建的新的网络命名空间内的子进程处理。子进程会继承父进程的文件描述符,这就可以存取连接的描述符了。另外一个可能的情况是父进程通过UNIX套接字将连接的文件描述符发送到一个给定的命名空间。不论哪种方式,命名空间中缺少合适的网络设备将使子进程或者工作进程的其它网络连接失败。

    命名空间同样可以被用来在所有的网络命名空间中测试复杂的网络配置。在锁定的,限定防火墙的命名空间中运行敏感的服务同样也是一个使用案例。很明显,container依赖网络命名空间为每一个container提供各自的网络视图。

    命名空间提供了一个分割系统资源以及将进程组的进程与它们的资源隔离的方法。网络命名空间也是这样,但是由于网络是安全敏感的一个领域,提供各种类型的隔离具有很到的价值。当然,使用多种类型的命名空间可以实现超越安全和其它应用需要的隔离。

    原文网址

    http://lwn.net/Articles/531114/

    展开全文
  • 简介 Yii 2.0最显著的特征之一就是引入了命名空间,因此对于自定义类的引入方式也同...1.在应用中的任意位置可以使用该类名或命名空间,而不用显式调用require()/include()。 2.利用Yii的autoloader,仅在类被
  • 使用命名空间、头文件和实现文件

    千次阅读 2016-03-03 12:19:14
    //至今没有解决的一个问题, 如a.cpp中有一个命名空间ming, 现在b.cpp想要使用这个命名空间, 或者使用该空间中的某一个类/普通函数/变量, 至今不知道怎么解决 //下面的方法是一种通用的正确方法 //自定义命名空间...
  • C++命名空间namespace的使用规范

    千次阅读 2016-09-09 20:30:04
    1.命名空间 namespacens { ………//将内容添加到命名空间中 } using namespace ns; ……..//声明命名空间后,表示ns命名空间里面的内容对于这条声明后的代码是可见的。 但是如果声明了两个命名空间ns1,ns2。且ns...
  • soap的命名空间

    千次阅读 2017-06-06 17:13:28
    如果命名空间 URI 未被定义,将使用 URL 子句中的域组件。服务器端的 SOAP 处理器使用此 URI 来了解请求的消息主体中各种实体的名称。CREATE PROCEDURE 和 CREATE FUNCTION 语句的 NAMESPACE 子句指定命名空间 URI。
  • 命名空间的定义与使用

    千次阅读 2016-11-13 20:04:28
    1. 命名空间的定义:由关键字 namespace后边接着命名空间名字,之后接一对花括弧括住的一块声明和定义; [html] view plain copy print? //17.2.A.h 头文件定义命名空间 primer_17_2 namespace...
  • std命名空间

    千次阅读 2019-05-15 19:05:00
    命名空间使用目的是为了将逻辑相关的标示符限定在一起,组成相应的命名空间,可使整个系统更加模块化,最重要的是它可以防止命名冲突。就好比在两个函数或类中定义相同名字的对象一样,利用作用域标示符限定该对象...
  • C#源代码—演示抽象类与抽象属性的使用命名空间使用
  • ROS之命名空间

    千次阅读 多人点赞 2017-07-21 17:02:24
    已经学ROS快两个月了,一开始对ROS 命名空间,参数,参数服务器,重映射没认真看,后来发现很重要,它是学习ROS代码的基础。我们都知道ros以topic通信,但是只靠topic通信是远远不够的,于是使用客服端服务器、...
  • Namespace(命名空间)的使用

    千次阅读 2004-10-30 16:19:00
    作者:飞刀 关于Namespace(命名空间)的使用常用,这是在引用M$为我们提供的Namespace,这和ASP不同的,我们贏SP.net必须先引用与我们操作有关的Namespace后才能使用相应的功能。其实说白了,一个Namespace; 就是一个...
  • C++ std命名空间

    万次阅读 多人点赞 2012-02-04 21:44:42
    命名空间使用目的是为了将逻辑相关的标示符限定在一起,组成相应的命名空间,可使整个系统更加模块化,最重要的是它可以防止命名冲突。就好比在两个函数或类中定义相同名字的对象一样,利用作用域标示符限定该对象...
  • #include&lt;iostream&gt; using namespace :: std;... int M = 200; int inf = 10; } namespace Two{ int x; int inf = 100; } int main() { One :: inf *= 1; cout &lt;&...
  • Vuex 命名空间 namespaced 介绍

    万次阅读 多人点赞 2019-04-18 21:04:40
    Vuex由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 因此,Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、m....
  • K8S命名空间

    千次阅读 2020-05-25 22:16:53
    1、简介Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。这些虚拟集群被称为命名空间。2、如何使用命名空间命名空间适用于存在很多跨多个团队或项目的用户的场景。对于只有...
  • c++ 命名空间

    千次阅读 2011-09-19 22:04:13
    使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。下面是一个简单的命名空间的例子: namespace MyNames { int iVal1 = 100; int iVal2 = 200; }   这里有两个整型变量iVa
  • linux命名空间(namespace)学习(一)

    千次阅读 2018-12-05 20:42:53
    关于linux命名空间网络上有很多是关于docker 的,也有关于linux的专门的linux的namespace介绍的,没有专门介绍Linux命名空间的应用的。所以我想先介绍一下linux命名空间的应用,然后再介绍linux内核对于命名空间的...
  • 原来std命名空间使用了max变量啊

    千次阅读 2009-08-22 14:16:00
    今天在CSDN看了一个贴子,为什么...突然发现,哦……原来在std命名空间中也用了max变量了啊!程序如下:#include "iostream"using namespace std;const int max = 10; int main(){ int a[max]={0}; for (int i = 0;
  • 关于xml文件转为java实体类,如果不带命名空间 “<m:”,用jaxb的unmarshaller方法,还有注解@XmlRootElement 等方法可以转成功,但是xml包含了"<m:"前缀(命名空间)就有问题。网上找了好久也没能解决。还请高手帮我...
  • C++std命名空间详解

    千次阅读 2017-07-19 10:16:48
    命名空间使用目的是为了将逻辑相关的标示符限定在一起,组成相应的命名空间,可使整个系统更加模块化,最重要的是它可以防止命名冲突。就好比在两个函数或类中定义相同名字的对象一样,利用作用域标示
  • C++17之嵌套的命名空间

    千次阅读 2019-09-01 11:39:41
    c++标准委员会于2003年首次提出,最终接受嵌套名称空间的定义如下: ...注意,嵌套不支持内联命名空间。这仅仅是因为内联应用于最后一个名称空间还是应用于所有名称空间并不明显(两者都同样有用)。 这里简单介绍下...
  • Python的命名空间解析

    千次阅读 2017-11-26 11:54:47
    什么是命名空间 命名空间有哪些 变量查找原则 分析一个UnboundLocalError的例子
  • 文章目录查看所有的Pod查看所有的deployment查看所有namespace对应的Pod...在默认的命名空间namespace中查看所有的Pod显示没有找到,加入-A参数表示查看所有命名空间(namesqpace)上的Pod。 查看所有的Pod [root@...
  • 这个命名空间绝对没有什么特别之处,只是Kubernetes工具是开箱即用的设置使用这个命名空间,而且你无法删除它。 它很适合入门和小型生产系统,我建议不要在大型生产系统中使用它。 这是因为团队很容易在没有意识到的...
  • C#命名空间和注释

    千次阅读 2014-07-29 10:27:15
    1、 命名空间使用? 2、 代码注释?   基础的东西咱就略微讲讲,大家都懂得!!   第一个问题: 命名空间使用? C#程序是利用命名空间组织起来的。C#的各命名空间就好像是一个存储了不同类型的仓库,而...
  • C++编程之命名空间、const常量

    千次阅读 2020-03-01 23:04:16
    1、C++命名空间 解决命名冲突,保证数据一致性 可以放变量、函数、结构体、类等 必须声明在全局的作用域下 命名空间可以嵌套命名空间 命名空间是开放的,随时可以给其中添加新成员 namespace B { int m_A = 100; } ....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 197,349
精华内容 78,939
关键字:

命名空间m使用方法