「Linux 系统编程」孤儿进程、僵尸进程、wait 和 waitpid 子进程回收
1 孤儿进程和僵尸进程
1.1 孤儿进程
孤儿进程:父进程已经终止或退出,但仍然在运行中的子进程。
通常情况下,Linux 系统通过 init 进程(进程号 PID 为 1)来自动接管这些孤儿进程,成为它们的新父进程。
1 |
|
一开始,创建的子进程 PID 为 24919
,其父进程 PID 为 24918
。之后父进程退出,子进程还在执行,子进程 24919
变为孤儿进程,被新的父进程 2145
收养。
最后可以通过 kill
命令杀死一直进程的子进程。
在我的电脑中,2145
对应的进程信息为:
1 |
|
示例中,孤儿进程被 /lib/systemd/systemd --user
进程收养。用户级 systemd 进程(示例中的 PID=2145)本身是由系统级 init/systemd(PID=1)启动的,这形成了一个层次化的进程管理结构。
1.2 僵尸进程
僵尸进程:已经执行完毕但仍在进程表中占用位置的进程
子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。(每个进程结束后都必然会经历僵尸态,只是时间长短上有差别)
子进程终止时,子进程残留资源 PCB 存放于内核中,PCB 记录了进程结束原因,进程回收就是回收 PCB。
1 |
|
可以看到执行到后面子进程的状态变为 [zombie] <defunct>
,表示子进程已经处于终止的状态。
最后可以通过 kill
命令杀死父进程。
2 wait() 函数
在 Linux 中,当一个进程创建子进程(通常使用 fork())后,子进程会独立运行。父进程需要通过 wait()
来 回收子进程资源、获取子进程状态:
-
回收子进程资源:当子进程终止时,不会立即从系统中完全消失,而是会变成一个僵尸进程。
父进程通过
wait()
函数来获取这些信息并彻底释放子进程占用的系统资源(如进程号、进程表项等)。如果父进程不进行回收,僵尸进程会一直存在。 -
获取子进程状态:父进程可能需要暂停执行,等待一个或所有子进程结束,然后再继续。
或者,父进程需要知道子进程是正常退出(及其退出码)还是被信号终止。
2.1 wait() 函数原型及参数
wait()
:等待 任意一个 子进程结束。
函数原型:
1 |
|
参数:
status
:一个指向整型的指针,用于 存储子进程的退出状态信息。如果不关心子进程的退出状态,可以传入NULL
。
返回值:
- 成功:返回一个被等待回收的子进程的 PID
- 失败:返回
-1
,设置 errno。(例如,当前进程已经没有需要等待的子进程)
工作方式:
-
wait()
函数会 阻塞(block)父进程的执行,直到它的任意一个子进程终止。 -
一旦有子进程终止,
wait()
会立即回收该子进程,并将其退出状态信息填入status
指向的变量中,然后返回该子进程的 PID。
2.2 子进程 退出状态 和 异常终止信号
一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但其 PCB 还保留着,内核在其中保存了一些信息:
- 如果是正常终止则保存着 退出状态;
- 如果是异常终止则保存着 导致该进程终止的信号。
可以使用一系列宏来解析 wait()
返回的状态信息:
-
WIFEXITED(status)
: 如果子进程正常退出,返回真 -
WEXITSTATUS(status)
: 如果 WIFEXITED 为真,提取子进程的退出码 -
WIFSIGNALED(status)
: 如果子进程因信号而终止,返回真 -
WTERMSIG(status)
: 如果 WIFSIGNALED 为真,提取导致终止的信号编号 -
WIFSTOPPED(status)
: 如果子进程当前已停止,返回真 -
WSTOPSIG(status)
: 如果 WIFSTOPPED 为真,提取导致停止的信号编号
2.2 wait() 代码示例
1 |
|
3 waitpid() 函数
3.1 waitpid() 函数原型及参数
waitpid()
提供了更加精确的 wait 控制,是现在开发更加常用的子进程回收方式。
函数原型:
1 |
|
参数:
-
pid
:指定要等待回收的子进程的 PID。- 小于
-1
:等待进程组 ID 等于pid
绝对值的任何一个子进程。 -1
:等待 任意一个 子进程,行为与wait()
相同。0
:等待与父进程 同一个进程组 的任何一个子进程。- 大于
0
:等待进程 ID 等于pid
的特定子进程。
- 小于
-
status
:同wait()
,用于 存储状态信息。 -
options
:修改函数的行为,可以是一个或多个选项的按位或(|)。0
:默认行为,与wait()
一样阻塞。WNOHANG
:非阻塞模式。如果没有子进程退出,立即返回0
,而不是阻塞父进程。WUNTRACED
:除了返回已终止的子进程信息外,还返回因信号而停止(stopped)的子进程信息。WCONTINUED
:返回因收到 SIGCONT 信号而恢复执行(continued)的子进程信息。
返回值:
-
成功:返回状态发生变化的子进程的 PID。
-
失败:返回
-1
(并设置 errno)。 -
如果使用了
WNOHANG
且没有子进程退出,则返回0
。
waitpid()
的好处:
-
可以 等待特定的子进程。
-
可以非阻塞(WNOHANG):父进程可以周期性地检查子进程是否结束,而不必挂起。这在事件循环(如网络服务器)中非常有用。
-
可以获取更多状态(停止、恢复)。
3.2 waitpid() 回收指定的子进程
示例中,循环创建 5 个子进程,指定回收第 3 个子进程。
1 |
|
3.3 waitpid() 回收多个子进程
示例中,循环创建 5 个子进程,需要回收所有的子进程。
使用 非阻塞方式(WNOHANG)回收所有子进程,循环检查是否有子进程退出。当检查到有子进程退出时,回收该子进程;当暂时没有子进程退出时(waitpid 返回 0),父进程可以暂时去处理其他事。
1 |
|