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

019 进程控制 —— 进程程序替换

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

    • 进程控制 —— 进程程序替换
      • 1. 替换原理
      • 2. execl 的单进程使用
        • 1. 函数原型
      • 3. execl 的多进程使用
      • 4. `exec` 函数“全家桶”
        • 1. `man` 手册查询
        • 2. 核心函数详解(实战场景)
          • ① `execl`(列表传参 + 绝对路径)
          • ② `execlp`(列表传参 + 自动搜索 `PATH`)
          • ③ `execle`(列表传参 + 自定义环境变量)
          • ④ `execv`(数组传参 + 绝对路径)
          • ⑤ `execvp`(数组传参 + 自动搜索 `PATH`)
          • ⑥ `execvpe`(数组传参 + 搜索 `PATH` + 自定义环境变量)
          • ⑦ `execve`(系统调用 + 完全控制)
      • 5. 部分代码实战
        • 1. 使用 `execvp` 执行动态命令
        • 2. 使用 `execle` 自定义环境变量
      • 6. 代码验证 `exec` 执行系统命令和自定义命令
        • 1. 执行系统命令(如 `ls`)
        • 2. 执行自定义程序(如编译后的 `my_program`)
      • 7. 向子进程传递环境变量
        • 1. 使用 `execle` 或 `execvpe` 自定义环境变量
      • 8. 环境变量是覆盖还是追加?
        • 1. 核心规则
        • 2. 示例验证
      • 9. 总结
        • 1. 核心原理
        • 2. 关键函数实战场景
        • 3. 环境变量规则
        • 4. 实战口诀
    • 共勉

进程控制 —— 进程程序替换

1. 替换原理

进程程序替换是指在一个已经存在的进程中,通过系统调用将当前进程的代码、数据等全部替换为新程序的内容,也就是说,新程序加载到当前进程的地址空间中,原来进程的内容被完全覆盖。在这一过程中,进程的 PID 保持不变,但内存空间、寄存器的内容和代码逻辑均变为新程序的内容。

image-20250416124958825

【关键点】

  1. 进程替换不创建新进程,而是复用现有进程的 PID,这对需要快速切换任务、降低资源开销有很大好处。
  2. 一旦替换成功,原进程的代码 立即终止,从新程序的 main 函数开始执行。
  3. 替换过程会关闭原进程中打开的文件描述符(除非这些描述符被标记为在 exec 之后保留),fcntl(fd, F_SETFD, FD_CLOEXEC) 标记文件描述符在 exec 后关闭(了解)。

2. execl 的单进程使用

1. 函数原型
int execl(const char* path, const char* arg, ..., (char*)NULL);注意:execl 接受变长参数,需要以 NULL 结束参数列表,参数顺序要求严格,第一个参数通常写为程序文件的绝对路径,第二个参数是新程序的“名称”,随后是实际参数。
int execl(const char* path,       // 新程序的路径(核心!)const char* arg0,       // 第一个参数(通常为程序名)const char* arg1,       // 第二个参数(如命令行选项)...,                    // 可变参数(灵活传递)(char*)NULL             // 参数结束标志(必须!忘记会导致崩溃或参数错乱)
);

参数解析

  • path新程序的绝对路径(如 /bin/ls),必须明确指定位置!
  • arg:命令行参数列表,第一个参数通常是程序名,最后必须加 NULL 表示结束。
#include <unistd.h>
#include <stdio.h>int main()
{printf("原进程即将被替换!\n");execl("/bin/ls", "ls", "-l", NULL);     // 执行 ls -l 命令printf("这里不会被执行!你看不到我!\n");perror("execl failed");                 // 若替换失败才会执行到这里return 1;
}
  • 替换成功后,printf("原进程...") 之后的代码 不再执行
  • 若替换失败(如路径错误),perror 会输出错误信息。

运行示例:

image-20250416132648832


3. execl 的多进程使用

我们先 fork() 创建一个 子进程,在子进程中使用 execl() 替换自身为另一个程序,而 父进程继续执行原有逻辑。这种方式不会影响父进程,同时能让子进程跑一个完全不同的程序,是 非常经典的进程控制模式

#include <stdio.h>
#include <unistd.h>  // 包含 fork() 的头文件
#include <stdlib.h>  // 包含 exit() 的头文件int main()
{pid_t pid = fork();         // 创建子进程if (pid == 0)               // 子进程:使用 execl 替换为新程序{printf("我是子进程,PID: %d,现在执行 execl 替换为 ls 命令\n", getpid());execl("/bin/ls", "ls", "-l", NULL);perror("execl failed"); // 若 execl 出错,才会继续执行下面语句exit(1);}else if (pid > 0)           // 父进程继续执行原有逻辑{printf("我是父进程,PID: %d,创建了子进程: %d\n", getpid(), pid);sleep(2);printf("父进程结束\n");}else{perror("fork failed");return 1;}return 0;
}

运行示例:

image-20250416134826280

我是父进程,PID: 18882,创建了子进程: 18883
我是子进程,PID: 18883,现在执行 execl 替换为 ls 命令
total 32
-rwxrwxr-x 1 hcc hcc 8720 Apr 16 13:47 Multi-process
-rw-rw-r-- 1 hcc hcc  833 Apr 16 13:44 Multi-process.c
-rwxrwxr-x 1 hcc hcc 8464 Apr 16 13:25 test1
-rw-rw-r-- 1 hcc hcc  325 Apr 16 13:24 test1.c
父进程结束

4. exec 函数“全家桶”

Linux 下有 7 种以 exec 开头的函数,统称 exec 函数。

  • 系统调用类:exec 是由内核提供的系统调用,属于 man 手册的第 2 章节。
  • 库函数类:其他 exec 函数均是 C 标准库对 execve 的封装,属于 man 手册第 3 章节。

exec 函数族成员一览(记熟)

函数名参数类型使用说明
execl列出参数(arg0, arg1, …, NULL)最常用,适合参数个数固定 ✅
execv参数数组(char * argv [])参数可变,用数组表示(* *程序参数动态构造(任务调度器)**)
execle列出参数 + 环境变量(envp)适合需要指定环境变量
execve参数数组 + 环境变量最底层函数,系统调用接口 ✅
execlp自动查 $PATH + 列出参数不指定绝对路径,依赖环境 PATH ✅
execvp自动查 $PATH + 参数数组最常见 Shell 结构 ✅(支持 PATH 查找 + 动态参数
execvpe自动查 $PATH + 参数数组 + envp非标准,GNU 扩展

image-20250416185202635

记忆:

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

1. man 手册查询

一般来说使用 man 3 exec(6 个)和 man 2 execve(1 个)就能进行查询,但是不妨有朋友像我一样被提示“No manual entry for execl”,如何解决?根据搜索,使用命令 sudo yum install man-pages man-pages-posix 即可(如果是其他情况还请自行搜索)。

image-20250416170533773

image-20250416171812992

2. 核心函数详解(实战场景)
execl(列表传参 + 绝对路径)
execl("/bin/ls", "ls", "-l", NULL); 	// 执行 /bin/ls -l
  • 重点:必须指定完整路径,参数以列表形式传递,末尾必须加 NULL
execlp(列表传参 + 自动搜索 PATH
execlp("ls", "ls", "-l", NULL); 	// 执行系统命令 ls(自动搜索 PATH 环境变量)
  • 优势:直接使用命令名(如 ls),无需写绝对路径。
execle(列表传参 + 自定义环境变量)
// 自定义环境变量并执行程序
char *env[] = {"MY_ENV=hello", NULL};
execle("/path/to/my_program", "my_program", NULL, env); 
  • 应用场景:需要为子进程指定独立环境变量(如容器化任务)。
execv(数组传参 + 绝对路径)
// 参数以数组形式传递
char *argv[] = {"ls", "-l", NULL};
execv("/bin/ls", argv); 
  • 适用场景:参数动态生成(如从用户输入或配置文件读取)。
execvp(数组传参 + 自动搜索 PATH
// 动态参数数组 + 自动搜索 PATH
char *argv[] = {"ls", "-l", NULL};
execvp("ls", argv); 
  • 高频用法:实现类似 Shell 的功能(动态解析命令参数)。
execvpe(数组传参 + 搜索 PATH + 自定义环境变量)
// GNU 扩展,非所有系统支持
char *argv[] = {"my_program", NULL};
char *env[] = {"CUSTOM_ENV=1", NULL};
execvpe("my_program", argv, env); 
  • 注意execvpeGNU 扩展函数,需确认系统支持(如 Linux 可用)。
execve(系统调用 + 完全控制)
// 系统调用级函数,直接控制环境变量和参数
char *argv[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/bin", NULL};
execve("/bin/ls", argv, env); 
  • 本质:其他 exec 函数最终调用 execve 实现功能。

[!NOTE]

所有 exec 函数 成功时不返回,失败时返回 -1 检查返回值并处理错误:

if (execl(...) == -1)
{perror("执行失败!");exit(1);
}
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{char* const argv[] = { "ps", "-ef", NULL };char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };// execl: 需要完整路径if (execl("/bin/ps", "ps", "-ef", NULL) == -1){perror("execl");exit(1);}// execlp: 使用 PATH 环境变量if (execlp("ps", "ps", "-ef", NULL) == -1){perror("execlp");exit(1);}// execle: 需要自己设置环境变量if (execle("ps", "ps", "-ef", NULL, envp) == -1){perror("execle");exit(1);}// execv: 参数通过数组传递if (execv("/bin/ps", argv) == -1){perror("execv");exit(1);}// execvp: 使用 PATH 环境变量if (execvp("ps", argv) == -1){perror("execvp");exit(1);}// execve: 需要自己设置环境变量if (execve("/bin/ps", argv, envp) == -1){perror("execve");exit(1);}return 0;
}

5. 部分代码实战

1. 使用 execvp 执行动态命令
#include <unistd.h>
#include <stdio.h>int main()
{char *args[] = {"ls", "-l", "-a", NULL}; // 参数数组execvp("ls", args);perror("execvp 失败!");return 1;
}
  • 输出:执行 ls -l -a,参数通过数组动态传递。
2. 使用 execle 自定义环境变量
#include <unistd.h>int main()
{char *env[] = {"USER=test", "PATH=/usr/bin", NULL};execle("/bin/ls", "ls", "-l", NULL, env);return 1; // 只有出错才会执行
}
  • 作用:子进程的环境变量被替换为 env 数组中的内容。

6. 代码验证 exec 执行系统命令和自定义命令

1. 执行系统命令(如 ls
#include <unistd.h>
#include <stdio.h>int main()
{execlp("ls", "ls", "-l", NULL);     // 执行系统命令 ls -l,自动搜索 PATH 环境变量perror("execlp 失败!");            // 若替换失败才会执行以下代码return 1;
}

执行结果:输出当前目录的文件列表(与终端直接运行 ls -l 效果一致)。

2. 执行自定义程序(如编译后的 my_program
#include <unistd.h>
#include <stdio.h>int main()
{// 执行我们用户自己的程序(假设 my_program 在 /home/user/bin 下)execl("/home/user/bin/my_program", "my_program", "arg1", "arg2", NULL);perror("execl失败!");return 1;
}

关键点

  • 路径必须正确:需指定自定义程序的绝对路径或确保其在 PATH 环境变量中。
  • 参数自由传递:可传递任意参数给自定义程序(my_programmain 函数接收这些参数)。

7. 向子进程传递环境变量

1. 使用 execleexecvpe 自定义环境变量
#include <unistd.h>
#include <stdio.h>int main()
{char* env[] = { "MY_ENV=hello", "PATH=/usr/bin", NULL };    // 自定义环境变量数组(必须以 NULL 结尾)execle("/bin/ls", "ls", "-l", NULL, env);                   // 执行程序并传递自定义环境变量perror("execle失败!");return 1;
}

验证方法:在 ls 程序中无法直接看到环境变量(因为 ls 不读取这些变量),但可通过以下方式验证:

// 编写一个测试程序 test_env.c
#include <stdio.h>
#include <stdlib.h>int main()
{printf("MY_ENV=%s\n", getenv("MY_ENV"));    // 输出自定义环境变量return 0;
}// 编译后执行:
execle("./test_env", "test_env", NULL, env);    // 输出 "MY_ENV = hello"

8. 环境变量是覆盖还是追加?

1. 核心规则
  • 若显式传递环境变量(如 execleexecve):完全覆盖 父进程的环境变量,子进程仅保留传递的环境变量。
  • 若不传递环境变量(如 execlpexecv):继承父进程的所有环境变量
2. 示例验证
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{setenv("PARENT_ENV", "parent_value", 1);            // 父进程设置一个环境变量char* env[] = { "CHILD_ENV=child_value", NULL };    // 自定义子进程环境变量if (fork() == 0)                                    // 子进程使用 execle 传递自定义环境变量{execle("./test_env", "test_env", NULL, env);perror("execle failed");_exit(1);}else{wait(NULL);}return 0;
}

test_env 程序输出

CHILD_ENV=child_value
PARENT_ENV=(null)   # 父进程的环境变量被覆盖!

9. 总结

1. 核心原理
  • 不创建新进程:复用现有进程的 PID 和资源(文件描述符等),仅替换代码和数据段。
  • 执行即替换:成功后原进程代码立即终止,从新程序的 main 开始执行。
  • 资源处理:默认关闭未标记的文件描述符,避免资源泄漏。
2. 关键函数实战场景
函数名参数形式路径搜索环境变量适用场景
execl列表传参继承父进程参数固定 + 绝对路径(如 /bin/ls
execlp列表传参继承父进程执行系统命令(如 ls,依赖 PATH
execle列表传参自定义覆盖需要独立环境变量(如容器化任务)
execv数组传参继承父进程动态生成参数(如用户输入解析)
execvp数组传参继承父进程Shell 类动态命令执行(如 ls -l -a
execve数组传参自定义覆盖系统级控制(底层实现)
3. 环境变量规则
  • 覆盖规则:使用 execleexecve 时,子进程环境变量完全替换为传入的数组。
  • 继承规则:默认继承父进程环境变量,适用于 execlexecvexeclpexecvp
4. 实战口诀
  • 列表传参用 l,数组传参用 v
  • 自动搜索加 p,环境变量加 e
  • 路径要对、参数要全、NULL 收尾、错误要检!

共勉

在这里插入图片描述
在这里插入图片描述

http://www.lryc.cn/news/588463.html

相关文章:

  • SpringMVC2
  • 力扣-138.随机链表的复制
  • 一分钟K线实时数据数据接口,逐笔明细数据接口,分时成交量数据接口,实时五档委托单数据接口,历史逐笔明细数据接口,历史分时成交量数据接口
  • 深入理解MyBatis延迟加载:原理、配置与实战优化
  • 美丽田园发布盈喜公告,预计净利增长超35%该咋看?
  • 现场设备无法向视频汇聚EasyCVR视频融合平台推流的原因排查与解决过程
  • CA-IS3082W 隔离485 收发器芯片可能存在硬件BUG
  • 第十五节:Vben Admin 最新 v5.0 (vben5) + Python Flask 快速入门 - vue前端 生产部署
  • Laravel 中 chunk 分页漏掉数据?深度解析原因与解决方案
  • Unity3D + VS2022连接雷电模拟器调试
  • 4、qt窗口(沉淀中)
  • iOS APP 上架流程:跨平台上架方案的协作实践记录
  • ConcurrentHashMap 原子操作详解:computeIfAbsent、computeIfPresent和putIfAbsent
  • C语言-数据输入与输出
  • 《甘肃棒球》国家级运动健将标准·棒球1号位
  • c#进阶之数据结构(动态数组篇)----Queue
  • Javaweb使用websocket,请先连上demo好吧!很简单的!
  • Vim库函数
  • 【DOCKER】-4 dockerfile镜像管理
  • 纯C++11实现!零依赖贝叶斯情感分析系统,掌握机器学习系统工程化的秘密!
  • 学习 Flutter (三):玩安卓项目实战 - 上
  • 机器学习、深度学习、神经网络之间的关系
  • redis配置(Xshell连接centos7的基础上)
  • Mysql数据库学习--多表查询
  • Python中使用Re模块TypeError: cannot use a string pattern on a bytes-like object 解决办法
  • Leaflet面试题及答案(81-100)
  • 九、官方人格提示词汇总(中-1)
  • 项目进度图不直观,如何优化展示方式
  • Go泛型完全指南:从基础到实战应用
  • 进程---基础知识+命令+函数(fork+getpid+exit+wait+exec)