【Docker 内核详解】namespace 资源隔离(三):PID namespace
namespace 资源隔离(三):PID namespace
- 1.PID namespace 中的 init 进程
- 2.信号与 init 进程
- 3.挂载 proc 文件系统
- 4.unshare() 和 setns()
PID namespace 隔离非常实用,它对进程 PID 重新标号,即两个不同 namespace 下的进程可以有相同的 PID。每个 PID namespace 都有自己的计数程序。内核为所有的 PID namespace 维护了一个树状结构,最顶层的是系统初始时创建的,被称为 root namespace。它创建的新 PID namespace 被称为 child namespace(树的子节点),而原先的 PID namespace 就是新创建的 PID namespace 的 parent namespace(树的父节点)。通过这种方式,不同的 PID namespace 会形成一个层级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点却不能看到父节点 PID namespace 中的任何内容,由此产生如下结论。
- 每个
PID namespace中的第一个进程 “PID 1”,都会像传统 Linux 中的init进程一样拥有特权,起特殊作用。 - 一个
namespace中的进程,不可能通过kill或ptrace影响父节点或者兄弟节点中的进程,因为其他节点的 PID 在这个namespace中没有任何意义。 - 如果你在新的
PID namespace中重新挂载/proc文件系统,会发现其下只显示同属一个PID namespace中的其他进程。 - 在
root namespace中可以看到所有的进程,并且递归包含所有子节点中的进程。
到这里,大家可能已经联想到一种在外部监控 Docker 中运行程序的方法了,就是监控 Docker daemon 所在的 PID namespace 下的所有进程及其子进程,再进行筛选即可。
下面通过运行代码来感受一下 PID namespace 的隔离效果。修改 前一篇博客 中提到的代码,加入 PID namespace 的标识位,并把程序命名为 pid.c。
// [...]
int child_pid = clone(child_main, child_stack + STACK_SIZE,CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
// [...]
编译运行可以看到如下结果。
root@local:~# gcc -Wall pid.c -o pid.o && ./pid.o
程序开始:
在子进程中!
root@NewNamespace:~# echo $$
1 <<--注意此处 shell 的 PID 变成了 1
root@NewNamespace:~# exit
exit
已退出
打印 $$ 可以看到 shell 的 PID,退出后如果再次执行可以看到效果如下。
root@local:~# echo $$
17542
已经回到了正常状态。有的读者可能在子进程的 shell 中执行了 ps aux 或 top 之类的命令,发现还是可以看到所有父进程的 PID,那是因为还没有对文件系统挂载点进行隔离,ps 或 top 之类的命令调用的是真实系统下的 /proc 文件内容,看到的自然是所有的进程。所以,与其他的 namespace 不同的是,为了实现一个稳定安全的容器,PID namespace 还需要进行一些额外的工作才能确保进程运行顺利,下面将逐一介绍。
1.PID namespace 中的 init 进程
在传统的 Unix 系统中,PID 为 1 1 1 的进程是 init,地位非常特殊。它作为所有进程的父进程,维护一张进程表,不断检查进程的状态,一旦有某个子进程因为父进程错误成为了 孤儿 进程,init 就会负责收养这个子进程并最终回收资源,结束进程。所以在要实现的容器中,启动的第一个进程也需要实现类似 init 的功能,维护所有后续启动进程的运行状态。
当系统中存在树状嵌套结构的 PID namespace 时,若某个子进程成为孤儿进程,收养该子进程的责任就交给了该子进程所属的 PID namespace 中的 init 进程。
至此,可能读者已经明白了内核设计的良苦用心。PID namespace 维护这样一个树状结构,有利于系统的资源监控与回收。因此,如果确实需要在一个 Docker 容器中运行多个进程,最先启动的命令进程应该是具有资源监控与回收等管理能力的,如 bash。
2.信号与 init 进程
内核还为 PID namespace 中的 init 进程赋予了其他特权 —— 信号屏蔽。如果 init 中没有编写处理某个信号的代码逻辑,那么与 init 在同一个 PID namespace 下的进程(即使有超级权限)发送给它的该信号都会被屏蔽。这个功能的主要作用是防止 init 进程被误杀。
那么,父节点 PID namespace 中的进程发送同样的信号给子节点中的 init 进程,这会被忽略吗?父节点中的进程发送的信号,如果不是 SIGKILL(销毁进程)或 SIGSTOP(暂停进程)也会被忽略。但如果发送 SIGKILL 或 SIGSTOP,子节点的 init 会强制执行(无法通过代码捕捉进行特殊处理),也即是说父节点中的进程有权终止子节点中的进程。
一旦 init 进程被销毁,同一 PID namespace 中的其他进程也随之接收到 SIGKILL 信号而被销毁。理论上,该 PID namespace 也不复存在了。但是如果 /proc/[pid]/ns/pid 处于被挂载或者打开状态,namespace 就会被保留下来。然而,保留下来的 namespace 无法通过 setns() 或者 fork() 创建进程,所以实际上并没有什么作用。
当一个容器内存在多个进程时,容器内的 init 进程可以对信号进行捕获,当 SIGTERM 或 SIGINT 等信号到来时,对其子进程做信息保存、资源回收等处理工作。在 Docker daemon 的源码中也可以看到类似的处理方式,当结束信号来临时,结束容器进程并回收相应资源。
3.挂载 proc 文件系统
前文提到,如果在新的 PID namespace 中使用 ps 命令查看,看到的还是所有的进程,因为与 PID 直接相关的 /proc 文件系统(procfs)没有挂载到一个与原 /proc 不同的位置。如果只想看到 PID namespace 本身应该看到的进程,需要重新挂载 /proc,命令如下。
root@NewNamespace:~# mount -t proc proc /proc
root@NewNamespace:~# ps a
PID TTY STAT TIME COMMAND
1 pts/1 S 0:00 /bin/bash
12 pts/1 R+ 0:00 ps a
可以看到实际的 PID namespace 就只有两个进程在运行。
此时并没有进行 mount namespace 的隔离,所以该操作实际上已经影响了 root namespace 的文件系统。当退出新建的 PID namespace 以后,再执行 ps a 时,就会发现出错,再次执行 mount -t proc proc /proc 可以修复错误。后面还会介绍通过 mount namespace 来隔离文件系统,当我们基于 mount namespace 实现了容器 proc 文件系统隔离后,我们就能在 Docker 容器中使用 ps 等命令看到与 PID namespace 对应的进程列表。
4.unshare() 和 setns()
前一篇博客 就提到了 unshare() 和 setns() 这两个 API,在 PID namespace 中使用时,也有一些特别之处需要注意。
unshare() 允许用户在原有进程中建立命名空间进行隔离。但创建了PID namespace后,原先 unshare() 调用者进程并不进入新的 PID namespace,接下来创建的子进程才会进入新的 namespace,这个子进程也就随之成为新 namespace 中的 init 进程。
类似地,调用 setns() 创建新 PID namespace 时,调用者进程也不进入新的 PID namespace,而是随后创建的子进程进入。
为什么创建其他 namespace 时 unshare() 和 setns() 会直接进入新的 namespace,而唯独 PID namespace 例外呢?因为调用 getpid() 函数得到的 PID 是根据调用者所在的 PID namespace 而决定返回哪个 PID,进入新的 PID namespace 会导致 PID 产生变化。而对用户态的程序和库函数来说,它们都认为进程的 PID 是一个常量,PID 的变化会引起这些进程崩溃。
换句话说,一旦程序进程创建以后,那么它的 PID namespace 的关系就确定下来了,进程不会变更它们对应的 PID namespace。在 Docker 中,docker exec 会使用 setns() 函数加入已经存在的命名空间,但是最终还是会调用 clone() 函数,原因就在于此。
相关文章:
【Docker 内核详解】namespace 资源隔离(三):PID namespace
namespace 资源隔离(三):PID namespace 1.PID namespace 中的 init 进程2.信号与 init 进程3.挂载 proc 文件系统4.unshare() 和 setns() PID namespace 隔离非常实用,它对进程 PID 重新标号,即两个不同 namespace 下的…...
1600*C. Game On Leaves(博弈游戏树)
Problem - 1363C - Codeforces 解析: 我们将目标结点 x 当作树的根,显然,到当 x 的度为 1 的时候,此时行动的人胜利。 我们假设现在的情况为,只剩余三个点,再选择任意一个点,则对方获胜。但是两…...
Apache Ant的安装
介绍 Apache Ant是一个Java库和一个 命令行工具,可以用来构建Java应用。Ant提供了许多内置的任务(tasks),可以编译、组装、测试、运行Java应用。Ant也可以构建非Java应用,例如C、C应用。 Ant非常灵活,没有…...
考研:数学二例题--∞−∞和0⋅∞型极限
前言 本文只是例题,建议先参考具体如何做这类型例题。请到主文章中参考:https://blog.csdn.net/grd_java/article/details/132246630 ∞ − ∞ 和 0 ⋅ ∞ \infin - \infin 和 0\infin ∞−∞和0⋅∞ 例题 例1: lim x → ∞ x 2 x 2 −…...
C++算法:图中的最短环
题目 现有一个含 n 个顶点的 双向 图,每个顶点按从 0 到 n - 1 标记。图中的边由二维整数数组 edges 表示,其中 edges[i] [ui, vi] 表示顶点 ui 和 vi 之间存在一条边。每对顶点最多通过一条边连接,并且不存在与自身相连的顶点。 返回图中 …...
C++学习——类其实也是一种作用域
以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。 其实也是一种作用域,每个类都会定义它自己的作用域。在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象…...
Seata入门系列【4】undo_log、global_table、branch_table、lock_table字段及作用详解
1 客户端 1.1 undo_log 在AT模式中,需要在参与全局事务的数据库中,添加一个undo_log表,建表语句如下: SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for undo_log -- --…...
虚幻引擎:数据表格的C++常用API
1.将数据表格中的所有数据存到一个数组中 //参数1 // 错误提示 //参数2 // 存储的数组 TArray<FKeyInfoHeader*> array; KeyInfoDT->GetAllRows<FKeyInfoHeader>(TEXT("错误"),array); 2.获取表格中所有的行名称 TArray<FName>array; …...
Java日期格式化(DateFormat类和SimpleDateFormat类)
格式化日期表示将日期/时间格式转换为预先定义的日期/时间格式。例如将日期“Fri May 18 15:46:24 CST2016” 格式转换为 “2016-5-18 15:46:24 星期五”的格式。 在 java 中,可以使用 DateFormat 类和 SimpleDateFormat 类来格式化日期,下面详细介绍这两…...
centos 7 lamp owncloud
OwnCloud是一款开源的云存储软件,基于PHP的自建网盘。基本上是私人使用,没有用户注册功能,但是有用户添加功能,你可以无限制地添加用户,OwnCloud支持多个平台(windows,MAC,Android&a…...
屏幕亮度调节保护您的眼睛
官方下载地址: 安果移动 视频演示:屏幕亮度调节-保护您的眼睛_哔哩哔哩_bilibili 嗨,亲爱的用户,你是否有过这样的体验:夜晚安静的时刻,想要在抖音上看看热门的舞蹈、在快手上发现生活的 趣味、或是在哔…...
CentOS Linux下CMake二进制文件安装并使用Visual Studio调试
cmake安装——二进制安装(很简单,推荐!!) 1)下载二进制包。首先就是官网下载二进制安装包(我们是64位系统,就下载对应的包),这里。 例如:在/home/DOWNLOAD目录下执行,即下载二进制…...
ASP.net相关目录,相关配置文件和.后缀名解释
App_Data:用于存储应用程序的数据文件,例如数据库文件或其他本地文件。 App_Start:包含应用程序的启动配置文件,例如路由配置、日志配置等。 Content:存放应用程序的静态资源文件,如 CSS、JavaScript、图…...
一键批量转换,轻松将TS视频转为MP4视频,实现更广泛的播放和分享!
在享受精彩视频内容的同时,有时我们可能会面临一个问题:某些视频格式可能不太适合我们的播放设备或分享平台。特别是TS格式的视频,在一些情况下可能无法直接播放或上传。但是不用担心,因为我们为您提供了一款强大的视频剪辑工具&a…...
【Redis】使用Java客户端操作Redis
目录 引入jedis依赖连接Redis命令get/setexists/delkeysexpire/ttltype 引入jedis依赖 连接Redis 命令 get/set exists/del keys expire/ttl type...
BSPHP 未授权访问 信息泄露
漏洞描述 BSPHP 存在未授权访问 泄露用户 IP 和 账户名信息 漏洞复现 访问url: 构造payload访问: /admin/index.php?madmin&clog&atable_json&jsonget&soso_ok1&tuser_login_log&page1&limit10&bsphptime16004073…...
Learning Sample Relationship for Exposure Correction 论文阅读笔记
这是中科大发表在CVPR2023的一篇论文,提出了一个module和一个损失项,能够提高现有exposure correction网络的性能。这已经是最近第三次看到这种论文了,前两篇分别是CVPR2022的ENC(和这篇文章是同一个一作作者)和CVPR20…...
Vue项目 -- 解决Eslint导致的console报错问题
在利用vue-cli3构建的项目中引入eslint进行语法检查时,使用console.log(‘xxx’)时,控制台抛出了Unexpected console statement (no-console) 异常, 例:一使用console就提示报错 解决办法是: 在 .eslintrc.js 文件中…...
uni-app 在已有的数据对象中动态添加更多的数据对象
原数据对象 flowData: {list: [], // 数据值column: 2, // 瀑布列数columnSpace: 2 // 瀑布列宽间距 } 动态添加后的数据对象 flowData: {list: [], // 数据值column: 2, // 瀑布列数columnSpace: 2, // 瀑布列宽间距column_1: [],column_2: [] } 动态添加更多的数据对象的…...
【LeetCode】17. 电话号码的字母组合
1 问题 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1: 输入:digits “23” 输出&…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
