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


如果在创建进程时候没有传入CLONE_NEWNS等几个flag , 还是会复用之前的默认命名空间 。 这几个flag的含义如下 。
CLONE_NEWPID:是否创建新的进程编号命名空间 , 以便与宿主机的进程PID进行隔离
CLONE_NEWNS:是否创建新的挂载点(文件系统)命名空间 , 以便隔离文件系统和挂载点
CLONE_NEWNET:是否创建新的网络命名空间 , 以便隔离网卡、IP、端口、路由表等网络资源
CLONE_NEWUTS:是否创建新的主机名与域名命名空间 , 以便在网络中独立标识自己
CLONE_NEWIPC:是否创建新的IPC命名空间 , 以便隔离信号量、消息队列和共享内存
CLONE_NEWUSER:用来隔离用户和用户组的 。
因为我们本节开头假设传入了CLONE_NEWPID标记 。 所以会进入到create_new_namespaces中来申请新的命名空间 。 //file:kernel/nsproxy.cstaticstructnsproxy*create_new_namespaces(unsignedlongflags,structtask_struct*tsk,structuser_namespace*user_ns,structfs_struct*new_fs){//申请新的nsproxystructnsproxy*new_nsp;new_nsp=create_nsproxy();//拷贝或创建PID命名空间new_nsp-pid_ns=copy_pid_ns(flags,user_ns,tsk-nsproxy-pid_ns);}
create_new_namespaces中会调用copy_pid_ns来完成实际的创建 , 真正的创建过程是在create_pid_namespace中完成的 。 //file:kernel/pid_namespace.cstaticstructpid_namespace*create_pid_namespace(...){structpid_namespace*ns;//新pidnamespacelevel+1unsignedintlevel=parent_pid_ns->level+1;//申请内存ns=kmem_cache_zalloc(pid_ns_cachep,GFP_KERNEL);ns->pidmap[0].page=kzalloc(PAGE_SIZE,GFP_KERNEL);ns->pid_cachep=create_pid_cachep(level+1);//设置新命名空间levelns->level=level;//新命名空间和旧命名空间组成一棵树ns->parent=get_pid_ns(parent_pid_ns);//初始化pidmapset_bit(0,ns->pidmap[0].page);atomic_set(&ns->pidmap[0].nr_free,BITS_PER_PAGE-1);for(i=1;i<PIDMAP_ENTRIES;i++)atomic_set(&ns->pidmap[i].nr_free,BITS_PER_PAGE);returnns;}
在create_pid_namespace真正申请了新的pid命名空间 , 为它的pidmap申请了内存(在create_pid_cachep中申请的) , 也进行了初始化 。
另外还有一点比较重要的是新命名空间和旧命名空间通过parent、level等字段组成了一棵树 。 其中parent指向了上一级命名空间 , 自己的level用来表示层次 , 设置成了上一级level+1 。
其最终的效果就是新进程拥有了新的pidnamespace , 并且这个新pidnamespace和父pidnamespace串联了起来 , 效果如下图 。
如果大家有过在容器中执行ps命令的经验|docker核心之一pid命名空间的工作原理
文章图片
如果pid有多层的话 , 会组成更直观的树形结构 。 2.2申请进程id
创建完命名空间后 , 在copy_process中接下来接着就是调用alloc_pid来分配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);}
注意传入的参数是p->nsproxy->pid_ns 。 前面进程创建了新的pidnamespace , 这个时候该命名空间就是level为1的新pid_ns 。 我们继续来看alloc_pid具体pid的过程 。 //file:kernel/pid.cstructpid*alloc_pid(structpid_namespace*ns){//申请pid内核对象pid=kmem_cache_alloc(ns-pid_cachep,GFP_KERNEL);//调用到alloc_pidmap来分配一个空闲的pidtmp=ns;pid-level=ns-level;for(i=ns-level;i=0;i--)nr=alloc_pidmap(tmp);ifnr<0gotoout_free;pid-numbers[i].nr=nr;pid-numbers[i].ns=tmp;tmp=tmp-parent;}returnpid;}
在上面的代码中要注意两个细节 。
我们平时说的pid在内核中并不是一个简单的整数类型 , 而是一个小结构体来表示的(structpid) 。
申请pid并不是申请了一个 , 而是使用了一个for循环申请多个出来
之所以要申请多个 , 是因为对于容器里的进程来说 , 并不是在自己当前的命名空间申请就完事了 , 还要到其父命名空间中也申请一个 。 我们把for循环的工作工程用下图表示一下 。