[Linux 进程控制(一)] 进程等待

在这里插入图片描述

1、进程等待

1.1 为什么要进程等待

  • 解决子进程僵尸问题带来的内存泄漏问题。
  • 子进程将父进程交给的任务完成的如何,父进程需要通过进程等待的方式,获取子进程退出的信息。本质是获取 退出码和信号编号。

1.2 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏.
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

1.3 进程等待的方法

1.3.1 wait方法

我们首先查看一个wait接口,并简单介绍一下:
在这里插入图片描述
wait这里先不说参数,下面waitpid会谈,可以传NULL。
在这里插入图片描述
我们了解了用法,接下来就开始写代码试试:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        sleep(6); // 不会让父进程立即回收子进程
        // father
        pid_t rid = wait(NULL);
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

以上的测试验证了,进程等待是可以回收僵尸进程的!

我们再来看,如果我们不让父进程回收前休眠,直接就是回收子进程会怎么样,测试一下:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        //sleep(6); // 不会让父进程立即回收子进程
        // father
        // wait before和wait after连着打出来那么就说明直接没有在等待时卡住
        printf("wait before\n");
        pid_t rid = wait(NULL);
        printf("wait after\n"); 
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
我们发现,子进程先跑完才打印的wait after,并且这次子进程没有显示出僵尸状态(变成僵尸立即被回收),这说明:如果子进程没有退出,父进程必须在wait上进行阻塞等待,直到子进程僵尸,wait自动回收,返回!
一般而言,谁先运行不知道,但是父进程一般都是最后退出(wait阻塞等待)!

1.3.2 waitpid方法

还是先了解一下接口再说:
在这里插入图片描述
我们继续写代码试试:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        sleep(6); // 不会让父进程立即回收子进程
        // father
        printf("wait before\n");
        pid_t rid = waitpid(id, NULL, 0); // 等待子进程
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
这里可以看到子进程僵尸状态一下就别回收了。
我们这里讲一下waitpid的第二个参数:它是一个整型指针变量,做输出型参数,os将数值赋值给status。它是将子进程的退出码写入然后带回来。
我们继续写代码测试:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, status: %d\n", getpid(), status);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
我们子进程退出码为10,但是父进程接收后打印出来不是10,而是2560!怎么回事?我们看下一个小节,里面细讲!!!

1.4 获取子进程的status

status是一个int的整数,32位,但是status只取低16位。
在这里插入图片描述

今天先不谈core dump标志。我们刚给子进程的退出码设置的是10,由上面的图可以看出,status的低16位的次低8位代表了退出状态,因此10的二进制表示在次低8位,10二进制序列为1010,所以16位的表示为0000 1010 0000 0000。
在这里插入图片描述
因此,我们知道了,status不能直接使用,得我们对二进制序列经过位运算后才能得出正确的结果。 下面我们就从三个测试入手:代码跑完结果对,代码跑完结果不对,异常。
代码跑完结果对:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, rid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status&0x7F, (status>>8)&0xFF);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
这里我们退出码设置的10,所以是我们的预料之内。
所以,信号为0,退出码为0,代表代码跑完结果正常。
异常:

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

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

    int a = 10;
    a /= 0;
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, rid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status&0x7F, (status>>8)&0xFF);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
我们故意写了一个除零错误,信号就是8,信号8为SIGFPE。
在这里插入图片描述
但是退出状态为0,这里 信号为非0,退出码为0,代表异常!
所以, 代码跑完结果不对的表现就是,信号为0,退出码为非0!
父进程如何得知子进程的退出信息呢?原理是什么?
我们来看下面的图:
在这里插入图片描述
还有一个 问题:父进程在等待子进程的时候是阻塞状态,那么它是在哪里阻塞呢?
PCB中也维护了等待队列,当父进程等待子进程的时候,父进程的PCB会被链入到子进程的等待队列中,等子进程结束了,父进程的PCB就被链入到运行队列中,等待调度,被调度后立马回收子进程的PCB。这就叫做 软件条件。
大家记住,进程只要是阻塞了,就会被链入到等待队列中。
上面我们拿到退出码和信号是我们自己手动使用位操作实现的,那么下面我们来介绍一下Linux下封装好的系统调用,以后我们直接调用即可。
在这里插入图片描述
我们继续写代码测试一下:

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

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(1);
    }
    else 
    {
        //sleep(6); // 不会让父进程立即回收子进程
        // father
        printf("wait before\n");
        //pid_t rid = wait(NULL);
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            if(WIFEXITED(status))
            {
                printf("child process normal quit, exit code: %d\n", WEXITSTATUS(status));
            }
            else 
            {
                printf("child process quit except!\n");
            }
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
这里应该有人会疑惑,获取子进程的status这么麻烦,为什么不定义个全局变量直接拿呢?
如果定义一个全局变量,子进程势必需要对变量在自己的进程中写入,但是父子进程是两个进程,进程具有独立性,子进程对数据的更改,父进程是看不到的!!!

1.5 waitpid的第三个参数options

在这里插入图片描述
我们先来说waitpid的返回值,返回值分三类:

  • 0<:等待成功;
  • == 0:等待是成功的,但是进程还没有退出;
  • <0:等待失败。
    我们之前说options直接给0就可以,这里说一下,**0代表的是阻塞等待(子进程不退出,wait不返回)!**下面再说其他的:
    WNOHANG:等待的时候,以非阻塞(等待条件不满足,不阻塞,直接返回)的方式等待!非阻塞等待,往往要进行重复调用(轮询)。可以顺便做一些占据时间并不多的事情!非阻塞等待可以与返回值并用,实现轮询。
    这里我们来试一下非阻塞等待,并使用轮询的方式等待:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void Worker(int cnt)
{
    printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    sleep(1);
}
// waitpid第三个参数测试的单个子进程
int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        int cnt = 3;
        while(cnt)
        {
            Worker(cnt);
            cnt--;
        }
        exit(0);
    }

    // father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG);
        if(rid > 0) // waitid返回值大于0,等待成功
        {
            printf("child quit success, exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);
            break;
        }
        else if(rid == 0) // 返回值等于0,等待成功,但是子进程没有退出,可以做占用时间少的事
        {
            printf("father do other thing...\n");
            sleep(1);
        }
        else // 等待失败 
        {
            printf("wait failed!\n");
            break;
        }
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
我们能看到父进程最后退出。这就是非阻塞轮询等待。这样的方式等待,父进程就可以去做一些自己的事情,这样就可以更好的将时间利用起来!
我们来写一个这样的场景:
这里我们定义一个函数指针,里面存放一些函数指针,使用回调方式将代码封装一下:

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

#define TASK_NUM 5

typedef void (*task_t)();

/ 
void download()
{
    printf("this is a download task is runing!\n");
}
void printLog()
{
    printf("this is a write log task is runing!\n");
}
void show()
{
    printf("this is a show info task is runing!\n");
}
/ 


void initTasks(task_t tasks[], int num)
{
    for(int i = 0; i < num; i++) tasks[i]= NULL;
}

int addTask(task_t tasks[], task_t t)
{
    int i = 0;
    for(; i < TASK_NUM; i++) // 循环+判断将函数指针依次放入函数指针数组中
    {
        if(tasks[i] == NULL)
        {
            tasks[i] = t;
            return 1;
        }
    }
    return 0;
}

void executeTask(task_t tasks[], int num)
{
    for(int i = 0; i < num; i++)
    {
        if(tasks[i]) tasks[i]();
    }   
}

void worker(int cnt)
{
    printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
}
// waitpid第三个参数测试的单个子进程
int main()
{
    task_t tasks[TASK_NUM]; // 函数指针数组
    initTasks(tasks, TASK_NUM);
    addTask(tasks, download);
    addTask(tasks, printLog);
    addTask(tasks, show);

    pid_t id = fork();
    if(0 == id)
    {
        // child
        int cnt = 3;
        while(cnt)
        {
            worker(cnt);
            sleep(1);
            cnt--;
        }
        exit(0);
    }

    // father
    while(1)
    {
        int status = 0;
        // 非阻塞等待,可以让等待方在等待的时候,顺便做做自己的事情
        pid_t rid = waitpid(id, &status, WNOHANG);
        if(rid > 0)
        {// waitid返回值大于0,等待成功
            printf("child quit success, exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);
            break;
        }
        else if(rid == 0)
        {// 返回值等于0,等待成功,但是子进程没有退出,可以做自己的事
            printf("####################################\n"); // 分隔符
            printf("father do other thing...\n");
            // 该函数内部,其实是函数回调方式
            executeTask(tasks, TASK_NUM);
            printf("####################################\n");
            sleep(1);
        }
        else 
        {// 等待失败
            printf("wait failed!\n");
            break;
        }
    }
    return 0;
}

在这里插入图片描述
虽然是打印的方式说明在执行其他任务,但是这个原理用这简单的方式讲通了。在子进程没有退出时,父进程可以去做一些自己的任务!

  • 37
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Linux中的进程等待(Process Waiting)是指一个进程在执行时需要等待某些条件满足后才能继续执行的情况。 在Linux中,进程等待通常有以下几种情况: 1. I/O等待:当一个进程需要进行输入输出操作时,比如读写文件或者网络通信,由于这些操作是相对慢速的,进程需要等待数据的读取或写入完成才能继续执行。 2. 锁等待:多个进程访问临界资源时,为了避免竞态条件,需要使用锁来实现同步。当一个进程试图获取已经被其他进程占用的锁时,它会被阻塞,并等待锁被释放。 3. 睡眠等待:当一个进程调用了sleep()或wait()等系统调用后,它会主动释放CPU资源,并进入睡眠状态,等待指定的时间或者某个事件发生后才会被唤醒。 4. 信号等待:当一个进程正在等待某个信号的到来时,它会进入阻塞状态,直到该信号被发送给该进程进程才会被唤醒并继续执行。 针对进程等待的情况,Linux提供了一些机制来管理这些等待进程,比如使用信号量、条件变量、管道等方式来实现进程间的同步与通信。此外,Linux还提供了一些工具和命令来查看进程等待的状态,比如top命令可以查看每个进程等待时间,ps命令可以查看进程的状态等。 总之,Linux中的进程等待是一个重要的概念,合理管理进程等待可以提高系统的性能和资源利用率。进程等待是多任务操作系统中的常见现象,对于了解和掌握Linux进程管理至重要。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白在努力jy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值