【Linux】进程程序替换

文章目录

  • 替换原理
    • 站在进程的角度
    • 站在程序的角度
    • 初体验及理解原理
  • 替换函数
    • 函数解释
    • 命名理解
    • exec系列函数与main函数之间的关系
    • 在一个程序中调用我们自己写的程序

替换原理

创建子进程的目的是什么?
->想让子进程执行父进程代码的一部分

执行父进程对应的磁盘代码中的一部分

->想让子进程执行一个全新的程序

让子进程想办法加载磁盘上指定的程序,执行新程序的代码和数据->进程的程序替换

![[进程控制 2023-01-30 15.38.00.excalidraw|700]]

站在进程的角度

没有创建新的进程(PCB尤其是pid是没有变化的),只是程序的替换

站在程序的角度

  1. 这个程序被加载到内存中了->称exec函数为"加载器"
  2. 既然我自己的代码能加载到新的程序,操作系统也是
    1. 先创造进程数据结构(PCB),在再需要的时候加载代码和数据

初体验及理解原理

int execl(const char *path, const char *arg, ...);
将指定的程序加载到内存中,让制定进程进行执行

需要解决两个问题:1.找到程序 2.如何执行(cmd 选项一 选项二)
后面的三个点:可变参数列表:可以给函数不同数量的参数,例如:printf\scanf

示范程序:

#include<stdio.h>
#include<unistd.h>

int main()
{
	//.c->exe->load->process->运行->执行我们现在所写的程序
	printf("process is running...\n");

	//load->exe
	execl(
		"/usr/bin/ls"/*要执行哪一个程序*/, 
		"ls", 
		"-a",
		"-l",
		NULL/*你想怎么执行*/
		);//所有的exel程序都必须以NULL结尾,证明你把参数传完了

//但是此时就不能打印出下面的句子
//因为他也是代码,是在execl的,execl执行完毕的时候,代码已经被完全覆盖,开始执行新的程序的代码了,所以printf就无法执行了!
//但是如果调用失败了,就是没有替换成功,就会打印出来下面的句子
	printf("process running done...\n");
}

由上述的例子我们可以看出这个函数的特点:
只有失败才会返回-1(只需要做错误处理,即:只要返回值,就一定是调用错误了)
perror("execl");可以打印错误原因.
调用成功时则不返回,因为此时已经替换了,后续的代码已经没有意义了,没有必要返回值了.(成功了就和接下来的代码无关了)

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
![[Pasted image 20221115142310.png]]

由虚拟地址空间+页表来保证进程的独立性,一旦有执行流想替换代码或者数据,就发生写时拷贝.->说明写时拷贝在代码区也可以发生的!

但是我们一般不直接在父进程进行程序替换,而是创建新的子进程进行程序替换.
于是我们优化上面的代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
##include<sys/types.h>
#include<sys/wait.h>
int main()
{
	printf("process is running...\n");
	pid_t id = fork();
	assert(id != -1);

	if (id == 0)
	{
		//这里的替换会影响父进程吗?
		//不会。进程具有独立性,在执行exec时会发生写时拷贝
		//类比:命令行怎么写,这里就怎么传参
		sleep(1);
		execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
		exit(-1);//must failed
	}

	int status = 0;
	pid_t ret = waitpid(id,&status,0);
	if (ret > 0)
	{
		printf("wait success: \nexit code: %d,sig: %d\n",(status>>8)&0xFF,status&0x7F);
	}

}

替换函数

其实有六种以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[]);

其中,execve是系统调用,其余的都是封装,是为了让我们有更多的选择性

函数解释

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值
不用对该函数进行返回值判断,只要继续向后运行一定是失败的

命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
v(vector) : 参数用数组

可以将所有的执行参数放入数组中,统一传递,而不用进行使用可变参数方案.

char* const argv_[] = {
	"-ls",
	"-a",
	"-l",
	"--color=auto",
	NULL
};
execv("usr/bin/ls",argv_);

p(path) : 有p自动搜索环境变量PATH

带p字符的函数,不用告诉我程序的路径,你只要告诉我程序的名字,我会自动在环境变量PATH中进行可执行程序的查找.
execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);
这里前面的ls是告诉系统我要执行谁,第二个是告诉系统我想要怎么执行

e(env) : 表示自己维护环境变量

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
##include<sys/types.h>
#include<sys/wait.h>
int main()
{
	printf("process is running...\n");
	pid_t id = fork();
	assert(id != -1);

	if (id == 0)
	{
		sleep(1);
		char* const envp_[] = {
			(char*)"MYENV=1122233334444",
			NULL;
		}
		extern char** environ;//声明系统默认的环境变量列表
		execle("./mybin","mybin",NULL,environ);//这里是只传了系统默认的环境变量
		
		putenv((char*)"MYENV=444333222111");//将指定环境变量导入系统中environ指向对应的环境变量表中
		
		execle("./mybin","mybin",NULL,envp_);//这里是只传了你自定义的环境变量
		
		execle("./mybin","mybin",NULL,environ);//实际上,默认环境变量你不传,子进程也能获取. 这里是传了默认的环境变量加上你自己添加的环境变量

		exit(1);
	}

	int status = 0;
	pid_t ret = waitpid(id,&status,0);
	if (ret > 0)
	{
		printf("wait success: \nexit code: %d,sig: %d\n",(status>>8)&0xFF,status&0x7F);
	}

}

exec系列函数与main函数之间的关系

exec*系列函数的意义是:
将我们的程序加载到内存中

int main(int argc,char* argv[],char* env[])

所以应该是先加载到内存中,之后才是调用main函数,main函数中的argc argc[]分别是exec函数中的前面的两个参数,env[]是从exec函数的环境变量参数中加载出来的

![[Pasted image 20221115142525.png]]

exec调用举例如下:

#include <unistd.h>
int main()
{
	char* const argv[] = { "ps", "-ef", NULL };
	char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };
	execl("/bin/ps", "ps", "-ef", NULL);
	// 带p的,可以使用环境变量PATH,无需写全路径
	execlp("ps", "ps", "-ef", NULL);
	// 带e的,需要自己组装环境变量
	execle("ps", "ps", "-ef", NULL, envp);
	execv("/bin/ps", argv);
	// 带p的,可以使用环境变量PATH,无需写全路径
	execvp("ps", argv);
	// 带e的,需要自己组装环境变量
	//没有带e的话会默认传入系统默认的环境变量
	execve("/bin/ps", argv, envp);
	exit(0);
}

在一个程序中调用我们自己写的程序

首先,我们需要在makefile文件中同时创建两个文件并且同时删除两个文件,那该怎么书写呢?

.PHONY:all
all: mybin myexec

mybin:mybin.c
	gcc -o $@ $^ -std=c99
myexec:myexec.c
	gcc -o $@ $^ -std=c99	

.PHONY:clean
clean:
	rm -f myexec mybin

myexec.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
##include<sys/types.h>
#include<sys/wait.h>
int main()
{
	printf("process is running...\n");
	pid_t id = fork();
	assert(id != -1);

	if (id == 0)
	{
		execl("./mybin", "mybin", NULL);
		exit(-1);
	}

	int status = 0;
	pid_t ret = waitpid(id,&status,0);
	if (ret > 0)
	{
		printf("wait success: \nexit code: %d,sig: %d\n",(status>>8)&0xFF,status&0x7F);
	}

}

可以使用程序替换调用任何后端语言所对应的可执行!!!


事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子:
![[Pasted image 20221115142624.png]]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/129075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s存储

nfs 理论上nfs 其实并不是存储设备&#xff0c;它是一种远程共享存储服务。 k8s 存储卷 volume emptyDir&#xff1a;可以实现pod中的容器之间共享数据&#xff0c; 但是存储卷不能持久化数据&#xff0c;且会随着pod的生命周期一起删除。 hostpash&#xff1a;可以实现持久…

SIMULIA--Abaqus结构仿真篇

什么是 SIMULIA? 基于3DEXPERIENCE平台的品牌 多学科多领域的协同仿真与分析优化 三大核心仿真领域&#xff1a;电磁仿真 流体仿真 结构仿真 SIMULIA结构仿真是什么? 对结构进行力学、热学、声学等多学科计算&#xff0c;辅助于设计方案优化.采用数字化技术模拟产品性能&am…

Apifox日常使用(一键本地联调)

背景说明&#xff1a;现在的项目一般都是前后分离&#xff0c;线上出bug或者在进行联调时&#xff0c;有些时候后端需要重复模拟前端数据格式&#xff0c;在使用Apifox的情况下&#xff0c;如何快速造出后端需要的数据呢&#xff1f; 随便找一个网站&#xff0c;点开f12&#…

计算机类同学想要快速找到自己需要的毕设项目看这里

**点个关注不迷路 下面介绍如何快速找到自己需要的项目&#xff1a; 首先打开下面网址 https://www.yuque.com/mick-hanyi/javaweb 关键词搜索 一&#xff1a;pc 端搜索方法 下面是搜索方法举例&#xff1a;下面举得只是例子&#xff0c;具体根据自己得需求输入搜索 然后…

SQL第五次上机实验

1.向图书表&#xff08;Book&#xff09;插入以下记录 USE TSGL GO INSERT INTO Book VALUES(7-5402-1800-3,文学类,边城,沈从文,燕山出版社,10,5,5)2.向借阅表插入以下两条记录 USE TSGL GO INSERT INTO Lend VALUES(201207034201,7-5402-1800-3,00366240,2013-04-22),(2012…

管理员模式运行cmd或则bat文件的时候,出现路径错误的问题

最近在使用Comfyui, 不清楚啥原因&#xff0c;有时候Git无法访问&#xff0c;有时候文件夹无法访问的。就想把它的运行bat命令直接用 管理员模式运行&#xff0c;给到最高的权限&#xff0c;试试。但就这么简单的问题&#xff0c;搜了半天&#xff0c;都是一大堆不靠谱的教程&a…

教你遇到vcomp120.dll无法继续执行代码的解决方法

分享关于vcomp120.dll丢失的4个修复方法。在此之前&#xff0c;我想先简要介绍一下vcomp120.dll的作用以及它是什么。 首先&#xff0c;让我们来了解一下vcomp120.dll的作用。vcomp120.dll是一个动态链接库文件&#xff0c;它是由Microsoft Visual C 2012 Redistributable Pac…

nvm使用教程:node.js的管理工具

假如你有多个项目&#xff0c;每个项目对应node.js版本不同&#xff0c;就可以使用nvm版本管理工具。 都不用自己再去下载node.js了&#xff0c;通过管理工具下载就行 下载地址&#xff1a; https://nvm.uihtm.com/ 找个版本下载即可。 安装到你自定义目录&#xff1a;(除了…

PostgreSQL简介及安装步骤

PostgreSQL简介 PostgreSQL是一款开源的关系型数据库管理系统&#xff0c;具有强大的扩展性、高度的可定制性和可靠的稳定性&#xff0c;因此在企业级应用和开发领域中得到了广泛的应用。本文将介绍PostgreSQL的基本概念以及在各种操作系统上的安装步骤。 安装步骤 1. Window…

Vuex使用一文搞懂

什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 是什么&#xff1f; | Vuex (vuejs.org) 图片来源&#xff0c;vuex官方文档 vuex安装 …

【ARM Trace32(劳特巴赫) 使用介绍 3 - trace32 访问运行时的内存】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 trace32 访问运行时的内存1.1.1 侵入式 运行时内存访问1.1.2 非侵入式运行时访问1.1.3 缓存一致性的非侵入式运行时访问 1.2 Trace32 侵入式和非侵入式 运行时访问1.2.1 侵入式访问1.2.2 非侵入式运行时访问 1…

STL常用库函数复习

文章目录 pairvectorliststackqueuequeuepriority_queuequeue双端队列 set✨set集合✨multiset 多重集合了解&#xff1a;unordered_set 无序集合 map&#x1f31f;map几乎不用&#xff1a;multimap一般不用&#xff1a;undered_map pair utility示例 #include <iostream&…

人工智能基础——Python:Numpy与矩阵

人工智能的学习之路非常漫长&#xff0c;不少人因为学习路线不对或者学习内容不够专业而举步难行。不过别担心&#xff0c;我为大家整理了一份600多G的学习资源&#xff0c;基本上涵盖了人工智能学习的所有内容。点击下方链接,0元进群领取学习资源,让你的学习之路更加顺畅!记得…

vue项目中订单完成提交按钮动画

1. 动画1 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>Order</title><!-- <link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/re…

时序预测 | MATLAB实现WOA-CNN-LSTM-Attention时间序列预测(SE注意力机制)

时序预测 | MATLAB实现WOA-CNN-LSTM-Attention时间序列预测&#xff08;SE注意力机制&#xff09; 目录 时序预测 | MATLAB实现WOA-CNN-LSTM-Attention时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描述 1.MATLAB实…

【C++】STL 标准模板库 ③ ( STL 容器简介 | STL 容器区别 | STL 容器分类 | 常用的 STL 容器 )

文章目录 一、STL 容器简介1、STL 容器区别2、STL 容器分类3、常用的 STL 容器 一、STL 容器简介 1、STL 容器区别 STL 容器 用于管理 一组 数据元素 , 不同类型的 STL 容器 的区别 主要是 节点 和 节点之间的关系模型 不同 ; 容器的内存空间是否连续 : 向量 vector 的内存空间…

微软宣布称Windows 再不会偷偷下载更新文件,真的吗?

导读时钟拨回到2015年&#xff0c;微软刚刚推出Windows 10操作系统时&#xff0c;一些Windows 7用户首次在线Update的升级文件大小居然高达6~8GB。这件事引发了大量的不满&#xff0c;一些按照流量计费和宽带不给力的用户怨言极为严重&#xff0c;其中德国用户把此事闹上了当地…

嵌入式中常见的显示屏接口有哪些?

显示屏接口一般有I2C、SPI、UART、RGB、LVDS、MIPI、EDP和DP等。下面简要总结一下。 01 中小屏接口I2C、SPI、UAR 一般3.5寸以下的小尺寸LCD屏&#xff0c;显示数据量比较少&#xff0c;普遍采用低速串口&#xff0c;如I2C、SPI、UART。 I2C&#xff1a; I2C总线是半双工&…

TCC分布式事务----以Hmily框架为例

插曲&#xff1a;RocketMQ的Half Message 先引入一个插曲&#xff0c;RocketMQ为什么要有Half Message 为什么不在本地事务提交之后&#xff0c;直接发一个commit消息不就行了&#xff0c;为什么还要先发一个可以撤回的、不能被消费的half message&#xff0c;再执行本地事务…

剑指JUC原理-18.同步协作

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&…