news 2026/5/15 16:21:38

【Linux系统】--进程替换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux系统】--进程替换

进程替换

1-1定义进程替换

通过 exec* 函数,把磁盘中的其它程序(代码+数据)加载到内存中,替换当前进程的代码和数据,让页表重新构建映射关系,这期间不会创建新的进程。

进程替换就是把一个命令变成“会消失的临时文件”,让那些原本只读文件的命令也能直接处理命令的输出结果。在处理多输入对比、避免子 shell 变量赋值失败、或需要将数据同时喂给多个命令时非常有用。

1-2替换原理

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

【问题补充】

1.进程替换的原因?

  • 执行父进程的部分代码,完成特定功能。
  • 执行其它新的程序,用新程序的代码和数据替换父进程的代码和数据,让子进程执行。

2.操作系统是如何做到重新建立映射的呢?

操作系统可以对父进程的全部代码和数据进行写入,fork() 阶段子进程通过复制页表共享父进程物理内存, exec() 阶段子进程申请新物理内存、加载磁盘第三方程序并重建页表映射,最终子进程指向第三方程序的物理内存,父进程保持原有映射。


而在进程替换中,“重新建立映射”主要不是指页表,而是指 Shell 通过pipe()dup2()把子进程的标准输出重新映射到管道的内核对象,并把管道的读端伪装成路径(如/dev/fd/63)交给另一个命令使用exec。也就是说exec重建的是虚拟内存到物理内存的页表映射;而进程替换重建的是文件描述符到内核管道对象的映射

3.在进行程序替换的时候,有没有新的进程的创建?

没有。进程的程序替换,不改变内核相关的数据结构,只修改部分的页表数据,将新程序的1代码和数据加载带内存,重新构建映射关系,和父进程彻底脱离。

1-3如何替换(exec系列函数)

库函数

#include <unistd.h> int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...,char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);

系统调用execve 函数,功能:执行文件名 filename 指向的程序,文件名必须是一个二进制的 exe 可执行文件。

#include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);

1-3-1 函数解释

(1)execl 函数

exec 函数解释:

  • 这些函数如果调用成功,则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回 -1。
  • 所以 exec 函数只有出错的返回值而没有成功的返回值。
#include <unistd.h> int execl(const char *path, const char *arg, ...); //path: 要执行程序的路径,路径中要包括程序名,比如:usr/bin/ls //arg: 要执行的程序名/命令名 //...: 可变参数列表,必须以NULL结尾,表示参数传入完毕(就相当于列表)

实例演示:

#include <stdio.h> #include <unistd.h> // exec int main() { printf("程序运行开始\n"); execl("/usr/bin/ls", "ls", "-l", "-a", NULL); // 进程的程序替换 printf("程序运行结束\n"); return 0; }

运行的结果:没有执行后面的程序运行结束

注意:上述程序,因为只有一个进程,所以发生进程替换后,该进程自己就被替换了,不能去做自己的事情了。

  • 因此我们一般是让父进程创建子进程,让子进程通过进程替换,去执行其它程序,而父进程去检测执行结果和等待回收子进程
#include <stdio.h> #include <stdlib.h> // exit #include <sys/types.h> // getpid, getppid, waitpid #include <sys/wait.h> // waitpid #include <unistd.h> // exec, fork, getpid, getppid int main() { pid_t id = fork(); if (id == 0) { // 子进程 printf("我是子进程, pid: %d\n", getpid()); execl("/usr/bin/pwd", "pwd", NULL); // 进程替换 exit(1); } else if (id > 0) { // 父进程 printf("我是父进程, pid: %d\n", getpid()); int status = 0; // 进程退出信息 pid_t ret = waitpid(id, &status, 0); // 进程等待 if (ret > 0) { // 等待成功,打印子进程的ID、退出码、终止信号 printf(" 父进程waits for success, ret: %d, code: %d, sig: %d\n", ret, (status >> 8) & 0xff, status & 0x7f); } else { // wait failure } } else { // fork failure } return 0; }

运行结果:

  • 调用 exec 函数,不用考虑当前进程的返回值,因为 exec 函数下面的代码不会被执行(因为当前进程的代码和数据已经被替换了)。所以如果当前进程返回了,则说明 exec 函数调用失败了
  • exec 函数有点像特殊的加载器,把程序的代码数据加载到内存中,然后执行。
(2)execv 函数

在功能上和 execl 没有任何区别,只有传参的区别。

int main() { pid_t id = fork(); if (id == 0) { // 子进程 printf("我是子进程, pid: %d\n", getpid()); // 字符指针数组 char* const my_argv[] = { "ls", "-l", "-a", NULL }; execv("/usr/bin/ls", my_argv); // 进程替换 exit(1); } else if (id > 0) { // 父进程 } else { // fork failure } return 0;

运行结果:

(3)execlp 函数

功能上和 execl 没有任何区别,区别是,只需要给出要执行程序的名称即可,自动去 PATH 中寻找,不需要给出绝对路径。

注意:只有系统的命令,或者自己的命令(前提导入到 PATH中了),才能够找到。

int main() { pid_t id = fork(); if (id == 0) { // 子进程 printf("我是子进程pid: %d\n", getpid()); execlp("ls", "ls", "-l", "-a", NULL); // 进程替换 exit(1); } else if (id > 0) { // 父进程 } else { // fork failure } return 0; }

运行结果:

(4)execle 函数
// 调用 execle 或 execve 函数进行进程替换时,可以把在当前程序中定义的环境变量 //递给要替换的程序 ,此时在程序中通过 getenv 就可以获取到这些环境变量 int execle(const char *path, const char *arg, ..., char * const envp[]); int execve(const char *filename, char *const argv[], char *const envp[]);
int main() { // 获取环境变量 printf("my_cmd process is running, getenv --> MYENV: %s\n", getenv("MYENV")) return 0; }

运行结果:

int main() { pid_t id = fork(); if (id == 0) { // 子进程 printf("我是子进程, pid: %d\n", getpid()); // 定义环境变量MYENV char* const my_env[] = { "MYENV=hello world!", NULL }; /* * 通过进程替换,执行proc程序,同时把定义的环境变量传递给test程序 * 这样我们执行test程序,就可以获取到环境变量MYENV了 */ execle("./test", "test", NULL, my_env); // 进程替换 exit(1); } else if (id > 0) { // 父进程 } else { // fork failure } return 0; }

运行结果:

如上:如果在 proc 程序中,调用 execle 函数进行进程替换(执行test程序)时,可以把在 proc程序中定义的环境变量通过传递给要替换的test程序,运行proc 程序,进行进程替换(执行 test 程序),发现在test 中获取到了环境变量

环境变量具有全局属性,可以被子进程继承,那么它是如何做到的呢?

答:进程在运行的时候,自动会通过execle 函数执行新程序的时候,把系统的环境变量传给了新程序。

这里的小技巧,我定义了两个文件的目标编写,后面就可以定义多个按照这个格式

【补充】

多个文件的makefile通用写法

# ============================================ # 最简单通用 Makefile # ============================================ # 编译器 CC = gcc # 编译选项:-Wall 显示所有警告,-g 加入调试信息 CFLAGS = -Wall -g # 目标可执行文件名字 TARGET = myapp # 自动查找当前目录下所有 .c 文件 SRCS = $(wildcard *.c) # 将 .c 文件列表变成 .o 文件列表 OBJS = $(SRCS:.c=.o) # 默认目标:编译整个程序 all: $(TARGET) # 链接:把所有的 .o 文件链接成可执行文件 $(TARGET): $(OBJS) $(CC) $^ -o $@ # 编译:把每个 .c 文件编译成 .o 文件 %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ # 清理:删除编译产生的文件 clean: rm -f $(OBJS) $(TARGET) # 声明伪目标(不是真正的文件) .PHONY: all clean 使用方法: bash make # 编译 make clean # 清理

1-3-2 命名理解

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

exec调⽤举例如下: #include <unistd.h> int main() { char *const argv[] = {"ps", "-ef", NULL}; char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL}; execl("/bin/ps", "ps", "-ef", NULL); // 带p的,可以使⽤环境变量PATH,⽆需写全路径 execlp("ps", "ps", "-ef", NULL); // 带e的,需要⾃⼰组装环境变量 execle("ps", "ps", "-ef", NULL, envp); execv("/bin/ps", argv); // 带p的,可以使⽤环境变量PATH,⽆需写全路径 execvp("ps", argv); // 带e的,需要⾃⼰组装环境变量 execve("/bin/ps", argv, envp); exit(0); }

事实上,只有 execve 是真正的系统调⽤,其它五个函数最终都调⽤ execve,所以 execve 在 man⼿册 第2节,其它函数在 man ⼿册第3节。这些函数之间的关系如下图所⽰。
下图exec函数簇 ⼀个完整的例⼦:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 0:11:35

想给电脑“加把锁”?分享六款好用的电脑加密软件,建议收藏

企业核心资产的隐形保险箱在数字化办公的今天&#xff0c;企业的核心资产不再是仓库里的货物&#xff0c;而是电脑里的一份份设计图纸、一套套源代码或是一张张客户清单。很多老板直到员工离职带走核心资料&#xff0c;或者U盘丢失导致商业机密泄露时&#xff0c;才意识到电脑安…

作者头像 李华
网站建设 2026/5/15 0:48:18

高校毕业季必备:毕业论文写作有哪些提高效率的神器和软件?

又到一年一度高校毕业季&#xff0c;毕业论文成为所有应届生的头等难题。选题难、写初稿慢、查重重复率高、AI 疑似率超标、文献排版格式混乱&#xff0c;一步步流程下来耗费大量时间和精力。其实选对论文写作神器和软件&#xff0c;就能少走弯路、大幅提升写作效率&#xff0c…

作者头像 李华
网站建设 2026/5/15 6:17:10

OpenWRT软件中心架构解析:iStore标准化解决方案深度指南

OpenWRT软件中心架构解析&#xff1a;iStore标准化解决方案深度指南 【免费下载链接】istore 一个 Openwrt 标准的软件中心&#xff0c;纯脚本实现&#xff0c;只依赖Openwrt标准组件。支持其它固件开发者集成到自己的固件里面。更方便入门用户搜索安装插件。The iStore is a a…

作者头像 李华
网站建设 2026/5/13 22:46:09

AI 漫剧风口崛起,彻底改写创作者收入结构

2026三掌柜赠书活动第三十期 AI漫剧创作一本通&#xff1a;脚本/生图/动效/剪辑从入门到实战 目录 前言 选对赛道&#xff1a;AI漫剧才是可长期经营的内容生意 关于《AI漫剧创作一本通&#xff1a;脚本/生图/动效/剪辑从入门到实战》 编辑推荐 内容简介 作者简介 图书目…

作者头像 李华
网站建设 2026/5/13 22:43:28

从手机到汽车座舱:MIPI DSI协议如何驱动你身边的每一块屏?

从手机到汽车座舱&#xff1a;MIPI DSI协议如何驱动你身边的每一块屏&#xff1f; 当我们每天滑动手机屏幕、查看智能手表通知或使用车载中控导航时&#xff0c;很少有人会思考这些显示背后的技术支柱。事实上&#xff0c;从消费电子到汽车工业&#xff0c;MIPI DSI协议正悄然成…

作者头像 李华