首页 > 系统相关 >进程的替换

进程的替换

时间:2024-11-05 13:17:28浏览次数:5  
标签:const 函数 程序 替换 进程 环境变量

进程控制块(PCB):

什么是进程替换?

进程替换是一种在计算机操作系统中,通过特定的系统调用(如exec系列函数)实现的进程执行内容的改变过程。进程替换的本质是将当前进程的用户空间代码和数据全部替换为另一个程序的内容,而进程的标识符(PID)保持不变。

进程替换的影响

虚拟地址空间的替换:当进程调用exec系列函数时,操作系统会为新程序分配一个全新的虚拟地址空间。这意味着旧的虚拟地址空间不再有效,需要被释放

页表的更新:新的虚拟地址空间需要新的页表来管理虚拟地址与物理内存之间的映射关系。操作系统会创建新的页表来支持这个新的虚拟地址空间

资源回收:旧的虚拟地址空间和旧的页表占用内存需要被回收,以方便系统可以重用这些资源。操作系统的内存管理单元(MMU)会处理这些资源的回收工作。

进程状态的重置:除了替换虚拟地址空间和页表外,exec调用还会重置进程状态,包括堆栈,寄存器等,确保新程序在一个干净的环境启动。

性能和安全性考虑:回收旧的虚拟地址空间和页表有助于防止内存泄漏和其他潜在的安全问题。此外,这也是操作系统高效内存管理的一部分。

从不同角度看待进程替换

从程序的角度看待进程替换

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

从程序的角度看待进程替换

程序原本是存放于磁盘中的,当调用了exec函数时,程序的代码和数据分别会加载到当前进程对应的代码段和数据段,代码和数据一旦替换后,相当于用一个老进程的壳子去执行一个新的程序的代码和数据。程序替换就相当于程序加载器,我们平时说的程序被加载到内存中,其实就是调用了exec函数。在创建进程的时候,是先创建进程的数据结构PCB,再把代码和数据加载到内存中的。

深度理解进程替换

当调用 exec 函数时,程序的代码和数据分别加载到当前进程对应的代码段和数据段,代码和数据一旦替换之后,相当于用一个老进程的壳子,而这个老进程的壳子就是原本的子进程的task_struct,进程程序的替换不是进程替换,也就是说,进程程序的替换并没有创造一个新的PCB,而是修改了其中的mm_struct的指定内容。

当使用了exec函数之后,把只属于子进程的内存的数据和代码、旧的虚拟地址空间、旧的页表回收,而子进程和父进程共享的内存的数据和代码并没有回收。而没有修改task_struct中的 int exit_code 与 int exit_signal ,也没有丢失task_structPPID等的数据。

替换函数exec系列

进程替换的本质就是把程序的进程代码+数据加载到特定的进程的上下文中,C/C++程序要运行,必须要先使用加载器加载到内存中,这就要用到exec*系列程序的替换函数,他们充当加载器,把磁盘中的程序加载到内存中

分析:3号手册的exec()系列函数都通过封装2号手册的execve函数。其他exec系列的函数都是C语言的库函数,而execve函数都是系统调用函数。

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

参数:path:程序的路径,arg为命令+命令参数。如:"ls","-a","-l",最后以NULL结尾表示选项传递结束

返回值:程序替换成功就不会出现返回值,如果替换失败就继续执行老代码,并且execl一定会返回-1

如果程序替换成功后,新程序的退出码也会返回给子程序,同样可以被父进程拿到子进程的退出状态

int main()
{
    printf("I am a process! pid:%d\n",getpid());
    execl("/bin/ls","ls","-a","-l",NULL);//程序替换,不再执行execl函数之后的代码了
 
    printf("you can see me?\n");
 
    return 0;
}

分析结果:该可执行程序它打印了printf函数的结果并且还打印出了指令“ls -la”的运行结果,但是并没有把最后一个printf函数的结果打印出来

结论:当进程执行了exec()系列函数时,原本程序的代码和数据会全部被新的程序所替换。程序的替换是整体替换而不是局部替换。

分析现象:ls找不到对应的文件,所以进程替换失败,在进程替换失败后,会返回-1,并且设置了错误码error为2,也就是进程退出状态码。而这个错误码/进程退出状态码一个进程只能设置一次,由于execl进程替换失败后,会赋值给error2,因此,父进程收到的退出码为2,而非1。

函数int execv(const char *path, char *const argv[]);

参数

  • path 为程序路径 
  • argv 数组内存放 命令 + 命令参数 
  • execl 与 execv 只在传参形式上有所不同,execl用的是可变参数列表,而execv用的是指针数组,数组元素个数由我们来定

函数int execlp(const char *file, const char *arg, ...);

execlp 中的 p 表示能够自动搜索环境变量 PATH,在执行特定程序时,只要知道程序名系统就会自动在环境变量path中搜索程序位置,不需要知道这个程序在哪里。使用 execlp  替换程序更加方便,只要待替换程序路径位于 PATH 中,就不会替换失败

函数int execvp(const char *file, char *const argv[]);

execlp 中的 p 表示能够自动搜索环境变量 PATH,在执行特定程序file时,只要知道程序名系统就会自动在环境变量path中搜索程序位置,不需要知道这个程序在哪里,argv 数组内存放 命令 + 命令参数 

函数int execle(const char *path, const char *arg, ..., char * const envp[]);

envp 为自定义环境变量,可以将自定义或当前程序中的环境变量表传给待替换程序

在这个可执行程序中,因为没有添加MYENV,可以发现该子进程没有环境变量 MYENV 为空 

可以看见在test1进程中可以发现环境变量MYENV,但是环境变量PATH却没有了,这是因为函数execle传递环境变量表是覆盖式传递的,老的环境变量表会被我们传递的自定义的环境变量表所取代覆盖,而如果是想在原本的环境变量表上面添加环境变量,需要用到putenv(),在环境变量表*environ上面添加

现在可以理解为什么在 bash 中创建程序并运行,程序能继承 bash 中的环境变量表了

 在 bash 下执行程序,等价于在 bash 下替换子进程为指定程序,并将 bash 中的环境变量表 environ 传递给指定程序使用
其他没有带 e 的替换函数,默认传递当前程序中的环境变量表
因此,我们称环境变量具有全局属性。 

函数int execvpe(const char* file, char* const argv[], char* const envp[]);

对 execvp 的再一层封装,使用方法与 execvp 一致,不过最后一个参数可以传递环境变量表

函数int execve(const char* filename, char* const argv[], char* const envp[]);

execve 是系统真正提供的程序替换函数,其他替换函数都是在调用 execve 

总结

  • execl 相当于将链式信息转化为 argv 表,供 execve 参数2使用
  • execlp 相当于在 PATH 中找到目标路径信息后,传给 execve 参数1使用
  • execle 的 envp 最终也是传给 execve 中的参数3 

替换函数除了能替换为 C++ 编写的程序外,还能替换为其他语言编写的程序,如 JavaPythonPHP等等,虽然它们在语法上各不相同,但在 OS 看来都属于 可执行程序,数据位于 代码段 和 数据段,直接替换即可。

系统级接口是不分语言的,因为不论什么语言最终都需要调用系统级接口,比如文件流操作中的 openclosewrite 等函数,无论什么语言的文件流操作函数都需要调用它们。

标签:const,函数,程序,替换,进程,环境变量
From: https://blog.csdn.net/2301_79991997/article/details/143504208

相关文章

  • 【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)
                                 作者主页:   作者主页                           本篇博客专栏:Linux                ......
  • Electron: 渲染器进程到主进程(双向)
    双向IPC的一个常见应用:从渲染器进程代码调用主进程模块并等待结果【ipcRenderer.invoke】《==============》【ipcMain.handle()】main.jsconst{app,BrowserWindow,ipcMain,dialog}=require('electron/main')constpath=require('node:path')asyncfunctionha......
  • 强噪声下基于mscnn-bigru-attention深度学习模型CWRU(凯斯西储大学)轴承故障诊断(Pytho
     1.效果视频(以0HP数据集为例,在-30DB下的测试准确率效果)强噪声下基于mscnn-bigru-attention深度学习模型CWRU(凯斯西储大学)轴承故障诊断_哔哩哔哩_bilibili对原始信号分别添加不同强度的高斯白噪声,以模拟实验数据遇到的实际环境中干扰噪声。原始信号(以0HP数据为例进行展示,可......
  • 带界面下的基于mscnn-bigru-attention深度学习模型江南大学轴承故障诊断(Python代码,很
     1。效果视频:基于mscnn-bigru-attention深度学习模型江南大学轴承故障诊断带界面_哔哩哔哩_bilibili 2.江南大学轴承数据集介绍采样频率:50khz,采样时间:10s转速:6008001000/rpm内圈故障:ib外圈故障:ob滚动体故障:tb正常:N 以600转速下的内圈故障数据为例展示:开始数据......
  • Python中的生产者-消费者模型:多进程与多线程的实践
    Python中的生产者-消费者模型:多进程与多线程的实践在现代编程中,生产者-消费者模型是一种常见的设计模式,用于处理任务队列和并发执行。Python提供了多种工具来实现这一模型,包括threading模块和multiprocessing模块。本文将通过一个实际的案例——从网页上批量下载图片——来......
  • 定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
    场景定时任务每10秒插入一批数据,由于过去频繁导致锁表,从而无法再插入数据解决方案具体查看博客:https://blog.csdn.net/weberhuangxingbo/article/details/88709556数据库中执行sql:SELECT*FROMinformation_schema.innodb_trxSELECT*FROMinformation_schema.innodb_lo......
  • Linux系统编程IPC通信之---守护进程讲解(很重要)
    绪论首先在正式介绍守护进程之前,这里先给大家介绍一下进程组和会话。进程组一组相关进程的集合,所有进程的标识符相同.会话一组相关进程组的集合,一个会话中的所有进程共享单个控制终端.在任意时刻,会话中的其实中一个进程组会成为终端的前台进程组.其他进程组会成为......