从理论到实践:Linux 进程替换与 exec 系列函数
个人主页:chian-ocean
文章专栏-Linux
前言:
在Linux中,进程替换(Process Substitution)是一个非常强大的特性,它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操作中。
进程替换原理
进程替换(Process Replacement)是操作系统用来用一个新程序完全替换当前进程用户态内容的机制,其本质是清空当前进程的用户态内容并加载新程序,同时保留内核态资源(如 PID、文件描述符等)。它通过 exec
系列系统调用实现,以下是进程替换的详细原理。
进程替换的核心是:
- 清空当前进程的用户态地址空间,包括代码段、数据段、堆、栈等。
- 加载新程序到当前进程的地址空间,并切换到新程序的入口点执行。
- 保留进程的内核态资源,如 PID、打开的文件描述符、父子关系等。
- 如果
exec
调用成功,原进程的代码永远不会被执行。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib> using namespace std;int main() {// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)cout << "I'm a process: " << "PID: " << getpid() << " PPID: " << getppid() << endl;// 调用 fork() 创建子进程pid_t id = fork();// 子进程逻辑if (id == 0) {cout <<"Child PID: "<< getpid() << endl;//打印子进程的PID// 使用 execl() 替换当前子进程为 /usr/bin/ls 程序// 第一个参数是程序路径,第二个参数是程序名称(通常为 argv[0]),后面是命令行参数execl("/usr/bin/ls", "ls", "-l", "-a", NULL);// 如果 execl() 执行失败(例如文件不存在),会执行以下代码perror("execl failed"); // 输出错误信息exit(1); // 子进程以退出码 1 结束}// 父进程逻辑// 使用 waitpid() 等待子进程结束int ret = waitpid(id, NULL, 0); // 第二个参数为 NULL,表示忽略子进程的退出状态if (ret > 0) {// 如果 waitpid() 成功返回,表示子进程已结束cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << endl;return 0; // 父进程正常退出
}
执行流程
- 程序开始:
- 父进程运行,打印自己的 PID 和 PPID(错误地显示 PPID 为自己的 PID)。
- 创建子进程:
fork
创建一个子进程。
- 子进程执行
execl
:- 子进程替换为
/usr/bin/ls
程序,并执行ls -l -a
命令,列出当前目录中所有文件(包括隐藏文件)的详细信息。 - 如果
execl
成功,子进程的地址空间完全被ls
程序覆盖。 - 如果
execl
失败,执行exit(1)
,子进程退出,返回码为1
。
- 子进程替换为
- 父进程等待子进程:
- 父进程调用
waitpid
,阻塞等待子进程终止。 - 当子进程完成后,
waitpid
返回子进程的 PID。
- 父进程调用
- 父进程打印结果:
- 父进程输出自己的 PID 和已终止的子进程的 PID。
- 子进程的PID没有变化,发成了进程替换。
exec
系类函数
exec
系列函数是 UNIX/Linux 系统中用于进程替换的函数集合。通过 exec
系列函数,当前进程的用户态内容(如代码段、数据段、堆、栈等)会被新程序替换,而进程的内核态资源(如 PID、打开的文件描述符等)被保留。
exec
系列函数不创建新进程,只是在当前进程中加载并运行一个新程序。
exec
系列函数的成员
L:可以理解list
V:可以理解Vector
execl
int execl(const char *path, const char *arg0, ..., NULL);
参数说明
path
:- 新程序的文件路径(可以是绝对路径或相对路径)。
- 如
/bin/ls
或./myprogram
。
arg0, ..., NULL
:- 传递给新程序的参数列表,按照顺序传递给新程序的
argv
数组。 arg0
通常是程序名,相当于argv[0]
。- 后续的参数是传递给新程序的命令行参数,相当于
argv[1], argv[2], ...
。 - 参数列表必须以
NULL
结束。
- 传递给新程序的参数列表,按照顺序传递给新程序的
- 示例:
execl("/bin/ls", "ls", "-l", "-a", NULL);
execlp
int execlp(const char *file, const char *arg0, ..., NULL);
参数说明
file
:- 新程序的文件名。
- 如果
file
不包含斜杠(/
),execlp
会根据PATH
环境变量搜索可执行文件。 - 如果
file
包含斜杠,则直接视为路径,无需搜索PATH
。
arg0, ..., NULL
:- 传递给新程序的参数列表,必须以
NULL
结束。 arg0
通常是程序名,相当于argv[0]
。- 后续参数为程序的命令行参数,相当于
argv[1]
、argv[2]
等。
- 传递给新程序的参数列表,必须以
- 示例
execlp("ls", "ls", "-l", "-a", NULL);
execle
int execle(const char *path, const char *arg0, ..., NULL, char *const envp[]);
参数说明:
path
:
- 新程序的文件路径,可以是绝对路径或相对路径。
- 如
/bin/ls
或./myprogram
。
arg0, ..., NULL
:
- 传递给新程序的参数列表,必须以
NULL
结束。 arg0
通常是程序名,相当于argv[0]
。- 后续参数为程序的命令行参数,相当于
argv[1], argv[2], ...
。
envp
:
- 一个指向环境变量字符串数组的指针。
- 每个环境变量字符串的格式为
key=value
(例如,PATH=/usr/bin
)。 - 如果希望新程序继承当前进程的环境变量,可以手动传递当前进程的
environ
。
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>using namespace std;int main()
{ cout << "I'm a process: "<<"PID:"<<getpid()<< " PPID: "<< getpid()<< endl; pid_t id = fork(); char *envp[] = { "MY_VAR=HelloWorld", "PATH=/bin:/usr/bin", NULL }; if(id == 0) { cout <<"Child PID: "<< getpid() << endl; execle("/usr/bin/env","env",NULL,envp); exit(1); } int ret = waitpid(id,NULL,0); if(ret > 0) cout << "Father PID: "<<getpid()<< " " <<"Child PID: "<< ret << endl; return 0;
}
execv
int execv(const char *path, char *const argv[]);
参数说明
path
: 指向可执行文件路径的字符串(以\0
结尾)。argv
: 一个字符串指针数组,用于传递给新程序的参数列表。数组的第一个元素通常为程序名称(argv[0]
),最后一个元素必须为NULL
,以标记参数列表结束。
示例:
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h> 。
#include<sys/types.h> int main()
{// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)std::cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getppid() << std::endl;// 创建子进程pid_t id = fork();// 定义一个字符指针数组,用于存储传递给 `execv` 的参数char *argv[] = { "ls", // argv[0]: 通常是程序名称"-l", // argv[1]: 参数,表示以长格式列出文件"-a", // argv[2]: 参数,显示隐藏文件NULL // 终止符,必须为 NULL};if(id == 0) // 子进程执行的代码块{ // 子进程输出自己的 PIDstd::cout << "Child PID: " << getpid() << std::endl; // 用 execv 替换当前进程的执行映像execv("/usr/bin/ls", argv);// 如果 execv 返回,说明执行失败exit(1); // 退出子进程,返回非零值表示错误}// 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果 `waitpid` 成功返回std::cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << std::endl;return 0;
}
逐步功能分析
- 主进程输出信息
使用getpid()
和getppid()
分别获取当前进程 ID 和父进程 ID,并输出信息。 - 创建子进程
使用fork()
创建一个子进程:- 返回值
id == 0
:表示当前是子进程。 - 返回值
id > 0
:表示当前是父进程,id
为子进程的 PID。
- 返回值
- 子进程执行新程序
在子进程中调用execv
:- 替换当前进程映像为
/usr/bin/ls
。 - 参数数组
argv
指定了程序名称和选项。 - 如果
execv
成功,后续代码不会执行;否则会继续执行并调用exit(1)
终止子进程。
- 替换当前进程映像为
- 父进程等待子进程
父进程调用waitpid
:- 阻塞当前进程,直到子进程终止。
- 返回值
ret
是子进程的 PID。
- 父进程输出信息
输出父进程和子进程的 PID 信息。
execvp
int execvp(const char *file, char *const argv[]);
参数说明
file
- 要执行的程序名称或路径。
- 如果提供的是程序名称(非路径),
execvp
会根据环境变量PATH
中的目录列表查找该程序。
argv
- 一个字符串数组,表示传递给新程序的参数。
argv[0]
通常是程序名称,最后一个元素必须为NULL
。
execvp
与 execv
的区别
execv
要求指定程序的完整路径,且不会从环境变量PATH
中查找。execvp
可以仅提供程序名称,函数会自动从PATH
中查找程序。
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h> 。
#include<sys/types.h> int main()
{// 输出当前进程的 PID(进程 ID)和 PPID(父进程 ID)std::cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getppid() << std::endl;// 创建子进程pid_t id = fork();// 定义一个字符指针数组,用于存储传递给 `execv` 的参数char *argv[] = { "ls", // argv[0]: 通常是程序名称"-l", // argv[1]: 参数,表示以长格式列出文件"-a", // argv[2]: 参数,显示隐藏文件NULL // 终止符,必须为 NULL};if(id == 0) // 子进程执行的代码块{ // 子进程输出自己的 PIDstd::cout << "Child PID: " << getpid() << std::endl; // 用 execvp 替换当前进程的执行映像execvp("ls", argv); // 区别于execv// 如果 execv 返回,说明执行失败exit(1); // 退出子进程,返回非零值表示错误}// 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果 `waitpid` 成功返回std::cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << std::endl;return 0;
}
ecexvpe
int execvpe(const char *file, char *const argv[], char *const envp[]);
参数说明
file
- 要执行的程序名称或路径。
- 如果提供的是程序名称,
execvpe
会根据环境变量PATH
自动查找该程序。
argv
- 一个字符串数组,用于传递给新程序的参数。
argv[0]
通常是程序的名称,最后一个元素必须是NULL
。
envp
- 一个字符串数组,用于指定新程序的环境变量。
- 每个字符串的格式为
KEY=VALUE
,例如"PATH=/usr/bin"
。 - 最后一个元素必须为
NULL
。
示例
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>using namespace std;int main()
{ // 输出当前进程的 PID 和父进程 ID(PPID)cout << "I'm a process: "<< "PID:" << getpid() << " PPID: " << getpid() << endl;// 创建子进程pid_t id = fork(); // 自定义环境变量数组char *envp[] = {"MY_VAR=HelloWorld", // 自定义变量 MY_VAR,值为 "HelloWorld""PATH=/bin:/usr/bin", // 自定义 PATH,确保能找到可执行文件NULL // 终止标志}; // 命令参数数组,传递给 `ls` 命令char *argv[] = {"ls", // argv[0] 通常为程序名称"-l", // 参数:长格式输出"-a", // 参数:显示隐藏文件NULL // 终止标志}; if(id == 0) // 子进程{cout <<"Child PID: " << getpid() << endl;// 使用 execvpe 执行 ls 命令,传递自定义环境变量execvpe("ls", argv, envp);// 如果 execvpe 执行失败exit(1); // 退出子进程,返回非零值表示错误} // 父进程等待子进程完成int ret = waitpid(id, NULL, 0);if(ret > 0) // 如果子进程正常退出cout << "Father PID: " << getpid() << " " << "Child PID: " << ret << endl;return 0
功能分析
-
父进程输出信息
- 使用
getpid()
获取当前进程的 ID。 - 使用
getpid()
显示父进程的 PPID(此处写错,正确用法应是getppid()
)。
- 使用
-
创建子进程
-
调用
fork()
创建子进程:
- 返回值
id == 0
:表示当前是子进程。 - 返回值
id > 0
:表示当前是父进程,id
为子进程的 PID。
- 返回值
-
-
定义环境变量和参数
-
envp
是自定义的环境变量数组:
- 包括
MY_VAR=HelloWorld
和PATH=/bin:/usr/bin
。
- 包括
-
argv
是传递给
execvpe
的参数列表:
- 包括
ls
命令及其参数-l
和-a
。
- 包括
-
-
子进程执行新程序
-
子进程调用
execvpe("ls", argv, envp)
- 替换当前子进程的映像为
ls
命令。 - 使用自定义的环境变量。
- 替换当前子进程的映像为
-
如果
execvpe
失败,子进程调用exit(1)
退出。
-
-
父进程等待子进程完成
- 调用
waitpid
等待子进程完成。 - 输出父进程和子进程的 PID 信息。
- 调用
exec
系列函数总结
函数名称 | 程序路径 | 参数传递 | 环境变量 | 特点 |
---|---|---|---|---|
execl | 完整路径 | 列表传参 | 继承父进程环境 | 手动传递每个参数;易用但不适合动态参数数量。 |
execlp | 搜索 PATH | 列表传参 | 继承父进程环境 | 在 PATH 中查找程序;适合提供命令名称的情况。 |
execle | 完整路径 | 列表传参 | 自定义环境 | 与 execl 类似,但支持自定义环境变量。 |
execv | 完整路径 | 数组传参 | 继承父进程环境 | 参数通过数组传递,适合动态生成参数的情况。 |
execvp | 搜索 PATH | 数组传参 | 继承父进程环境 | 在 PATH 中查找程序,适合命令名称和动态参数。 |
execve | 完整路径 | 数组传参 | 自定义环境 | 底层实现函数;用户可完全控制路径、参数和环境变量。 |
execvpe | 搜索 PATH | 数组传参 | 自定义环境 | GNU 扩展,结合 execvp 和 execve 的优点。 |
相关文章:

从理论到实践:Linux 进程替换与 exec 系列函数
个人主页:chian-ocean 文章专栏-Linux 前言: 在Linux中,进程替换(Process Substitution)是一个非常强大的特性,它允许将一个进程的输出直接当作一个文件来处理。这种技术通常用于Shell脚本和命令行操作中…...

Flutter常用Widget小部件
小部件Widget是一个类,按照继承方式,分为无状态的StatelessWidget和有状态的StatefulWidget。 这里先创建一个简单的无状态的Text小部件。 Text文本Widget 文件:lib/app/app.dart。 import package:flutter/material.dart;class App exte…...

微信小程序实战0 设置
1.调节模拟器到右侧位置 2.设置编辑页面的字体和行距。...

2025开源DouyinLiveRecorder全平台直播间录制工具整合包,多直播同时录制、教学直播录制、教学视频推送、简单易用不占内存
一、DouyinLiveRecorder软件介绍(文末提供下载) 官方地址:GitHub - ihmily/DouyinLiveRecorder 本文信息来源于作者GitHub地址 一款简易的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制…...

使用 postman 测试思源笔记接口
思源笔记 API 权鉴 官方文档-中文:https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md 权鉴相关介绍截图: 对应的xxx,在软件中查看 如上图:在每次发送 API 请求时,需要在 Header 中添加 以下键值对&a…...

当WebGIS遇到智慧文旅-以长沙市不绕路旅游攻略为例
目录 前言 一、旅游数据组织 1、旅游景点信息 2、路线时间推荐 二、WebGIS可视化实现 1、态势标绘实现 2、相关位置展示 三、成果展示 1、第一天旅游路线 2、第二天旅游路线 3、第三天旅游路线 4、交通、订票、住宿指南 四、总结 前言 随着信息技术的飞速发展&…...

阿里最新普通x231 逆向分析
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向前言 12月份的时候更新了过一次…...

php的使用及storm环境部署
php语法 环境搭建:在小皮中新建网站,注意先填写域名再点击选择根目录。 成功创建网站后,打开发现forbidden,因为新建的网站里是空的,需要新建index.php文件----> 在Phpstorm中左上角打开文件,打开那个文…...

高可用 Keepalived 服务部署流程
一、配置文件 vim /etc/keepalived/keepalived.confGLOBAL CONFIGURATION --- 全局配置部分VRRPD CONFIGURATION --- VRRP协议配置部分LVS CONFIGURATION --- LVS服务管理配置部分[rootlb01 ~]# cat /etc/keepalived/keepalived.…...

【新春特辑】2025年1月科技浪潮中的AI最新时事与科技趋势
2025年1月科技浪潮中的AI最新时事与科技趋势 一、AI科技时事 人工智能代理(AI Agent)的发展 最新进展:人工智能代理正逐步成为科技领域的新热点。这些代理能够自主执行特定任务,如管理日程、回复邮件等。然而,它们仍…...

解决Django非ORM模型提示初始化request问题
提问 Django在DRF时候自定义显示一些非model的字段提示TypeError: Field.__init__() got an unexpected keyword argument request 解答1 错误提示 TypeError: Field.__init__() got an unexpected keyword argument request 显示在创建序列化器实例时,传递了一个…...

G. XOUR
题目链接:Problem - G - Codeforces 题目大意:给你一个n长的序列, 其中你可以将a[i] XOR a[j] 的值 严格小于4的数对进行交换。 你可以操作任何几次, 让最后的数列最小。如果在 x 和 y 不同的第一个位置, xi<yi &…...

有没有个性化的UML图例
绿萝小绿萝 (53****338) 2012-05-10 11:55:45 各位大虾,有没有个性化的UML图例 绿萝小绿萝 (53****338) 2012-05-10 11:56:03 例如部署图或时序图的图例 潘加宇 (35***47) 2012-05-10 12:24:31 "个性化"指的是? 你的意思使用你自己的图标&…...

【RAG】SKLearnVectorStore 避免使用gpt4all会connection err
gpt4all 列表中包含了多个开源的大模型,如 Qwen2.5、Llama 3、DeepSeek、Mistral 等,但 不包含 OpenAI 的 GPT-4o。GPT-4o 是 OpenAI 提供的闭源模型,目前只能通过 OpenAI API 或 ChatGPT 官方应用(网页版、移动端)访问,并不支持本地运行,也没有 GGUF 量化格式的模型文件…...

vue框架技术相关概述以及前端框架整合
vue框架技术概述及前端框架整合 1 node.js 介绍:什么是node.js Node.js就是运行在服务端的JavaScript。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎。 作用 1 运行java需要安装JDK,而Node.js是JavaScript的运行环…...

Spring Boot + Facade Pattern : 通过统一接口简化多模块业务
文章目录 Pre概述在编程中,外观模式是如何工作的?外观设计模式 UML 类图外观类和子系统的关系优点案例外观模式在复杂业务中的应用实战运用1. 项目搭建与基础配置2. 构建子系统组件航班服务酒店服务旅游套餐服务 3. 创建外观类4. 在 Controller 中使用外…...

牛客周赛 Round 78
题目目录 A-时间表查询!解题思路参考代码 B-一起做很甜的梦!解题思路参考代码 C-翻之解题思路参考代码 D-乘之解题思路参考代码 E-在树上游玩解题思路参考代码 A-时间表查询! \hspace{15pt} 今天是2025年1月25日,今年的六场牛客寒…...
【机器学习】自定义数据集 ,使用朴素贝叶斯对其进行分类
一、贝叶斯原理 贝叶斯算法是基于贝叶斯公式的,其公式为: 其中叫做先验概率,叫做条件概率,叫做观察概率,叫做后验概率,也是我们求解的结果,通过比较后验概率的大小,将后验概率最大的…...

02.01 生产者消费者
请使用条件变量实现2生产者2消费者模型,注意1个生产者在生产的时候,另外一个生产者不能生产。 1>程序代码 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h>…...

mac 手工安装OpenSSL 3.4.0
如果你希望继续安装 openssl-3.4.0 而不是降级到 3.1.1,可以尝试以下解决方案。根据你提供的错误信息,问题可能出在测试阶段(make test),我们可以尝试跳过测试或修复测试失败的原因。 --- ### **解决方案:…...

kamailio-ACC_JSON模块详解【后端语言go】
要确认 ACC_JSON 模块是否已经成功将计费信息推送到消息队列(MQueue),以及如何从队列中取值,可以按照以下步骤进行操作: 1. 确认 ACC_JSON 已推送到队列 1.1 配置 ACC_JSON 确保 ACC_JSON 模块已正确配置并启用。以下…...

ArkTS语言介绍
文章目录 一、基本知识声明类型运算符语句函数函数声明可选参数Rest参数返回类型函数的作用域函数调用函数类型箭头函数(又名Lambda函数)闭包函数重载类字段方法构造函数可见性修饰符对象字面量抽象类接口接口属性接口继承抽象类和接口泛型类型和函数泛型类和接口泛型约束泛型…...

海外问卷调查之渠道查,企业经营的指南针
海外问卷调查,是企业调研最常用到的方法,有目的、有计划、有系统地收集研究对象的现实状况或历史状况的一种有效手段,是指导企业经营的有效手段。 海外问卷调查充分运用历史法、观察法等方法,同时使用谈话、问卷、个案研究、测试…...

spring和Mybatis的逆向工程
在现代企业级开发中,使用Spring和MyBatis进行快速、高效的数据库操作是非常常见的。本文将深入探讨如何使用Spring和MyBatis进行逆向工程,帮助开发者自动生成数据库相关的代码,提高开发效率和代码质量。 一、什么是逆向工程 逆向工程是指从…...

【Android】问deepseek存储访问
这些天deepseek爆火,我们来问问android问题看看,如果问android中的应用怎么访问外部存储,回答的很清楚,但是如果问的深入一些,比如Android中是怎么控制让应用不能读取其他应用的外部存储文件的,回答的比较抽…...

Android记事本App设计开发项目实战教程2025最新版Android Studio
平时上课录了个视频,从新建工程到打包Apk,从头做到尾,没有遗漏任何实现细节,欢迎学过Android基础的同学参加,如果你做过其他终端软件开发,也可以学习,快速上手Android基础开发。 Android记事本课…...

python学习——函数的返回值
在 Python 中,函数的返回值决定了调用该函数后得到的结果。默认情况下,如果函数没有使用 return 语句或没有明确返回一个值,函数将返回 None。为了实现更复杂的逻辑,可以通过 return 语句返回多个值、错误信息或其他数据类型。 返…...

【竞技宝】裂变天地S1:BB0-2PARI淘汰出局
北京时间2月1日,DOTA2裂变天地S1继续进行,昨日共进行三场比赛,第三场比赛迎来败者组第二轮PARI对阵BB。以下是本场比赛的详细战报。 第一局: 首局比赛,BB在天辉方,PARI在夜魇方。阵容方面,BB点出了圣堂、卡尔、玛尔斯、奶绿、亚巴顿,PARI则是拿到小娜迦、凤凰、大圣、玛西、萨…...

数据分析系列--⑨RapidMiner训练集、测试集、验证集划分
一、数据集获取 二、划分数据集 1.导入和加载数据 2.数据集划分 2.1 划分说明 2.2 方法一 2.3 方法二 一、数据集获取 点击下载数据集 此数据集包含538312条数据. 二、划分数据集 1.导入和加载数据 2.数据集划分 2.1 划分说明 2.2 方法一 使用Filter Example Range算子. …...

实践Rust:编写一个猜数字游戏
如果你正在学习Rust,并且想通过一个有趣的小项目来巩固所学知识,那么“猜数字游戏”是一个绝佳的选择!这个游戏的逻辑非常简单:程序会随机生成一个数字,玩家需要猜测这个数字是多少,程序会告诉玩家猜大了还…...