Linux系统编程 —— 回收子进程 wait

孤儿进程

一个父进程运行结束,其某些子进程还在运行,这些子进程则为孤儿进程。这些孤儿进程将被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都是父进程调用,回收子进程的,误区是兄弟间回收。

 

扩展阅读:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值