Linux —— 进程的控制(2)

简介: Linux —— 进程的控制(2)

三、进程等待

1.进程等待的必要性

1.子进程退出,父进程如果不获取到子进程的退出信息,就可能造成 僵尸进程 的问题,进而造成内存泄漏。

2.进程一旦变成僵尸状态,所谓的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

3.父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。

4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2.进程等待方法

1.wait方法

函数原型以及所需头文件

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

返回值:等待成功则返回等待进程的PID,等待失败,返回-1;

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

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
    代码含义:通过fork创建子进程,实现5次打印,然后终止掉子进程,子进程便处于僵尸状态。同时父进程是处于等>    待状态的,在10秒后开始对子进程进行处理(也就是获取子进程的pid),处理结束,子进程退出。父进程此时再次等    待10秒后退出。                                                                                         
*/
int main()
{
    pid_t id = fork();
    if(id == 0){
        int ret = 5;
        while(ret){
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    printf("father wait begin..\n");
    pid_t cur = wait(NULL);
    if(cur > 0){
        printf("father wait:%d success\n", cur);
    }
    else{
        printf("father wait failed\n");
    }
    sleep(10);
}

对以上代码编译之后,编写一个shell脚本,进行进程的持续检测。

[mlg@VM-20-8-centos lesson4-进程控制]$ while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; echo "**********************"; done

1ecd1b2606ed46e9956a89f231c9802c.png

2.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:

       Pid=-1,等待任一个子进程。与wait等效。

       Pid>0.等待其进程ID与pid相等的子进程。

status:

       WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

       WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:

       WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的PID。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
    代码含义:通过fork创建子进程,实现5次打印,然后终止掉子进程,子进程便处于僵尸状态。同时父进程是处于等>    待状态的,在10秒后开始对子进程进行处理(也就是获取子进程的pid),处理结束,子进程退出。父进程此时再次等    待10秒后退出。                                                                                         
*/
int main()
{
    pid_t id = fork();
    if(id == 0){
        int ret = 5;
        while(ret){
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    printf("father wait begin..\n");
    //pid_t cur = waitpid(id, NULL, 0);//等待指定一个子进程
    pid_t cur = waitpid(-1, NULL, 0);//等待任意一个子进程
    if(cur > 0){
        printf("father wait:%d success\n", cur);
    }
    else{
        printf("father wait failed\n");
    }
    sleep(10);
}

结果和wait一样

3.获取子进程status

1.什么是status

int* status:它是一种输出型的参数

所谓获取子进程的status,就是获取子进程退出时的退出信息;        

首先,在子进程中分别用exit(0)和exit(10)来中断子进程,父进程获取status值,判断进程的退出状态

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id == 0){
        int ret = 3;
        while(ret){
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);
        }
        exit(0);//比较exit(10)或任意值
    }
    printf("father wait begin..\n");
    int status = 0;
    pid_t cur = waitpid(id, &status, 0);
    if(cur > 0){
        printf("father wait:%d success,status:%d\n", cur, status);
    }
    else
        printf("father wait failed\n");
    }
}

1ecd1b2606ed46e9956a89f231c9802c.png

 通过上面的运行结果,我们本来以为status应该是0和10,但和预期的结果却有所不同。这里我们仔细思考一下: 父进程拿到什么样的status结果,一定是和子进程如何退出强相关的。子进程退出问题不就是进程退出嘛。


进程退出不就是三种情况:


       1.代码运行完毕,结果正确

       2.代码运行完毕,结果不正确

       3.代码异常终止

也就是父进程只要通过status反馈出这三种情况,做出相应的决策。

所以代码中的 int status 就不能简单的理解为单纯的整数了!!!!!!

2.status的构成

       在上文中,我们对status有了一定的了解后,接下来谈一谈status的构成。

status是由32个比特位构成的一个整数,目前阶段我们只使用低16个位来表示进程退出的结果,如下图所示,就是status低16位的表示图;

1ecd1b2606ed46e9956a89f231c9802c.png

进程正常退出有两种,与退出码有关,异常退出与信号有关;(结合上文的进程退出的概念)所以这里我们就需要获取到两组信息:退出码与信号;如果没有收到信号,就表明我们所执行的代码是正常跑完的,然后在判断进程的退出码,究竟是何原因使进程结束的;反之则是异常退出,也就不需要关心退出码了;

3.如何获取status

     结合下图,我们用次低8位表示进程退出时的退出状态,也就是退出码;用低7位表示进程终止时所对应的信号;

       此时,我们想要拿到这个退出码和信号的值,我们是不是只要拿到了这低16个比特位中的次低8位和低7位就可以了;具体操作如下图所示

1ecd1b2606ed46e9956a89f231c9802c.png

status exit_code = (status >> 8) & 0xFF; //退出码
status exit_code = status7 & 0x7F;       //退出信号

接下来将上面的代码重新整理如下,测试子进程的退出状态时的退出码及是否获取到了信号

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id == 0){
        int ret = 3;
        while(ret){
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);
        }
        exit(10);
    }
    printf("father wait begin..\n");
    int status = 0;
    pid_t cur = waitpid(id, &status, 0);
    if(cur > 0){
        printf("father wait:%d success, status exit_code:%d, status exit_signal:%d\n", cur, (status >> 8)& 0xFF, status & 0x7F);
    }
    else
        printf("father wait failed\n");
    }
}

1ecd1b2606ed46e9956a89f231c9802c.png

通过运行结果可以看出,刚好与我们所给的退出码对应,并且没有接受到信号,意味着正常退出,但是这里我们所给的退出码是10,至于退出码的含义可以自由设定。

综上所述,我们理解一下进程退出的三种情况:

1.代码运行完毕,结果正确;对应如下:

1ecd1b2606ed46e9956a89f231c9802c.png

2.代码运行完毕,结果不正确;对应如下:

1ecd1b2606ed46e9956a89f231c9802c.png

3.代码异常终止;对应如下:

1ecd1b2606ed46e9956a89f231c9802c.png

接下来我们在看一段代码以及运行结果:

1ecd1b2606ed46e9956a89f231c9802c.png

2020062310470442.png

20200623104134875.png

       我们通过这三张图,可以发现,命令行解释器(bash)能够获取到退出码,并且bash是命令行启动的所有进程的父进程(不难看出,我们通过进程查看7120,相应的进程就是bash)所以,bash也一定是通过子进程去执行这段程序,也一定通过wait方式得到子进程的退出结果,刚好我们能看到echo $?能够查到子进程的退出码!

其实这里就是想说,系统也是有自带的,能够获取退出码与退出信号的宏

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
#include <stdio.h>                                                          
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
    pid_t id = fork();
    if(id == 0){
        int ret = 3;
        while(ret){                                  
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);                          
        }            
        exit(1);         
    }                                   
    printf("father wait begin..\n");
    int status = 0;
    pid_t cur = waitpid(id, &status, 0);                           
    if(cur > 0){                                                 
        if(WIFEXITED(status)){//没有收到任何退出信号的    
            //正常结束,获取对应的退出码
            printf("exit code:%d\n",WEXITSTATUS(status));
        }                                                                   
        else{
            printf("error:get a signal!\n");
        }             
    }
}

1ecd1b2606ed46e9956a89f231c9802c.png

4.阻塞等待与非阻塞等待

这里我们所讲的阻塞等待和非阻塞等待,其实就是waitpid函数的第三个参数,我们之前并未提及,直接给的是0,这种是默认行为,阻塞等待;如果设置为WNOHANG,表示的是非阻塞等待方式。


阻塞等待:父进程一直在等待子进程,什么事都不干,直到子进程正常退出。


非阻塞等待:父进程的PCB由运行队列转变为等待队列,直达子进程结束,操作系统获取到子进程退出的信号时,再将父进程从等待队列中调度到运行队列,由父进程去获取子进程的退出码以及退出信号。

//基于阻塞等待的轮询访问
#include <stdio.h>                                                          
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() 
{                                                       
    pid_t id = fork();                                             
    if(id == 0){                                                   
        int ret = 10;                                                                                                                                               
        while(ret){
            printf("child[%d] is running:ret is %d\n", getpid(), ret);
            ret--;
            sleep(1);
        }
        exit(1);
    }
    int status = 0;
    while(1){  
    pid_t cur = waitpid(id, &status, WNOHANG);
    if(cur == 0){
        //子进程没有退出,但是waitpid等待是成功的,需要继续重复进行等待
        printf("Do father things!\n");
    }
    else if(cur > 0){
        //子进程退出了,waipid也成功了,获取到了对应的结果
        printf("father wait:%d success, status exit_code:%d, status exit_signal:%d\n", cur, (status >> 8)& 0xFF, status & 0x7F);
        break;
    }
    else{
        //等待失败了
        perror("waitpid");
        break;
    }
    sleep(1);
    }                                                    
} 

1ecd1b2606ed46e9956a89f231c9802c.png

       从运行结果可以看出,父进程在不断的查看子进程是否退出,若未退出,则父进程先去忙自己的事情,过一段时间再来查看,直到子进程退出后读取子进程的退出信息。


目录
相关文章
|
17天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
43 1
|
2月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
48 0
|
5天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
47 13
|
12天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
20天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
138 4
linux进程管理万字详解!!!
|
25天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
77 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
71 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?