Linux pidfd怎样安全等待进程结束
在守护进程、任务执行器或容器运行时里,我们经常需要等待“某一个进程”结束。只保存一个 PID 看起来够用,但 PID 会被系统回收后重新分配;如果轮询 /proc/ 或延迟调用 kill(pid, 0),就可能把已经结束的旧进程误认成同一个新进程。
Linux 的 pidfd 把进程绑定成一个文件描述符,之后可以把它交给 poll、epoll 监听生命周期;如果它指向调用者的子进程,还可以用 waitid(P_PIDFD, ...) 读取退出状态。这样,“等待它结束”和“取得它的退出原因”都有稳定的对象可追踪。
最实用的组合是:pidfd_open获取句柄,poll/epoll判断进程是否结束,waitid(P_PIDFD)只在目标是自己的子进程时负责读取并回收退出状态。不要对 pidfd 调用read,它不是可读的普通文件。
官方参考地址:https://www.man7.org/linux/man-pages/man2/pidfd_open.2.html
先把进程身份从 PID 换成 pidfd
pidfd_open(pid, 0) 的输入仍然是 PID,但返回值是指向该任务的文件描述符。这个描述符在目标进程结束后仍然代表原来的任务,不会因为 PID 号码后来被复用而自动指向另一个进程。对于已经存在的进程,官方手册把 pidfd_open 作为优先获取方式。
这和打开 /proc/pid 目录不是一回事:后者不能用于 poll,也不能交给 waitid。因此,如果程序需要把多个子进程统一放进一个 epoll 集合,pidfd 是更自然的对象。

支持范围决定你能把它用到哪一步
写代码前先确定内核与目标对象的边界:
| 能力 | 内核起点 | 它解决的问题 | 注意事项 |
|---|---|---|---|
pidfd_open | Linux 5.3 | 为已有 PID 获取任务文件描述符 | 进程不存在时返回错误;权限与 PID 命名空间仍然有效 |
poll/epoll 监视 pidfd | 随 pidfd 能力提供 | 在任务变成 zombie 时收到可读事件 | pidfd 本身不能 read;被回收后会出现挂起事件 |
waitid(P_PIDFD) | Linux 5.4 | 按 pidfd 读取子进程的退出状态 | 目标必须是调用者的子进程 |
PIDFD_NONBLOCK | Linux 5.10 | 让针对 pidfd 的等待不阻塞 | 目标尚未结束时要处理 EAGAIN |
PIDFD_THREAD | Linux 6.9 | 让句柄指向线程而非线程组组长 | 线程与整个线程组的可读时机不同 |
其中最容易误解的是 waitid(P_PIDFD):它不是“所有 pidfd 都能读取退出码”。手册限定了目标必须是调用者的子进程;对于只是观察同一机器上已有的非子进程,通常只能把 pidfd 当作可轮询的生命周期信号。
最小 C 示例:poll 等结束,waitid 取状态
下面的示例创建一个短生命周期子进程。父进程先用 pidfd_open 得到稳定句柄,再用 poll 等待句柄变为可读,最后用 waitid(P_PIDFD) 读取 siginfo_t。代码展示接口关系,不把终端输出当作文章证据。
#define _GNU_SOURCE #include#include #include #include #include #include #include #include // 某些较旧的头文件没有暴露 P_PIDFD,数值来自 Linux waitid 接口定义。 #ifndef P_PIDFD #define P_PIDFD 3 #endif static int open_pidfd(pid_t pid) { // glibc 环境可能没有 pidfd_open 包装器,因此直接调用系统调用。 return (int)syscall(SYS_pidfd_open, pid, 0U); } int main(void) { pid_t child = fork(); if (child == -1) { perror("fork"); return EXIT_FAILURE; } if (child == 0) { // 子进程只用于提供一个可等待的生命周期,不读取任何外部输入。 sleep(1); _exit(7); } int pidfd = open_pidfd(child); if (pidfd == -1) { perror("pidfd_open"); return EXIT_FAILURE; } struct pollfd watched = { .fd = pidfd, .events = POLLIN, // 进程变成 zombie 后,pidfd 会报告可读事件。 }; while (poll(&watched, 1, -1) == -1) { // 被信号打断时继续等待;其它错误必须关闭句柄并退出。 if (errno != EINTR) { perror("poll"); close(pidfd); return EXIT_FAILURE; } } siginfo_t info = {0}; // 目标是自己的子进程,所以可以按 pidfd 读取并回收退出状态。 if (waitid(P_PIDFD, (id_t)pidfd, &info, WEXITED) == -1) { perror("waitid"); close(pidfd); return EXIT_FAILURE; } if (info.si_code == CLD_EXITED) { printf("child exited with status %d\\n", info.si_status); } else { // 被信号终止时,si_status 表示导致终止的信号编号。 printf("child changed state, code=%d status=%d\\n", info.si_code, info.si_status); } // waitid 已完成回收,pidfd 自身也要释放。 close(pidfd); return EXIT_SUCCESS; }
这段代码里有两个不能互换的动作。poll 只负责把“还没结束”变成“可以处理”;它不会把退出码写进 pidfd。waitid 才负责取得 si_status,并且在没有使用 WNOWAIT 时完成子进程回收。
poll 和 waitid 各自回答什么问题
把两个接口混成一个“读取进程结果”的动作,是 pidfd 代码最常见的误区。可以按下面的职责拆开:
| 接口或事件 | 能回答 | 不能回答 |
|---|---|---|
POLLIN/EPOLLIN | 被监视的任务已经终止并成为 zombie,可以进入状态处理 | 不能直接得到退出码;也不能对 pidfd 使用 read |
EPOLLHUP | 任务已被回收,句柄进入挂起状态 | 不能说明是正常退出还是信号终止 |
waitid(P_PIDFD, WEXITED) | 为子进程填写 siginfo_t 并完成回收 | 不能等待不属于调用者的任意进程 |
waitid(..., WNOWAIT) | 读取状态但暂时保留子进程的可等待状态 | 不能代替最终的回收动作 |

如果程序用 epoll 管理大量任务,收到 EPOLLIN 后可以把 pidfd 从“等待集合”转入“状态处理集合”,随后调用 waitid。处理完成就关闭描述符;不要反复把同一个已经回收的 pidfd 当成普通输入源。
已有进程、子进程和线程 pidfd 要分开判断
只观察已有的非子进程
对于已经运行的、但不属于当前进程的任务,pidfd_open 可以建立稳定引用,也可以用 poll 或 epoll 观察它结束。但此时不能把 waitid(P_PIDFD) 当成通用的退出码查询接口;权限、父子关系和目标是否已经被其它地方回收都可能使等待失败。
自己创建的子进程
自己通过 fork 创建的子进程,适合使用本文的组合。若程序一开始就需要在创建动作中拿到 pidfd,可以考虑带 CLONE_PIDFD 的 clone 方案,避免“先拿 PID、稍后再打开 pidfd”之间的额外窗口。
线程级句柄
不带 PIDFD_THREAD 时,pidfd 默认针对进程;线程组内还有其它线程时,整个线程组的结束边界才是主要关注点。使用 PIDFD_THREAD 后,句柄可以指向特定线程,线程退出成为 zombie 时的可读时机也不同。只有确实需要线程级生命周期时才启用它,不要把线程 pidfd 和普通进程 pidfd 的事件语义混用。
旧内核和降级路径怎么处理
部署环境低于 Linux 5.3 时,不能假设 pidfd_open 存在;低于 Linux 5.4 时,也不能假设 P_PIDFD 可用于 waitid。建议把能力探测放在启动或任务管理器初始化阶段,并把“等待对象”抽象出来:
- 目标是自己的子进程且内核支持 pidfd:优先使用 pidfd + poll/epoll + waitid。
- 目标是自己的子进程但内核较旧:使用
waitpid,不要在外层再用 PID 轮询模拟稳定句柄。 - 目标不是自己的子进程且内核支持 pidfd:使用 pidfd 做结束监视,但把退出码查询视为能力之外。
- 只有在明确接受限制时才使用
/proc/PID:它不是 pollable 文件,也不能消除 PID 复用风险。
降级路径的关键不是“找一个语法相同的替代函数”,而是向上层说明能力差异:是否能可靠识别原对象、是否能收到结束事件、是否能读取退出状态、是否由当前进程负责回收。
生产代码里的资源和安全注意
- 始终关闭 pidfd:每个监视对象都会消耗文件描述符;任务结束、超时取消或错误返回都要走统一清理路径。
- 处理 EINTR 和 EAGAIN:
poll可能被信号打断;启用PIDFD_NONBLOCK后,目标尚未结束时waitid可能直接返回EAGAIN。 - 不要把事件当退出码:
POLLIN只代表生命周期进入可处理状态,最终结果仍由siginfo_t的si_code和si_status解读。 - 尊重命名空间与权限:pidfd 稳定的是任务引用,不会绕过 PID namespace、权限检查或目标已不存在的事实。
- 避免重复回收:多个线程不要同时对同一个子进程调用 wait 系列接口;如果需要先观察后回收,明确使用
WNOWAIT并安排唯一的最终回收者。
一句话记住这套组合
Linux pidfd 解决的是“我监视的到底是不是原来那个进程”这一层身份问题。pidfd_open 建立稳定句柄,poll/epoll 提供可复用的结束事件,waitid(P_PIDFD) 只在子进程场景下负责退出状态和回收。把这三个职责分清,再根据内核版本、父子关系和线程粒度选择降级路径,就不会把 PID 复用、不可读句柄和重复回收混在一起。
pidfd 可以直接 read 吗?
不可以。当前实现中对 pidfd 调用 read 会失败,应该通过 poll、epoll 或 waitid 获取生命周期与状态信息。
收到 EPOLLIN 后还要 waitid 吗?
如果需要子进程退出码或完成回收,仍然需要。EPOLLIN 只是告诉你进入了可以处理的状态。
pidfd 能替代所有 kill(pid, 0) 检查吗?
它能避免用裸 PID 反复确认同一对象的复用竞态,但不能替代所有权限判断、信号发送和进程管理策略。发送信号时应使用与 pidfd 对应的专用接口,并继续处理权限和目标状态错误。
MySQL INNODB_CACHED_INDEXES 查看每个索引的缓存页
- 上一篇
- MySQL INNODB_CACHED_INDEXES 查看每个索引的缓存页
- 下一篇
- 农产品经营者如何准备质量追溯台账
-
- 文章 · linux | 3小时前 |
- Linux perf record定位CPU热点
- 418浏览 收藏
-
- 文章 · linux | 3小时前 | linux运维 · Linux 命名空间 挂载树 Mount API open_tree move_mount
- Linux open_tree怎样复制挂载树
- 376浏览 收藏
-
- 文章 · linux | 3小时前 | linux运维 · Linux 防火墙规则 nftables nft list ruleset 地址族 规则集导出
- nftables list ruleset怎样按地址族导出配置
- 308浏览 收藏
-
- 文章 · linux | 3小时前 | linux运维 · 服务管理 · Linux systemd socket激活 systemd.socket systemd.service
- Linux systemd socket激活服务
- 249浏览 收藏
-
- 文章 · linux | 3小时前 | Linux · Linux systemd Cron Timer OnCalendar
- Linux systemd定时器替代cron任务
- 380浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 1043次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1040次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1187次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1143次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 987次使用
-
- Linux vmstat 如何分辨内存抖动与磁盘等待:si、so、wa 与复测顺序
- 2026-08-30 501浏览
-
- Linux搭建vsftpdFTP服务器教程
- 2026-04-30 501浏览
-
- Shell脚本安装教程:.sh一键安装指南
- 2026-03-16 501浏览
-
- Linux清空文件内容的几种方法
- 2025-12-01 501浏览
-
- Linux命令行下载文件技巧
- 2025-11-23 501浏览

