目录
[1. 父进程 & 子进程(fork 创建的核心)](#1. 父进程 & 子进程(fork 创建的核心))
[2. 孤儿进程](#2. 孤儿进程)
[3. 僵尸进程](#3. 僵尸进程)
[1. ps:查看进程快照](#1. ps:查看进程快照)
[2. pstree:以树形结构展示进程关系](#2. pstree:以树形结构展示进程关系)
[1. fork ():创建子进程](#1. fork ():创建子进程)
[2. getpid ()/getppid ():获取进程 PID](#2. getpid ()/getppid ():获取进程 PID)
[3. execl ():进程替换](#3. execl ():进程替换)
[4. 进程终止](#4. 进程终止)
[5. wait ()/waitpid ():监控并回收子进程](#5. wait ()/waitpid ():监控并回收子进程)
[(1)wait ():阻塞等待子进程退出](#(1)wait ():阻塞等待子进程退出)
[(2)waitpid ():灵活监控子进程](#(2)waitpid ():灵活监控子进程)
一、核心进程概念
1. 父进程 & 子进程(fork 创建的核心)
- 定义 :通过
fork()系统调用创建的新进程为子进程,发起创建的进程为父进程。当子进程被创建后将和父进程并行运行,但是父进程的运行速度要比子进程的运行速度快,要注意子进程变成孤儿进程。 - 核心特性 :
fork()执行后,操作系统会复制父进程的内存、代码、文件描述符等资源,形成两个完全独立的进程,子进程会完整复制父进程的内存空间(包括全局变量、堆、栈等),此时父子进程的全局变量值完全相同;- 父子进程有各自独立的 PID,由操作系统独立调度(宏观上并行运行),子进程的执行 / 退出 / 崩溃不会直接影响父进程;
- 父进程执行路径:
fork()返回子进程 PID(正数),继续执行原代码逻辑; - 子进程执行路径:
fork()返回 0,从fork()所在行开始执行,可通过execl()替换为其他程序。
- 易错点 :子进程调用
execl()成功后,会完全替换自身代码,永远不会回到原进程代码;只有execl()失败时,才会执行后续代码(如打印错误、退出)。
2. 孤儿进程
- 定义 :父进程先于子进程退出,子进程失去父进程,此时子进程会被系统的
init进程(PID=1)接管,成为 "孤儿进程"。 - 特点:孤儿进程本身是正常运行的进程,只是父进程已消亡,由 init 进程负责回收其退出资源,不会占用系统资源,无需手动处理。
3. 僵尸进程
- 定义 :子进程先于父进程退出,但父进程未调用
wait()/waitpid()回收其退出状态,子进程残留的 "退出信息 + PID" 会成为僵尸进程(状态标记为defunct)。 - 核心原理 :
- 子进程退出后,PID 不会立即销毁,内核会保留其退出状态(如退出码、终止原因),等待父进程查询;
- 若父进程不回收,僵尸进程会一直占用 PID 资源,大量僵尸进程会导致系统无法创建新进程。
- 解决方式 :父进程通过
wait()/waitpid()主动回收子进程资源,释放 PID;若父进程退出,孤儿化的僵尸进程会被 init 进程自动回收。
二、核心工具:查看进程状态
1. ps:查看进程快照
- 常用命令 :
ps -ef:查看所有进程的完整信息(UID、PID、PPID、运行命令等);ps -ef | grep defunct:筛选出所有僵尸进程;ps -p <PID>:查看指定 PID 进程的状态。
- 关键字段 :
- PID:进程唯一标识;
- PPID:父进程 PID;
- STAT:进程状态(Z = 僵尸进程,R = 运行,S = 睡眠)。
2. pstree:以树形结构展示进程关系
- 常用命令 :
pstree -p(显示 PID,直观看到父子进程层级); - 作用 :快速定位进程的父子关系,比如查看
fork()创建的子进程归属。
三、核心函数
1. fork ():创建子进程
- 函数原型 :
pid_t fork(void); - 返回值 :
- 父进程中:返回子进程 PID(正数);
- 子进程中:返回 0;
- 失败:返回 -1(如系统资源不足)。
- 实战注意 :
-
全局变量在父子进程中是 "复制关系",修改子进程的全局变量不会影响父进程;
-
代码示例(进程守护核心): c
运行
pid_t pid = fork(); if (pid == 0) { // 子进程逻辑:执行目标程序 execl("./hello", "./hello", NULL); exit(1); // execl失败才执行 } else if (pid < 0) { // fork失败处理 perror("fork error"); exit(1); } // 父进程逻辑:继续执行监控代码
-
2. getpid ()/getppid ():获取进程 PID
getpid():返回当前进程的 PID;getppid():返回当前进程的父进程 PID;- 作用:调试时确认父子进程关系,比如打印父 / 子进程 PID 验证独立性。
3. execl ():进程替换
- 函数原型 :
int execl(const char *path, const char *arg, ... /* (char *)NULL */); - 核心作用 :将当前进程的代码、数据、栈完全替换为指定程序,从新程序的
main()开始执行。 - 关键规则 :
- 参数必须以
NULL结尾(如execl("/bin/ls", "ls", "-l", NULL)); - 成功:当前进程代码被完全替换,原代码后续逻辑永远不执行;
- 失败:返回 -1,执行后续代码(需打印错误并退出);
- 路径要求:必须指定可执行文件的完整路径(或当前目录相对路径)。
- 参数必须以
4. 进程终止
- 正常终止 :
- 子进程执行完
main()或调用exit(int status)(status 为退出码,0 = 正常,非 0 = 异常); _exit(int status):直接终止进程,不刷新缓冲区(区别于exit())。
- 子进程执行完
- 异常终止 :被信号杀死(如
kill <PID>、段错误、断言失败)。
5. wait ()/waitpid ():监控并回收子进程
(1)wait ():阻塞等待子进程退出
- 原型 :
pid_t wait(int *wstatus); - 特点:父进程阻塞,直到任意一个子进程退出,回收其资源并返回退出子进程的 PID;
- 缺点:无法指定监控某个子进程,也无法非阻塞监控。
(2) waitpid():灵活监控子进程
函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
核心参数
-
pid:指定要监控的子进程 PID-1:监控任意子进程(等价于wait())> 0:监控指定 PID 的子进程0:监控与当前进程同组的所有子进程< -1:监控进程组 ID 等于|pid|的所有子进程
-
wstatus:存储子进程退出状态的指针- 可通过宏
WIFEXITED、WEXITSTATUS、WIFSIGNALED等解析子进程的退出原因和状态码 - 若不需要状态,可传入
NULL
- 可通过宏
-
options:等待模式选项0:阻塞等待,直到指定子进程终止WNOHANG:非阻塞模式,立即返回,不等待子进程终止WUNTRACED:同时捕获子进程停止(未终止)的状态WCONTINUED:捕获子进程从停止状态恢复运行的状态(Linux 特有)
返回值(核心)
1. 调用成功
- 返回被捕获的子进程 PID (正数)
- 子进程正常退出、被信号终止或停止时,
waitpid成功回收其状态,返回其 PID - 若
pid=-1,则返回任意一个已退出 / 停止子进程的 PID
- 子进程正常退出、被信号终止或停止时,
2. 非阻塞模式(options = WNOHANG)
- 返回
0:子进程仍在运行,未退出、未停止,且无状态可回收 - 返回 子进程 PID:子进程已退出 / 停止,成功回收其状态
3. 调用失败
- 返回
-1,并设置全局变量errno表示错误原因,常见场景:ECHILD:没有可等待的子进程(子进程已全部回收或不存在)EINTR:调用被信号中断EINVAL:options参数无效
补充说明
- 当
options=0(阻塞模式)时,不会返回 0,它会一直阻塞直到子进程终止或调用失败。 - 子进程退出后,若未被父进程通过
waitpid回收,会变成僵尸进程(zombie),占用系统资源。 - 父进程退出后,未被回收的子进程会被
init进程(PID=1)接管并自动回收。
-
实战场景(进程守护监控) :
c
运行
while (1) { int status; pid_t result = waitpid(child_pid, &status, WNOHANG); if (result > 0) { // 子进程已退出,重启子进程 printf("子进程退出,重启...\n"); fork() + execl(); // 重新创建子进程 } else if (result == -1) { perror("waitpid error"); exit(1); } usleep(10*1000); // 10ms检查一次,降低CPU占用 }
四、实战核心逻辑梳理
- 父进程调用
fork()创建子进程,子进程通过execl()执行目标程序(如./hello); - 父进程不退出,进入
while(1)循环,通过waitpid(pid, &status, WNOHANG)非阻塞监控子进程; - 子进程正常运行时,
waitpid返回 0,父进程每隔 10ms 重复检查; - 子进程退出时,
waitpid返回子进程 PID(>0),自动回收僵尸进程、释放 PID; - 父进程调用
fork()+execl()重启子进程,更新监控的 PID,循环往复; - 全程父子进程独立并行:父进程只负责监控重启,子进程只负责运行目标程序,互不干扰。
五、关键易错点总结
execl()成功后,子进程代码被完全替换,原代码后续逻辑永不执行;- 僵尸进程的核心解决方式是父进程调用
wait()/waitpid(),而非等待系统自动释放; WNOHANG是实现 "非阻塞监控" 的关键,无此参数则父进程会阻塞到子进程退出;fork()创建的是独立进程(非线程),父子进程有独立 PID 和资源,线程则共享进程资源;- 父进程的
main()逻辑(如监控循环)会持续执行,不受子进程execl()/ 退出的影响。