• 【Linux】进程等待


    img

    Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。


    在这里插入图片描述

    0. 进程等待概念

    一个进程的退出码关乎这个进程的运行状态.那我们如何获取子进程运行状态,这就是进程等待的作用.

    • 什么是进程等待:

    通过系统调用wait()||waitpid()来对子进程进行状态检测与回收功能.

    image-20231113190713347

    • 为什么我们需要进程等待这一功能呢?
      1. 我们有时需要关注这个进程的退出状态
      2. 当子进程结束为僵尸进程的时候,我们需要父进程去等待回收这个子进程.否则会造成内存泄漏的问题

    1. wait()函数解析

    在man手册里可以看到其参数为int * status.我们先不传入参数.先这样使用

    pid_t ret=wait(NULL);
    
    • 1

    若回收成功则返回捕获回收成功的子进程ID.若回收失败则返回-1

    我们先试着这样使用,编译运行以下程序.

    #include
    #include
    #include
    #include
    #include
    void runchild()
    {
        int cnt = 5;
        while(cnt--)
        {
          printf("i am a child process my pid is : %d,my parent is: %d,cnt :%d\n",getpid(),getppid(),cnt);
            sleep(1);
        }
    }
    int main()
    {
        pid_t id = fork();
        if(id == 0)
        {
            runchild();
            exit(0);
        }
        else if(id < 0)
        {
            printf("create faild\n");
        }
        else
        {
            sleep(10);
            pid_t ret = wait(NULL);
            if(ret == id)
            {
                printf("wait success!\n");
            }
            sleep(10);
        }
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们可以观察到该进程的每一个状态:

    • 首先,父子进程被创建,子进程打印相关信息.

      image-20231113194128677

      image-20231113194259435

    • 五秒后子进程退出为僵尸状态.父进程还未回收子进程

      image-20231113194343364

    • 十秒过后,父进程回收完毕子进程

      image-20231113194422928

      image-20231113194452080

    • 父进程退出

      image-20231113194515162

    父进程在等待回收子进程时,不回做接下去的事情.这就是阻塞调用.整个进程都在等待子进程回收的完成

    2. waitpid() 函数解析

    image-20231113190713347

    waitpid相较于wait多了两个参数.

    1. pid_t pid 为需要等待的进程pid
    2. int options 为是否需要进行阻塞调用
    3. *int wstatus 为进程退出码与退出状态

    从上到下我们一个个来解析.

    2.1 pid_t pid

    我们传入-1,则表示等待任意一个子进程.传入固定的pid,则为等待固定的子进程.

    waitpid(-1,NULL,0);
    
    • 1

    此时的waitpid函数与我们上面使用的wait函数等价.

    2.2 int * wstatus

    之前我们提到过了,我们需要把一个子进程的退出状态(退出码,退出信号)带出来.如何从一个函数中带出一个变量呢?

    除了返回值,我们可以使用指针带出.

    #include
    #include
    #include
    #include
    #include
    void runchild()
    {
        int cnt = 3;
        while(cnt--)
        {
            printf("i am a child process my pid is : %d,my parent is: %d,cnt :%d\n",getpid(),getppid(),cnt);
            sleep(1);
        }
    }
    int main()
    {
        pid_t id = fork();
        if(id == 0)
        {
            runchild();
            exit(1);
        }
        else if(id < 0)
        {
            printf("create faild\n");
        }
        else
        {
            int status=0;
            pid_t ret = waitpid(-1,&status,0);
            if(ret == id)
            {
                printf("wait success!,status: %d\n",status);
            }
            sleep(10);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    子进程的退出码是1.而运行这段程序 我们发现获取的退出码是256.这是为什么呢?

    image-20231113195830726

    status的设计类似bitmap的思想.在一个32位整型数中,我们现阶段只用到低16位

    进程结束有三种状态:

    1. 正常终止结果正确
    2. 正常终止结果错误
    3. 异常终止

    815d3649764511255babc1ca05d92be

    第一、二种情况都为正常终止,此时低8位(0-7)为0.从第8位开始保存退出码.

    第三种情况位被异常终止,本质为被信号杀死.则其低8位用来表示被杀死的信号类别.

    所以我们需要按位与就可以取出对应的退出码和终止信号.

    其中sig code= status& 0x7f 提取出低7位

    ​ exit code = (status>>8) & 0xff 提取出高8位

    当然我们系统也提供了三个宏,来判断子进程是否正常退出?退出码以及退出信号

    image-20231113201852273

    2.3 int options

    有一个参数为WNOHANG,非阻塞调用.

    也就是父进程只会询问一次,若执行到这时,子进程等待回收,则回收.若无,则继续往下执行.

    通常我们可以通过轮询等待(通过循环,每隔一段时间问一次)来提高效率.

    3.Task_struct中的退出状态信息

    为什么我们不直接取,要么麻烦通过函数来访问呢?

    因为这个退出信息是存在子进程的Task_struct中的.访问系统的数据只能通过系统调用来进行.

    image-20231113203221438

    在多进程运行时,我们并不知道哪个进程先开始运行,但是一定要保证,父进程是最后一个退出的进程,否则就会出现长时间的僵尸状态,进而导致内存泄漏。
    image-20230905164632777

  • 相关阅读:
    【仿真建模】AnyLogic入门基础课程 第二课
    Docker Swarm集群部署
    数据仓库dws层,DWS层搭建--商品主题宽表,md,review第1遍,220622,
    怎么批量获取文件名,并保存到excel?
    年薪30万+的HR这样做数据分析!(附关键指标&免费模版)
    【react】 手把手学习react - state 及组件的生命周期
    如何快速看懂英文论文?
    AJAX and Javaweb
    开发跨端微信小程序框架选型指南
    gitlab在项目中创建自己的分支的顺序操作,一整套流程
  • 原文地址:https://blog.csdn.net/qq_62839589/article/details/134439337