进程等待

文章目录

  • 一、进程的结果
  • 二、进程等待

一、进程的结果

在现实生活中找别人帮忙办事,别人同意帮忙之后,会反馈给自己的结果无非就是三种:

  1. 别人把事办完了,结果是自己想要的
  2. 别人把事办完了,由于办事的方法错误,导致结果并不是自己想要的
  3. 别人办事时遇到阻碍了,没办法继续完成这件事情

同样的,进程创建出来也是用来帮忙做事的,进程运行完成后,进程的结果有三种情况:

  • 进程运行正常,结果正确
  • 进程运行正常,结果错误
  • 进程运行异常

对于进程运行正常而言,我们可以通过进程的退出码,来判断进程的运行结果是否正常

进程可以通过 main 函数中 return int 或者 任意代码处调用的 exit(int) 函数 正常退出,其中 return 返回的值和 exit 函数的参数,即为进程的退出码

echo $? 可以查看最近一次运行的程序的退出码

#include 
#include 
#include 

int main()
{
    // 计算
    printf("begin ...\n");
    int sum = 0;
    for (int i = 1; i <= 100; ++i) sum += i;
    sleep(1);
    printf("end, result: %d\n", sum);

    exit(11);  // 或者 return 11 表示运行正常,结果错误
    // return 0; // 或者 exit(0) 表示运行正常,结果正确
}

进程的退出码为 0 表示结果正确
进程等待_第1张图片

进程的退出码非 0 表示结果错误
进程等待_第2张图片

打印错误码对应的错误信息

#include 
#include 
#include 
#include 

int main()
{
    for (int i = 0; i < 255; ++i)
        printf("errno %d : %s\n", i, strerror(i));
    
    return 0;
}

这里只截取了一部分,感兴趣的读者可以自行运行查看
进程等待_第3张图片

系统调用 _exit(int) 也可以结束进程,与库函数 exit(int) 的关系是:库函数 exit(int) 会先进行关闭文件流,冲刷缓冲区等操作,然后再调用 _exit(int) 结束进程

#include 
#include 
#include 
#include 

int main()
{
    printf("hello world");

    // 系统调用 _exit(int) 不会关闭文件流,冲刷缓冲区等,直接结束进程
    _exit(0);

    // 库函数 exit(int) 会关闭文件流,冲刷缓冲区等,在结束进程
    // exit(0);
}

exit(int) 函数会冲刷缓冲区,所以打印了 hello world
进程等待_第4张图片

_exit(int) 函数直接结束进程,不会打印 hello world
进程等待_第5张图片

对于进程运行异常,是由于进程收到了操作系统的信号,参考

二、进程等待

当子进程先于父进程退出时,子进程会处于僵尸状态,为了回收子进程的资源,防止内存泄漏,以及可以选择性的接收子进程的结果,父进程需要等待子进程

系统调用 wait / waitpid,头文件 sys/types.h 和 sys/wait.h

  • pid_t wait(int* status),阻塞式等待任意一个子进程(如果没有子进程退出,父进程会阻塞在 wait 函数)

返回值:等待成功返回子进程的 pid,出错返回 -1,并且 errno 被设置为相应的出错信息

参数:status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    // 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d\n我还剩余 %d s\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(0);
    }
    
	// slepp(10); 模拟父进程的任务

    // 等待子进程
    printf("我是父进程,我的 pid 是: %d, 开始等待子进程 %d ...\n", getpid(), id);
    pid_t cid = wait(NULL);
    assert(cid != -1);
    printf("我是父进程,我的 pid 是: %d, 等待子进程 %d 成功\n", getpid(), cid);

    return 0; 
}

父进程阻塞在 wait 函数处
进程等待_第6张图片

加上 sleep(10) 之后,子进程会先进入僵尸状态,父进程调用 wait 回收资源后,子进程的僵尸状态随之消失
进程等待_第7张图片

  • pid_t waitpid(pid_t pid, int* status, int options)

返回值:等待成功返回子进程的 pid,出错返回 -1

参数:

  • pid 如果设置为 -1,表示等待任意一个子进程,pid 如果大于 0,则表示等待指定 pid 的子进程
  • status 为输出型参数,用于存储子进程的退出码,如果不关心子进程的退出码,可以设置为 NULL
  • options 如果设置为 0,表示阻塞式等待,如果设置为 WNOHANG,则表示非阻塞等待(调用时子进程还未退出,waitpid 函数会返回 0)

进程的结果有三种情况,整数 status 是采用如下位图的方式来存储进程的结果

  • 正常终止:0 ~ 7 位为 0,8 ~ 15 位表示退出码

  • 异常终止:0 ~ 6 位表示收到的信号,第 7 位表示是否为核心转储,8 ~ 15 位没有使用
    进程等待_第8张图片

  • 宏 WIFEXITED(status),用于判断 status 是否收到信号,即子进程是否运行正常

  • 宏 WEXITSTATUS(status),当子进程运行正常时,用于获取子进程的退出码

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    assert(id != -1);
    if (id == 0)
    {
        // 子进程
        int cnt = 5;
        while (cnt)
        {
            printf("我是子进程,我的 pid 是: %d,我的 ppid 是: %d,我还剩余 %ds\n", getpid(), getppid(), cnt--);
            sleep(1);
        }

        exit(0);
    }
    
    // 父进程
    int status = 0;
    while (1)
    {
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        assert(ret_id != -1);
        if (ret_id > 0)
        {
            // 等待成功
            printf("我是父进程,我的 pid 是: %d,等待子进程 %d 成功\n", getpid(), ret_id);

            // 判断是否是信号导致
            if (WIFEXITED(status))
                printf("子进程 %d 运行正常,退出码: %d\n", ret_id, WEXITSTATUS(status));
            else 
                printf("子进程 %d 运行异常", ret_id);

            break;
        }

        // 父进程的任务
        printf("我是父进程,我的 pid 是: %d, 正在执行任务中 ...\n", getpid());
        sleep(1);
    }

    return 0;
}

父进程非阻塞等待,子进程进入僵尸状态,父进程调用 waitpid 回收资源后,子进程的僵尸状态随之消失
进程等待_第9张图片

wait / waitpid 是如何得到子进程的结果呢?其实就是通过子进程的 task_struct 中的属性得到的

你可能感兴趣的:(linux)