[Linux]进程替换
🥁作者: 华丞臧.
📕专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站
文章目录
- 一、进程替换
- 1.1 替换原理
- 1.2 替换函数
- 二、exec函数
- 2.1 execl
- 2.2 execv
- 2.3 execlp
- 2.5 execle
- 2.4 execvp
- 2.5 execve
- 补充快捷键
- 批量化注释
- 批量化取消注释
一、进程替换
前面我们学习了如何创建子进程,也知道了子进程执行的是父进程的代码片段;那么如果我们想让创建出来的子进程执行全新的程序呢?这时候就需要进程的程序替换。
一般在Linux编程的时候,需要子进程做两类事情:
- 让子进程执行父进程的代码片段(服务器代码);
- 让子进程执行磁盘中的一个全新的程序,如:shell可以让客户端执行其他人写的代码。
1.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

程序替换原理总结:
- 将磁盘中的程序,加载到内存结构;
- 重新建立页表映射,谁执行程序替换,就重新建立谁的页表映射效果;让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序。
注意在程序替换的过程中没有创建新的进程,这个过程指的是程序替换的过程,此时子进程已经创建好了不算新创建的进程。
1.2 替换函数
Linux中有六种exec开头的函数,统称exec函数:
以下六种函数都可以用来进程替换。

#include <unistd.h>`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[]);
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函数只有出错的返回值而没有成功的返回值。
命名理解:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list):表示参数采用列表;
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH;
- e(env):表示自己维护环境变量;
| 函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
|---|---|---|---|
| execl | 列表 | 不是 | 是 |
| execlp | 列表 | 是 | 是 |
| execle | 列表 | 不是 | 不是,必须自己组装环境变量 |
| execv | 数组 | 不是 | 是 |
| execvp | 数组 | 是 | 是 |
| execve | 数组 | 不是 | 不是,须自己组装环境变量 |
二、exec函数
接下来我们挑几个exec函数来说明其使用方式。
2.1 execl
int execl(const char *path, const char *arg, ...);
说明:
path:程序所在的路径。arg:替换的程序名。...:可变参数列表,这个我们在使用printf和scanf的时候见过。- execl函数替换失败返回-1;
首先使用exec函数之前,我们需要知道如果想执行一个全新的程序,需要做下面两件事:
- 找到执行程序路径;
- 程序可能携带选项进行执行,也可以不携带;
这里我们可以参照Linux上的命令行是如何使用的,如下:

Linux中的指令本质上就是程序,所以命令行怎么写我们程序携带选项时传参就怎么写。既然Linux中的指令也是程序,我们首先来试试用指令来替换我们写的程序,代码如下:
#include <stdio.h>
#include <unistd.h>int main()
{printf("进程pid:%d\n", getpid());// arg表示目标程序名,而...参数列表必须以NULL结尾execl("/usr/bin/ls", "ls", NULL); //不带参数//execl("/usr/bin/ls", "ls", "-al", NULL); //带参数printf("进程替换成功\n");return 0;
}
不带参数的如下图:

带参数的如下图:

观察上述两幅图片,我们发现代码中execl下面的那句 printf并没有执行;这是因为一旦替换成功,会将当前进程的代码和数据全部替换了,因此执行完execl函数进程中的代码和数据已经全部被替换了。
程序替换函数也有返回值,为int类型;这个返回值不需要判断,一旦进程替换成功就不会有返回值,而替换失败必然会继续向后执行,这个返回值最多让我们知道是什么原因导致替换失败。
使用fork创建子进程,再将子进程进行程序替换,会不会影响父进程呢?
- 答案是不会,下面我们来实验其过程。
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());execl("/usr/bin/ls", "ls", "-al", NULL);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}

可以看到子进程进行程序替换不会影响父进程,因为进程具有独立性,在数据层面发生写时拷贝:当程序替换的时候,可以理解为代码和数据都发生了写时拷贝,完成了父子进程的分离。
2.2 execv
int execv(const char *path, char *const argv[]);
函数说明:
path:目标程序的路径;argv:数组用来传参;- 替换失败返回-1,其中
v表示数组(vector)。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());// 使用argv进行传参,数组中第一个元素argv[0]必须为目标程序名//argv也必须以NULL结尾//char *const myenv[] = {// (char*)"ls",// (char*)"-a",// (char*)"-l",// NULL//};char *const myenv[] = {(char*)"pwd",NULL};//execv("/usr/bin/ls", myenv);execv("/usr/bin/pwd", myenv);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}


2.3 execlp
int execlp(const char *file, const char *arg, ...);
函数说明:
- file:目标程序名;
- arg:参数,传目标程序名;
...:可变参数列表;- 函数命名带p表示可以不带路径,只需要说明目标程序名;系统会通过环境变量
PATH查找。 - 替换失败返回-1。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid()); // 使用execlp,可以不带路径:// 其中两个ls含义不同// 第一个为供系统查找,后面一个加上选项表示如何执行execlp("ls", "ls", "-a", NULL);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}

2.5 execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
函数说明:
path:目标程序所在路径;arg:参数,传目标程序名;...:可变参数列表;envp:用户传给目标程序的环境变量;- 失败返回-1。
使用exec函数也可以替换用户自己写的程序:
//replace.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const env_[] = {(char*)"MYPATH=HELLOWORLD",NULL};execle("./mytest", "mytest", NULL, env_);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}//mytest.cpp
#include <iostream>
#include <stdlib.h>using namespace std;int main()
{cout << "PATH: " << getenv("PATH") << endl;//cout << "MYPATH: " << getenv("MYPATH") << endl;cout << "hello world1" << endl;cout << "hello world2" << endl;cout << "hello world3" << endl;cout << "hello world4" << endl;cout << "hello world5" << endl;return 0;
}

如上图,当直接使用execle函数时,进程替换成功了但是运行时进程崩溃了,可以看到此时进程中找不到PATH这个环境变量,那么MYPATH(这是execle函数的传参)呢?

因此可以得出结论:带e的exec函数会添加环境变量给目标进程,执行的是覆盖式的。
//这里是使用execl替换代码片段
if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());execl("./mytest", "mytest", NULL);exit(1);}

使用execl函数PATH环境变量可以正常打印。
如果想要将系统的环境变量保存并且也可以使用用户自己的环境变量,可以使用export指令添加环境变量:
//命令行
export MYPATH="HELLO WORLD"

2.4 execvp
int execvp(const char *file, char *const argv[]);
函数说明:
file:目标程序名;argv:按照命令行参数格式统一将程序名和选项字符串放入数组中;- 命名带
p表示会使用环境变量PATH。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const myenv[] = {(char*)"ls",(char*)"-a",(char*)"-l",NULL};execvp("ls", myenv);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}

2.5 execve
与其他的exec函数不同的是:execve函数是系统调用接口。
int execve(const char *path, char *const argv[], char *const envp[]);
前面学习的那些exec函数基本都是对系统调用接口直接或者间接的封装,适应与不同的适用场景。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{printf("进程pid:%d\n", getpid());pid_t id = fork();if(id == 0){//childprintf("这是子进程pid:%d,ppid:%d\n", getpid(), getppid());char *const myenv[] = {(char*)"ls",(char*)"-a",(char*)"-l",NULL};char *const env_[] = {(char*)"MYPATH=HELLOWORLD",NULL};execve("./mytest", myenv, env_);exit(1);}//parentprintf("这是父进程pid:%d,ppid:%d\n", getpid(), getppid());pid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("子进程回收成功,ret:%d\n", ret);return 0;
}

补充快捷键
批量化注释
ctrl + v(按一次即可),从需要注释的第一行开始,然后使用HJKL上下左右键选中区域;如果vim被配置过,键盘上的箭头上下左右功能可能改变,因此建议使用HJKL。

- 切换大写输入
I,输入//,再按下Esc就可以完成批量化注释。

批量化取消注释
-
ctrl + v(按一次即可),从需要取消注释的第一行开始,然后使用HJKL上下左右键选中区域;

-
输入
d就可以完成批量化取消注释。
相关文章:
[Linux]进程替换
🥁作者: 华丞臧. 📕专栏:【LINUX】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文…...
常见的锁策略面试题
你是怎么理解乐观锁和悲观锁的,具体怎么实现呢? 悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁 乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的…...
设计师一定要知道这几个网站,解决你80%的设计素材。
本期推荐一波设计师必备的设计素材网站,设计党赶紧马住!能解决你日常设计中80%的素材。 1、菜鸟图库 菜鸟图库-免费设计素材下载 这是一个为新手设计师提供免费素材的设计网站,站内有超多平面模板、海报、UI设计、电商设计等相关素材&#x…...
QT基础入门
学习视频:QT开发概述_哔哩哔哩_bilibili 1.QT开发概述 1.什么是QT QT是一个1991年由Qt Company开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架&#…...
高数不定积分72题解答
题目来源:这72道积分题目会积了,绝对是高高手 作者: 湖心亭看雪 第一题 原式∫15x3dx15∫15x3d(5x3)15ln(5x3)C\begin{aligned} \text{原式}&\int \frac{1}{5x3}dx \\ &\frac{1}{5} \int\frac{1}{5x3}d(5x3) \\ &\frac{1}{5} ln…...
基于北方苍鹰算法优化LSTM(NGO-LSTM)研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Linux内核启动(理论,0.11版本)分段与分页
为什么要虚拟内存 我们知道,在之前上微机原理时,我们的程序是可以直接访问内存的,而且访问的是直接的物理内存,在实模式下,寄存器是16位的,数组总线(data bus)是16位的,…...
数据与C(字符串)
目录 一.概念引入 二.字符串(数组存储,必须以\0结尾) 三.错误示范 四.strlen()和sizeof()相对于字符串的不同 一.概念引入 “a”,a哪个是字符哪个又是字符串,嘿嘿不用猜了 我们在上一章中说过&#x…...
Python+Go实践(电商架构三)
文章目录服务发现集成consul负载均衡负载均衡算法实现配置中心nacos服务发现 我们之前的架构是通过ipport来调用的python的API,这样做的弊端是 如果新加一个服务,就要到某个服务改web(go)层的调用代码,配置IP/Port并发…...
基于 MySQL 排它锁实现分布式可重入锁解决方案
一、MySQL 排它锁和共享锁 在进行实验前,先来了解下MySQL 的排它锁和共享锁,在 MySQL 中的锁分为表锁和行锁,在行锁中锁又分成了排它锁和共享锁两种类型。 1. 排它锁 排他锁又称为写锁,简称X锁,是一种悲观锁&#x…...
【大数据】Hadoop-HA-Federation-3.3.1集群高可用联邦安装部署文档(建议收藏哦)
背景概述 单 NameNode 的架构使得 HDFS 在集群扩展性和性能上都有潜在的问题,当集群大到一定程度后,NameNode 进程使用的内存可能会达到上百 G,NameNode 成为了性能的瓶颈。因而提出了 namenode 水平扩展方案-- Federation。 Federation 中…...
【设计模式之美 设计原则与思想:面向对象】14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
在上一节课中,针对接口鉴权功能的开发,我们讲了如何进行面向对象分析(OOA),也就是需求分析。实际上,需求定义清楚之后,这个问题就已经解决了一大半,这也是为什么我花了那么多篇幅来讲…...
工作技术小结
2023/1/31 关于后端接口编写小结 1,了解小程序原型图流程和细节性的东西 2,数据库关联结构仔细分析,找到最容易查询的关键字段,标语表之间靠什么关联 2023/2/10 在web抓包过程中,如果要实现批量抓取,必须解…...
无重复字符的最长子串-力扣3-java
一、题目描述给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: s "abcabcbb"输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:输入: s "bbbbb"输出: 1解释: 因为…...
java ssm高校教材管理平台 idea maven
设计并且实现一个基于JSP技术的高校教材管理平台的设计与实现。采用MYSQL为数据库开发平台,SSM框架,Tomcat网络信息服务作为应用服务器。高校教材管理平台的设计与实现的功能已基本实现,主要学生、教材管理、学习教材、教材入库、教材领取、缴…...
【Python学习笔记】25.Python3 输入和输出(1)
前言 在前面几个章节中,我们其实已经接触了 Python 的输入输出的功能。本章节我们将具体介绍 Python 的输入输出。 输出格式美化 Python两种输出值的方式: 表达式语句和 print() 函数。 第三种方式是使用文件对象的 write() 方法,标准输出文件可以用…...
C++复习笔记8
泛型编程:编写的是与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。 1.函数模板:类型参数化,增加代码复用性。例如对于swap函数,不同类型之间进行交换都需要进行重载,但是函数…...
RabbitMQ入门
目录1. 搭建示例工程1.1. 创建工程1.2. 添加依赖2. 编写生产者3. 编写消费者4. 小结需求 官网: https://www.rabbitmq.com/ 需求:使用简单模式完成消息传递 步骤: ① 创建工程(生成者、消费者) ② 分别添加依赖 ③ 编…...
【计算机网络】Linux环境中的TCP网络编程
文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七…...
idekCTF 2022 比赛复现
Readme 首先 []byte 是 go 语言里面的一个索引,比如: package mainimport "fmt"func main() {var str string "hello"var randomData []byte []byte(str)fmt.Println(randomData[0:]) //[104 101 108 108 111] }上面这串代码会从…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
