当前位置: 首页 > news >正文

【Linux仓库】进程的“夺舍”与“飞升”:exec 驱动的应用现代化部署流水线

🌟 各位看官好,我是egoist2023!

🌍 Linux == Linux is not Unix !

🚀 今天来学习exec系列的进程程序替换,从"fork"的"克隆"到"exec"的"重生"

👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦!

目录

书接上文

进程程序替换

原理

替换操作

理解可变参数

execl函数

execv函数

execlp函数

execvp函数

execvpe函数

系统默认环境变量 

自带环境变量

系统+自带 环境变量 

execle函数 

总结  


书接上文

上文介绍了Linux中进程等待的必要性和实现方法。主要内容包括:

  1. 子进程退出后若父进程不回收会导致僵尸进程和内存泄漏;
  2. wait和waitpid函数的使用方法,重点讲解了如何获取子进程退出状态和信号编号;
  3. 非阻塞等待的实现方式,通过WNOHANG选项让父进程在等待子进程时能同时处理其他任务;
  4. 解释了孤儿进程无法被Ctrl+C终止的原因。文章还通过代码示例演示了如何正确获取子进程退出码和信号,以及父进程如何通过轮询方式非阻塞等待子进程。

进程程序替换

通过上一节的学习,我们掌握了如何使用 wait 和 waitpid 来回收子进程资源,避免僵尸进程的产生,并让父进程能够知晓子进程的结束状态。无论是阻塞还是非阻塞等待,其核心目的都是管理一个已经结束或即将结束的“任务”

然而,我们不禁要思考一个更根本的问题:创建子进程的初衷,难道仅仅是为了让它复制一份父进程的代码,然后执行一模一样的操作吗?显然不是。在绝大多数实际应用中,我们创建子进程主要有两个目的:

  1. 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

如何让子进程执行一个全新的程序呢?

子进程有自己的代码,有自己的数据 --> 成为一个真正的独立的进程!

原理

程序替换的过程有没有必要创建新的进程呢? --> 没有必要,为什么呢? --> 通过将代码和数据加载到内存里,覆盖代码和数据段。而pcb、虚拟内存空间、页表不会有太大变化,顶多修改页表的映射关系,因此没必要创建一个新的进程。(打印程序替换前后的pid即可证明)

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种exec函数以执⾏另⼀个程序。当进程调⽤⼀种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的id并未改变。

正如上文所述,exec 函数族的核心功能是用一个新的程序替换当前进程的映像。这个功能本身是明确的,但在实际应用时,我们调用新程序的需求却是多变的:命令有时需要带上复杂的参数列表,例如 ls -l -a -i

为了理解exec系列的函数,这里需要先提一嘴可变参数,只有理解了可变参数,才能方便引入接下来的exec系列函数.

替换操作

理解可变参数

可变参数函数是指能够接受数量不确定的参数函数。那又是如何做到接受不确定参数数量的呢?毫无疑问,肯定需要一个"路标"指引它从哪里开始走,要走多少步啊!!!

"路标":就是函数里的第一个固定参数 count。它的最大作用就是告诉函数:“从我后面开始,一共有 count 个参数”。

va_start:根据最后一个固定参数,找到可变参数的起始位置

va_arg: 取出当前位置的一个值;把位置移动到下一个参数那里。

va_end:打扫战场,清理工作。


int execl(const char *pathname, const char *arg, ... /*, (char *) NULL */);
int execlp(const char *file, const char *arg, ... /*, (char *) NULL */);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

理解了可变参数后,讲述进程程序替换的操作:六种以exec开头的函数,统称exec函数。

  • 这些函数如果调⽤成功则加载新的程序从启动代码开始执⾏,不再返回。
  • 如果调⽤出错则返回-1
  • 所以exec函数只有出错的返回值⽽没有成功的返回值

execl函数

 int execl(const char *pathname, const char *arg, ... /*, (char *) NULL */); 

  • 第一个参数是传一个程序,程序也是一个文件,是文件就需要有路径 + 文件名;

  • 第二个参数是要执行程序的程序名称;

  • 第三个参数是可变参数,必须以NULL结尾。

举个梨子:

int main()
{printf("我是一个进程,pid:%d,getpid());sleep(1);int n = execl("/usr/bin/ls","ls","-l","-a","-n",NULL);printf("进程结束:%d\n",n);return 0;
}

 

可以看到我们的程序确实被替换了,但同时也会发现显示器没有打印 “ 进程结束 : ... ” ,直接结束了。为什么会没有后续的打印了呢?不就是被替换了嘛,进程已经执行另一个程序的代码,原来的代码已经没有了。因此程序替换函数一旦调用成功,后续代码不再执行。(失败返回-1)

那么,我们可不可以替换子进程的代码和数据呢?当然可以,子进程会自己执行一个全新的程序。那么会影响到父进程吗?不会,因为进程之间必须具有独立性啊 ! (可以理解成代码如果要被替换,也要进行写时拷贝,如上图所示.)

execv函数

int execv(const char *pathname, char *const argv[]);

  • 第二个参数是一个指针数组,即是把程序名称+可变参数存放在该数组中,传递给子进程的命令行参数。

举个梨子:

char *const myargv[] =
{(char*)"ls",(char*)"-l",(char*)"-i",(char*)"-n",(char*)"-l",NULL
};char **myargv = &argv[1];
execv(myargv[0],myargv);

在前面章节中,我们清楚main函数也是可以带参数的,其中一个就包含命令行参数表。此时./myexe执行自己的执行流,后半部分传递给子进程,让子进程来执行。 (./myexe不就是我们的bash,后半部分不就是我们的命令+选项吗?)

execlp函数

int execlp(const char *file, const char *arg, ... /*, (char *) NULL */); 

  • 第一个参数指明要执行谁,只需要告诉名字,如指令ls。执行指令的命令,需要让execlp自己在环境变量PATH中寻找指定的程序!

execvp函数

int execvp(const char *file, char *const argv[]);

 

execvpe函数

int execvpe(const char *file, char *const argv[], char *const envp[]);

  • 第三个参数是一个指针数组,指向环境变量表。 

那么这是否意味着我们同样能通过程序替换函数获取环境变量的内容。这里通过三种操作来观察。

系统默认环境变量 
extern char **environ;char *const myargv[] = {"mycmd",NULL};
execvpe("./mycmd",myargv,environ);

自带环境变量
char *const myargv[] = {"mycmd",NULL};
char *const myenv[] = {"hello=1234","world=1",NULL}
execvpe("./mycmd",myargv,myenv);

系统+自带 环境变量 
char *my = "hello=123456789";putenv(my);
char *const myargv[]={"mycmd",NULL};
execvpe("./mycmd",myargv,environ);

execle函数 

int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */); 

execle的参数在上面都有谈及,就不再进行过多赘述。 

总结  

  • l(list) : 表⽰参数采⽤列表
  • v(vector) : 参数⽤数组
  • p(path) : 有p⾃动搜索环境变量PATH
  • e(env) : 表⽰自己维护环境变量

为什么程序替换函数要设计这么多个呢?只有传参形式的不同,满足我们未来不同的应用场景!!!

虽然在上层传参形式有所不同,但每个函数内部实际上都调用了同一个系统调用execve!

int execve(const char *pathname, char *const _Nullable argv[], char *const _Nullable envp[]);

http://www.dtcms.com/a/344445.html

相关文章:

  • 前端github-workflows部署腾讯云轻量服务器
  • 学云计算还是网络,选哪个好?
  • Linux:网络层IP协议
  • alicloud 阿里云有哪些日志 审计日志
  • css的white-space: pre
  • Docker 命令大全
  • VsCode 上的Opencv(C++)环境配置(Linux)
  • 四种方法把 Proxy 对象代理数组处理成普通数组
  • URP+Unistorm5.3.0 -> webGL天空黑屏的处理
  • 如何精准高效地比对两份合同的差异?
  • Java数据结构——7.2 二叉树-二叉树
  • MPLS原理
  • 新能源知识库(84)什么是IEC白皮书
  • 初识数据结构——Map和Set:哈希表与二叉搜索树的魔法对决
  • CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行本地操作--读取店铺信息(6)
  • 循环神经网络实战:GRU 对比 LSTM 的中文情感分析(三)
  • UE5关卡蓝图能不能保存副本呀?
  • Pandas 合并数据集:concat 和 append
  • 2025年城市建设与公共管理国际会议(UCPM 2025)
  • Linux之Ubuntu入门:Vmware中虚拟机中的Ubuntu中的shell命令-常用命令
  • C++实现简易线程池:理解 function 与 bind 的妙用
  • CMake进阶:Ninja环境搭建与加速项目构建
  • JVM-(8)JVM启动的常用命令以及参数
  • 期货Level-2五档订单簿每250毫秒高频数据及分时、日度历史行情深度解析
  • 【大模型采样策略(Greedy、Top-k、Top-p、温度调节)三化学习】
  • 旧物新生,交易无界——探索二手交易小程序系统的绿色革命
  • 优考试局域网系统V6.0.0版
  • 谁才是多快好省的数据分析选择:SelectDB vs. ClickHouse vs. Snowflake
  • 关于常用线程池CompletableFuture和Future的介绍
  • 我从零开始学习C语言(13)- 循环语句 PART2