ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux 进程处理下:waitpid、进程树与孤儿进程

Linux 进程处理下:waitpid、进程树与孤儿进程 Linux 进程处理下waitpid、进程树与孤儿进程课程尚硅谷《嵌入式 Linux 应用层开发》第 3 章进程处理依据2026-09-23 18:25 录音转写课程 PDF 的 PDF 第 74—85 页。本节边界录音主体讲waitpid、进程树和孤儿进程PDF 第 85 页开始进入进程间通信本笔记只把它作为下一章预览。1. 本节解决什么问题上一节建立了标准模型父进程 fork → 子进程 execve 新程序还缺少最后一步父进程必须处理子进程结束后的状态。sleep只能拖延时间无法可靠判断子进程是否结束waitpid才是等待和回收子进程的接口。父进程 fork父进程继续工作子进程 execve 或执行任务waitpid子进程终止留下退出状态父进程取得状态内核回收僵尸表项本节主线waitpid 等待与回收 → 退出状态解码 → 僵尸进程 → Linux 进程树 → 孤儿进程与重新托管 → 下一章 IPC2. 为什么子进程结束后还要回收子进程终止时内核不能立刻丢掉全部信息因为父进程还可能需要知道子进程的 PID正常退出码是否被信号终止使用过的部分资源统计。因此子进程的代码、地址空间和大部分资源会释放但内核暂时保留一小份终止状态。父进程调用wait、waitpid或waitid取得这份状态后内核才能完成回收。2.1 僵尸进程如果子进程已经终止而仍存活的父进程一直没有执行等待操作该子进程就处于僵尸状态ps的状态列通常显示Z命令名后可能出现defunct。子进程已经结束 ↓ 父进程没有 wait/waitpid ↓ 内核保留 PID 与退出状态 ↓ 僵尸进程僵尸不再执行代码也不继续占用原来的用户态内存但会占用进程表项。大量长期僵尸会消耗可用 PID 或进程表资源。3.wait、waitpid与waitid#includesys/types.h#includesys/wait.hpid_twait(int*wstatus);pid_twaitpid(pid_tpid,int*wstatus,intoptions);intwaitid(idtype_tidtype,id_tid,siginfo_t*infop,intoptions);wait等待任意一个直接子进程发生可等待的状态变化。waitpid可以选择具体子进程、进程组以及阻塞或非阻塞方式。waitid返回更详细的siginfo_t状态信息。wait(status)的常见行为相当于waitpid(-1,status,0);本阶段重点掌握waitpid。4.waitpid三个参数pid_twaitpid(pid_tpid,int*wstatus,intoptions);4.1 参数一pid等谁pid的值等待对象 0PID 等于该值的一个直接子进程-1任意一个直接子进程0与调用者当前进程组相同的任意直接子进程 -1进程组 ID 等于abs(pid)的任意直接子进程最常用的两种写法waitpid(child_pid,status,0);/* 等待指定子进程 */waitpid(-1,status,0);/* 等待任意直接子进程 */重要校正waitpid只能报告调用进程的直接子进程不能跨代等待孙进程或所有后代。录音和 PDF 中“-1儿孙都算”的说法不准确。4.2 参数二wstatus保存编码后的状态intstatus;pid_tresultwaitpid(child_pid,status,0);status不是可以直接打印的普通退出码而是内核编码后的等待状态。必须使用WIF...和W...STATUS宏解释。如果完全不关心状态可以传NULLwaitpid(child_pid,NULL,0);4.3 参数三options怎样等选项作用0默认阻塞等待指定子进程终止WNOHANG没有符合条件的状态时立即返回 0WUNTRACED未被跟踪的子进程因信号停止时也报告WCONTINUED已停止的子进程因SIGCONT恢复时也报告多个选项使用按位或WNOHANG|WUNTRACED|WCONTINUED课程的基本回收场景使用options 0即可。5.waitpid的返回值返回值含义 0发生状态变化并被报告的子进程 PID0使用了WNOHANG目前没有符合条件的子进程状态-1出错检查errno常见错误ECHILD没有符合pid条件、尚未回收的直接子进程。EINTR等待被信号处理函数中断通常应重新调用。EINVALoptions含无效标志。可靠的阻塞等待循环pid_twaited;do{waitedwaitpid(child_pid,status,0);}while(waited-1errnoEINTR);if(waited-1){perror(waitpid);}6. 正确解释子进程退出状态if(WIFEXITED(status)){printf(正常退出退出码%d\n,WEXITSTATUS(status));}elseif(WIFSIGNALED(status)){printf(被信号终止信号%d\n,WTERMSIG(status));}elseif(WIFSTOPPED(status)){printf(被信号停止信号%d\n,WSTOPSIG(status));}elseif(WIFCONTINUED(status)){printf(收到 SIGCONT 后恢复运行\n);}判断宏为真时表示配套取值宏WIFEXITED(status)调用exit/_exit或从main返回WEXITSTATUS(status)WIFSIGNALED(status)被未处理信号终止WTERMSIG(status)WIFSTOPPED(status)因信号停止WSTOPSIG(status)WIFCONTINUED(status)停止后又恢复无需额外取值只有WIFEXITED为真时才能使用WEXITSTATUS。不能不判断就直接解码。7. 一个完整、可重复的waitpid练习这个例子不依赖网络和ping更适合反复验证。#includeerrno.h#includestdio.h#includestdlib.h#includesys/types.h#includesys/wait.h#includeunistd.hintmain(void){printf(父进程 PID%ld\n,(long)getpid());fflush(stdout);pid_tpidfork();if(pid-1){perror(fork);returnEXIT_FAILURE;}if(pid0){printf(子进程 PID%ld开始工作\n,(long)getpid());sleep(2);printf(子进程工作结束返回 7\n);fflush(stdout);_exit(7);}printf(父进程等待子进程 %ld\n,(long)pid);intstatus;pid_twaited;do{waitedwaitpid(pid,status,0);}while(waited-1errnoEINTR);if(waited-1){perror(waitpid);returnEXIT_FAILURE;}if(WIFEXITED(status)){printf(已回收子进程 %ld退出码%d\n,(long)waited,WEXITSTATUS(status));}elseif(WIFSIGNALED(status)){printf(子进程被信号 %d 终止\n,WTERMSIG(status));}returnEXIT_SUCCESS;}注意子进程调用_exit不会自动刷新stdio缓冲所以示例在_exit前显式调用了fflush(stdout)。也可以使用dprintf/write直接输出。这里保留_exit是为了强调fork子进程的退出语义。8.WNOHANG查询而不阻塞pid_trwaitpid(pid,status,WNOHANG);if(r0){/* 子进程状态已取得并完成相应回收 */}elseif(r0){/* 子进程尚未结束父进程可以继续其他工作 */}else{/* 错误 */}不要无间隔地反复调用形成忙等while(waitpid(pid,status,WNOHANG)0){/* 空循环会持续占用 CPU */}实际程序可以结合事件循环、信号机制或带间隔的轮询。只需要等子进程完成时直接用阻塞的waitpid(pid, status, 0)更简单。9. 如何观察僵尸进程下面的演示只让僵尸状态保持 10 秒随后父进程主动回收不会长期残留pid_tpidfork();if(pid0){_exit(42);}if(pid0){printf(父%ld 子%ld10 秒后回收\n,(long)getpid(),(long)pid);sleep(10);waitpid(pid,NULL,0);}在另一个终端观察ps-opid,ppid,pgid,stat,cmd-p父PID,子PID睡眠期间子进程的STAT通常包含Z父进程调用waitpid后子进程表项消失。10. 进程树Linux 进程通过父子关系形成树状结构。每个普通进程至多有一个直接父进程但可以创建多个直接子进程。PID 1init / systemd 或容器 initshell / terminalmake 或课程测试程序父进程子进程 1子进程 2孙进程常用观察命令ps-efps-opid,ppid,pgid,sid,stat,cmd pstree pstree-ppstree-pPID相比不断执行ps -ef | grep PIDpstree -p更直观。使用grep时结果中通常还会包含grep自身可改用ps-opid,ppid,stat,cmd--ppid父PID10.1 PID 1 与内核线程在传统主机的初始 PID 命名空间中PID 1 通常是systemd或其他 init 实现负责启动用户空间服务并回收被重新托管的进程。现代 Linux 还有 PID 命名空间因此容器内部也有自己的 PID 1不能把宿主机systemd当成所有环境下唯一可见的祖先。ps中方括号包围的名字通常表示内核线程例如[kthreadd]。教材所说“PID 2 是所有内核线程祖先”符合常见宿主机初始命名空间的现象但不应作为所有容器和内核配置中的固定结论。内核线程没有普通用户态地址空间这比“完全不占用用户空间资源”更准确。11. 孤儿进程孤儿进程是父进程已经结束但子进程仍在运行。PID 1 或最近的 subreaper子进程原父进程PID 1 或最近的 subreaper子进程原父进程fork 创建父进程先结束内核把子进程重新托管子进程继续运行PPID 发生变化子进程终止由新父进程回收Linux 通常把孤儿重新指定给所在 PID 命名空间的 init或者最近的子收割器subreaper。getppid()会反映新的父进程 PID。11.1 孤儿与僵尸不要混淆类型子进程是否仍在运行原父进程状态核心问题僵尸进程已经结束通常仍活着但没回收等待状态尚未被取得孤儿进程仍在运行已经结束被重新托管给 init 或 subreaper孤儿不是僵尸。孤儿随后结束时新父进程负责回收僵尸已经没有运行能力。11.2 父进程结束不等于自动与终端断开教材第 85 页说孤儿会“和终端切断联系”这不是孤儿进程定义带来的必然结果。子进程会继承打开的文件描述符、会话和控制终端关系父进程退出本身不会自动关闭子进程的标准输入输出。具体是否还能从终端读取取决于shell 的作业控制进程组和会话控制终端是否仍存在标准输入描述符指向何处是否收到SIGHUP等信号。孤儿进程也不是必须彻底避免的错误。守护进程和服务管理器可能有意安排重新托管普通业务程序则应明确设计生命周期和回收关系。12. 课程孤儿进程实验应怎样理解课程让子进程执行sleep(100)父进程立即结束然后用ps/pstree观察 PPID 改变。这个实验只验证两件事原父进程先结束时仍运行的子进程不会立刻随之消失。子进程会被重新指定父进程getppid()或ps中的 PPID 会变化。更简洁的实验#includestdio.h#includestdlib.h#includesys/types.h#includeunistd.hintmain(void){pid_tpidfork();if(pid-1){perror(fork);returnEXIT_FAILURE;}if(pid0){printf(父进程 %ld 创建子进程 %ld 后退出\n,(long)getpid(),(long)pid);returnEXIT_SUCCESS;}printf(子进程 %ld原 PPID%ld\n,(long)getpid(),(long)getppid());sleep(2);printf(两秒后新的 PPID%ld\n,(long)getppid());returnEXIT_SUCCESS;}第二次打印的 PPID 通常会变成 PID 1 或某个 subreaper但在 shell、容器和服务管理器中具体数值可能不同。13. 对录音和 PDF 的重点校正waitpid(-1, ...)只等待任意直接子进程。不包括孙进程或所有后代。waitpid(0, ...)等待同进程组的直接子进程。没有“排除组长”这一通用规则。状态整数必须用宏解码。不能把status本身直接当退出码。WNOHANG表示不阻塞。返回 0 说明目标子进程存在但当前没有可报告状态。sleep不能代替waitpid。子进程运行时长不确定。僵尸与孤儿是两种状态。前者已经结束但未回收后者还在运行但原父进程已结束。孤儿通常交给 PID 1 或最近的 subreaper。不一定逐级交给“父进程的父进程”后就停止。孤儿不会必然失去终端。终端行为由文件描述符、会话和作业控制共同决定。PID 1 不一定总是宿主机 systemd。容器的 PID 命名空间中有独立的 PID 1。应用代码使用pid_t。不应直接使用实现内部类型__pid_t。14. PDF 第 85 页下一章 IPC 预览进程地址空间彼此隔离。多个进程要交换数据或协调执行需要进程间通信IPC。教材列出IPC 方式主要用途后续学习重点Unix Domain Socket同一主机上的双向通信地址、连接、字节流或报文匿名管道有亲缘关系进程之间的字节流pipe、描述符继承有名管道 FIFO无亲缘关系进程通过路径通信mkfifo、阻塞语义共享内存高速共享大块数据映射、同步与生命周期消息队列按消息边界传递数据消息类型、优先级、容量信号量进程间同步和互斥计数、等待、唤醒System V IPC 主要包括消息队列、信号量和共享内存Linux 同时也支持相应的 POSIX IPC 接口。二者 API、命名方式和生命周期管理不同后续按课程逐项学习。15. 本节最低掌握标准学完后应能回答为什么子进程退出后父进程还要调用waitpidwaitpid(pid, status, 0)的三个参数分别是什么waitpid返回正数、0、-1分别代表什么怎样判断子进程是正常退出还是被信号杀死僵尸进程与孤儿进程有什么区别为什么sleep不能代替进程同步怎样使用ps和pstree -p查看父子关系最简记忆fork创建子进程 exec替换子进程所运行的程序 waitpid等待并回收直接子进程 已结束但没回收 僵尸 还在运行但父进程先结束 孤儿16. 资料页码与录音时间索引主题PDF 页码录音时间wait/waitpid/waitid原型与参数74—7500:07—04:23fork execve waitpid案例75—7704:24—09:57进程树与ps/pstree77—8209:58—14:20孤儿进程实验82—8514:21—17:28IPC 与 System V IPC 预览85PDF 补充录音本段未正式展开进一步核对资料Linux man-pageswaitpid(2)、getpid/getppid(2)、PID namespaces、System V IPC。
返回列表