Linux:进程替换
什么是进程替换?
我们的可执行程序,在运行起来的时候就上一个进程
一个进程就会有他的内核数据结构+代码和数据
把一个已经成型的进程的代码和数据替换掉,这就叫进程替换
也就是可以通过系统调用把当前进程替换位我们需要的进程
那么替换后,会创建一个新进程吗?
不会,只是在旧进程的壳子执行新进程;替换进程后,之前的代码不会执行,因为已经被替换了
进程怎样替换?
进程替换是需要接口函数的,总不能什么都没有直接替换吧
来看看都有什么:

上图的【int execl(const char *pfin,“const char *arg, ...);】这种后面的省略号,是指参数可变
来举个栗子捏:
test1.c
#include<stdio.h>
#include<unistd.h>int main()
{printf("test1.c ... begin!\n");execl("/usr/bin/ls", "ls", "-l", "-a", NULL);printf("test1.c ... end!\n");return 0;
}
makefile:
test1.out:test1.cgcc -o $@ $^
.PHONY:clean
clean:rm -f test1.out

解释一下:就是把我们本来的代码通过execl来执行execl引用的程序,成为替换的新进程
替换的过程?
而execl的返回值我们并不关心,因为一旦替换成功,就不会向后继续运行旧进程了
而只要继续运行旧进程,那就一定是替换失败了
所以我们的替换接口函数只有失败返回值,没有成功返回值
替换的过程本质上是把这个新程序加载到内存上
一个程序怎样加载到内存上呢?
别忘了exec*!它类似一种Linux上的加载函数,做的是从外设拷贝到内存是操作系统的活
多进程版的进程替换
将代码改成多进程版就是fork创建子进程,让子进程自己替换,父进程只需要等待就好了
也就是说这里被替换的是子进程捏,父进程还在等待
test1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){printf("exec begin\n");//进程替换execl("/usr/bin/ls", "ls", "-al", NULL);printf("exec end\n");exit(0);}int rid = waitpid(id, NULL, WUNTRACED);if(rid > 0){printf("wait success\n");}return 0;
}
makefile:
test1.out:test1.cgcc -o $@ $^
.PHONY:clean
clean:rm -f test1.out

解释一下:我们在if(rid==0)分支里,先打印了一下,然后进入我们的接口,接口里是我们的ls命令,我们ls命令也是文件,也是C语言写的,我们平时执行命令也是执行该程序,所以可以在接口里替换为ls命令

这样就是替换成功了
我们刚刚说了替换失败会继续执行原进程,来看看吧:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ... begin!\n");pid_t id = fork();if (id == 0){sleep(2);execl("/usr/bin/lsss", "lsss", "-l", "-a", NULL);printf("替换失败捏");exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}

你看,没执行execl里面的程序
以前创建子进程让它完成任务,是让子进程执行父进程代码的一部分,现如今可以让子进程执行一个全新的程序
进程有对应的数据和代码,创建子进程会有新的地址空间和页表;替换本身就是覆盖,子进程在数据部分进行写时拷贝,当执行新程序的时候新程序也要进行写时拷贝,所以此后父子间几乎完全独立(本来数据和代码是继承父进程的,现在是全新的了)

来仔细介绍一下我们的替换接口函数:
这些函数的前四个字母都是exec,后面跟着的:
p: 表示自动搜索路径;e: 表示自己维护环境变量;l:表示采用列表传参;v:表示采用数组传参;
execl
int execl(const char *path, const char *arg, ...);
l就是list(列表),传参数列表
剩下的...带选项,在命令行中怎样写命令的参数,就怎么写(下图有例子捏)结束时传NULL
路径表明我们想要执行谁,后面的选项表示我们想要怎样执行
execv
v是vector的意思,动态数组
int execv(const char *path, char *const argv[]);
前面的参数是路径,后面的参数是指针数组,execl的是可变参数,execv的传参需要传指针数组指针数组是自己写的
execvp
带p的意思就是用户可以不用传要执行文件的路径(但要传文件名)
char *const argv[] = {"ps", "-ef", NULL};
execvp("ps", argv);
那它是怎么做到的呢?
别忘了p是可以自己搜路径,在环境变量里搜路径
execlp
就是采用列表传参,但是不用输路径
execlp("ps", "ps", "-ef", NULL);
execvpe
execvpe
带e的需要自己组装环境变量
int execvp(const char *file, char *const argv[], char *const envp[]);
以替换 "ls -al" 为例,示例一下剩余接口:
//l代表传参数列表,分成一个个字符串传
execl("/usr/bin/ls", "ls", "-al", NULL);//p表示系统会去PATH环境变量中找
execlp("ls", "ls", "-al", NULL);//v代表传数组,把命令分成字符串,放进字符串数组里,一起传
char* argv[] = {"ls", "-al", NULL};
execv("/usr/bin/ls", argv);
替换自己写的进程
刚刚我们引用了系统的进程,我们也可以引用我们自己写的程序
下面就来替换段C++代码(文件后缀: .cpp , .cc ,.cxx)
来先写一个自己的程序:
#include<iostream>using namespace std;int main()
{cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}
然后再也一个父进程:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){sleep(2);execl("./mypragma", "mypragma", NULL);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}
tips:别忘了makefile默认是从上到下形成可执行程序,所以第一个要是主程序,其他的依赖程序依次往下排哦
.PHONY:all
all:testexec mypragmamypragma:mypragma.ccg++ -o $@ $^ -std=c++11testexec:testexec.cgcc -o $@ $^
.PHONY:clean
clean:rm -f testexec mypragma
来看看:

程序替换并未形成新进程,我们来验证一下:
test1.c:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){ printf("i am a process,pid:%d\n",getpid());sleep(2);execl("./mypragma", "mypragma", NULL);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}
mypragma.cpp
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;int main()
{printf("i am a new process,pid:%d\n",getpid());cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}

看,这两个进程pid是一样的
自己组装环境变量
我们刚刚说环境变量可以自己组装,来引用
看看吧:
test1.c:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){sleep(2);char *const argv[] = { (char*)"mypragma",NULL };char* const envp[] = { (char*)"LIKE=521",(char*)"LOVE=1314",NULL };execvpe("./mypragma", argv, envp);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n"); return 0;
}
mypragma.cpp:
#include<iostream>using namespace std;int main(int argc,char *argv[],char *env[])
{int i = 0;for (; argv[i]; i++){printf("argv[%d]: %s\n", i, argv[i]);}printf("-------------------------------\n");for (i = 0; env[i]; i++){printf("env[%d]: %s\n", i, env[i]);}printf("-------------------------------\n");cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}

哦?
父进程本身自己的环境变量,来自于父进程的父进程:bash
你也可以用全新的环境变量,也可以用修改后的环境变量:使用putenv()

往进程加环境变量: putenv() ,程序替换不会替换环境变量,我们可以通过带e的接口函数(例如execpe())设置新的环境变量,这会覆盖原本的环境变量。
用法:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(){char *p;if((p = getenv("USER")))printf("USER =%s\n", p);putenv("USER=test");printf("USER+5s\n", getenv("USER"));}

这些都是:
#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[]);

tips:只有execve是真正的系统调用,其它五个函数最终都调用 execve(execve在man手册第2节,其它函数在man手册第3节)
相关文章:
Linux:进程替换
什么是进程替换? 我们的可执行程序,在运行起来的时候就上一个进程 一个进程就会有他的内核数据结构代码和数据 把一个已经成型的进程的代码和数据替换掉,这就叫进程替换 也就是可以通过系统调用把当前进程替换位我们需要的进程 那么替换…...
带你认识:数据仓库宽表~~~浅显易懂
1. 构建宽表的目的 讲宽表我想从为什么需要宽表入手,而不是一上来就抠概念。因为我觉得一门知识叫什么名字并不是最核心的,关键是搞清楚它的诞生背景以及如何在特定场景用好它。 构建宽表的目的很简单,就是为了"一站式"尽可能多的展示我们需要…...
记录|MessageBox.Show()的使用
目录 前言一、解析1.1 代码1.2 具体图片解析 更新时间 前言 遇到了其他人写的MessageBox.Show()的用法,有点懵,特此记录。 一、解析 1.1 代码 MessageBox.Show("登录失败!", "用户登录", MessageBoxButtons.OK, MessageBoxIcon.E…...
LabVIEW软件定制开发公司的前景如何?
LabVIEW软件定制开发公司的前景在当前的技术发展环境下展现出一定的潜力与挑战。这一领域的市场前景主要受到工业自动化、物联网、智能制造等技术趋势的推动,同时也受到行业竞争、技术更新以及人才市场的制约。 市场需求与增长潜力 随着工业4.0、物联网和智能制…...
vue3列表页搜索条件封装
搜索框组件 封装常用搜索框组件,类型有: input(默认值)selectselectV2 (value/label键值对数组)datePickeryear 集成新增、修改、删除、导入、导出按钮,支持slot自定义其他按钮封装搜索、重置按钮封装按钮权限封装导入弹框 本例仅…...
十三、切片的复制
1、使用函数copy 注意点:复制前必须再声明一个与要复制对象类型相同的切片 var cheeses make([]int, 5)cheeses[0] 1cheeses[1] 2cheeses[2] 3cheeses[3] 4cheeses[4] 5var myCheeses make([]int, 5)copy(myCheeses, cheeses) 使用copy函数将cheeses的数据…...
Java Stream API 的应用:提取并处理多属性集合
Java Stream API 是一个功能强大的工具,可以帮助开发者高效地处理集合数据。本篇博客将专注于一个具体的应用示例,即如何使用 Java Stream API 从一个对象列表中提取多个属性值,并进行过滤和去重。这种技术在处理需要从多个字段中提取数据的情…...
【技术方案】智慧城市大数据平台技术方案(Doc原件)
第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障体系 4.1 政策…...
vue项目中引入字体文件样式
需求:关于一些样式需要自定义的,所以需要ui提供字体文件,然后引入项目中,就可实现自定义 首先看一下实现效果图: 第一步:新建一个字体样式文件用于放字体文件和css样式 font.css文件: /* 数字特殊字体 */ font-face {/*给字体命名*/font-family: DINCondBold;/*引入字体文件*…...
Android 11强制App固定user_rotation方向显示
Android11 强制App按照user_rotation方向显示。 diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b1d349d8e93d..f7fd2983c668 100644 --- a/services/core/java/com/an…...
Harbor仓库push显示
背景: 在做测试时发现harbor仓库端口开放这,却一直登录不上去,重启harbor资源包docker-compose还是不行,修改了docker.service文件不行,json文件也不行,以下是涉及到的命令和报错(好像是这个&am…...
Windows 上设置 MySQL 的主从复制
Windows 上设置 MySQL 的主从复制 一、前言1. 环境准备2. 主服务器配置3. 从服务器配置6. 测试复制7. 注意事项 一、前言 在 Windows 上设置 MySQL 的主从复制涉及几个步骤。下面是一个详细的指南,帮助你实现这一过程。 1. 环境准备 安装 MySQL: 确保你…...
鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航
基本概念 在支持多任务的操作系统中,修改一块内存区域的数据需要“读取-修改-写入”三个步骤。然而同一内存区域的数据可能同时被多个任务访问,如果在修改数据的过程中被其他任务打断,就会造成该操作的执行结果无法预知。 使用开关中断的方…...
vue3+ts封装axios以及解决跨域问题
目录 一、前言二、封装axios三、 解决跨域四、调用接口五、运行结果 一、前言 前端请求后端数据时,会用到axios,但是如果不将axios封装好,会导致代码冗余 二次封装的好处如下: 求头能统一处理便于接口的统一管理解决回调地狱配置…...
各厂家BI对比
帆软BI、奥威BI、永洪BI、思迈特BI、亿信华辰BI是国内知名的BI产品,不少企业在选型BI软件时都需要对这些BI软件进行了解,从中选择适合自己的一款。经过过年的发展,这些BI(商业智能)软件各自在多个行业中都有广泛的应用…...
SQL - 触发器
触发器是在插入、更新和删除语句前后自动执行的一堆SQL代码,但是触发器被触发后只会执行一次,通常我们使用触发器增强数据的一致性。创建触发器 -- 创建触发器 drop trigger if exists payments_after_insert; delimiter $$ -- 在 payments表 insert 之后…...
Redis中缓存穿透、缓存击穿、缓存雪崩的详解
如何理解Redis缓存的穿透、击穿、雪崩问题: 缓存穿透 是指缓存中和数据库中都没有数据,而用户不断访问,导致这个不存在的数据每次请求都要到存储层去查询,这样失去了意义。 缓存穿透的解决方案有哪些? 缓存null值布隆过滤增强…...
[Meachines] [Medium] Popcorn SQLI+Upload File+PAM权限提升
信息收集 IP AddressOpening Ports10.10.10.6TCP:22,80 $ nmap -p- 10.10.10.6 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 5.1p1 Debian 6ubuntu2 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: …...
【Linux】python进程管理之supervisor安装使用教程
安装supervisor pip install supervisor生成配置文件 echo_supervisord_conf > /etc/supervisord.conf修改配置文件 vim /etc/supervisord.conf[unix_http_server] file/run/supervisor.sock ; the path to the socket file[supervisord] logfile/var/log/supervisord.log…...
BEM架构
视频 总结: BEM架构:一个命名类的规范而已,说白了就是如何给类起名字使用sass的目的:在<style>中模块化的使用类名,同时减少代码数量 1、 BEM架构 (通义灵码查询结果) BEM (Block Ele…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
MeshGPT 笔记
[2311.15475] MeshGPT: Generating Triangle Meshes with Decoder-Only Transformers https://library.scholarcy.com/try 真正意义上的AI生成三维模型MESHGPT来袭!_哔哩哔哩_bilibili GitHub - lucidrains/meshgpt-pytorch: Implementation of MeshGPT, SOTA Me…...
