Linux进程终止进程等待进程程序替换

news2024/9/30 12:51:07

目录

一、进程终止

1.1进程退出的场景

1.2进程常见的退出方法

​1.3多进程的退出

​1.4exit( )和_exit( )

二、进程等待

 2.1进程等待的必要性

2.2进程等待的方式

2.3获取子进程的statue

2.4非阻塞轮询

2.5进程等待的底层原理

三、进程程序替换 

3.1单进程程序替换

3.2多进程程序替换--验证各种程序替换接口

3.3 系统调用execve( )


一、进程终止

1.1进程退出的场景

  • 程序运行正常结束,退出结果正确
  • 程序运行正常结束,退出结果错误
  • 程序异常退出

1.2进程常见的退出方法

正常终止(可以通过 echo $? 查看进程退出码,返回的是最近一次进程退出时的退出码):
  •  从main返回
  •  调用exit
  • _exit
异常退出:
  • ctrl + c

return 退出

  • return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
  • 在函数内return,只会返回该函数的返回值,在函数内exit就会直接退出进程
  • 也就是说,在本进程内的任意函数位置调用exit都会使该进程直接退出

代码测试:

a7dbb26049a94db6b6cb0a42869c250b.png

exit( )直接退出进程,后续代码不再执行ed39d9379d6d482cb5bd59c4e6d0fbad.png

return返回该函数的返回值,继续执行函数调用处之后的代码

58c7f1c8c8c041cbbd6e765c7eed7085.png

查看退出码对应的信息:

  • 进程的退出码和退出信息是一一对应的关系
  • 我们也可以自己设计出一套退出码体系

be44e241ebec42528f971f3150914514.png

b084646ed8244ef8863753a848e13a84.png

为什么需要退出码?

  • 程序在退出时需要退出码,mian()函数的退出码本质上是表示程序运行结束后的结果是否正确,如果不正确,可以用不同的退出码值来表示不同的错误原因
  • 程序运行结束时,父进程要关心子进程的运行情况,获得子进程的退出信息
  • 如果程序运行的结果是正确的,退出码为0,就不需要再关心进程的运行情况原因;如果退出码为其他数字,说明程序运行的结果是错误的,父进程必须拿到该结果的原因返回给上层用户,知道错误原因决定是否重新运行

 异常的本质?

  • 异常的本质通俗来说是代码没有跑完,此时我们不再需要关心退出码的问题了,转而关心出现异常的原因
  • 进程异常的发生本质上是进程收到了某种信号(我们曾经使用过的kill -9 就是一种信号)
  • 通过kill -l 可以查看到异常退出的信息

9fd07f414d80465b81fff098b95d192f.png

  •  实例

79a0fabdbf76406e9ef5aec908360e00.png

85fabc92a15c410d97b8ba2b5277ded7.png

130b0ce071c44fd8954da8654f39d4f0.png 1.3多进程的退出

8feae1015fd144abb3c677179261213f.png

f2ede91f41744c65865df05385b590ae.png1.4exit( )和_exit( )

 exit( )和_exit( )的区别:

  • exit( )是库函数,_exit( )是一种系统调用
  • 本质上exit( )是对_exit( )的封装
  • 调用exit( )会执行缓冲区的刷新和关闭流等操作再退出进程,调用_exit( )不做任何其他操作,直接退出进程
  •  exit最后也会调用_exit 但在调用_exit之前,还做了其他工作:
  1. 执行用户通过 atexit或on_exit定义的清理函数
  2.  关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit 

代码测试:

9a1274142ef94b25b9b553ed13108e3e.png

exit( )进程退出后会刷新缓冲区

fedc2ccaccd04fa0a1fca27473a9364f.png

_exit( )进程退出后不会刷新缓冲区 ,没有打印信息

a93e7bc1de6b49a88aa28e70ceff0e37.png

2f53ccf953324febba1e2cac772a032b.png

  • 这样说明了printf输出的数据存入的缓冲区不在Linux内核里,而是由c语言层面提供的一个缓冲区 

二、进程等待

 2.1进程等待的必要性

  • 进程等待是指:通过调用wait( )或者waitpid( )来实现对子进程进行状态检查和回收的功能
  • 子进程在退出时,它的退出信息(退出码和接收到的信号)会被保存在该子进程的pcb数据结构对象内,如果父进程一直不对子进程的退出信息进行回收,该pcb数据结构对象就无法被释放,就会造成僵尸进程,进而引发内存泄露的问题
  • 僵尸进程无法被直接杀死,进程等待可以杀死僵尸进程,从而解决内存泄露的问题--必须解决
  • 同时,通过进程等待可以获取子进程的退出信息,让关心子进程的父进程知道子进程将任务完成得如何了--非必要
  • 如果子进程一直不退出,父进程调用wait( )这个系统调用的时候,就一直不会返回,处于阻塞状态

2.2进程等待的方式

  • wait( )等待
  • pid_t wait(int*status);
  • 返回值:成功返回被等待进程pid,失败返回-1
  • 参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

代码测试:

wait( )等待的是任意一个进程

b4967bf984984be2aa086701f8bae7af.png

父进程没有回收子进程前,子进程退出形成僵尸进程

ffc5127a4cfa4c10bbd5b3d27c613c7d.png

休眠十秒后父进程开始循环回收每一个子进程:

80f11d043ba444f88c77b683b2956661.png

  • waitpid( )
  • pid_ t waitpid(pid_t pid, int *status, int options)
  1. 返回值:当正常返回的时候waitpid返回收集到的子进程的进程id,如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0(非阻塞轮询),如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
  2. 参数:
    pid:
    pid=-1,等待任一个子进程,与wait等效。
    pid>0.等待其进程id与pid相等的子进程。
    status:
    WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(查看进程是否是正常退出)
    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(查看进程的退出码)
    options:
    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回该子进程的id

代码测试:

032777486ca3418a8e7fefa4a5605c3c.png

使用WEXITSTATUS()可以查看进程的退出码  21be9e92de4b45a398190ebb656a0402.png

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞

代码测试:

父进程等待子进程退出,处于阻塞状态

3328a2a27f774d19ae20600874230a48.png

1d0e263a26014016bb2b513a011f781b.png

  • 如果不存在该子进程,则立即出错返回

2.3获取子进程的statue

子进程在退出的时候会有哪些退出信息?

  1. 程序正常运行结束,返回结果正确
  2. 程序正常运行结束,返回结果错误
  3. 异常退出

父进程要关心子进程的什么退出信息?

  1. 子进程是否出现异常
  2. 没有出现异常,程序正常运行结束,结果是否正确
  3. 结果不正确是什么原因--通过获得不同的错误码来表示不同的错误信息

statue参数是什么?

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充,如果传递NULL,表示不关心子进程的退出状态信息
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)
  • 0-7比特位表示的是异常的信号,因为异常信号是从1开始的,如果0-7为0就说明程序是正常运行结束的,如果为其他数字,那么就表示异常终止,此时前9-15比特位没有使用
  • 8比特位是core dump标志
  • 9-15比特位表示的是退出状态,如果为0就说明结果正确,不为0表示不同的出错信息(前提是0-7比特位必须都为0)
2bd982d1fc344a7fbd08fc8a95683093.png

代码测试:

 03a90035264c4bd383da5ad3b86f6f18.png

2.4非阻塞轮询

  • 子进程还没退出,父进程调用wait( )或者waitpid( ),就会处于阻塞状态,不会返回,这种等待方式叫作阻塞等待,父进程在此期间不做任何事情,一直等待子进程的退出
  • 除此之外,还有一种等待方式叫作非阻塞轮询等待,父进程在等待子进程退出的期间可以去完成自己的任务--while循环+非阻塞
  • 非阻塞轮询等待要使用一个参数:WNOHANG:--若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,若正常结束,则返回该子进程的id

代码测试:

184eb1a342e747969a63a931cc105ddf.png

91d8cac3e32e4b94ac156cd9f37a6836.png

实现在父进程在等待子进程退出的时候完成相应的任务:

 21 #define TASK_NUM 10
 22 
 23 typedef void(*task_t)();
 24 task_t tasks[TASK_NUM];
 25 
 26 void task1()
 27 {
 28     printf("这是一个执行打印日志的任务, pid: %d\n", getpid());
 29 }
 30 
 31 void task2()
 32 {
 33     printf("这是一个执行检测网络健康状态的一个任务, pid: %d\n", getpid());
 34 }
 35 
 36 void task3()
 37 {
 38     printf("这是一个进行绘制图形界面的任务, pid: %d\n", getpid());
 39 }
 40 
 41 int AddTask(task_t t);
 42 
 43 // 任务的管理代码
 44 void InitTask()
 45 {                                                                                      
 46     for(int i = 0; i < TASK_NUM; i++) tasks[i] = NULL;
 47     AddTask(task1);
 48     AddTask(task2);
 49     AddTask(task3);
 50 }
 51 
 52 int AddTask(task_t t)
 53 {
 54     int pos = 0;
 55     for(; pos < TASK_NUM; pos++) {
 56         if(!tasks[pos]) break;
 57     }
 58     if(pos == TASK_NUM) return -1;
 59     tasks[pos] = t;
 60     return 0;
 61 }
 62 
 63 void DelTask()
 64 {}
 65 
 66 void CheckTask()
 67 {}
 68 
 69 void UpdateTask()
 70 {}
 71 
 72 void ExecuteTask()
 73 {
 74     for(int i = 0; i < TASK_NUM; i++)
 75     {
 76         if(!tasks[i]) continue;
 77         tasks[i]();
 78     }
 79 }

ada2e9d864cc4bfc96f07e5aa03f3001.png

d286d6194ffb4498aee06f7b39f00984.png

2.5进程等待的底层原理

  • 父进程要拿到子进程的退出信息,拿到子进程的任意数据,为什么要使用系统调用呢?因为进程具有独立性!

  • 父进程不能直接拿到子进程的数据!因为父进程浅显来说是运行用户自己写的代码,任何用户都不能直接访问操作系统内的数据,访问操作系统内的数据都必须通过系统调用!这就要求父进程要拿到子进程的退出信息必须使用wait或waitpid系统调用接口

  • 子进程退出的时候,对应的代码和数据会被释放,但会把自己的sig code和exit code保存在pcb数据结构对象里,通过位运算将两者整合放到参数statue里后,pcb数据结构才会被释放销毁,然后通过wait或waitpid被调用者(父进程)拿到子进程的退出信息

三、进程程序替换 

3.1单进程程序替换

代码测试:

  • 程序运行结果并没有执行第二句printf,这是因为在进行程序替换的时候,调用exec后会把该进程的用户空间代码和数据完全被新程序替换,从新程序的启动,进程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
  • 执行程序的第一个步骤要先找到程序,exec的第一个参数决定了如何找到这个程序
  • 找到该程序后,就要判断如何执行该程序!需要涵盖哪些选项就传哪些选项
  • 单进程的程序替换,操作系统会根据程序的路径在磁盘中找到该程序所对应的代码和数据,再加载到内存中,对原代码和数据进行覆盖
  • 程序替换成功后序代码不再执行,exec只有失败的返回值,没有成功的返回值,替换失败继续执行后序的代码
  • Linux中形成的可执行程序是有ELF格式的,ELF中存在一个表,可执行程序的入口就存在这个表头里,当一个程序被运行起来,他的代码和数据不一定立马被加载到内存中,但是表头里的可执行程序的入口一定要先被加载到内存中,程序执行从该入口开始执行
  • 环境变量也是一份数据,环境变量在子进程创建的时候,就已经被子进程继承下来了,程序替换不会替换环境变量的信息

3.2多进程程序替换--验证各种程序替换接口

  • 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序
当子进程调用一种exec函数时,该子进程的用户空间代码和数据会发生写时拷贝,完全被新程序替换,从新程序的启动,子进程开始执行
  • 替换方法
  1. int execl(const char *path, const char *arg, ...)
  2. int execlp(const char *file, const char *arg, ...);
  3. int execle(const char *path, const char *arg, ...,char *const envp[]);
  4. int execv(const char *path, char *const argv[]);
  5. int execvp(const char *file, char *const argv[]);
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

代码示例:

execl前面已经演示过,这边就不再重复演示

execlp( ):l选项表示选项参数采用列表的形式,p选项表示操作系统在找该程序时会自动到环境变量PATH里去搜索 ,命令行参数表默认以NULL结尾,所以传选项参数时也要以NULL结尾

 

execle( ):e选项表示自己传入环境变量

  • 如果不传环境变量,子进程默认也有一份从父进程继承下来的环境变量
  • 如果传入环境变量,可以直接传char** environ
  • 如果要改变环境变量,有两种方式:
  1.  putenv给父进程的地址空间中导入新的环境变量
  2. 调用含e选项的函数接口,传入自己定义一份环境变量表,覆盖子进程原有的环境变量表

代码示例:

execl可以执行系统的命令,也可以执行自己的命令

创建一个otherExe.cpp文件来打印子进程的命令行参数表和环境变量表

#include <iostream>

using namespace std;


int main(int argc, char *argv[], char *env[])
{
    cout << argv[0] << " begin running" << endl;
    cout << "这是命令行参数: \n";
    for(int i=0; argv[i]; i++)
    {
        cout << i << " : " << argv[i] << endl;
    }
    cout << "这是环境变量信息: \n";
    for(int i = 0; env[i]; i++)
    {
        cout << i << " : " << env[i] << endl; 
    }
    cout << argv[0] << " stop running" << endl;
    return 0;
}

 不传环境变量表:

  •  可以看到,execl会把对应的选项参数当成命令行参数表传递给新替换的程序

 传环境变量表并putenv:

 覆盖替换子进程的环境变量表:

execv( ):v表示采用数组的形式(命令行参数表)传入选项

 execvp( ):表示自动到PATH里搜索程序路径,并且采用数组的形式传入选项

 execvpe( ):采用数组形式,从PATH中搜索路径,并可以自己传入环境变量(e选项和execle用法类似)

3.3 系统调用execve( )

  • 只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册第2节,其它函数(库函数)在 man手册第3节,这些函数之间的关系如下图所示:

  • 各个库函数之间的区别只是传参的不同,他们都是对系统函数execve( )的封装 
  • 一个C程序可以fork/exec另一个程序,并传给它一些参数,这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait来获取exit的返回值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2179663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

踩坑Resilience4j @Bulkhead

先说问题情况 系统环境 java version: 17spring boot: 3.xspring cloud: 4.x 项目配置和代码 项目中使用了Spring Cloud Circuit Breaker <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-circuitbre…

Android 简单实现联系人列表+字母索引联动效果

效果如上图。 Main Ideas 左右两个列表左列表展示人员数据&#xff0c;含有姓氏首字母的 header item右列表是一个全由姓氏首字母组成的索引列表&#xff0c;点击某个item&#xff0c;展示一个气泡组件(它会自动延时关闭)&#xff0c; 左列表滚动并显示与点击的索引列表item …

Solidity智能合约调用其他合约的三种主要方式

在 Solidity 中&#xff0c;智能合约之间的交互非常重要。调用其他合约的功能可以增强合约的灵活性&#xff0c;使其能够执行跨合约操作&#xff0c;比如获取数据、转移资金或触发其他合约的功能。本文将详细介绍 Solidity 中调用其他合约的不同方式及其应用场景。 1. 合约间调…

【Unity踩坑】Unity更新Google Play结算库

一、问题描述&#xff1a; 在Google Play上提交了app bundle后&#xff0c;提示如下错误。 我使用的是Unity 2022.01.20f1&#xff0c;看来用的Play结算库版本是4.0 查了一下文档&#xff0c;Google Play结算库的维护周期是两年。现在需要更新到至少6.0。 二、更新过程 1. 下…

JAVA内存模型!=JVM内存模型

文章目录 前言JVM内存模型JAVA内存模型JAVA内存模型解释的问题可见性问题一致性问题 总结 前言 有很多JAVA开发人员&#xff0c;在被问起&#xff1a;“你知道Java内存模型吗&#xff1f;”&#xff0c;都会回答&#xff1a;“知道&#xff0c;JAVA内存模型分为方法区、堆、……

悟透自己、悟透生活、悟透人生(此文无价)

很多人都会有这样的疑问&#xff1a;“为什么听了很多道理&#xff0c;却依然没有过好这一生&#xff1f;” 古人给出了这样的回答。 王阳明曾说&#xff1a;“知行合一。” 老子则言&#xff1a;“知人者智&#xff0c;自知者明。” 可见&#xff0c;一切问题的根源都出在了我…

CSDN 的 GIt 是没东西吗

虽然说吧 CSDN 的博客也就那样&#xff0c;记得去年的时候 CSDN 出了一个 Git 代码库&#xff0c;被骂得要死&#xff0c;基本上是从外面搬了一堆代码回来。 这回 CSDN 又玩了个新东西&#xff0c;干脆你可以把你的博客文章同步到你在 CSDN 开的代码库上了。 如何同步 在 CS…

数据增强之imgaug的使用

包的导入 path = r"D:\\" # sometimes = lambda aug: iaa.Sometimes(0.5, aug) img = cv2.imread("D:\\photo\\test.jpg") img = cv2.resize(img,(128,128)) # img = cv2.cvtColor(img,cv2.COLOR_RGBA2GRAY) cv2.imwrite(path+"img.jpg",img)随…

python并发编程实战

python并发编程有三种 多线程Thread多进程Process多协程Coroutine cpu密集型计算 cpu密集型也叫计算密集型&#xff0c;是指I/O在很短的时间就可以完成&#xff0c;cpu需要大量的计算处理&#xff0c;特点是cpu占用率相当高 例如&#xff1a;压缩解压缩、加密解密、正则表达…

【Qt】开发环境与下载

这里写目录标题 1 Qt的开发工具概述2 Qt的下载2.1 下载Qt SDK 3. 认识SDK中的重要工具 1 Qt的开发工具概述 Qt支持持多种开发工具&#xff0c;其中⽐较常⽤的开发工具有&#xff1a;Qt Creator、Visual Studio、Eclipse. (1) QtCreator Qt Creator 是⼀个轻量级的跨平台集成…

iot网关是什么?iot网关在工业领域的应用-天拓四方

一、IoT网关的定义 IoT网关&#xff0c;即物联网网关&#xff0c;是物联网&#xff08;IoT&#xff09;系统中的重要组成部分。它主要实现感知网络与通信网络&#xff0c;以及不同类型感知网络之间的协议转换&#xff0c;既能够支持广域互联&#xff0c;也能满足局域互联的需求…

windows系统下Telnet工具的安装步骤

通过控制面板启用Telnet客户端 点击“确定”按钮&#xff0c;按照系统提示完成安装。 打开cmd&#xff0c;输入telnet就可以了

APISIX 联动雷池 WAF 实现 Web 安全防护

Apache APISIX 是一个动态、实时、高性能的云原生 API 网关&#xff0c;提供了负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。 雷池是由长亭科技开发的 WAF 系统&#xff0c;提供对 HTTP 请求的安全请求&#xff0c;提供完整的 API 管理和…

【盘一盘】加密软件有哪些?10款电脑文件加密软件超好用推荐!让您的数据更安全!

在信息洪流中&#xff0c;数据安全如古战场上的坚固堡垒&#xff0c;至关重要。 古人云&#xff1a;"机密深藏&#xff0c;方能安身立命。" 为此&#xff0c;我特意搜罗了10款电脑文件加密软件&#xff0c;它们如同现代版的"八卦阵"&#xff0c;既能保护…

华为/海思 Hi3516CV610 4K@20,6M@30 分辨率,1T 算力 NPU

总体介绍 Hi3516CV610 是一颗应用在安防市场的 IPC SoC 。在开放操作系统、新一代视频编解码标准、 网络安全和隐私保护、人工智能方面引领行业发 展&#xff0c;主要面向室内外场景下的枪机、球机、半球 机、海螺机、枪球一体机、双目长短焦机等产品 形态&#xff0c;打…

Spring - @Import注解

文章目录 基本用法源码分析ConfigurationClassPostProcessorConfigurationClass SourceClassgetImportsprocessImports处理 ImportSelectorImportSelector 接口DeferredImportSelector 处理 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 接口 处理Configuratio…

全同态加密算法概览

我们前面有谈到《Paillier半同态加密算法》&#xff0c;半同态加密算法除了支持密文加法运算的 Paillier 算法&#xff0c;还有支持密文乘法计算的 RSA 算法&#xff0c;早期的PSI(隐私求交)和PIR(匿踪查询)都有使用基于RSA盲签名技术来实现。今天我们来谈谈能够有效支持任意函…

【Git原理与使用】分支管理

分支管理 1.理解分支2.创建分支2.1创建分支2.2切换分支2.3合并分支 3.删除分支4.合并冲突4.分支管理策略5.分支策略6.bug分支7.删除临时分支8.小结 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&…

美客多自养号测评的常见问题与解决方案,从零开始的技术指南

美客多(MercadoLibre)主要专注于拉丁美洲市场。涵盖了多个国家&#xff0c;包括阿根廷、巴西、墨西哥、智利、哥伦比亚等&#xff0c;在这些国家占据了重要份额。对于卖家来说&#xff0c;要充分了解平台的特点和市场需求&#xff0c;制定合理的营销策略&#xff0c;不断提升自…

vue使用高德地图,点标记+轨迹

<template><!-- 轨迹--><divv-if"visible"ref"pageTotal"v-loading"loading"class"page-total"><div><divref"pageHead"class"page-head"><div class"head-title" /&…