《Linux从练气到飞升》No.19 进程等待

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

目录

前言

在操作系统中,进程等待是一种关键的机制,用于实现进程之间的同步和协作。通过等待子进程的结束并获取其退出状态,父进程可以控制程序的执行顺序和处理子进程的结果。本篇博客将介绍进程等待的原理和用法,帮助读者深入理解进程间通信的重要概念和技术。

进程等待必要性

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

模拟僵尸进程

在我们讲述进程状态的时候,我们讲述过僵尸进程指的是:子进程退出,父进程不管不顾

模拟代码:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int code = 0;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1); //标识进程运行完毕,结果不正确
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 5;
        while(cnt)
        {
            printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
            sleep(1);
            cnt--;

        }

    }
    else
    {
        //父进程
        printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
        sleep(7);
    }
}

运行结果:

查看状态的bash命令:

bash 复制代码
while :; do ps ajx | head -1 && ps ajx | grep mycode | grep -v grep; sleep 1; echo "-----------------------"; done

查看状态:

模拟成功!

进程等待的方法

子进程被创建出来,谁先运行,是有调度器说了算的。

那么谁先退出呢? 一般而言,我们通常要让子进程先退出。

为甚?

因为父进程可以很容易对子进程进行管理(垃圾回收)、处理业务,需要让父进程帮我们拿到子进程执行的结果。

一般子进程是需要被等待的,被父进程等,wait/waitpid.

wait方法

是什么?

是父进程通过wait等系统调用,用来等待子进程状态的一种现象,是必须的

为什么?

1.防止子进程发生僵尸问题,进而产生内存泄漏

2.读取子进程状态

怎么办?

wait/waitpid, status (signal, exit code).

cpp 复制代码
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

参数:

输出型参数:将wait函数内部计算的结果通过status返回给调用者
输入型参数:调用者给被调用函数的传参

输入输出型参数编码的时候,小小的代码规范

输入型:给引用
输入输出,输出型参: 给指针

测试代码

cpp 复制代码
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        printf("fork error\n");
    }
    else if (pid == 0)
    {
        int count = 0;
        while (1)
        {
            sleep(1);
            printf("i am child\n");
            if (count == 3)
            {
                break;
            }
            count++;
        }
        exit(0);
    }
    else
    {
        int count = 0;
        while (1)
        {
            sleep(1);
            printf("i am father\n");
            while (count == 5)
            {
                wait(NULL);
            }
            count++;
        }
        exit(0);
    }
    return 0;
}

运行结果

waitpid方法

pid_ t waitpid(pid_ _t pid, int *status, int options) ;
pid :
Pid=-1,等待任一个子进程。与wait等效。
Pid>0,等待其进程ID与pid相等的子进程。.
status :同wait
options :
0 :阻塞模式
WNOHANG :非阻塞 模式
非阻塞模式需要搭配循环使用

cpp 复制代码
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

测试代码

cpp 复制代码
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        printf("fork error!\n");
    }
    else if (pid == 0)
    {
        //child
        int count = 0;
        while (count < 5)
        {
            printf("child is running, pid=%d\n", getpid());
            sleep(1);
            count++;
        }
        exit(0);
    }
    else
    {
        //father
        printf("father wait before!\n");
        pid_t ret = waitpid(pid, NULL, 0);
        if (ret > 0)
        {
            printf("wait success!\n");
        }
        else
        {
            printf("wait failed\n");
        }
        printf("father wait after!\n");
    }
    return 0;
}

运行结果

看下面结果图发现当父进程调用了waitpid函数时父进程就被阻塞了,阻塞期间当子进程运行完毕父进程才执行完毕,所以只有子进程退出了父进程才会退出,那么子进程就一定不是僵尸进程。

获取子进程status

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

status:是一个整形指针,其实在传参的时候,该参数是一个输出型参数!

int st=0;

waitpid(pid, &st, 0); //开始等待,子进程退出,操作系统就会从进程PCB中读取退出信息,保存在status指向的变量中

返回之后,st中就保存的是我们进程退出的信息,int 是32bit,是否正常运行,退出码

是多少,退出信号是多少。

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
    • 次低8位表示子进程退出码
    • 最低7个比特位表示进程收到的信号
cpp 复制代码
//测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
    pid_t pid;
    if ( (pid=fork()) == -1 )
    perror("fork"),exit(1);
    if ( pid == 0 ){
    sleep(20);
    exit(10);
    } else {
    int st;
    int ret = wait(&st);
    if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
        printf("child exit code:%d\n", (st>>8)&0XFF);
    } else if( ret > 0 ) { // 异常退出
        printf("sig code : %d\n", st&0X7F );
    }
    }    
}

测试exit code,exit signal

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        printf("fork error!\n");
    }
    else if (pid == 0)
    {
        //child
        int count = 0;
        while (count < 5)
        {
            printf("child is running, pid=%d\n", getpid());
            sleep(1);
            count++;
        }
        exit(0);
    }
    else
    {
        //father
        printf("father wait before\n");
        int st = 0;
        pid_t ret = waitpid(pid, &st, 0);
        if (ret > 0)
        {
            printf("wait success!\n");
            printf("st=%d\n", st);
            printf("child exit signal=%d\n", st & 0x7f);
            printf("child exit code=%d\n", (st >> 8) & 0xff);
        }
        if (st & 0x7F)
        {
            printf("child run error!\n");
        }
        else
        {
            int code = (st >> 8) & 0xff;
            if (code)
            {
                printf("child run success, but result is not right: code=%d\n", code);
            }
            else
            {
                printf("child run success, and result is right: code=%d\n", code);
            }
        }
    }
    printf("wait after!\n");
    return 0;
}

1.父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid函数呢?直接全局变量不行吗?

进程具有独立性,那么数据就要发生写时拷贝,父进程无法拿到,况且,信号呢? ?

  1. 既然进程是具有独立性的,进程退出码,不也是子进程的数据吗? ?父进程又凭什么拿到呢? ?wait/waitpid究竟干了什么呢?

首先要知道僵尸进程至少要保留该进程的PCB信息!

task_struct里面保留了任何进程退出时的退出结果信息。

wait/waitpid 本质其实是读取子进程的task_struct结构 ,

task_struct 里面包含了: 【int exit_ code, exit_ signal;】

3.wait/waitpid有这个权利吗?

有,可以系统调用! ,不就是操作系统吗! ! task_ struct 是内核数据结构对象! !

阻塞与非阻塞

  • 阻塞等待是指一个任务在等待某个操作完成时,会被挂起,暂停执行直到操作完成后再继续执行。在阻塞等待期间,该任务无法进行其他的工作。
  • 非阻塞等待是指一个任务在等待某个操作完成时,会使用轮询或回调的方式不断查询操作状态,可以继续执行其他任务。非阻塞等待不会让一个任务暂停执行,即使操作未完成。
  • 两者的区别在于任务在等待某个操作完成时的行为表现:
    • 阻塞等待会暂停任务的执行,直到操作完成。
    • 非阻塞等待允许任务继续执行,并对操作状态进行查询或设置回调函数。
  • 具体区别如下:
    • 阻塞等待会造成任务阻塞,无法进行其他操作,而非阻塞等待允许任务继续执行其他操作。
    • 阻塞等待的操作结果通常是通过阻塞等待的方式获取,而非阻塞等待需要主动轮询或回调来获取操作结果。
    • 阻塞等待的效率较低,因为任务可能需要等待较长时间才能继续执行,而非阻塞等待可以提高任务的响应速度和并发性。
    • 阻塞等待通常使用在同步模式下,保证任务的执行顺序;非阻塞等待则常用于异步模式下,充分利用系统资源。

进程的阻塞等待方式

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
} else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(257);
} else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

进程的非阻塞等待方式

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\n",__FUNCTION__);
return 1;
}else if( pid == 0 ){ //child
printf("child is run, pid is : %d\n",getpid());
sleep(5);
exit(1);
} else{
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if( ret == 0 ){
printf("child is running\n");
}
sleep(1);
}while(ret == 0);
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}

后记

本篇讲述了进程等待的相关知识。

相关推荐
鱼饼6号9 分钟前
Prometheus 上手指南
linux·运维·centos·prometheus
Asher Gu15 分钟前
Linux系统编程入门 | 模拟实现 ls -l 命令
linux
希冀12318 分钟前
【操作系统】1.2操作系统的发展与分类
后端
PatrickYao042229 分钟前
记一次安装discuz时遇到的错误
服务器
c无序32 分钟前
【Linux进程控制】进程程序替换
linux
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
小宋10212 小时前
玩转RabbitMQ声明队列交换机、消息转换器
服务器·分布式·rabbitmq
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
m0_609000422 小时前
向日葵好用吗?4款稳定的远程控制软件推荐。
运维·服务器·网络·人工智能·远程工作