如果大家有过在容器中执行ps命令的经验|docker核心之一pid命名空间的工作原理

如果大家有过在容器中执行ps命令的经验 , 都会知道在容器中的进程的pid一般是比较小的 。 例如下面我的这个例子 。 #ps-efPIDUSERTIMECOMMAND1root0:00./demo-ie13root0:00/bin/bash21root0:00ps-ef
不知道大家是否和我一样好奇容器进程中的pid是如何申请出来的?和宿主机中申请pid有什么不同?内核又是如何显示容器中的进程号的?
前面我们在《Linux进程是如何创建出来的?》中介绍了进程的创建过程 。 事实上进程的pid命名空间、pid也都是在这个过程中申请的 。 我今天就来带大家深入理解一下docker核心之一pid命名空间的工作原理 。 一、Linux的默认pid命名空间
前面的文章《Linux进程是如何创建出来的?》中我们提到了进程的命名空间成员nsproxy 。 //file:include/linux/sched.hstructtask_struct{/*namespaces*/structnsproxy*nsproxy;}
Linux在启动的时候会有一套默认的命名空间 , 定义在kernel/nsproxy.c文件下 。 //file:kernel/nsproxy.cstructnsproxyinit_nsproxy={.count=ATOMIC_INIT(1),.uts_ns=&init_uts_ns,.ipc_ns=&init_ipc_ns,.mnt_ns=NULL,.pid_ns=&init_pid_ns,.net_ns=&init_net,};
如果大家有过在容器中执行ps命令的经验|docker核心之一pid命名空间的工作原理】其中默认的pid命名空间是init_pid_ns , 它定义在kernel/pid.c下 。 //file:kernel/pid.cstructpid_namespaceinit_pid_ns={.kref={.refcount=ATOMIC_INIT(2),},.pidmap={[0PIDMAP_ENTRIES-1]={ATOMIC_INIT(BITS_PER_PAGE),NULL}},.last_pid=0,.level=0,.child_reaper=&init_task,.user_ns=&init_user_ns,.proc_inum=PROC_PID_INIT_INO,};
在pid命名空间里我觉得最需要关注的是两个字段 。 一个是level表示当前pid命名空间的层级 。 另一个是pidmap , 这是一个bitmap , 一个bit如果为1 , 就表示当前序号的pid已经分配出去了 。
另外默认命名空间的level初始化是0 。 这是一个表示树的层次结构的节点 。 如果有多个命名空间创建出来 , 它们之间会组成一棵树 。 level表示树在第几层 。 根节点的level是0 。
如果大家有过在容器中执行ps命令的经验|docker核心之一pid命名空间的工作原理
文章图片
INIT_TASK0号进程 , 也叫idle进程 , 它固定使用这个默认的init_nsproxy 。 //file:include/linux/init_task.h#defineINIT_TASK(tsk){.state=0,.stack=&init_thread_info,.usage=ATOMIC_INIT(2),.flags=PF_KTHREAD,.prio=MAX_PRIO-20,.static_prio=MAX_PRIO-20,.normal_prio=MAX_PRIO-20,.nsproxy=&init_nsproxy,}
所有进程都是一个派生一个的方式生成出来的 。 如果不指定命名空间 , 所有进程使用的都是使用缺省的命名空间 。
如果大家有过在容器中执行ps命令的经验|docker核心之一pid命名空间的工作原理
文章图片
二、Linux新pid命名空间创建
在这里 , 我们假设我们创建进程时指定了CLONE_NEWPID要创建一个独立的pid命名空间出来(Docker容器就是这么干的) 。
在《Linux进程是如何创建出来的?》一文中我们已经了解了进程的创建过程 。 整个创建过程的核心是在于copy_process函数 。
在这个函数中会申请和拷贝进程的地址空间、打开文件列表、文件目录等关键信息 , 另外就是pid命名空间的创建也是在这里完成的 。 //file:kernel/fork.cstaticstructtask_struct*copy_process(){//2.1拷贝进程的命名空间nsproxyretval=copy_namespaces(clone_flags,p);//2.2申请pidpid=alloc_pid(p-nsproxy-pid_ns);//2.3记录pidp-pid=pid_nr(pid);p-tgid=p-pid;attach_pid(p,PIDTYPE_PID,pid);}2.1创建进程时构造新命名空间
在上面的copy_process代码中我们看到对copy_namespaces函数的调用 。 命名空间就是在这个函数中操作的 。 //file:kernel/nsproxy.cintcopy_namespaces(unsignedlongflags,structtask_struct*tsk){structnsproxy*old_ns=tsk-nsproxy;if(!(flags&(CLONE_NEWNS|CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWNET)))return0;new_ns=create_new_namespaces(flags,tsk,user_ns,tsk-fs);tsk-nsproxy=new_ns;}