结束进程、等待进程结束

写在前面

本文主要介绍一些系统编程中控制进程运行的函数

在C语言中,通常使用 exit()_exit() 来结束当前进程,而return则用于结束当前函数。

exit

作用:退出当前进程
函数原型:void exit(int value);
头文件:

  • #include <stdlib.h>

参数:

  • value:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。

_exit()

作用:结束调用此函数的进程。
函数原型:void _exit(int value);
头文件:

  • #include <unistd.h>

exit()_exit() 函数功能和用法是一样的,无非时所包含的头文件不一样,还有的区别就是:exit()属于标准库函数,_exit()属于系统调用函数。

从上图可知,通过exit()结束的进程会刷新缓冲区,而通过_exit()退出的则不会

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
    printf("Hello world");//没有换行符,不会刷新缓冲区
    exit(0);  //会输出上述文字
    //_exit(0); //不会输出上述文字
    while(1);  //不让子进程结束
    return 0;
}

return的话就不做过多解释了,C语言里面应该很好理解。

一个进程在终止时会关闭所有的文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果是正常终止则保存退出状态,如果是异常终止则保存导致进程终止的是哪个信号。这个进程的父进程可以调用waitwaitpid函数获取这些信息,然后彻底清除掉这个进程。

wait函数

作用:

  • 阻塞等待子进程退出
  • 回收子进程残留资源
  • 获取子进程退出状态(退出原因)

函数原型:pid_t wait(int *status);

头文件:

  • #include <sys/types.h>
  • #include <sys/wait.h>

参数:

  • status:传出参数,回收进程的状态。

如果参数 status 的值不是 NULL,wait() 就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的。
这个退出信息在一个 int 中包含了多个字段,直接使用这个值是没有意义的,我们需要用宏定义取出其中的每个字段
获取子进程正常终止值:
WIFEXITED(status) --> 为真 -->调用 WEXITSTATUS(status) --> 得到 子进程 退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status) --> 为真 -->调用 WTERMSIG(status) --> 得到 导致子进程异常终止的信号编号。
只有第一个宏为真时,调用第二个宏才有意义

返回值:

  • 成功:返回回收的子进程pid
  • 失败:返回 -1, 设置errno

waitpid

从本质上讲,系统调用 waitpid()wait() 的作用是完全相同的,但 waitpid() 多出了两个可由用户控制的参数 pidoptions,从而为我们编程提供了另一种更灵活的方式。

函数原型:pid_t waitpid(pid_t pid, int *status, int options);

参数:

  • pid:指定回收某一个子进程pid,可选项如下:
    • -1:代表任意回收一个子进程
    • 0:待回收的子进程的pid

    • 0:同组的子进程
  • status:指定回收某一个子进程pid
  • options:指定回收的方式
    • WNOHANG: 非阻塞回收
    • NULL:阻塞回收

返回值:

  • 0 : 表成功回收的子进程 pid

  • =0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
  • -1: 失败。errno

案例

通过wait函数回收子进程

int main(){
    pid_t pid; 
    pid = fork();
    
    if(pid < 0){ 
        perror("fork");
        exit(0);
    } else if(pid == 0) {
        int i = 0;
        for(i = 0;i < 5;i++){
            printf("I am child pid is %d\n",getpid());
            sleep(1);
        }
        exit(2);
    } else {
        int status;
        pid_t pid1 = wait(&status); //阻塞回收
        if(WIFEXITED(status)){
            printf("son process return %d pid is %d\n", WEXITSTATUS(status), pid1);
        }
        printf("This is parent\n");
    }   
    return 0;
}

image.png
从上图的执行结果可以得出,当使用wait函数回收子进程时,一直阻塞等到子进程结束,在继续执行父进程,并且通过宏函数获取到了status返回的进程退出状态。

通过waitpid指定回收第三个创建的子进程

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
    pid_t pid, tmppid; 
    int i = 0;
    for(i = 0;i < 5;i++){
        pid = fork();
        if(pid == 0)
            break;
        if(i == 2)
            tmppid = pid;
    }   
    
    if(i == 5){ 
        sleep(5);
        int status;
        pid_t pid1;
        //waitpid(-1, &status, 0); // 和 wait() 没区别,0:阻塞
        //waitpid(pid, &status, 0); // 指定等待进程号为 pid 的子进程, 0 阻塞
        pid1 = waitpid(tmppid, &status, WNOHANG);//// WNOHANG:不阻塞
    
        printf("This is parent waitpid is %d\n", pid1);

    } else {
        sleep(i);
        printf("I am %dth child pid is %d\n", i+1, getpid());
    }   
    return 0;
}

image.png

补充

wait、waitpid 一次调用,回收一个子进程,想要回收多个必须使用 while 循环

#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
    pid_t pid; 
    int i = 0;
    for(i = 0;i < 5;i++){
        pid = fork();
        if(pid == 0)
            break;
    }   
    
    if(i == 5){ 
        pid_t pid1;
        while( pid1 = wait(NULL)){
            if(pid1 == -1) 
                break;
            printf("This is parent waitpid is %d\n", pid1);
        }
    } else {
        printf("I am %dth child pid is %d\n", i+1, getpid());
    }   
    return 0;
}