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

进程替换(主要接口讲解)

在 Linux 进程控制的知识体系中,进程替换是连接 “创建子进程” 与 “执行新程序” 的关键环节。让子进程摆脱父进程的代码束缚,以全新身份完成特定任务,也是 Shell、服务器等核心工具的底层支撑。今天我们就顺着 “是什么 - 为什么 - 怎么做 - 用在哪” 的思路,带大家了解一下进程替换的核心逻辑。

一、进程替换是什么?

进程替换,通俗来讲就是   让一个已存在的进程 “抛弃” 原有代码和数据,加载并执行磁盘上另一个全新程序   的过程。

它有两个核心特征:

  1. 不创建新进程:进程的 PID、PCB(进程控制块)等内核数据结构保持不变,只是进程的用户空间代码段、数据段被完全替换。
  2. 替换后不返回:成功调用替换函数后,新程序从启动例程开始执行,原有代码再也不会运行;只有替换失败时,函数才会返回 - 1

可以用一个生动的比喻理解:进程就像一个 “演员”,PID 是它的 “身份证”,进程替换就是让这个演员 “换剧本、换角色”,但身份证没变,只是表演的内容完全不同了。

二、为什么需要进程替换?

先回顾进程创建的逻辑:fork () 函数会创建一个与父进程代码、数据完全相同的子进程。但实际开发中,子进程往往不需要重复父进程的工作(比如 Shell 创建子进程后,需要执行 ls、pwd 等命令,而非重复 Shell 自身逻辑)。

这时候就需要进程替换来解决两个核心问题:

  1. 实现 “分工协作”:父进程专注于管理、调度(如等待子进程完成)子进程通过替换执行具体任务(如处理客户端请求、运行外部命令)。
  2. 提高资源利用率:无需创建全新进程(避免 PCB、内存块的重复分配),仅替换用户空间内容,兼顾效率与灵活性。

没有进程替换的话,fork () 创建的子进程只能执行父进程的代码分支,无法独立运行外部程序,进程控制的实用性会大打折扣。

三、进程替换的底层原理

先明确进程在内存中的结构:每个进程都有独立的虚拟内存空间,通过页表映射到物理内存,虚拟内存中存放着代码段、数据段、堆、栈等区域。

进程替换的核心操作的是:

  1. 卸载旧程序:清空当前进程虚拟内存中的代码段、数据段内容。
  2. 加载新程序:从磁盘读取目标程序(如 ELF 格式文件),将其代码段、数据段加载到进程的虚拟内存对应区域。
  3. 重置执行上下文:更新程序计数器(PC),让 CPU 从新程序的启动例程开始执行,同时初始化栈、堆等运行环境。

这里要注意与 fork () 的 “写时拷贝” 区分:写时拷贝是父子进程共享物理内存,仅在写入时复制;而进程替换是直接替换虚拟内存中的内容,与原程序彻底切割。

下面举一个例子理解一下 “替换” :

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{printf("我的程序要运行了!\n");int n = execl("/usr/bin/ls","ls","-l","-a",NULL);printf("我的程序运行完毕了!%d\n",n);return 0;
}

四、认识相关接口

Linux 提供了 6 个以 exec 开头的函数(统称 exec 函数簇),它们功能一致,仅参数格式、使用场景有差异。核心记住:只有 execve 是系统调用,其他 5 个都是封装后的库函数,最终都会调用 execve

#include <unistd.h>
// 列表参数、需指定全路径、使用当前环境变量
int execl(const char *path, const char *arg, ...);
// 列表参数、自动搜索PATH、使用当前环境变量
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[]);
// 数组参数、自动搜索PATH、使用当前环境变量
int execvp(const char *file, char *const argv[]);
// 数组参数、需指定全路径、自定义环境变量
int execve(const char *path, char *const argv[], char *const envp[]);

4.1 命名理解

这些函数原型看起来很容易混淆 , 但是只要掌握了规律就很好记忆 。

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

4.2 execl

它的函数原型是:

int execl(const char *path, const char *arg, ...);

  • path:要执行的程序的全路径(比如 /bin/ls)。
  • arg, ...:执行该程序时的命令行参数列表最后必须以 NULL 结尾(标记参数结束)。
  • 函数返回值:只有替换失败时才会返回 -1;替换成功后,进程会直接执行新程序,不会再回到原代码。

反正,需要很清晰的知晓  , 我要执行谁 ? 我想如何去执行它!!!

4.2.1 子进程独立运行程序

有没有一种方法,让父进程安心的执行自己的程序,让另外的子进程执行程序替换?

4.2.2 加载器

对于加载器这个说法, 可能在学进程的时候有了解到~ 

比方说 , 程序运行时要加载  ;  在操作系统这里 , 我们说在载入程序之前 , 要变成进程 , 程序动态加载 , 动态创建进程 ; 在讲shell 这里 , 我们说shell 进程 , fork() 创建在进程 , 子进程程序替换 , 父进程wait() , 等待子进程 , 回收子进程 ~

1. 加载器是做什么的 ?

把磁盘上的   “静态程序”   转换成内存中   “动态运行的进程”  

具体来说,它要完成这些关键操作:

  1. 读文件:从磁盘读取可执行文件(比如 ELF 格式的二进制文件)。
  2. 分配内存:在操作系统的帮助下,为进程分配虚拟内存空间(代码段、数据段、堆、栈等)。
  3. 加载内容:把程序的代码、数据复制到对应的内存区域。
  4. 设置执行环境:初始化程序计数器(PC,让 CPU 知道从哪条指令开始执行)、栈指针等,让进程能 “跑” 起来。

2. 加载器和 exec* 接口的关系

你可以把 exec* 系列函数(execlexecv 等)理解为加载器的 “编程接口”—— 我们写代码时,通过调用 exec* 函数,让操作系统的加载器帮我们完成 “程序替换成进程” 的工作。

换句话说:

  • 加载器是 “幕后工具”,负责实际的 “加载、转换” 操作。
  • exec* 是 “调用入口”,让我们能在代码中触发加载器的工作,实现进程替换。

3. 结合 Shell 场景,看加载器的工作流程

比如你在 Shell 里输入 ls -l,整个过程是这样的:

  1. Shell 是父进程:它本身是一个运行着的进程。
  2. fork 子进程:Shell 调用 fork(),创建一个和自己几乎一样的子进程。
  3. 子进程调用 exec*:子进程调用 execl("/bin/ls", "ls", "-l", NULL),触发加载器工作。
  4. 加载器执行加载:加载器把磁盘上的 /bin/ls 程序读入内存,转换成进程,设置好执行环境,让子进程开始执行 ls -l 的逻辑。
  5. 父进程等待:Shell(父进程)调用 wait(),等待子进程(ls 进程)执行完毕,再继续接收新命令。

4.2.3 替换自己写的程序

除了能替换系统的程序之外 , 能替换自己写的程序吗 ?

一切能转化为  进程运行  的程序 , 全部都能替换 ~

C++

Python

#! : 识别解释器

解释型语言 , 要用解释器

这里我们执行的不是脚本 , 而是解释器 : "/usr/bin/python3"

shell 脚本:
 

4.2.4 验证:程序替换不会创建自己的子程序

4.3 execlp

4.4 execv

1.  现在是哪一个进程在执行我们的 execv?

通常 execv 会在子进程中调用(比如通过 fork 创建的子进程)。父进程负责管理,子进程通过 execv 替换成新程序执行任务。

2.    ls 是不是一个二进制程序?

是。ls 是系统内置的二进制可执行程序,内部有自己的 main 函数,能接收命令行参数argc 统计参数个数,argv 存储参数内容)。

3.    父进程如何给子进程传参?

就是通过 execv 的 argv 数组。父进程在创建 argv 数组时,把需要传递的参数按顺序存入,子进程替换后就能在自己的 main 函数中拿到这些参数。

4.5 execvp

4.6 execvpe

other.cc

但是在这里,我们有发现,原本的环境被我们新增的环境变量给覆盖了,那么我们会去思考,如何不以覆盖的方式 , 新增我们的环境变量 。

其实我们的exec* 系列的函数有 7 个 , 那么还有一个在哪里呢?

为什么这 6 个需要进行封装呢 ? 

因为程序替换的时候 , 我们需要面对各种各样不同的上层替换场景 

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

相关文章:

  • 网站开发常用语言总结怎么让别人访问我建的网站
  • 接平面设计私活的网站怎样做旅游城市住宿网站
  • nodejs同时做网站和后台管理怎么申请一个域名
  • 天津营销类网站设计网站建设管理要求
  • 地矿局网站建设方案深圳互联网设计公司
  • ubuntu更改使用期望的内核版本
  • 芋道后端部署后总自己挂?从 Nginx 报错到 OOM Kill 的完整排查与修复(2核2G 服务器实战)
  • 哪个网站可以做照片分享申远空间设计公司
  • 系统开发必须遵守的原则有哪些网站可以做多少优化关键词
  • PyTorch深度学习进阶(一)(经典卷积神经网络 LeNet)
  • 北京搭建网站做棋牌网站
  • UiPath2025笔记第十节:利用java反射编写智能体
  • 如何查网站的空间wordpress 移动端模板下载
  • 基于萤火虫+Gmapping、分层+A*优化的导航方案
  • 网站开发师是做什么的wordpress固定链接静态化后打不开
  • 重庆城乡建设网站小程序开发哪个公司好
  • yolo地裂缝(wsl+ubuntu)
  • 湖北 网站 备案 时间个人网站可以做企业宣传
  • mvc架构购物网站开发成都必去的十大景点
  • 在线设计网站可以做ps和ppt爱吖网
  • 徐州市城乡建设局网站首页国外设计网站d开头的
  • Java 集成 onlyoffice 预览文件功能
  • Maven中的配置
  • 网站开发界面设计用什么工具商城建设开发
  • 访问阿里云主机网站免费打广告的平台app
  • docker拉取失败,更换docker的源
  • asp网站验证码不显示莱州网络推广公司
  • Android Gralde补全计划 productFlavors多渠道打包(变体/多客户)
  • 网站建设多少钱网站开发项目经理职责
  • 浙江腾鑫建设集团网站手机如何打开wordpress