尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

进程备忘录

进程备忘录
📅 发布时间:2026/6/30 5:56:05

目录

一、概念

1. 僵尸进程(Zombie)

2. 孤儿进程(Orphan)

二、wait 系列函数(回收子进程)

1. pid_t wait(int *status);

2. pid_t waitpid(pid_t pid, int *status, int options);

3. pid_t waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

三、代码示例

一、概念

进程调用fork()创建子进程时,进程 ID 的变化遵循以下核心规则:

  • 父进程:PID(进程ID)保持不变。fork()返回子进程的 PID 给父进程。

  • 子进程:获得一个新的唯一 PID。fork()返回 0 给子进程自身。

此外,还有一个容易被忽略的关键点:

  • PPID(父进程ID):子进程的 PPID 会被设置为父进程的 PID。这就像是子进程一出生就知道“谁是我的爸爸”。


关键特例:当“爸爸”先走一步时

如果父进程在子进程之前结束,那么子进程就变成了“孤儿进程”。此时,子进程会被PID 为 1 的init进程(或systemd)“收养”,它的PPID 会变成 1。


验证方法(代码示例)

下面这段 C 代码直观地验证:

c #include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid == 0) { // 子进程 printf("子进程: PID=%d, PPID=%d\n", getpid(), getppid()); } else if (pid > 0) { // 父进程 printf("父进程: PID=%d, 子进程PID=%d\n", getpid(), pid); } return 0; }

输出示例:

父进程: PID=1234, 子进程PID=1235 子进程: PID=1235, PPID=1234

1. 僵尸进程(Zombie)

  • 定义:子进程已终止,但其父进程尚未调用wait()/waitpid()来回收其退出状态,导致子进程的进程描述符仍保留在内核中。

  • 状态:ps aux中显示为Z(Defunct)。

  • 危害:少量僵尸无大碍,大量会耗尽 PID 和内存资源。

  • 产生原因:父进程未处理SIGCHLD信号或未主动回收。

  • 解决方法:

    • 父进程调用wait()/waitpid()。

    • 父进程忽略SIGCHLD(signal(SIGCHLD, SIG_IGN)),内核会自动回收。

    • 父进程终止,僵尸子进程会被 init(PID=1)收养并回收。

2. 孤儿进程(Orphan)

  • 定义:父进程先于子进程终止,此时子进程变为孤儿。

  • 处理:孤儿进程会被 init 进程(PID=1)自动收养,并负责回收(调用wait),因此不会变成僵尸。

  • 应用:常用于守护进程(daemon)——通过 fork 让父进程退出,子进程成为孤儿,被 init 收养,脱离终端控制。

二、wait 系列函数(回收子进程)

1.pid_t wait(int *status);

  • 阻塞等待任意子进程终止。

  • 返回终止子进程 PID,status存储退出状态。

  • 使用宏解析:

    • WIFEXITED(status)—— 正常退出 →WEXITSTATUS(status)取返回值。

    • WIFSIGNALED(status)—— 被信号杀死 →WTERMSIG(status)取信号编号。

2.pid_t waitpid(pid_t pid, int *status, int options);

  • 更灵活:

    • pid == -1:等待任意子进程(同wait)。

    • pid > 0:等待指定 PID 的子进程。

    • options =WNOHANG:非阻塞,立即返回 0(无子进程终止)。

  • 常用于循环 +WNOHANG实现非阻塞轮询。

3.pid_t waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

  • 更高级的等待接口,支持实时信号信息。

  • idtype:

    • P_PID:等待指定 PID。

    • P_PGID:等待指定进程组。

    • P_ALL:任意子进程。

  • options:WNOHANG、WEXITED、WSTOPPED、WCONTINUED。

  • infop返回子进程状态详细信息(如 si_signo, si_code, si_pid 等)。

/****************************************************************************************************/

wait(&state)返回的是一个pid_t类型的值,但它返回的是“已终止的子进程的 PID”。

具体来说,这个返回值有三种情况:

1. 正常情况(返回 > 0)
返回的是刚刚终止的那个子进程的进程 ID。

  • 因为一个父进程可能有多个子进程,通过这个返回值,你可以知道到底是哪一个子进程退出了。

2. 错误情况(返回 -1)

  • 表示调用失败。最常见的错误是ECHILD(没有子进程存在,或者子进程没有被等待)。

  • 此时可以通过errno查看具体错误原因。

3. 特殊信号中断(返回 0)
(仅在设置了WNOHANG选项时发生)

  • 如果你调用waitpid(-1, &state, WNOHANG),并且当前没有任何子进程终止,返回值是0。

  • 但标准的wait(&state)默认没有WNOHANG标志,如果子进程没结束它会一直阻塞,所以标准的wait不会返回 0。


补充一个重要细节:
虽然返回值是子进程 PID,但如果你想获取父进程自己的 PID,应该使用getpid()。
而wait返回的 PID 配合state参数(退出状态码)一起使用,可以精确处理每个子进程的退出结果

三、代码示例

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> void daemonize() { pid_t pid; // 1. fork 并让父进程退出(成为孤儿,被 init 收养) pid = fork(); if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS); // 父进程退出 /*exit(0); exit(EXIT_SUCCESS);通常是0 exit(EXIT_FAILURE);通常是1 exit(2); 自定义错误码 */ // 2. 创建新会话,脱离控制终端 if (setsid() < 0) exit(EXIT_FAILURE); // 3. 再次 fork(防止无意中重新获得终端) pid = fork(); if (pid < 0) exit(EXIT_FAILURE); if (pid > 0) exit(EXIT_SUCCESS); // 4. 修改工作目录为根目录(避免占用可卸载文件系统) chdir("/"); // 5. 重设文件权限掩码(更宽松) umask(0); // 6. 关闭所有打开的文件描述符(0,1,2) close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); // 7. 将 stdin/out/err 重定向到 /dev/null(可选) open("/dev/null", O_RDWR); // fd 0 dup(0); // fd 1 dup(0); // fd 2 //dup 是什么?复制一个“已存在的 fd” 规则:返回 最小可用 fd // 8. 忽略 SIGCHLD,避免僵尸(或使用 signal 处理) signal(SIGCHLD, SIG_IGN); } int main() { daemonize(); // 现在进程是守护进程,可以写日志、监听端口等 while (1) { // 守护进程主循环 sleep(10); } return 0; }

在 Linux 内核机制中,会话组长有权申请打开一个终端。如果这个守护进程以后运气不好(比如它去打开了一个串口设备,或者某个库函数试图打开/dev/tty),它就会重新获得一个控制终端。

  • 一旦重新获得终端,用户的键盘信号(比如Ctrl+C)就有可能意外地发送给这个守护进程,导致它被杀死。

  • 这违背了守护进程“默默在后台运行,不受终端干扰”的初衷。

  • 此时的孙子进程:

    • 它继承了会话 ID,但它不是会话组长(因为它的 PID 不等于会话组的 SID)。

    • 在 Linux 规则中,只有会话组长才能获取控制终端。既然孙子进程不是组长,它就永远、绝对、丧失了重新获得终端的资格。

    • 它变成了一个纯粹的、不受任何终端信号干扰的后台进程。

WIFEXITED(state)检查是否正常退出
WEXITSTATUS(state)提取退出码
WIFSIGNALED(state)检查是否被信号终止
WTERMSIG(state)提取终止信号编号
WIFSTOPPED(state)检查是否被暂停
WSTOPSIG(state)提取暂停信号编号

相关新闻

  • 比 iTerm2 更适合 Claude Code/Codex 的终端,我换成 Ghostty 了
  • 限时开放|Prompt Engineering 高阶训练营核心课件(仅剩最后87份,含GitHub私有仓库访问权限)
  • 模具全流程数字化验证三方案横评:CMM、激光扫描、蓝光3D扫描谁更香?

最新新闻

  • 【JAVA毕设源码分享】基于springboot教学管理自动化系统设计与实现(程序+文档+代码讲解+一条龙定制)
  • 企业上AI智能体,部署搭建阶段最容易被低估的那些事
  • 暗黑破坏神2存档编辑器:5分钟掌握游戏角色自定义全攻略
  • ChatGPT提示词失效真相(附结构化诊断矩阵):3分钟定位语义坍塌、角色错位与约束泄漏
  • 从零解读Web3:区块链、智能合约与DApp开发入门
  • 为什么物流系统越多,协调反而越困难?

日新闻

  • 【计算机毕业设计案例】基于 Spring Boot+Vue 的电影售票系统设计与实现 前后端分离架构下影院在线购票管理平台(程序+文档+讲解+定制)
  • 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦
  • Google限制Meta使用Gemini模型 凸显AI授权竞争白热化

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号