简 述: 这篇继承上一篇,先要自己梳理清楚一下进程相关的知识, 上上一篇的虚拟地址空间和进程控制块 PCB ,以及上一篇的进程相关知识,带着思考来学习 孤儿进程、僵尸进程、 以及进程回收 wait()waitpid()相关的概念。

[TOC]


本文初发于 “偕臧的小站“,同步转载于此。


编程环境:

  💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

  💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


孤儿进程:

  • 父进程创建子进程

  • 父进程死了,子进程还活着,该进程叫孤儿进程(Orphan Process)

  • 在传统的 Linux 系统中,孤儿进程被 init 进程领养,init 进程成为孤儿进程的父亲

    • init 进程这样做,主要是为了释放子进程占用的系统资源。
    • 因为进程结束之后,能够释放用户控空间;但是释放不了系统空间的 PCB,这块资源必须由父进程释放。
  • 写一个代码例子:

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        pid_t pid = fork();  //创建子进程
    
        if (pid == 0) { //存活时间大于 1s(此时父进程已经执行完成销毁), 为孤儿子进程
            sleep(1);
            printf("this is a child process, pid = %d  ppid = %d\n", getpid(), getppid());  
        } else if (pid > 0) {  //父进程
            printf("this is a parent process, pid = %d  ppid = %d\n", getpid(), getppid());  //执行完后,父进程就结束了
        }
    
        return 0;
    }
  • 代码分析:

    当父进程创建子进程之后,很快的就执行完后就结束销毁自己(小于 1s 内);因为子进程的生存时间大于 1s,故此时它为孤儿进程(下图可证明:此时其父进程号是 1 而非 17441),然后被系统的 init 进程领养,帮助销毁子进程的 PCB;

  • 运行结果:


僵尸进程:

  • 父进程创建子进程

  • 子进程死了,父进程还活着,且不去释放子进程的 PCB,该子进程就变成了僵尸进程(Zombie Process)。

  • 僵尸进程是一个已经死掉的进程。

  • 写一个代码例子:

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
        pid_t pid = fork();  //创建子进程
    
        if (pid == 0) { //(此时子进程已经执行完成销毁), 而父进程没有时间去销毁这个子进程的 pcb,故为僵尸进程
            printf("this is a child process, pid = %d  ppid = %d\n", getpid(), getppid());  
        } else if (pid > 0) {  //父进程
            while (true)
            {
                sleep(1);
               printf("this is a parent process, pid = %d  ppid = %d\n", getpid(), getppid());  //执行完后,父进程就结束了
            }
        }
    
        return 0;
    }
  • 代码分析:

    父进程创建子进程之后,子进程在极短的时间内执行完所有代码后,其用户区的资源被释放,其系统区的 pcb 等待其父进程来释放;但是父进程一直在忙其它的时间,没有做处理,释放自己成的 PCB,在下图左侧还没有运行 ctrl c 的时候,使用 ps 在管道里面查询该子进程的号 5563,可以发现其已经被标注为死亡的进程(其 Z+ 中的是单词僵尸 Zombie 的首字母)。

  • 运行结果:


进程回收:

wait():

pid_t wait(int *stat_loc);
  • 作用:

    • 阻塞并且等待子进程退出
    • 回收子进程残留的资源
    • 获取子进程结束的状态(退出原因)
    • 每次只能够回收一个进程,调用一次 wait() 也只能回收一个进程
  • 参数:

    • WIFEXITED(status): 为非 0 –> 进程正常退出
      • WEXITSTATUS(status): 如果这个宏为真,使用此宏 –> 获取进程退出状态(exit / return) 的参数
    • WIFSIGNALED(status): 为非 0 –> 进程异常终止
      • WTERMSIG(status): 如果这个宏为真,使用此宏 –> 获取使进程退出的那个信号的编号。
  • 返回值:

    • 成功:清理掉的子进程 ID
    • 失败:-1(没有子进程)
  • 写一个代码例子验证:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int main(int argc, char *argv[])
    {
        printf("-----开始-----\n");
    
        pid_t pid = fork();  //创建子进程
    
        if (pid == 0) { //(此时子进程已经执行完成销毁), 而父进程没有时间去销毁这个子进程的 pcb,故为僵尸进程
            while (true) {
                sleep(2);
                printf("this is a child process, pid = %d  ppid = %d\n", getpid(), getppid());  
            }
        } else if (pid > 0) {  //父进程
            //sleep(1);
            
            int status = 0;
            pid_t wpid = wait(&status);  //回收子进程
    
            if (WIFEXITED(status))   //判断是否正常退出
                printf("退出值是 val = %d\n", WEXITSTATUS(status));
    
            if (WIFSIGNALED(status))   //判断是被信号杀死
                printf("通过信号退出 val = %d\n", WTERMSIG(status));
    
            printf("this is a parent process, pid = %d  ppid =  %d\n", getpid(), getppid());  //执行完后,父进程就结束了
        }
    
        printf("+++++结束+++++\n");
    
        return 9;  //若是注释掉 12 和 15 行代码,会显示父父进程的 退出值是 9
    }
    • 代码分析:

      代码是在 MacOS(Unix)系统上面运行的;

      若是注释掉 12 和 15 行代码,则会运行第一个宏,可以看到进程结束的返回值是 9 (人为随机指定的一个数值,return 9);运行结果见下面第一个图。

      而不注释掉 12 和 15 行代码;运行程序后,执行 kill 进程号 杀死子进程, 则会执行第二个宏,显示是被信号(15)所杀「若果是 Linux 运行,这里会显示信号是 9」;运行结果见下面第二个图。

    • 运行截图:


waitpid():

pid_t waitpid(pid_t pid, int *stat_loc, int options);
  • 作用:

    同 wait(),但是可以指定要回收的进程 pid 号,且能够指定是阻塞还是非阻塞模式。

  • 参数:

    • pid:

      pid == -1;回收所有的子进程。与 wait 等效

      pid > 0; 回收指定 pid 的进程

      pid == 0; 回收当前组的所有子进程

      pid < -1; 回收进程的 pid 取反(加减号)

    • status:

      子进程的退出状态,用法同 wait()

    • options:

      设置为 WNOHANG,函数非阻塞,设置为 0,函数阻塞

  • 返回值:

    > 0  //返回清理掉的子进程 ID
    -1   //无子进程
    =0   //参数 3 为 WHOHANG,且子进程正在运行

下载地址:

10_orphan_zombie_process

欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。