Linux 回收子进程 wait/waitpid函数
前提
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终 止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在 Shell 中用特殊变量$?查看,因为 Shell 是它的父进程,当它终止时 Shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。
描述
wait for process to change state;
等待进程改变状态;
A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a “zombie” state;
等待子进程终止,回收任意已终止的子进程,释放其占用的资源;
如果有子进程存在且未终止,wait使父进程处于挂起状态;
waitpid的第三个参数控制是否等待,如果不等待,子进程终止后会处于僵尸状态;
wait
- 阻塞等待子进程退出;
- 回收子进程残留资源;
- 获取子进程结束状态(退出原因);
头文件
#include <sys/types.h>
#include <sys/wait.h>
函数签名
pid_t wait(int *status);
参数
status:
int *类型的传出参数,表示被回收子进程的终止原因(正常终止(最后的返回值)或被信号杀死(被什么信号杀死));
传NULL表示,不关系终止原因;
返回值
成功:
返回被回收进程的id;
失败:
返回-1;
并设置errno;
示例
父进程运行到wait,等待10秒,回收子进程(多个子进程,父进程运行几个wait随机回收几个子进程),结束;
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid, wpid;
pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}
if(!pid){
sleep(10);
exit(1);
}
wpid = wait(NULL);
if(wpid == -1){
perror("wait");
exit(1);
}
printf("wait for %d\n", wpid);
return 0;
}
status(终止原因)
可使用 wait 函数传出参数 status 来保存进程的退出状态;
借助宏函数来进一步判断进程终止的具体原因;
宏函数可分为如下三组:
WIFEXITED(正常终止)
WIFEXITED(status)
正常终止,返回true;
WEXITSTATUS(status)
如果正常终止,通过该宏获取进程终止时的状态;
示例
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid, wpid;
int status;
pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}
if(!pid){
sleep(10);
return 20;
}
wpid = wait(&status);
if(wpid == -1){
perror("wait");
exit(1);
}
printf("wait for %d\n", wpid);
if(WIFEXITED(status)){
printf("return val is %d\n", WEXITSTATUS(status));
}
return 0;
}
WIFSIGNALED(信号杀死)
WIFSIGNALED(status)
进程非正常终止,返回true;
WTERMSIG(status)
当WIFSIGNALED(status)为true时,获取终止进程的信号的编号;
示例
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid, wpid;
int status;
pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}
if(!pid){
sleep(30);
exit(1);
}
wpid = wait(&status);
if(wpid == -1){
perror("wait");
exit(1);
}
printf("wait for %d\n", wpid);
if(WIFSIGNALED(status)){
printf("the child process terminated by %d\n", WTERMSIG(status));
}
return 0;
}
kill signal
用 kill终止对应的进程,默认 15号信号;
kill [options] <pid> [...]
kill -9 1644 // 用 9号信号终止 1644进程
查看信号
kill -l
非实时信号: 34号往前(一般用的);
实时信号: 34号往后;
WIFSTOPPED(进程暂停)
waitpid
函数签名
pid_t waitpid(pid_t pid, int *status, int options);
参数
pid
pid | meaning |
---|---|
< -1 | 绝对值表示,对应进程组id,回收该组 id中,调用进程(父进程)的任意子进程 |
== -1 | 回收任意子进程,相当于wait |
== 0 | 回收和调用进程(父进程)同一进程组的任意子进程 |
> 0 | 回收指定pid的子进程 |
status
终止原因;
options
options | meaning |
---|---|
0 | 表示挂起方式回收,只要有子进程,至少等待一个子进程结束,回收子进程,返回 |
WNOHANG | 非挂起方式,如果没有子进程已经结束了,立即返回 |
返回值
成功:
返回被回收进程的id;
如果子进程存在,且均没有结束,并且 options为 WNOHANG,返回 0;
失败:
返回-1,并设置errno;
示例
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid, wpid, rpid;
int status;
for(int i=0; i<4; i++){
pid = fork();
if(pid == -1){
perror("fork");
exit(1);
}
if(i == 2 && pid){
wpid = pid;
}
if(!pid){
sleep(10);
return 10;
}
}
rpid = waitpid(wpid, &status, 0); // 挂起
// rpid = waitpid(wpid, &status, WNOHANG); // 非挂起
if(rpid== -1){
perror("wait");
exit(1);
}
if(!rpid){
printf("No hang on and there is no exited child process.\n");
exit(1);
}
printf("wait for %d\n", wpid);
if(WIFEXITED(status)){
printf("return val is %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){
printf("the child process terminated by %d\n", WTERMSIG(status));
}
return 0;
}
学习笔记
- 一次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环;
- 当进程终止时,操作系统的隐式回收机制会:
a. 关闭所有文件描述符 ;
b. 释放用户空间 分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。
2020/07/25 13:59
@luxurylu