【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” 输出&…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...