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

【Linux学习】Linux中的进程程序替换

大家好,我是程序员小青蛙,今天介绍进程程序替换。

一、什么是进程程序替换?

核心定义

fork()创建子进程后,子进程默认和父进程执行相同的程序。如果想让子进程执行一个全新的程序,就需要调用exec系列函数,完成进程程序替换

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

关键特点

  1. 不创建新进程:调用exec前后,进程的pid不会改变。
  2. 替换用户空间:新程序的代码段、数据段会完全覆盖原进程的用户空间,栈、堆也会被重置。
  3. 成功不返回exec调用成功后,原程序的后续代码不会再执行;调用失败时返回-1

二、exec 系列函数详解

Linux 提供了 6 个以exec开头的函数,统称为 exec 函数族。

1. 函数原型

#include <unistd.h> // l: list(可变参数列表) 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[]); // v: vector(参数数组) 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[]);

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

2. 命名规律(记忆技巧)

字母含义示例
llist:参数以可变列表形式传递,以NULL结尾execl("/bin/ls", "ls", "-l", NULL)
vvector:参数以数组形式传递,数组最后一个元素为NULLexecv("/bin/ls", argv)
ppath:自动搜索PATH环境变量,无需写全路径execlp("ls", "ls", "-l", NULL)
eenv:自定义环境变量,需手动传入envp数组execle("/bin/ls", "ls", "-l", NULL, envp)

3. 函数对比表

函数名参数格式是否带路径是否使用当前环境变量
execl列表必须写全路径
execlp列表可写程序名,自动搜PATH
execle列表必须写全路径否,需手动传入envp
execv数组必须写全路径
execvp数组可写程序名,自动搜PATH
execve数组必须写全路径否,需手动传入envp

4. 底层关系

只有execve是真正的系统调用,其他 5 个函数都是对execve的封装,以提供更灵活的调用方式。


三、exec 函数使用示例

1.execl示例(全路径 + 列表传参)

#include <unistd.h> int main() { // 执行 ls -l 命令,需要写全路径 execl("/bin/ls", "ls", "-l", NULL); // 只有调用失败才会执行这里 perror("execl"); return 1; }

2.execlp示例(自动搜 PATH + 列表传参)

#include <unistd.h> int main() { // 无需写全路径,自动在 PATH 中搜索 ls execlp("ls", "ls", "-l", NULL); perror("execlp"); return 1; }

3.execv示例(全路径 + 数组传参)

#include <unistd.h> int main() { char *argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv); perror("execv"); return 1; }
#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节。这些函数之间的关系如下图所示。


四、综合应用:简易 Shell 实现

fork+exec+wait是实现 shell 的核心逻辑。

核心流程

  1. 获取命令行:读取用户输入的命令。
  2. 解析命令:将命令字符串分割成argv数组。
  3. 创建子进程:调用fork()创建子进程。
  4. 程序替换:子进程调用execvp执行命令。
  5. 等待子进程:父进程调用waitpid等待子进程退出。

完整代码

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> #define MAX_CMD 1024 char command[MAX_CMD]; // 1. 读取用户输入 int do_face() { memset(command, 0x00, MAX_CMD); printf("myshell$ "); fflush(stdout); if (scanf("%[^\n]%*c", command) == 0) { getchar(); return -1; } return 0; } // 2. 解析命令为 argv 数组 char **do_parse(char *buff) { int argc = 0; static char *argv[32]; char *ptr = buff; while (*ptr != '\0') { if (!isspace(*ptr)) { argv[argc++] = ptr; while (*ptr != '\0' && !isspace(*ptr)) ptr++; } else { while (*ptr != '\0' && isspace(*ptr)) ptr++; *ptr = '\0'; } } argv[argc] = NULL; return argv; } // 3. 执行命令 int do_exec(char *buff) { char **argv = do_parse(buff); if (argv[0] == NULL) return -1; pid_t pid = fork(); if (pid == 0) { // 子进程:程序替换 execvp(argv[0], argv); perror("execvp"); exit(1); } else { // 父进程:等待子进程退出 waitpid(pid, NULL, 0); } return 0; } int main() { while (1) { if (do_face() < 0) continue; do_exec(command); } return 0; }

思考函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。


五、关键知识点总结

  1. 程序替换的本质:用新程序的代码和数据,覆盖进程原有的用户空间,进程pid不变。
  2. exec 函数的核心特点:成功不返回,失败返回-1
  3. 命名规律l/v表示参数传递方式,p表示自动搜PATHe表示自定义环境变量。
  4. 简易 shell 的核心逻辑fork创建子进程 →exec替换程序 →wait等待子进程。
http://www.jsqmd.com/news/911439/

相关文章:

  • 从图片到代码:Qwen3-VL-8B-Thinking视觉编码能力实战教程
  • 抖音批量下载终极指南:3分钟搞定全作品,免费去水印!
  • 面试官问我SHAP值怎么算?我用一个房价预测的例子给他讲明白了
  • 我把一个依赖安装到了本地仓库,但是IDEA 刷新 maven 提示远程私服仓库找不到,怎么解决
  • 3大功能+5个技巧:用Zotero Style插件让你的文献管理效率翻倍
  • L298N驱动直流电机,你的代码可能一直有隐患!详解电源隔离与共地的正确姿势
  • Easypoi停更了?别慌!手把手教你无缝迁移到Apache Fesod(FastExcel)并保留模板功能
  • Arduino驱动28BYJ-48步进电机:从硬件连接到代码优化的完整指南
  • 华为路由基础及静态路由详解
  • League Akari:英雄联盟玩家的终极智能助手,告别繁琐操作提升游戏体验
  • 如何用MindSpore-Lab/mobilenetv1实现高效图像分类:从理论到实践的完整指南
  • Lindy预约自动化实施失败率高达61%?资深架构师复盘12个真实故障案例(含日志级调试清单)
  • 从40G到400G:一文读懂Infiniband带宽演进与你的数据中心选型指南
  • 【计算机组成原理】 栈帧访问机制
  • AU‑60 全功能 AI 语音处理模组:工程师视角的一站式声学解决方案
  • VisionPro 9.0 C#脚本性能优化实战:从‘爆红’工具到毫秒级提速的避坑指南
  • Paperxie 智能排版:告别论文格式内耗,一键对齐全校规范
  • Spek音频频谱分析器:免费开源的声音可视化工具完整指南
  • 5分钟搞定三大音乐平台逐字歌词:ESLyric-LyricsSource终极使用指南
  • MVC、MVP、MVVM 架构 笔记
  • BERT Miniatures系列解析:为什么BERT uncased L-12 H-256 A-4适合资源受限环境
  • 终极Windows防撤回指南:微信QQ消息永久保存的简单解决方案
  • 如何解决终端开发效率瓶颈:终极WaveTerm自定义小部件指南
  • 在OpenClawAgent工作流中无缝接入Taotoken多模型
  • 行业首份Claude-3.5代码质量压测报告:10万行样本暴露的2个反直觉性能断层
  • 如何优化DistilBERT-base-cased推理速度:量化、剪枝与蒸馏进阶技巧
  • Arduino音频编程实战:从蜂鸣器驱动到旋律播放全解析
  • Irodori-TTS-500M-v2未来路线图:日语语音合成的下一步发展方向
  • 抖音视频批量采集助手:如何高效下载多用户视频内容
  • 告别手绘!用Unity Tilemap快速搭建2D像素风地图(附官方拓展包下载)