• 我的订阅
  • 科技

docker核心之一pid命名空间的工作原理

类别:科技 发布时间:2023-01-01 14:13:00 来源:浅语科技

如果大家有过在容器中执行ps命令的经验,都会知道在容器中的进程的pid一般是比较小的。例如下面我的这个例子。# ps -efPID USER TIME COMMAND 1 root 0:00 ./demo-ie 13 root 0:00 /bin/bash 21 root 0:00 ps -ef

不知道大家是否和我一样好奇容器进程中的pid是如何申请出来的?和宿主机中申请pid有什么不同?内核又是如何显示容器中的进程号的?

前面我们在《Linux进程是如何创建出来的?》中介绍了进程的创建过程。事实上进程的pid命名空间、pid也都是在这个过程中申请的。我今天就来带大家深入理解一下docker核心之一pid命名空间的工作原理。一、Linux的默认pid命名空间

前面的文章《Linux进程是如何创建出来的?》中我们提到了进程的命名空间成员nsproxy。//file:include/linux/sched.hstruct task_struct { /* namespaces */ struct nsproxy *nsproxy;}

Linux在启动的时候会有一套默认的命名空间,定义在kernel/nsproxy.c文件下。//file:kernel/nsproxy.cstruct nsproxy init_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,};

其中默认的pid命名空间是init_pid_ns,它定义在kernel/pid.c下。//file:kernel/pid.cstruct pid_namespace init_pid_ns = { .kref = { .refcount = ATOMIC_INIT(2), }, .pidmap = { [ 0 PIDMAP_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。

docker核心之一pid命名空间的工作原理

INIT_TASK0号进程,也叫idle进程,它固定使用这个默认的init_nsproxy。//file:include/linux/init_task.h#define INIT_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, \ }

所有进程都是一个派生一个的方式生成出来的。如果不指定命名空间,所有进程使用的都是使用缺省的命名空间。

docker核心之一pid命名空间的工作原理

二、Linux新pid命名空间创建

在这里,我们假设我们创建进程时指定了CLONE_NEWPID要创建一个独立的pid命名空间出来(Docker容器就是这么干的)。

在《Linux进程是如何创建出来的?》一文中我们已经了解了进程的创建过程。整个创建过程的核心是在于copy_process函数。

在这个函数中会申请和拷贝进程的地址空间、打开文件列表、文件目录等关键信息,另外就是 pid命名空间的创建也是在这里完成的。//file:kernel/fork.cstatic struct task_struct *copy_process(){ //2.1 拷贝进程的命名空间 nsproxy retval = copy_namespaces(clone_flags, p); //2.2 申请 pid pid = alloc_pid(p-nsproxy-pid_ns); //2.3 记录 pid p-pid = pid_nr(pid); p-tgid = p-pid; attach_pid(p, PIDTYPE_PID, pid); }2.1创建进程时构造新命名空间

在上面的copy_process代码中我们看到对copy_namespaces函数的调用。命名空间就是在这个函数中操作的。//file:kernel/nsproxy.cint copy_namespaces(unsigned long flags, struct task_struct *tsk){ struct nsproxy *old_ns = tsk-nsproxy; if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET))) return 0; new_ns = create_new_namespaces(flags, tsk, user_ns, tsk-fs); tsk-nsproxy = new_ns; }

如果在创建进程时候没有传入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.cstatic struct nsproxy *create_new_namespaces(unsigned long flags, struct task_struct *tsk, struct user_namespace *user_ns, struct fs_struct *new_fs){ //申请新的 nsproxy struct nsproxy *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.cstatic struct pid_namespace *create_pid_namespace(...){ struct pid_namespace *ns; //新 pid namespace level + 1 unsigned int level = 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); //设置新命名空间 level ns->level = level; //新命名空间和旧命名空间组成一棵树 ns->parent = get_pid_ns(parent_pid_ns); //初始化 pidmap set_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); return ns;}

在create_pid_namespace真正申请了新的pid命名空间,为它的pidmap申请了内存(在create_pid_cachep中申请的),也进行了初始化。

另外还有一点比较重要的是新命名空间和旧命名空间通过parent、level等字段组成了一棵树。其中parent指向了上一级命名空间,自己的level用来表示层次,设置成了上一级level+1。

其最终的效果就是新进程拥有了新的pidnamespace,并且这个新pidnamespace和父pidnamespace串联了起来,效果如下图。

docker核心之一pid命名空间的工作原理

如果pid有多层的话,会组成更直观的树形结构。2.2申请进程id

创建完命名空间后,在copy_process中接下来接着就是调用alloc_pid来分配pid。//file:kernel/fork.cstatic struct task_struct *copy_process(){ //2.1 拷贝进程的命名空间 nsproxy retval = copy_namespaces(clone_flags, p); //2.2 申请 pid pid = alloc_pid(p-nsproxy-pid_ns); }

注意传入的参数是p->nsproxy->pid_ns。前面进程创建了新的pidnamespace,这个时候该命名空间就是level为1的新pid_ns。我们继续来看alloc_pid具体pid的过程。//file:kernel/pid.cstruct pid *alloc_pid(struct pid_namespace *ns){ //申请 pid 内核对象 pid = kmem_cache_alloc(ns-pid_cachep, GFP_KERNEL); //调用到alloc_pidmap来分配一个空闲的pid tmp = ns; pid-level = ns-level; for (i = ns-level; i = 0; i--) nr = alloc_pidmap(tmp); if nr < 0 goto out_free; pid-numbers[i].nr = nr; pid-numbers[i].ns = tmp; tmp = tmp-parent; } return pid; }

在上面的代码中要注意两个细节。

我们平时说的pid在内核中并不是一个简单的整数类型,而是一个小结构体来表示的(structpid)。

申请pid并不是申请了一个,而是使用了一个for循环申请多个出来

之所以要申请多个,是因为对于容器里的进程来说,并不是在自己当前的命名空间申请就完事了,还要到其父命名空间中也申请一个。我们把for循环的工作工程用下图表示一下。

docker核心之一pid命名空间的工作原理

首先到当前层次的命名空间申请一个pid出来,然后顺着命名空间的父节点,每一层也都要申请一个,并都记录到pid->numbers数组中。

这里多说一下,如果pid申请失败的话,会报-ENOMEM错误,在用户层看起来就是“fork:无法分配内存”,实际是由pid不足引起的。这个问题我在《明明还有大量内存,为啥报错“无法分配内存”?》提到过。2.3设置整数格式pid

当申请并构造完pid后,将其设置在task_struct上,记录起来。//file:kernel/fork.cstatic struct task_struct *copy_process(){ //2.2 申请 pid pid = alloc_pid(p-nsproxy-pid_ns); //2.3 记录 pid p-pid = pid_nr(pid); p-tgid = p-pid; attach_pid(p, PIDTYPE_PID, pid); }

其中pid_nr是获取的根pid命名空间下的pid编号,参见pid_nr源码。//file:include/linux/pid.hstatic inline pid_t pid_nr(struct pid *pid){ pid_t nr = 0; if (pid) nr = pid-numbers[0].nr; return nr;}

然后再调用attach_pid是把申请到的pid结构挂到自己的pids[PIDTYPE_PID]链表里了。//file:kernel/pid.cvoid attach_pid(struct task_struct *task, enum pid_type type, struct pid *pid){ link = &task-pids[type]; link-pid = pid; hlist_add_head_rcu(&link-node, &pid-tasks[type]);}

task->pids是一组链表。三、容器进程pid查看

pid已经申请好了,那在容器中是如何查看当前层次的进程号的呢?比如我们在容器中看到的demo-ie进程的id就是1。# ps -efPID USER TIME COMMAND 1 root 0:00 ./demo-ie ...

内核提供了个函数用来查看进程在当前某个命名空间的命名号。//file:kernel/pid.cpid_t pid_vnr(struct pid *pid){ return pid_nr_ns(pid, task_active_pid_ns(current));}

其中在容器中查看进程pid使用的是pid_vnr,pid_vnr调用pid_nr_ns来查看进程在特定命名空间里的进程号。

函数pid_nr_ns接收连个参数

第一个参数是进程里记录的pid对象(保存有在各个层次申请到的pid号)

第二个参数是指定的pid命名空间(通过task_active_pid_ns(current)获取)。

当具备这两个参数后,就可以根据pid命名空间里记录的层次level取得容器进程的当前pid了//file:kernel/pid.cpid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns){ struct upid *upid; pid_t nr = 0; if pid && ns-level = pid-level { upid = &pid-numbers[ns-level]; if upid-ns == ns) nr = upid-nr; } return nr;}

在pid_nr_ns中通过判断level就把容器pid整数值查出来了。四、总结

最后,举个例子,假如有一个进程在level0级别的pid命名空间里申请到的进程号是1256,在level1容器pid命名空间里申请到的进程号是5。那么这个进程以及其pid在内存中的形式是下图这个样子的。

docker核心之一pid命名空间的工作原理

那么容器在查看进程的pid号的时候,传入容器的pid命名空间,就可以将该进程在容器中的pid号5给打印出来了!!

以上内容为资讯信息快照,由td.fyun.cc爬虫进行采集并收录,本站未对信息做任何修改,信息内容不代表本站立场。

快照生成时间:2023-01-01 15:45:14

本站信息快照查询为非营利公共服务,如有侵权请联系我们进行删除。

信息原文地址:

第一届智能融合产业论坛在成都召开
...展构想》为题,作专题报告。报告介绍了空间电推进基本原理和技术体制,从应用领域、典型场景、应用效益等维度阐述了空间电推进发展态势,总结了空间电推进技术在设计、制造、装配/测试、
2023-03-01 22:02:00
探索地下空间 解密未来密码
...地质、土木工程材料等学科基础课,以及混凝土结构基本原理、城市地下空间规划与设计、地下建筑结构、地下工程施工、地铁与轻轨工程等专业主干课。在特色实践环节,将开展地下结构工程综合
2022-12-18 14:12:00
...一项重要内容就是:以适当方式公示算法推荐服务的基本原理、目的意图、主要运行机制等。这是及时纠偏、防微杜渐之举,有助于净化网络风气,推动行业健康发展。诚然,算法有相当的专业门槛
2025-02-13 05:47:00
房顶为何装玻璃球?天然亮如白昼的神奇实现
...这个梦幻般景观的秘密。房顶为何装玻璃球:利用全反射原理实现室内自然采光全反射原理 全反射是光学中的一种现象,当光从介质中传播到另一种折射率较小的介质时,光线会尽量沿较小折射率
2023-12-04 11:40:00
...在任务准备过程中,我们深入研究了卫星分离过程、机理原理和判断依据,全新研发了星箭分离判断决策软件,显著提升了星箭分离判断智能化程度,并系统性完善了各类参数配置软件,提升了自动
2023-06-17 00:07:00
冷热冲击试验箱的基本工作原理和应用背景
...的大小时,我们首先需要了解冷热冲击试验箱的基本工作原理和应用背景。冷热冲击试验箱是一种用于测试材料、产品或其他物质在快速温度变化环境下的性能表现的专业设备。它能够在短时间内模
2024-06-22 11:35:00
全国爱耳日 | 耳机原理大揭秘:主动降噪和被动降噪如何保护你的听力?
...的?从几十元的入门款到上万元的旗舰产品,耳机的工作原理各有不同。本文将带你探索耳机的“黑科技”,揭开不同耳机背后的声学秘密。首先,我们需要了解声音是如何在空气中传播的。声音是
2025-03-04 06:08:00
微美全息(NASDAQ:WIMI)开发创新的多波长全息空间投影技术,引领全息行业新潮流
...(NASDAQ:WIMI)多波长全息空间投影技术是一种结合了全息原理和多波长光源的创新技术。通过光学系统设计和高精度的硬件设备,该技术能够在三维空间中实现真实、逼真的图像投影
2023-10-25 01:00:00
Dear Reality 发布新品 EXOVERB,解锁立体声制作空间感知新纬度
...供多达 50 种逼真的声学场景。EXOVERB 融合了空间听觉基本原理,能够对空间距离感知进行精准控制,从而清理立体混音
2023-01-31 11:00:00
更多关于科技的资讯:
深入实施“人工智能+”行动丨浪潮智能终端“超高清+AI”解锁场景新玩法
漫步浩瀚太空,秒回侏罗纪时代,与科幻机甲同框……济南方特东方神画“九州神韵”的XR拍摄专区,小学生李安安兴奋地“穿越”在异时空中
2025-10-11 10:21:00
近日,据大象新闻报道,疾控卫监部门在一次突击检查中发现,某美容院将普通的高频电灼仪包装为“黄金微针”项目进行推广。这类看似创新的营销话术
2025-10-11 12:36:00
中新经纬10月11日电 (李自曼)近期,多家险企推出1年期的短期重疾险产品。同样保额下,传统重疾险保费动辄千元甚至上万元
2025-10-11 13:55:00
滨州移动织就“数字救援网”,赋能红十字演练通信零死角
鲁网10月11日讯近日,中国红十字应急救援北部协作区综合救援演练在山东省滨州市惠民县黄河三角洲应急消防实训基地拉开帷幕
2025-10-11 14:44:00
看点十足!2025中国移动全球合作伙伴大会变身“AI嘉年华”
当碳基生命的创造力与硅基生命的智慧力深度融合,将迸发出怎样的“AI+”可能?2025年10月10日—10月12日,中国移动全球合作伙伴大会在广州保利世贸博览馆盛大召开
2025-10-11 15:31:00
中国移动董事长杨杰:碳硅共生 合创AI+时代
10月10日至12日,2025中国移动全球合作伙伴大会在广州举行。10月11日上午,中国移动董事长杨杰出席大会主论坛,并发表题为《碳硅共生 合创AI+时代》的主旨演讲
2025-10-11 15:31:00
北京apm多重活动焕活国庆长假新体验,解锁假日新乐趣!
国庆假日期间,北京apm以“沉浸式假日体验”为核心,精心打造多元互动活动、热门IP联名快闪与专属会员福利矩阵,为王府井商圈注入鲜活动能与浓郁假日氛围
2025-10-11 15:41:00
山东乐陵:“老味道”飘出消费新滋味
位于乐陵市经济开发区的山东金鹏德盛斋扒鸡有限公司扒鸡生产车间内,老师傅王强不再像过去那样,紧盯着油炸锅、凭经验调节火力
2025-10-11 16:03:00
近日,张家口农商银行钟楼北小贷中心积极组织开展一场针对光大新天地商城内各商户的专项走访活动。此次活动旨在深入了解商城内各类商户的经营状况
2025-10-11 16:20:00
易生支付:智慧支付护航国庆文旅消费 赋能区域商业活力升级
2025年国庆长假期间,全国文旅消费市场持续呈现旺盛活力。易生支付凭借高并发交易处理、全渠道聚合支付、实时资金清算等核心技术能力
2025-10-11 17:08:00
齐鲁晚报·齐鲁壹点 连宁燕今年以来,威海市商务局围绕促消费开展了一系列卓有成效的工作。紧跟国家、省级、市级各级各类消费提振部署安排
2025-10-11 17:10:00
从精装到智装:百川装饰20年新范式在深圳启航
新范式·新百川·新未来:20周年发布品牌与战略升级近日,行业专家、合作伙伴及媒体代表逾300人齐聚深圳百川装饰集团20周年现场
2025-10-11 17:11:00
聊城首家无缝钢管制造企业汇通集团:年产85万吨钢管,远销全球30余国
鲁网10月11日讯(记者 泮晓阳)10月11日上午,聊城市政府新闻办公室召开“产业链上的山东好品牌”企业家系列现场记者见面会(六)聊城黑色金属加工
2025-10-11 17:44:00
聊城新一代信息技术发展:创新水平稳步提高,多项成果达国际领先
鲁网10月11日讯(记者 泮晓阳)10月11日上午,聊城市政府新闻办公室召开“产业链上的山东好品牌”企业家系列现场记者见面会(六)聊城黑色金属加工
2025-10-11 17:49:00
三木众合主导专利技术填补省内网络安全领域技术空白
鲁网10月11日讯(记者 泮晓阳)10月11日上午,聊城市政府新闻办公室召开“产业链上的山东好品牌”企业家系列现场记者见面会(六)聊城黑色金属加工
2025-10-11 17:49:00