1 进程替换

  进程替换是为了让程序能在不创建新进程的情况下,让父进程和子进程执行不同的代码,以实现控制清晰、执行高效的程序调度机制。

1.1 先看效果

#include <stdio.h>
#include <unistd.h> int main()
{printf("before:I am a process, pid:%d,ppid:%d\n",getpid(),getppid()); // 标准写法  execl("/usr/bin/ls", "ls", "-a", "-l", NULL);// 想要执行程序的路径   怎么执行这个命令  最后必须NULL结尾printf("after:I am a process, pid:%d,ppid:%d\n",getpid(),getppid());return 0;
}

在这里插入图片描述

  我们会发现这里并没有if else但是子进程在execl后却没有执行父进程的代码,这说明子进程所执行的代码被替换了!! 这就是发生了进程替换!

1.2 进程替换的原理

  在使用 fork() 创建进程时,我们经常看到父进程和子进程执行的是不同的逻辑,但却没有使用 if/else 来区分函数入口。这是如何做到的?要理解这一点,就得先回答下面五个问题。

问题1:子进程执行了 ls 这个程序,是不是创建了一个新的子进程?

  • 答:不是的,子进程在执行 ls 的过程中,并不会再创建一个新的进程。相反,它会调用 exec 系列函数(如 execl、execvp 等),这类函数的作用是:将当前进程的代码和数据空间完全替换为另一个可执行程序的内容(比如 ls 的代码和数据)。这一过程由操作系统完成,原来的用户态执行内容被新程序接管。虽然进程内容变了,但进程的 PID 不变,内核中的 PCB(进程控制块)结构仍然保留,只是部分字段(如指令入口地址、页表等)发生了更新。(就是写时拷贝)

问题 2:既然子进程的内容被替换了,为什么父进程没有受到影响?

  • 答:这是因为 Linux 在 fork() 创建进程时采用了写时拷贝技术。简单来说:起初父子进程共享相同的内存页面(包括代码段和数据段),但在其中一个进程尝试修改内存时,操作系统才会为它单独分配新页面,实现真正的物理内存拷贝。当子进程执行 exec 系列函数时,它会重新加载目标程序的代码和数据,触发写时拷贝,从而不影响父进程的内存空间。这样,父进程可以继续执行自己原来的代码,而子进程则运行被替换的新程序。(就有点像你的第二人格出现,但是你已经不记得自己的第一人格做过什么或者说过什么)

问题 3:我们常说写时拷贝作用于数据段,那代码段也可以写时拷贝吗?

  • 答:可以,代码段同样可以触发写时拷贝,尽管它通常是只读的。这是因为:操作系统不相信任何人,通常情况下,用户程序无法修改代码段,但 exec 是一种特殊情况,它是由操作系统内核完成的程序替换操作;因此操作系统有权限回收原有代码段并重新加载新的代码段(如 ls),实现了用一份全新的代码段替换旧的的效果。这并不意味着代码段可以随意修改,而是说在 exec 的语义中,代码段可以被替换。

问题 4:如果进程替换失败了会怎样?

  • 答:如果替换失败了,就只能执行自己原先的代码了!!所以exec系列的函数只有失败的返回值而没有成功的返回值,因为一但成功后跑的就是新的代码和数据了,返回就没有意义了。

问题 5:我们说 main 是程序入口,但它不一定写在文件开头,Linux 是如何找到它的?

  • 答: Linux中的可执行程序,是有自己的组织形式的,也就是有自己的格式的(有一张表),我们把这个格式叫做ELF ,ELF 文件中包含一个头部结构体,里面记录了程序的各个段的起始地址,其中就有一个字段叫做 e_entry,它指向程序的真实入口地址,操作系统加载可执行文件时,根据 e_entry 所指的地址启动执行,从而精确找到程序入口,无需扫描整个文件内容。

1.3 探究各个程序替换的接口

在这里插入图片描述

函数名参数类型是否搜索 PATH可否传 envp使用方式
execlpath + 可变参数❌ 否❌ 否写死路径 + 手动写参数
execlpfile + 可变参数✅ 是❌ 否像终端命令一样写
execlepath + 可变参数❌ 否✅ 是自定义环境变量
execvpath + argv[]❌ 否❌ 否参数数组(变量较多时)
execvpfile + argv[]✅ 是❌ 否动态命令调用
execvepath + argv[] + envp[]❌ 否✅ 是最底层、最灵活的调用

1.3.1 execl:路径+变长参数(手动列出参数)

#include <unistd.h>
#include <stdio.h>// int execl(const char *path, const char *arg, ...);int main() 
{// 什么路径下,执行什么程序// 使用完整路径,执行 ls -l -a,最后一个一定是NULL结尾execl("/bin/ls", "ls", "-l", "-a", NULL);perror("execl failed");return 1;
}

1.3.2 execlp:文件名+变长参数(自动按 PATH 搜索)

#include <unistd.h>
#include <stdio.h>// int execlp(const char *file, const char *arg, ...);int main() 
{// 自动在 PATH 中找 ls,等同于直接在终端输入 ls -l -aexeclp("ls", "ls", "-l", "-a", NULL);perror("execlp failed");return 1;
}

1.3.3 execv:路径+参数数组

#include <unistd.h>
#include <stdio.h>// int execv(const char *path, char *const argv[]);int main()
{char *args[] = {"ls", "-l", "-a", NULL};execv("/bin/ls", args);perror("execv failed");return 1;
}

1.3.4 execvp:文件名+参数数组(PATH 搜索)

#include <unistd.h>
#include <stdio.h>// int execvp(const char *file, char *const argv[]);int main()
{char *args[] = {"ls", "-l", "-a", NULL};execvp("ls", args);perror("execvp failed");return 1;
}

execle/execvpe:多个一个envp[ ] 意思就是我们可以自己用一套自己的环境变量,而不是用从父进程继承下来的。 (后面补充!!!)

1.4 接口总结和加载器理解

  在 Linux 中,exec 系列函数虽然有多个变种,但它们的本质区别只在于参数的形式和功能的侧重点不同。这些不同的接口设计,实际上是围绕以下几个核心问题展开的:

(1)程序在哪里?——执行目标的位置问题

  • 进程替换的第一步是定位目标程序的位置。为此,exec 系列提供了两种方式:
    • 明确路径:如 execl、execv 等函数要求你传入程序的完整路径(如 /bin/ls)。
    • 自动搜索:如 execlp、execvp 则只需传入程序名,系统会自动从 PATH 环境变量中查找对应的可执行文件。

(2)参数如何传?——执行参数的组织方式问题

  • 找到程序之后,下一个问题是:我们需要为它传递哪些参数?如何传递?为此,exec 系列函数支持两种参数传递形式:
    • 可变参数列表(如 execl, execlp, execle):适合参数数量固定、较少的情况,手动列出每个参数,最后以 NULL 结尾。
    • 参数数组形式(如 execv, execvp, execve):适合动态构造参数列表,将参数统一组织在 char *argv[] 中传入。

(3)是否使用自定义环境变量?——环境隔离问题

  • 最后一个核心问题是:新执行的程序是否必须继承当前进程的环境变量?
    • 默认继承:大部分 exec 函数(如 execl, execvp, execv 等)会沿用当前进程的环境变量。
    • 自定义传入:而以 e 结尾的函数(如 execle, execve)允许你显式传入一组新的环境变量数组 envp[],实现更高的控制力和隔离性

  总结:exec 系列函数的多样设计,其实是从“程序在哪、参数怎么传、环境用谁的”这三个角度出发,对进程替换这一行为进行了细粒度的接口划分,满足了不同场景下的执行需求。

1.5 makefile一次生成两个可执行文件

补充知识:.cc .cpp .cxx 都是C++中的文件后缀

test1.c文件

#include <stdio.h>int main()
{printf("hello HYQ\n");printf("hello HYQ\n");printf("hello HYQ\n");printf("hello HYQ\n");return 0;
}

test2.cpp文件

#include <iostream>using namespace std;int main()
{printf("Hello C++ Linux\n");printf("Hello C++ Linux\n");printf("Hello C++ Linux\n");printf("Hello C++ Linux\n");return 0;
}

makefile文件

test1:test1.cgcc -o $@ $^
test2:test2.cppg++ -o $@ $^
.PHONY:clean
clean:rm -f test1 test2

  上面的makefile文件只能生成一个可执行文件,因为它一旦扫描到一个推导链,就会立马执行,执行一个推导链就结束了,不会去执行第二个。
  因此,如果我想生成两个可执行文件,就必须让这两个程序有一个关系,才可能一次执行两个文件。
  唯一的解决方法就是,谁都不要放前面,而是提前建立一个伪目标all放在前面,多一层推导关系,这样两个文件就会根据推导链的执行而被编译了。

.PHONY:all
all:test1 test2  // 不能有缩进test1:test1.cgcc -o $@ $^   // 一定是tab缩进,空格会出问题
test2:test2.cppg++ -o $@ $^
.PHONY:clean
clean:rm -f test1 test2

一个可执行程序能不能调另一个可执行程序了???答案是:可以的!!!

int main()
{printf("hello HYQ\n");printf("hello HYQ\n");printf("hello HYQ\n");printf("hello HYQ\n");// 第一次参数:什么路径执行什么东西// 第二个参数:怎么执行// 第三个参数:一定是NULL结尾execl("./test2","test2",NULL);printf("HYQ\n");return 0;
}

  所以语言和语言之间是可以相互调用的!!任何语言都有像exec这类的接口,语言可以互相调用的原因是无论什么语言写的程序在操作系统看来都是进程。

  补充关于环境变量:环境变量是在子进程创建的时候就默认继承了,即使没有传环境变变量参数,也可以在地址空间找到。所以进程替换中,环境变量信息不会被替换

1.6 总结进程替换系列的函数

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85679.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85679.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/85679.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

支持 TDengine 的数据库管理工具—qStudio

qStudio qStudio 是一款免费的多平台 SQL 数据分析工具&#xff0c;可以轻松浏览数据库中的表、变量、函数和配置设置。最新版本 qStudio 内嵌支持 TDengine。 前置条件​ 使用 qStudio 连接 TDengine 需要以下几方面的准备工作。 安装 qStudio。qStudio 支持主流操作系统包…

破解 VMP+OLLVM 混淆:通过 Hook jstring 快速定位加密算法入口

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ VMP 壳 OLLVM 的加密算法 某电商APP的加密算法经过dex脱壳分析&#xff0c;找到参数加密的方法在 DuHelper.doWork 中 package com.shizhuang.duapp.common…

Automatisch:开源的工作流自动化利器

在当今数字化的时代,企业和个人都在寻找高效的方式来自动化业务流程,减少手动操作带来的时间和成本消耗。Automatisch 作为一款开源的 Zapier 替代方案,为我们提供了一个强大而灵活的工具,让工作流自动化变得更加简单和可控。 一、Automatisch 简介 Automatisch 是一个商…

RAG应用效果评估框架与优化指南

1. 引言:为何RAG评估至关重要? 一个RAG系统通常包含多个可调参数和可替换组件(如不同的嵌入模型、向量数据库、LLM、Prompt模板等)。没有有效的评估机制,优化过程就像“盲人摸象”,难以判断改动是否带来了真正的提升。 RAG评估的核心目的: 量化系统性能:将RAG的“好坏…

豆包大模型应用场景

豆包作为通用大模型&#xff0c;应用场景其实覆盖了个人和企业两端。个人端要突出生活化功能——比如帮学生解题、帮上班族写周报&#xff1b;企业端则要强调降本增效&#xff0c;比如客服自动化、代码生成这些硬需求。用户没指定角度&#xff0c;那就都覆盖吧。 注意到用户用“…

OSITCP/IP

模型&协议 在互联网发展的早期,不同的计算机厂商有不同的网络传输协议,例如:IBM的SNA协议、苹果的AppleTalk协议等,这些协议互不兼容,导致虽然不同的产商计算机在物理层面是链接的,但是在网络上基本无法完成正常通信。这就导致一个用户如果使用了某个厂商的某个网络…

店匠科技闪耀“跨博会”,技术+生态打造灵活出海能力

2025年6月16日至18日&#xff0c;第八届全球跨境电商节暨第十届深圳国际跨境电商贸易博览会&#xff08;简称“跨博会”&#xff09;在深圳会展中心举行。作为全球跨境电商行业的年度盛会&#xff0c;本届展会以“文化跨境、品牌出海、智量强国”为主题&#xff0c;汇聚近 1500…

selenium弹框元素定位-冻结界面

有些网站上面的元素&#xff0c;我们鼠标放在上面&#xff0c;会动态弹出一些内容。 但是当我们的鼠标从音乐图标移开&#xff0c;这个栏目就整个消失了&#xff0c;就没法查看其对应的HTML。 怎么办&#xff1f;在开发者工具栏console里面执行如下js代码 &#xff1a; setTi…

美学心得(第二百七十九集)罗国正

美学心得&#xff08;第二百七十九集&#xff09; 罗国正 &#xff08;2025年6月&#xff09; 3299、分清不同本体、主体及其之间的关系&#xff0c;是 正确的审美、判断首先的关键 罗国正 &#xff08;2025年6月11日于广州&#xff09; “人也按照美的规律来建造。”这句话…

云祺容灾备份系统公有云备份与恢复实操-AWS

1、创建访问密钥 访问并登录AWS控制台&#xff0c;点击右上角用户名、安全凭证&#xff0c;在我的安全凭证窗口中&#xff0c;下拉找到访问密钥&#xff0c;并点击创建访问密钥&#xff0c;选择其他&#xff0c;点击下一步&#xff0c;即可获得密钥信息如图1至图6。 注意&…

windows内网穿透

内网穿透&#xff08;NAT穿透&#xff09;是一种通过技术手段将局域网&#xff08;内网&#xff09;中的服务暴露到公网&#xff08;外网&#xff09;的方法&#xff0c;使外部用户能够访问内网资源。其核心是解决因NAT&#xff08;网络地址转换&#xff09;或防火墙限制导致的…

threejs 实现720°全景图,;两种方式:环境贴图、CSS3DRenderer渲染

前提 有一个前提条件&#xff1a;六张大小一致的图片&#xff0c;六个图片分别对应的是720全景图的六个面&#xff1a;上、下、左、右、前、后。 这个不是那种无人机拍摄的全景图&#xff0c;是六个图片拼起来的&#xff0c;这样的取景方式要比无人机的要经济一些。 ---…

老牌软件 Ghost 备份还原操作基础

一、Ghost 简介 Symantec Ghost&#xff08;也称为 Norton Ghost&#xff09; 是一款强大的磁盘克隆和备份还原工具&#xff0c;广泛用于系统部署、数据恢复和灾难恢复。其主要功能包括&#xff1a; 创建磁盘镜像&#xff08;.GHO文件&#xff09;备份/还原分区或整个硬盘支持…

SSH连接服务器并同步本地文件

SSH连接服务器并同步本地文件 1. 复制本地公钥 cat ~/.ssh/id_rsa.pub如果不确定本地是否有公钥 ls ~/.ssh/id_rsa.pub# 如果出现如下&#xff0c;则说明你本地存在公钥 # /Users/username/.ssh/id_rsa.pub若没有公钥&#xff0c;需生成 # 使用下面命令&#xff0c;然后一路回…

中英泰马来语订货系统:助力东南亚批发贸易企业数字化转型升级

随着全球数字化转型浪潮的推进&#xff0c;东南亚地区的批发贸易企业也正逐步迈向数字化发展道路。特别是在中英泰马来语订货系统的推动下&#xff0c;东南亚的批发商和零售商能够更高效、便捷地开展跨国贸易与供应链管理。这不仅帮助传统企业提高了运营效率&#xff0c;还助力…

微信小程序获取指定元素,滚动页面到指定位置

微信小程序获取指定元素&#xff0c;滚动页面到指定位置 微信小程序获取指定元素的宽高等信息,并滚动页面到指定位置 微信小程序获取指定元素的宽高等信息,并滚动页面到指定位置 注&#xff1a;原生小程序开发&#xff1a; createSelectorQuery() 创建一个选择器查询实例。 sel…

LeetCode热题100—— 118. 杨辉三角

https://leetcode.cn/problems/pascals-triangle/description/?envTypestudy-plan-v2&envIdtop-100-liked 题解 代码 public List<List<Integer>> generate(int numRows) {List<List<Integer>> datatList new ArrayList<>();for(int i …

Python函数/Lambda/nested function/decorator/kwargs:全面教程

目录 函数简介基本函数语法函数参数返回值高级函数概念列表推导式与Lambda函数实用示例 函数简介 函数是可重用的代码块&#xff0c;用于执行特定任务。它们有助于组织代码&#xff0c;促进复用&#xff0c;并使程序更易于维护。可以将函数视为程序中的小型程序。 基本函数…

UG NX二次开发(C++)-创建草图(基于平面、X轴和参考点)

文章目录 1、前言2、在UG NX中的操作3、代码实现3.1 添加头文件3.2 在项目中声明一个创建草图的函数3.3 创建草图函数的实现代码3.4 函数调用3.5 实现效果1、前言 作为一款大型的CAD/CAM软件,UG NX在建模中草图的作用非常重要,功能也非常强大,所以在UG NX中学会草图的二次开…

计算机视觉课程笔记-机器学习中典型的有监督与无监督学习方法的详细分类、标签空间性质、解释说明,并以表格形式进行总结

✅ 一、有监督学习&#xff08;Supervised Learning&#xff09; 定义&#xff1a;有监督学习中&#xff0c;模型训练依赖于已标注的样本&#xff0c;即输入和输出&#xff08;标签&#xff09;成对出现。 标签空间可能是&#xff1a; 离散型&#xff08;Discrete&#xff09…