当前位置:首页 > 文章列表 > 文章 > linux > Linux PID namespace 中 init 进程怎样回收子进程

Linux PID namespace 中 init 进程怎样回收子进程

来源:17golang原创 2026-10-14 14:41:37 0浏览 收藏

Linux PID namespace 中 init 进程怎样回收子进程

在 PID namespace 里,最先创建的进程会拿到命名空间内的 PID 1。它不只是一个普通业务进程:当其他进程的父进程退出后,内核会把孤儿进程重新挂到合适的 init 或 subreaper 上;如果这个命名空间里的 init 进程不及时调用 wait 或 waitpid,退出的子进程就可能长期停留为僵尸进程。

实用结论是:把 namespace init 当作一个小型进程管理器设计。它至少要安装必要的信号处理逻辑,持续回收自己接管的子进程,并把“PID 1 退出会导致整个命名空间失效”当作生命周期约束,而不是普通服务重启细节。

PID namespace 中的 init 负责接管孤儿并回收已退出子进程;真正执行回收的是 wait 系列系统调用。PID 1 一旦退出,内核会终止该命名空间内的其他进程,不能再把它当作可热替换的普通父进程。

PID namespace 的 1 号进程到底特殊在哪里

新 PID namespace 的进程号从 1 开始。进程在自身命名空间和祖先命名空间中可能拥有不同的 PID,祖先命名空间可以观察子命名空间中的进程,而子命名空间不能反向看到祖先中的进程。因此,容器内看到的 PID 1 和宿主机看到的同一进程,通常不是同一个数字。

根 PID namespace、子 PID namespace、namespace init 和业务进程之间的静态关系
图1:PID namespace 层级与进程可见性的静态说明图,不是截图或运行证据。

“init”这个名字表示职责,而不要求进程必须是 systemd、BusyBox init 或某个固定实现。一个很小的 C、Go 或 shell 包装进程也可以成为命名空间内的 PID 1,但它需要主动承担信号转发、退出状态和子进程回收等工作。

孤儿进程为什么会交给 init

假设业务父进程先退出,而它创建的子进程仍在运行。这个子进程不能保留一个已经不存在的父进程,内核会把它重新父子关系挂到父进程所在 PID namespace 的 init 进程;如果更近的祖先设置了 PR_SET_CHILD_SUBREAPER,则可能由这个 subreaper 接管。

这里有一个容易弄错的边界:接管者取决于“父进程所在的命名空间”,不一定就是子进程当前视角里最靠近的容器 PID 1。跨 namespace 创建和 setns、unshare 会让进程的父子关系与命名空间树不完全重合,所以排查时要同时看父进程关系和 namespace 层级。

被接管并不等于已经回收。进程退出后,内核仍要保留退出码、资源使用等少量信息,直到父进程调用 wait 系列系统调用读取这些信息;这个等待动作完成后,僵尸进程对应的任务表项才会被释放。

PID 1 怎样用 SIGCHLD 和 waitpid 回收

常见做法是让 SIGCHLD 处理器只设置一个轻量标记,把真正的 waitpid 循环放到主循环或事件循环中。这样可以避免在信号处理器里塞入日志、内存分配和复杂业务逻辑。

#include 
#include 
#include 
#include 

static volatile sig_atomic_t child_event = 0;

static void on_sigchld(int signo) {
    (void)signo;
    // 信号处理器只记账,避免在异步上下文中做复杂工作。
    child_event = 1;
}

static void reap_children(void) {
    int status;
    for (;;) {
        // -1 表示回收当前进程有权等待的任意子进程;WNOHANG 避免阻塞主循环。
        pid_t pid = waitpid(-1, &status, WNOHANG);
        if (pid > 0) {
            // 这里可以记录 pid、退出码或信号,但不要把业务逻辑塞进信号处理器。
            continue;
        }
        if (pid == 0) {
            // 当前没有更多已经退出的子进程,下一次 SIGCHLD 再继续处理。
            break;
        }
        if (errno == EINTR) {
            // 被其他信号打断时重试,避免漏掉待回收的子进程。
            continue;
        }
        // ECHILD 表示已经没有可等待的子进程,其他错误应交给生命周期策略处理。
        break;
    }
}

int main(void) {
    struct sigaction action = {0};
    action.sa_handler = on_sigchld;
    sigemptyset(&action.sa_mask);
    // 建立 SIGCHLD 处理逻辑后,PID 1 才能稳定感知子进程退出。
    sigaction(SIGCHLD, &action, NULL);

    for (;;) {
        // 真实服务可在这里处理信号、管道或 socket 事件。
        pause();
        if (child_event) {
            child_event = 0;
            reap_children();
        }
    }
}

这段代码只是回收骨架,不负责启动容器,也没有假设任何具体业务协议。核心是 waitpid(-1, ..., WNOHANG) 要循环调用:一次信号到达时可能对应多个已经退出的子进程,单次调用会留下未回收的僵尸。

生产实现还应明确三件事:退出码是否需要传给上层、收到 SIGTERM 后是否先停止拉起新子进程、以及主循环被其他信号打断时怎样继续回收。若直接把 SIGCHLD 设置为忽略或使用 SA_NOCLDWAIT,内核可以避免保留僵尸,但应用也会失去读取子进程退出状态的机会,不能无条件替代显式 waitpid。

普通父进程回收和命名空间回收不是一回事

普通服务通常只需要等待自己直接创建的子进程;namespace init 还要准备接管那些原父进程已经退出的后代。于是它的回收范围不是“我启动的那几个 PID”,而是“当前有权等待的全部子进程”。这也是推荐使用 waitpid(-1, ...) 循环,而不是维护一份可能过期的 PID 白名单的原因。

/proc 的视图也要一起考虑。procfs 显示哪些进程,取决于挂载它的进程所在 PID namespace。创建新的 PID namespace 后,通常还要在对应的 mount namespace 中挂载新的 procfs,容器内的诊断工具才能看到与容器 PID 号一致的列表;否则你可能误以为 PID 1 没有子进程,实际只是看到了另一个层级的视图。

可以用下面的命令表达一个“创建 PID namespace 并把 procfs 挂载到新视图”的思路。它是操作示意,不代表本文在本机执行过。

# --pid 创建新的 PID namespace,--fork 让后续 shell 成为其中的 PID 1。
# --mount-proc 让 procfs 按新的 PID 视图呈现,便于观察容器内进程关系。
unshare --pid --fork --mount-proc /bin/sh

# 在命名空间内查看进程树;输出只用于观察,不替代 waitpid 回收职责。
ps -o pid,ppid,stat,comm

PID 1 退出时为什么会连带终止整个 namespace

Linux 把 namespace init 视为命名空间正确运行所必需的进程。如果它退出,内核会向该 PID namespace 中的进程发送 SIGKILL;之后再尝试向这个已经没有 init 的命名空间创建进程,会失败并返回 ENOMEM。因此,PID 1 的主循环不能只关心业务子进程,还要定义“收到终止信号后如何有序结束”和“所有关键子进程结束后自己何时退出”。

PID 1 通过 waitpid 回收孤儿和退出子进程并连接到命名空间生命周期边界
图2:PID 1、SIGCHLD、waitpid 与命名空间退出边界的静态关系图,不是截图或运行证据。

从容器运行时的角度看,这个约束解释了两个常见设计:一是让一个专门的 init wrapper 作为容器入口,再由它启动业务程序;二是让 wrapper 明确转发终止信号,并在回收全部子进程后返回合理退出码。wrapper 不是为了“伪造”一个 init,而是为了补上单进程应用经常缺少的子进程管理职责。

一张表记住回收边界

场景内核关系PID 1 应做什么
直接子进程退出保留退出信息,等待父进程读取处理 SIGCHLD,并循环 waitpid
原父进程提前退出孤儿重新挂到合适的 init 或 subreaper不要只等待自己启动的固定 PID
容器内 procfs 视图不匹配看到的 PID 列表来自另一个挂载层级在正确的 mount namespace 中挂载 procfs
namespace init 退出命名空间内进程被 SIGKILL,后续创建受限把信号转发、清理和退出策略放在生命周期设计里

常见问题

PID 1 必须使用 systemd 吗?

不必须。关键是它要实现与场景匹配的信号处理、子进程回收和退出策略。极简容器可以使用专门的 init wrapper,完整系统才可能选择 systemd。

为什么只调用一次 waitpid 还会有僵尸?

一次 SIGCHLD 可能对应多个子进程退出。单次 waitpid 只取走一个退出状态,应该在返回正数时持续循环,直到返回 0 或确认没有可等待的子进程。

子 namespace 里的 PID 1 能看到宿主机进程吗?

不能按子 namespace 的视角反向观察祖先 namespace。祖先可以观察子 namespace 中的进程,但同一进程在不同层级使用的 PID 可能不同。

本文依据的 Linux 资料在哪里?

Linux PID namespace 的生命周期、PID 1、孤儿接管和 procfs 视图可参考:https://man7.org/linux/man-pages/man7/pid_namespaces.7.html。本文示例只用于解释结构与职责,图像也是静态说明图,不是运行截图。

把 namespace init 设计成“会回收子进程、能处理信号、知道何时退出”的小型进程管理器,PID namespace 的核心边界就清楚了:孤儿由合适的 init 接管,退出状态由 wait 系列消费,而 PID 1 的退出会结束整个命名空间的可用生命周期。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux TCP backlog 怎样区分监听队列和连接队列Linux TCP backlog 怎样区分监听队列和连接队列
上一篇
Linux TCP backlog 怎样区分监听队列和连接队列
Linux perf stat 怎样先看 CPU 指标
下一篇
Linux perf stat 怎样先看 CPU 指标
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    471次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    530次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    550次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    500次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    318次使用