孤儿进程
一个父进程运行结束,其某些子进程还在运行,这些子进程则为孤儿进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
wait函数
// 成功:清理掉的子进程ID; 失败:-1(没有子进程)
pid_t wait(int *status);
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)
注意:一次调用只结束一个
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2.释放用户空间分配的内存。内核的PCB仍存在,其中保存该进程的退出状态(正常终止 → 退出值; 异常退出 → 终止信号 )。
可使用wait函数传出参数status来保存进程的退出状态,借助宏函数来进一步判断进程终止的具体原因。
宏函数分为以下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态(exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得进程终止的那个信号的编号(使用 kill -l 查看编号)
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得暂停终止的那个信号的编号
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
// 例1
pid_t wpid;
wpid = wait(NULL); // 不管子进程结束状态直接回收
if (wpid == -1) {
perror("wait error");
exit(1);
}
// 例2
pid_t wpid;
int status;
wpid = wait(&status);
if (wpid == -1) {
perror("wait error");
exit(1);
}
// 正常终止
if (WIFEXITED(status)) {
printf("child exit with %d\n", WIFEXITSTATUS(status)); // 获取子进程退出值return / exit
}
// 异常终止
if (WIFSIGNALED(status)) {
printf("child killed by %d\n", WTERMSIG(status));
}
while(wait(NULL)); // 循环回收子进程
waitpid函数
作用同wait,但可以指定 pid 进程清理,可以不阻塞。
// 成功:返回清理掉的子进程ID; 失败:-1(无子进程)
pid_t waitpid(pid_t pid, int *status, int options);
参数 options:
0阻塞
WNOHANG非阻塞。
参数 pid:
>0 回收指定ID的子进程
=-1 回收任意子进程(相当于wait)
=0 回收和当前调用waitpid一个组的所有子进程
<-1 回收指定进程组内的任意子进程(例:-10098表示进程组gpid = 10098的所有子进程)
返回 0:参数options值WNOHANG, 且子进程正在运行。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
非阻塞的情况使用轮询的方式回收。
可以通过杀死父进程的方式回收僵尸进程,因为父进程死亡,僵尸进程就变成孤儿进程被init进程领养,由内核进行回收。
练习:父进程 fork 3个子进程,三个子进程一个调用ps命令,一个调用自定义程序1(正常结束),一个调用自定义程序2(会出段错误)。父进程使用waitpid对子进程进行回收。
pgm1.c
#include <stdio.h>
int main (){
printf("pgm1 is running ...\n");
return 0;
}
pgm2.c
#include <stdio.h>
int main() {
printf("pgm2 is running ...\n");
char *str = "segment error";
str[0] = 'c';
return 0;
}
wait_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
pid_t pid, wpid;
int i;
for (i = 1; i <= 3; i++) {
pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) {
switch(i) {
case 1: execlp("ps", "ps", NULL); break;
case 2: execl("./pgm1", "pgm1", NULL); break;
case 3: execl("./pgm2", "pgm1", NULL); break;
}
break; // 退出循环
}
}
// 阻塞等价方式,清理僵尸进程
// while(wait(NULL) > 0);
// while(waitpid(-1, NULL, NULL) > 0);
// 轮询方式,清理僵尸进程
do{
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid == 0) { // 子进程正在运行
sleep(1);
}
if (wpid == -1) { // 没有子进程则退出循环
break;
}
} while(1);
while(1); // 保持运行...
return 0;
}
编译执行
> gcc -o wait_test wait_test.c
> ./wait_test
运行结果:
刚执行完可以看到僵尸进程pgm1,pgm2
使用命令查看进程状态。发现已经不存在僵尸进程
> ps ajx
坑:
wait和waitpid都是父进程调用,回收子进程的,误区是兄弟间回收。
扩展阅读: