【Linux】:进程等待

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux进程等待的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

​ 

1. 进程等待

通过系统调用接口(wait/waitpid)的方式,让父进程对子进程进行资源回收的等待过程。

2. 进程等待的必要性

1. 在之前的僵尸进程这一章节提到过,子进程在退出之后,父进程不进行回收资源,会造成内存泄露的问题,简而言之就是要解决僵尸进程带来的内存泄露问题。

2. 父进程创建子进程的目的就是为了让子进程做一些事情,那么父进程是需要知道子进程把事情完成的怎么样了,就需要通过等待来获取进程退出的信息(两个数字),获取这两个数字并不是必须的,但是系统需要提供这样的基础功能!

3. 进程等待的方法 

可以使用系统调用接口:wait()或者waitpid()

3.1 wait

wait可以等待任意一个进程。 

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

pid_t wait(int*status);

//返回值:
//成功返回被等待进程pid,失败返回-1。

//参数:
//输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

我们先不关心它的参数,直接设置为NULL,先来看一下能否回收掉子进程的僵尸问题:

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        pid_t rid = wait(NULL);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, \n", getpid(), rid);
        }
        sleep(5);
    }
    return 0;
}

上面这段代码我们期望看到的效果是:刚开始有两个进程在运行,5s之后子进程变成了僵尸状态,再过5s,父进程等待回收子进程,就剩下一个进程在运行。

如果子进程不退出我们就开始等待会发生什么呢?

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        //sleep(10);
        printf("wait befor\n");
        pid_t rid = wait(NULL);
        printf("wait after\n");

        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, \n", getpid(), rid);
        }
        sleep(5);
    }
    return 0;
}

通过实验可以看到,如果子进程没有退出,那么父进程就必须在wait上进行阻塞等待,直到子进程结束,wait自动回收并返回!

一般而言,父子进程谁先运行并不确定,但可以确定的是,父进程一定是最后退出的。

3.2 waitpid 

waitpid可以等待指定的进程。

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

pid_ t waitpid(pid_t pid, int *status, int options);

//返回值:
//当正常返回的时候waitpid返回收集到的子进程的进程ID;
//如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
//如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

pid:等待指定的进程id,设置为-1表示等待任意进程。

*status:获取子进程的状态,不关注可以设置为NULL。

optons:等待的方法,设置为0默认为阻塞式等待。

3.2.1 *status 

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

通过等待的方式解决了僵尸进程导致的内存泄漏问题,那么如何获取子进程的退出结果呢?

*status是输出型参数,我们可以定义一个status,然后传递给wait/waitpid,然后将返回信息带给我们。

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        sleep(1);
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        // pid_t rid = wait(NULL);
        int status = 0;  // 定义退出信息
        pid_t rid = waitpid(id, &status, 0);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, exit code: %d\n", getpid(), rid, status);
        }
        sleep(5);
    }
    return 0;
}

可以看到明明我们设置的退出码是10,为什么打印出了2560呢?

status是一个整形指针变量,有32个比特位,我们不能简单的当做整形来看待,要当做位图来看待,我们研究status只观察低16位:

 

正常结束:次低8位(8 ~ 15)表示的是进程退出码;

异常终止:低8位(0 ~ 7)表示的是异常终止信号。

正常结束时,退出码为10对应的status就是0000 1010 0000 0000转化为10进制就是2560

所以我们要正确的得到进程的退出码需要给status右移8位,按位于0xFF((status >> 8) & 0xFF);

正确得到进程终止信号需要给status按位于0x7F(status & 0x7F)。

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(10); // 退出码设置为10
    }
    else
    {
        // parent
        sleep(10);
        // pid_t rid = wait(NULL);
        int status = 0; // 定义退出信息
        pid_t rid = waitpid(id, &status, 0);
        if (rid == id) // 等待成功
        {
            printf("wait success, pid: %d, rpid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status & 0x7F, (status >> 8) & 0xFF);
        }
        sleep(5);
    }
    return 0;
}

关于status参数对退出码的提取还有一种简便的方式:现在外面定义status。

WIFEXITED(status)   // 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status) // 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
if (WIFEXITED(status))  // 正常退出
{
    printf("child process normal quit, exit code : %d\n", WEXITSTATUS(status));
} 
else                    // 异常终止 
{
    printf("child process quit except!\n");
}

① 当一个进程异常终止,此时的退出码就毫无意义。

② 如何判断有没有收到信号呢?只要终止信号为0(终止信号范围在1 ~ 64),就表示程序运行正常。

为什么不用全局变量来获取子进程的退出信息呢?而要用系统调用?

当父子进程要对全局变量写入时,会发生写时拷贝,又因为进程具有独立性,所以父进程是无法直接获得子进程的退出信息的。 

3.2.2 options

该参数表示等待的方式:

0表示阻塞式等待;

WNOHANG表示非阻塞式等待。

通过一个例子来理解一下阻塞与非阻塞等待:

如果我们要下载一个游戏,那么在下载的这段时间我们就啥也不干,直勾勾的盯着它,直到它下载好,这种行为就叫做阻塞式等待;

当游戏下载的这段时间,我们可以先看看它下没下好,没下好我们就可以看看视频,听听音乐,然后再看看它下没下好,没下好我们就再干干别的事情,直到它下载好,这种行为就叫做非阻塞式等待。

对比等待进程:

  • 阻塞式调用:使用wait/waitpid等待子进程时,如果子进程不退出,wait/waitpid不返回。
  • 非阻塞式调用:使用waitpid等待子进程时,如果我们等待的条件不满足,不阻塞,而是直接返回!

非阻塞等待往往需要重复调用,以非阻塞轮询的方案进行进程等待,它的好处就在于,当我们进行进程等待的过程中,可以顺便做一下自己占据时间并不多的事情。

当options设置为WNOHANG时,waitpid的返回值有三种情况:

  • rid > 0:等待成功
  • rid == 0:等待成功,但是对方还有退出
  • rid < 0:等待失败(pid填写错误)

下面我们就来用代码的方式来演示一下非阻塞等待:

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

void Worker()
{
    int cnt = 5;
    while (cnt--)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(2);
    }
}

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        Worker();
        exit(0); // 退出码设置为10
    }
    else
    {
        while (1) // 非阻塞轮询
        {
            int status = 0; // 定义退出信息
            pid_t rid = waitpid(id, &status, WNOHANG);
            if (rid > 0) // 等待成功
            {
                printf("child quit success, pid: %d, exit sig: %d, exit code: %d\n", getpid(), status & 0x7F, (status >> 8) & 0xFF);
                break;
            }
            else if (rid == 0) // 等待成功,但并未退出
            {
                printf("child is alive, wait again, father do other thing....\n");
                // 可以设置一些任务...
            }
            else // 等待失败
            {
                printf("wait failed!\n");
                break;
            }
            sleep(1);
        }
    }
    return 0;
}

3.3 OS层面的进程等待

 根据冯诺依曼体系结构,wait和waitpid属于系统调用,而进程在OS的内存中运行,我们再进行进程等待的时候,使用系统调用接口,要拿到子进程的退出信息,所以我们在外部定义了一个变量status,当子进程执行完毕之后,OS会执行它的退出程序,将进程的Z状态修改为X状态,并将子进程的的退出码和退出信号写入到它的PCB中,这时就可以通过父进程中的status指针来获取子进程的退出信息。

创建多进程循环fork,等待多进程循环waitpid,waitpid第一个参数设置为-1,表示等待任意一个进程。 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stackY、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值