【Linux】详解进程终止&&进程等待

简介: 【Linux】详解进程终止&&进程等待

一、页表&&写时拷贝的进一步理解

       页表中不仅仅只有虚拟地址到物理地址的映射,还包括了很多选项,其中就包括了映射条目的权限。当我们进程的代码和数据加载到内存并和进程地址空间建立映射关系时,如果数据的内容不允许被修改(比如说常量字符串),对应数据在页表中的映射条目的权限就会被设置为'r',表示该数据是只读的,不能被修改。这就是为什么当我们要对常量字符串的内容做修改程序运行阶段会报错的底层原因。

       通过页表的权限位,我们也可以很好地解释写时拷贝是如何做到的。当父进程创建子进程时会发生写时拷贝,写时拷贝会把大部分映射条目的权限都由‘rw’设置成‘r’。当子进程或者是父进程要对数据进行修改时,发现要修改数据对应的映射条目的权限位‘r’,无法进行修改,这时操作系统就会介入。操作系统发现子进程(假设是子进程要对数据进行修改)要对数据进行修改,且操作时合法的,这是就会在内存中申请一块空间重新建立映射关系,再将父子进程该对应的映射条目都改成‘rw’,这才算完成了一次写时拷贝。通过以上过程操作系统就可以按需进行写时拷贝。

二、进程终止

       main函数的返回值我们叫做进程的退出码。一般0表示进程执行成功,非0表示进程执行失败可以用非0的数字表示进程失败的原因。错误码可以转换成错误描述,可以使用语言和系统自带的方法进行转化,也可以自定义。其他函数错误码仅仅表示函数调用结束。

2.1、echo $?

       bash进程会记录最近一个进程退出的退出码,可以查看echo $?可以查看最近一个进程退出的退出码。

       调用函数我们通常想看到两种结果,一是函数的执行结果(比如说fopen打开文件,打开成功就返回文件指针,打开失败返回NULL,这叫函数的执行结果),二是函数的执行情况(比如说同样是fopen打开文件,我们函数的执行情况对应的数字会被保存在errno(错误码)这个变量中)。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
    FILE* fp = fopen("./log.txt", "r");
    printf("%d %s\n", errno, strerror(errno));
    return 0;
}

 错误码和main函数的退出码本质上是一样的。 错误码只会记录系统提供的函数的执行情况

2.2、进程退出的场景

进程提出的场景与三种:

1、进程代码执行完了,结果是正确的。

2、进程代码执行完了,结果不正确。

3、进程代码没有执行完,进程出异常了。进程出异常时,进程的退出码是没有意义的。

        第三种情况进程出异常是进程收到了操作系统发出的异常信号,每个信号都有不同的编号,不同的信号编号表示异常的原因。

       任何进程最终的执行情况我们都可以用两个数字来表示。一个是进程的退出信号(exit_signal)(退出信号为0表示进程没有出异常),一个是进程的退出码(exit_code)进程的退出信号是由操作系统发送给进程,以指示进程应该终止或进行某种操作的信号,是上对下的操作。而进程的退出码则是当进程结束运行时返回给操作系统的一个整数值,用于表示进程的执行状态或结果,是下对上返回的结果。

2.3、exit

       exit就是用来终止进程的,exit括号中的内容就是进程的退出码。在我们的代码进程中,在任意地方调用exit都表示进程退出

三、进程等待

3.1、进程等待的必要性

1、之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

2、另外,进程一旦变成僵尸状态,那就刀枪不入,kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

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

4、父进程通过进程等待(wait)的方式,回收子进程资源,获取子进程退出信息

3.2、进程等待的方法

3.2.1、wait方法

       wait方法里的参数为输出型参数,可以设置为NULL。调用wait函数父进程默认进行阻塞等待,会等待任意一个子进程退出。等待成功,wait会返回子进程的pid,等待失败返回小于0的值

       fork之后父子进程谁先运行不确定,但fork之后一定是父进程后退出,因为父进程要回收子进程。

3.2.2、waitpid方法

       以上pid参数为要回收子进程的pid(pid如果为-1,表示等待任意一个子进程,与wait等效),wstatus参数同样为输出型参数,可以设置为NULL(也可以设置为一个int变量的地址,可以查看子进程的退出码), options参数设置为0表示阻塞等待,设置为宏 WNOHANG表示非阻塞等待采用非阻塞的方法等待,子进程退出成功返回子进程的pid,子进程还在继续自己的工作返回0,子进程出错返回小于0的数。阻塞等待时父进程会阻塞在waitpid这里一直等待子进程返回,非阻塞等待采用轮询的方法查看子进程的退出信息,在轮询的间隙父进程可以继续做别的工作。

3.2.3、wstatus参数详解

       *wstatus表示一个int整形变量,由三十二个比特位组成,其中前16个比特位我们不用,第17到第24个比特位用来表示进程退出时的退出码,第26到第32个比特位用来表示进程退出时收到的退出信号。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child
        int cnt = 5;
        while(cnt)
        {
            printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待
    if(rid > 0)
    {
        printf("wait success, rid: %d, status: %d\n", rid, status);
    }
    return 0;
}

       这就可以解释为什么上面的status变量为256了。因为子进程的退出码为1,status的第24个比特位被设置为1,没有收到退出信号,所以status后8个比特位都为0,所以status等于2的8次方等于256。

3.2.4、使用位操作从status变量中提取出进程的退出信号和退出码。

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待
    if(rid > 0)
    {
        printf("wait success, rid: %d, status: %d, exit_signal: %d, exit_code: %d\n", rid, status, status&0x7f, (status>>8)&0xff);
    }
    return 0;
}

3.2.5、使用宏获取进程的退出码

       一般用户不是很关心进程的退出状态只想获取子进程的退出码就可以使用这种方法。

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

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("Child is running, pid: %d, ppid: %d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0); // 阻塞等待
    if(WIFEXITED(status))
    {
       printf("wait success, rid: %d, status: %d, exit_code: %d\n", rid, status,WEXITSTATUS(status));
    }
    return 0;
}

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