【Linux】进程的程序替换
文章目录
- 1. 程序替换
- 1.创建子进程的目的是什么?
- 2.了解程序是如何进行替换的
- 3. 程序替换的基本原理
- 当创建进程的时候,先有进程数据结构,还是先加载代码和数据?
- 程序替换是整体替换,不是局部替换
- execl 返回值
- 4. 替换函数
- 1. execl
- 2. execv
- 3. execlp
- 4. execvp
- 5. execle
- 2. 自定义shell
- 缓冲区问题
- fgets 使用出现空格问题
- 完整代码
- 动图演示
1. 程序替换
1.创建子进程的目的是什么?
目标:为了让子进程帮父进程执行特定的任务
- 具体做法:1. 让子进程执行父进程的一部分代码
红框中的代码实际上是父进程的代码,在没有执行fork之前代码就有了,在没有创建子进程之前,父进程的代码加载到内存了,子进程被创建出来是没有独立的代码,这个代码是父进程的代码,父进程通过if判断分流让子进程去跑了
- 2.创建一个子进程不执行父进程的代码,而是让子进程在磁盘当中执行全新的程序,这种操作称之为进程的程序替换
2.了解程序是如何进行替换的
程序替换函数 execl
输入 man execl 查看程序替换接口
int execl(const char *path, const char *arg, …);
括号内部的 . . . 称为 可变参数列表,可以给c函数传递任意个数的参数
第一个参数为 要执行什么命令
第二个参数 为 要怎样执行程序
最后以NULL结尾表示参数传完了
创建test.c文件并输入以下内容
#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n");}
运行可执行程序发现,只有begin 以及执行 ls -l -a显示的指令
再次修改test.c文件内容如下
#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("我已经是一个进程啦,我的PID:%d\n",getpid()); execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n"); printf("end........\n"); printf("end........\n"); printf("end........\n");}
test.c 经过编译形成mytest可执行程序,./可执行程序就变成进程了,CPU调度进程 ,打印出代码中的打印语句,同时调用程序替换execl,将ls程序执行起来了
[yzq@VM-8-8-centos nn]$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped
使用的 /bin/ls 命令 实际上是一个可执行程序,所以ls程序是在磁盘上的
前面执行的是自己代码的一部分,当调用execl时,将磁盘中可执行程序替换当前进程的代码和数据
后半部分就不执行自己的代码了,执行ls所对应的代码 ,这个现象就叫做程序替换
程序替换就是让一个进程去执行另一个在磁盘中的程序,让一个进程把一个新的程序运行起来
3. 程序替换的基本原理

当前的进程执行当前代码时,如果执行了函数execl等接口,就会根据你所传入的程序的路径以及你要执行的名称及选项,把磁盘当中的一个其他的程序加载到对应的内存,
用新程序的代码替换当前进程的代码段,用当前进程的数据替换老进程的数据段
站在进程的角度
进程的程序替换有没有创建新的进程呢?
没有,只是将新的程序加载到当前进程的代码段和数据段,用CPU去调度当前进程就可以跑起来了
站在程序的角度
程序被加载了内存中,就可以称程序替换的接口(execl) 为加载器
当创建进程的时候,先有进程数据结构,还是先加载代码和数据?
修改test.c文件为以下内容
#include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 int main()5 {6 execl("/bin/ls", "ls", "-a", "-l", NULL); 7 }
此时运行可执行程序,自己就写了一个ls命令
创建子进程,让子进程调用execl,在调用execl把代码和数据加载到内存
所以当创建进程的时候,先有进程数据结构,再加载代码和数据
程序替换是整体替换,不是局部替换
修改test.c文件内容如下
#include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 #include<sys/wait.h>5 int main()6 {7 pid_t id=fork();8 if(id==0)9 {10 //child11 printf("我是子进程:%d\n",getpid());12 execl("/bin/ls", "ls", "-a", "-l", NULL); 13 }sleep(5);14 printf("我是父进程:%d\n",getpid());15 waitpid(id,NULL,0); 16 }
查看子进程完成替换后会不会影响父进程,如果影响父进程,就不应该打印父进程的这句话了
过了5秒钟,父进程结果打印出来,说明父进程不受子进程影响
程序替换只会影响调用进程,进程具有独立性
父子进程都有自己独立的PCB 地址空间 页表 也会自己的映射关系
虽然代码有可能是跟父进程共享,当子进程进行程序替换的时候,子进程会加载新进程的代码和数据
操作系统会发生写时拷贝,将代码和数据进行区分 ,使子进程形成新的映射关系,从而使子进程不会影响到父进程
execl 返回值
如果出错了,execl返回值为-1
修改test.c文件内容如下
#include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 #include<sys/wait.h>5 int main()6 {7 pid_t id=fork();8 if(id==0)9 {10 //child11 printf("我是子进程:%d\n",getpid());12 int n=execl("/bin/lsss", "lsss", "-a", "-l", NULL);//lsss命令不存在 13 printf("you can see me:%d\n",n);14 exit(0);15 }16 sleep(5);17 printf("我是父进程:%d\n",getpid()); 18 waitpid(id,NULL,0); 19 } 20
输入的lsss命令不存在,查询报错后的execl的返回值
程序替换只要成功,就会跑去执行新程序,失败了就会继续向后运行
所以execl程序替换成功不会有返回值——>如果替换失败,一定有返回值——>如果失败了,必定返回——>只要有返回值就失败了
说明不用对execl函数的返回值进行判断,只要继续向后运行一定失败
4. 替换函数
1. execl
int execl(const char *path, const char *arg, …);
l 代表 list 链表
path:代表你想执行谁 (需要带路径)
执行一个程序最基本的原则为:找到它,加载执行它
arg:你想怎么执行它(若想执行ls指令,是只执行ls,还是执行ls- l 、ls -l -a指令
在命令行怎么执行这个命令,就把参数一个一个的传递给execl就可以了
最终以NULL结尾
具体的实现以及返回值问题上面在演示程序替换时已经使用过啦
2. execv
int execv(const char *path, char *const argv[]);
v代表vector 容器
path:代表你想执行谁 (需要带路径)
把原来需要一个一个传的参数放在argv[]数组中
修改test.c文件内容
1 #include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 #include<sys/wait.h>5 int main()6 {7 pid_t id=fork();8 if(id==0)9 {10 //child11 printf("我是子进程:%d\n",getpid());12 char *const myargv[]={"ls", "-l", "-a",NULL};
13 execv("/bin/ls",myargv);14 exit(0);15 }16 sleep(5);17 printf("我是父进程:%d\n",getpid());18 waitpid(id,NULL,0);19 }20
执行可执行程序,依旧可以执行ls指令
3. execlp
int execlp(const char *file, const char *arg, …);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
file: 不需要传路径,只需要把在PATH环境变量的指令传过来
最后以NULL结尾
#include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<sys/wait.h> 5 int main() 6 { 7 pid_t id=fork(); 8 if(id==0) 9 { 10 //child 11 printf("我是子进程:%d\n",getpid()); 12 execlp("ls", "ls", "-l", "-a",NULL);13 exit(0);14 }15 sleep(5); 16 printf("我是父进程:%d\n",getpid()); 17 waitpid(id,NULL,0); 18 }
执行可执行程序,依旧可以执行ls指令
4. execvp
int execvp(const char *file, char *const argv[]);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
v代表vector 容器
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> int main() { pid_t id=fork(); if(id==0) { //child printf("我是子进程:%d\n",getpid()); char* const myargv[]={"ls", "-l", "-a",NULL}; execvp("ls", myargv); exit(0); } sleep(5); printf("我是父进程:%d\n",getpid()); waitpid(id,NULL,0); }

5. execle
int execle(const char *path, const char *arg,
…, char * const envp[]);
path:代表你想执行谁 (需要带路径)
envp[]:代表自定义环境变量
如果调用程序替换时,若不想让子进程使用父进程的环境列表,想自定义环境变量,就可以自己传一个环境变量
在另一个目录中创建other.cc (以cc为结尾说明是一个c++程序),并输入以下内容
#include <iostream>
#include <unistd.h>
#include<stdlib.h>
using namespace std; int main()
{ for(int i = 0; i < 5; i++) { cout<< "我是另一个程序,我的pid是:"<< getpid()<<endl; cout << " MYENV: " << (getenv("MYENV")==NULL?"NULL":getenv("MYENV")) << endl; sleep(1); } return 0;
}
修改test.c文件为以下内容
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> int main() { pid_t id=fork(); if(id==0) { //child printf("我是子进程:%d\n",getpid());
W> char*const myenv[]={"MYENV=YouCanSeeMe", NULL}; execle("/home/mydir/my/mm/myother", "myother", NULL,myenv); exit(0); } sleep(1); printf("我是父进程:%d\n",getpid()); int status=0; waitpid(id,&status,0); return 0; }

第一个路径为other.cc生成的可执行程序 myother 所在的 绝对路径
2. 自定义shell
编写极简版本的shell(bash)
目标:为了深刻的理解shell的运行原理
输入 ps ajx |grep bash ,发现bash就是一个进程

由于shell是一个进程,所以用while死循环
缓冲区问题

正常来说,运行可执行程序会显示命令行,但是由于没有\n刷新缓冲区,也没有使用相关的刷新库函数,所以命令行会一直在缓冲区中 直到 程序结束才显示,但是这是个死循环,所以什么都不会显示


执行可执行程序后即可显示命令行
fgets 使用出现空格问题
fgets 标准输入 按行获取
char *fgets(char *s, int size, FILE *stream);
从特定的标准输入当中获取对应的命令行输入,把对应的数据放在缓冲区中
执行可执行程序后,发现在命令行中输入 ls -a ,就会打印出 ls -a,但是输入时会多出一个空行

正常来说,都会使用回车来到下一行,而这个回车被fgets读取到了

将最后的回车符替换成’\0’

此时就没有空格出现了
完整代码
: mybash.c ? ? ?? buffers #include<stdio.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/types.h>#include<sys/wait.h>#include<stdlib.h>#define MAX 1024#define ARGC 64#define SEP " "int split (char*commandstr,char*argv[]){assert(commandstr);assert(argv);argv[0]=strtok(commandstr,SEP);//在commandstr以空格作为分割符if(argv[0]==NULL)//切割失败{return -1;}int i=1;while(1){argv[i]=strtok(NULL,SEP);//继续切割空格if(argv[i]==NULL){break; }i++; }
W>}void print(char*argv[]){int i=0;for(i=0;argv[i];i++){ printf("%d:%s\n",i,argv[i]);}}int main(){ while(1){char commandstr[MAX]={0};char*argv[ARGC]={NULL};printf("[yzq@mymachine currpath]# ");fflush(stdout);char*s= fgets(commandstr,sizeof(commandstr),stdin);assert(s);(void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用commandstr[strlen(commandstr)-1]='\0';//将最后的回车符用'\0'覆盖掉int n= split(commandstr,argv); //从命令行中获取的字符串传入argv中if(n!=0)continue;//切割失败就什么都不做// print(argv);//打印字符串pid_t id=fork();assert(id>=0);(void)id;if(id==0){//childexecvp(argv[0],argv);exit(1);}int status=0;waitpid(id,&status,0);}}
动图演示

相关文章:
【Linux】进程的程序替换
文章目录1. 程序替换1.创建子进程的目的是什么?2.了解程序是如何进行替换的3. 程序替换的基本原理当创建进程的时候,先有进程数据结构,还是先加载代码和数据?程序替换是整体替换,不是局部替换execl 返回值4. 替换函数1…...
【C++】模板(上)
文章目录1、泛型编程2、函数模板函数模板的实例化模板参数的匹配原则3、 类模板类模板的实例化1、泛型编程 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {double temp left;left …...
express框架利用formidable上传图片
express框架,在上传图片功能方面,用formidable里面的incomingform功能,很方便。很多功能都已经封装好了,非常好用,简单,不需要写更深层次的代码了。确实不错。 下面是我自己跟着黑马教程的博客系统的部分&…...
测试背锅侠?入职软件测试后大d佬给我丢了这个bug分类分析,至今受益匪浅......
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 刚成为入职…...
STM32 OTA应用开发——通过内置DFU实现USB升级(方式1)
STM32 OTA应用开发——通过内置DFU实现USB升级(方式1) 目录STM32 OTA应用开发——通过内置DFU实现USB升级(方式1)前言1 硬件介绍2 环境搭建2.1 Keil uVsion2.2 zadig2.3 STM32CubeProgrammer2.4 安装USB驱动3 OTA升级结束语前言 …...
基于MFC的JavaScript进行网页数据交互
目录 前言 一、创建html对话框工程 二、使用步骤 1.引入JavaScript接口代码 2.重写相关接口 3.在html网页中添加C/C调用的接口 4.在MFC工程中添加调用接口 5.设置确认按键触发调用 6.运行结果 总结 前言 如何快速的进行MFC开发,这里我介绍一种JavaScript与C/C交互的…...
AUTOSAR-Fee
Fee模块 全称Flash EEPROM Emulation Module,属于ECU抽象层 Fee模块本身是脱离硬件的,但是Fee模块可能会引用的Fls模块定制API,所以只能算半抽象. FEE模块应从设备特定的寻址方案和分段中抽象出来,并为上层提供虚拟寻址方案和分段(virtual addressing scheme and segment…...
Linux基本命令——操作演示
Linux基本命令——操作演示Linux的目录结构Linux命令入门目录切换相关命令(cd/pwd)相对路径、绝对路径和特殊路径符创建目录命令(mkdir)文件操作命令part1 (touch、cat、more)文件操作命令part2 (cp、mv、rm)查找命令 …...
【Linux】目录和文件的权限
Linux中的权限有什么作用Linux权限管理文件访问者的分类文件类型和访问权限(事物属性)**文件权限值的表示方法**文件访问权限的相关设置方法chmodchownchgrpumaskumask使用 sudo分配权限目录的权限Linux中的权限有什么作用 Linux下有两种用户࿱…...
Unity 优化之Player Setting
Quality SettingPixel Light Count 使用前向渲染时最大像素光源数。也是性能关键。数量越大消耗越多。Texture Quality:贴图质量,可以选择Half Res,这样速度会更快,但是贴图质量会轻微下降。Anisotropic Textures 纹理各向异形Ant…...
Qt——通过一个简单的程序例程熟悉使用Qt Creator软件进行项目搭建的基本流程(新建项目、项目的文件组成、修改ui文件、编译运行与调试)
【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》 《QT开发实战》 《Android开发实战》...
Linux 如何使用 git | 新建仓库 | git 三板斧
文章目录 专栏导读 一、如何安装 git 二、注册码云账号 三、新建仓库 配置仓库信息 四、克隆远端仓库到本地 五、git 三板斧 1. 三板斧第一招:git add 2. 三板斧第二招:git commit 解决首次 git commit 失败的问题 配置机器信息 3. 三…...
3.springcloud微服务架构搭建 之 《springboot自动装配ribbon》
1.springcloud微服务架构搭建 之 《springboot自动装配Redis》 2.springcloud微服务架构搭建 之 《springboot集成nacos注册中心》 ribbon工作原理自己网上百度,说的都很详细 目录 1.项目引入openfeign和ribbon配置 2.新建lilock-ribbon-spring-boot-starter 3…...
【一】进程到底是个啥?
1. 什么是进程 进程(process):一个运行起来的程序,就是进程!,我们可以在任务管理中看到进程。 进程是操作系统进行资源分配的基本单位 2. 进程的管理 所谓的进程管理,其实就是分为两步&…...
[蓝桥杯] 双指针、BFS和DFS与图论问题
文章目录 一、日志统计 1、1 题目描述 1、2 题解关键思路与解答 二、献给阿尔吉侬的花束 2、1 题目描述 2、2 题解关键思路与解答 三、红与黑 3、1 题目描述 3、2 题解关键思路与解答 3、2、1 dfs题解代码 3、2、2 bfs题解答案 四、交换瓶子 4、1 题目描述 4、2 题解关键思路与…...
编译原理陈火旺版第四章课后题答案
下面答案仅供参考! 1.考虑下面文法G1: (1) 消去 Q 的左递归。然后,对每个非终结符,写岀不带回溯的递归子程序。 (2) 经改写后的文法是否是LL(1)的?给出它的预测分析表。 2.对下面的文法G: P→(E)lalblΛ (1)计算这个文法的每个非…...
【LeetCode】剑指 Offer(25)
目录 题目:剑指 Offer 49. 丑数 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 49. 丑数 - 力扣&…...
【数据结构】链表OJ
Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 编辑 编辑二、分享:OJ调试技巧 编辑三、链表的中间结点 编辑四、链表中倒数第k个结点 一、移除链表元素 示例 1: 输入:head [1,2,6,3,4,5,6], val 6 输出:[1,2,3,4,…...
电子工程师必须掌握的硬件测试仪器,你确定你都掌握了?
目录示波器示例1:测量示波器自带的标准方波信号输出表笔认识屏幕刻度认识波形上下/左右移动上下/左右刻度参数调整通道1的功能界面捕获信号设置Menu菜单触发方式触发电平Cursor按钮捕捉波形HLEP按钮参考资料频谱分析仪器信号发生器示波器 示例1:测量示波…...
高速PCB设计指南系列(四)
第二篇 抗干扰3(部分) 3 提高敏感器件的抗干扰性能 提高敏感器件的抗干扰性能是指从敏感器件这边考虑尽量减少对干扰噪声 的拾取,以及从不正常状态尽快恢复的方法。 提高敏感器件抗干扰性能的常用措施如下: (1&…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
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所在文件夹)加入系统变量…...
路由基础-路由表
本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中,往往存在多个不同的IP网段,数据在不同的IP网段之间交互是需要借助三层设备的,这些设备具备路由能力,能够实现数据的跨网段转发。 路由是数据通信网络中最基…...
【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能 查看官网:https://vxetable.cn 效果 代码 通过 checkbox-config.isShift 启用批量选中,启用后按住快捷键和鼠标批量选取 <template><div><vxe-grid v-bind"gri…...
