Linux 进程回收的系统调用(wait、waitpid)
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。
父进程可以通过调用 wait
或 waitpid
得到它的退出状态同时彻底清除掉这个进程。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
wait 系统调用
- 函数接口
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
-
函数功能:等待任意一个子进程结束,如果任意一个子进程结束,则会回收子进程的PCB块
-
参数:
status
:进程退出时的状态信息,传入的是int*
,传出参数
- 返回值
- 调用成功,返回被回收的子进程ID
- 调用失败,返回-1(所以子进程结束 / 调用函数出错)
调用 wait
函数,则进程会被挂起(阻塞),直到一个子进程退出,或者收到一个不能被忽略的信号,进程才会被唤醒。如果没有子进程,函数立刻返回 -1
。如果子进程都结束,函数仍立刻返回 -1
。
示例1:僵尸进程
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid;
// 创建 5 个子进程
for (int i = 0; i < 5; i++){
pid = fork();
if (pid == 0){ // 子进程中不再创建新的子进程
break;
}
}
if (pid > 0){ // 父进程
while(1){
printf("I am parent process: pid = %d\n", getpid());
sleep(1);
}
}
else if (pid == 0){ // 子进程
printf("child: pid = %d\n", getpid());
}
return 0;
}
编译执行,子进程在执行输出语句后结束,而父进程则仍在执行。
查看进程状态,可以看到5个子进程处于僵尸状态
示例2:wait 阻塞
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid;
// 创建 5 个子进程
for (int i = 0; i < 5; i++){
pid = fork();
if (pid == 0){ // 子进程中不再创建新的子进程
break;
}
}
if (pid > 0){ // 父进程
while(1){
printf("I am parent process: pid = %d\n", getpid());
int ret = wait(NULL); // wait 阻塞,等待子进程退出
if (ret == -1){ // 所有子进程退出,返回 -1,则退出循环
break;
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0){ // 子进程
while(1){ // 子进程一直执行
printf("child: pid = %d\n", getpid());
sleep(1);
}
}
return 0;
}
编译执行,父进程在 wait
处阻塞,等待子进程结束,回收子进程。如果在另一个终端,kill 某一个子进程如kill -9 83215
,则wait
函数会回收该子进程,输出 child die, pid = 83215
。
示例3:返回进程状态
宏 | 含义 |
---|---|
WIFEXITED(status) | 非0,进程正常退出 |
WEXITSTATUS(status) | 如果上宏为真,获取进程退出的状态(exit的参数) |
WIFSIGNALED(status) | 非0,进程异常终止 |
WTERMSIG(status) | 如果上宏为真,获取使进程终止的信号编号 |
WIFSTOPPED(status) | 非0,进程处于暂停状态 |
WSTOPSIG(status) | 如果上宏为真,获取使进程暂停的信号的编号 |
WIFCONTINUED(status) | 非0,进程暂停后已经继续运行 |
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid;
// 创建 5 个子进程
for (int i = 0; i < 5; i++){
pid = fork();
if (pid == 0){ // 子进程中不再创建新的子进程
break;
}
}
if (pid > 0){ // 父进程
while(1){
printf("I am parent process: pid = %d\n", getpid());
int st;
int ret = wait(&st);
if (ret == -1){ // 所有子进程退出,返回 -1,则退出循环
break;
}
// 是否正常退出
if (WIFEXITED(st)){
printf("退出状态码:%d\n", WEXITSTATUS(st));
}
// 是否为信号中断
if (WIFSIGNALED(st)){
printf("信号中断: %d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0){ // 子进程
printf("child: pid = %d\n", getpid());
sleep(1);
}
return 0;
}
5个子进程在执行输出后结束,wait
依次回收子进程资源,在所有子进程退出后,父进程的wait
返回 -1
,退出循环,结束父进程。
waitpid 系统调用
- 函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
-
函数功能:
waitpid
回收指定进程号的子进程。可以指定是否阻塞。 -
参数
- pid > 0:某个子进程的pid
- pid = 0:回收当前进程组的所有子进程
- pid = -1:回收所有子进程,相当于调用
wait()
- pid < -1:等待任意进程组的组ID的绝对值,回收指定进程组的子进程。
- options:设置阻塞(0) / 非阻塞(
WNOHANG
)
- 返回值
>0
:返回子进程id=0
:option=WNOHANG
表示还有子进程没有退出-1
:表示错误 / 没有子进程
示例4:wait(&st)
等价于 waitpid(-1, &st, 0)
,-1
代表回收所有子进程,0
表示阻塞等待
编译执行,与示例3结果相同,父进程阻塞等待,在子进程执行结束,回收子进程。
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid;
// 创建 5 个子进程
for (int i = 0; i < 5; i++){
pid = fork();
if (pid == 0){ // 子进程中不再创建新的子进程
break;
}
}
if (pid > 0){ // 父进程
while(1){
printf("I am parent process: pid = %d\n", getpid());
int st;
int ret = waitpid(-1, &st, 0); // 等价于 wait(&st);
if (ret == -1){ // 所有子进程退出,返回 -1,则退出循环
break;
}
// 是否正常退出
if (WIFEXITED(st)){
printf("退出状态码:%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)){
printf("信号中断: %d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
}
else if (pid == 0){ // 子进程
printf("child: pid = %d\n", getpid());
sleep(1);
}
return 0;
}
示例5:非阻塞waitpid
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid;
// 创建 5 个子进程
for (int i = 0; i < 5; i++){
pid = fork();
if (pid == 0){ // 子进程中不再创建新的子进程
break;
}
}
if (pid > 0){ // 父进程
while(1){
printf("I am parent process: pid = %d\n", getpid());
sleep(1);
int st;
int ret = waitpid(-1, &st, WNOHANG);
if (ret == -1){ // 所有子进程退出,返回 -1,则退出循环
break;
}
else if (ret == 0){ // 还有其他子进程在执行
continue; // 父进程进行下一个判断
}
else if (ret > 0){ // 某个子进程退出,输出状态信息
// 是否正常退出
if (WIFEXITED(st)){
printf("退出状态码:%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)){
printf("信号中断: %d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
}
}
}
else if (pid == 0){ // 子进程
while(1){
printf("child: pid = %d\n", getpid());
sleep(1);
}
exit(0);
}
return 0;
}
在另一个终端 kill -9 子进程号
,则父进程会输出中断信号
今天介绍了两个进程回收的两个系统调用 wait
、waitpid
,其区别是wait
是阻塞模式等待,waitpid
可以设置阻塞 或者 非阻塞,还可以设置回收哪个/哪些进程。
一键三连是对我的支持与鼓励!欢迎关注编程小镇,每天学一点新姿势😄。