进程程序替换和shell实现

        先前fork说创建子进程执行代码,如何让子进程执行和父进程完全不一样的代码?程序替换。

一 单进程替换演示

        1 execl函数使用

    最近转到在vs code下写代码,之前也在xhell下用过execl函数,所以才想写篇博客总结总结,没想到在vs code下写了一句简单的execl却总是替换失败。想来想去也不知为何,感觉翻车了。

        后来才知道execl的路径没给对,usr前面要带/,因为usr是根目录下的,路径少了个根目录系统也不知道去哪找usr,因为可能有很多目录都叫usr,可是当我改了后。

        嗯?还是失败了,我记得早上就是加了/没反应,我才奇怪的,临近中午我才意识到vs code的一个坑人的点,你修改了代码,不保存,编译的还是旧代码,只有ctrl + s保存一下,再编译才是新程序。最最重要的是这个ctrl + s不能在输入终端的时候保存,必须得在代码处保存。

哪里是终端呢,下面这就是终端,ctrl+~可进入。

二 替换原理

        替换原理

        单进程下是直接把原程序的所有代码替换,那如何从头开始执行,cpu上的寄存器是有保存下一句指令的地址的,现在代码要更换了,你说这个地址还有用吗那如何拿到新代码的地址呢,先来聊聊替换时做了什么,如上图,execl函数的其中一个参数是/usr/bin/ls,此时系统会把这个ls可执行文件的代码加载到内存。

        由编译原理的内容可得,代码在编译的时候就已经分段了,因为要方便操作系统拿去分段映射,也已经给每条数据代码生成了地址,这个地址是逻辑地址,和虚拟地址几乎没区别,应该可以直接填入页表左侧,而且还会生成一个表头,让系统知道各段的起始位置在哪,而这样cpu拿到表头就可以从代码段开始执行了,此时页表,将新程序的物理地址填入,替换成功后,先前的代码和数据占的内存按理说是要被释放的,会不会没成功,然后也被释放了,os应该不会不会干出还没替换完就断自己后路的事。

三  多进程替换

        当我们了解了单进程的替换工作后,我们就知道此时父子进程一定会发生写时拷贝,因为父子进程代码和数据是独立的,我们还发现pid一直不变,这说明进程替换没有创建新进程。

补充 1

        为什么子进程进程内的下一句代码为啥不执行,因为已经被替换了,替换成功就不执行,失败了呢?继续往后执行。

补充 2 exec函数总结

        第一个参数是为了找到程序,要么写全路径,要么带P,去PATH变量里存的路径去找,这个PATH变量也是怪怪的,有时候我把自己的可执行程序的路径写入了,然后我第一个参数就不传具体路径了,结果还是说没找到。

        第二个参数是和选项有关,我之前好奇明明我第一个参数/usr/bin/ls不是已经传了ls吗(那为什么第二个参数还要传ls呢,不知道啊,大佬就是这么设计的,我们直接用就好了)如果是带l的,给main函数(这个main函数就是替换程序ls的main函数)传的参数就像命令行参数一个一个传,以NULL结尾,带v的,则是把这些命令行参数归成数组传参。所以说exec函数带l和带v是冲突的。带e的则与环境变量有关

        exec函数还可以用自己写的程序,甚至是其它语言的代码来替换。

       先前说了替换本质是修改页表和加载代码文件到内存,这个工作肯定是调用了系统调用完成的,而且任意语言写的代码本质都是01数据,那就可以用统一的方式加载到内存,修改页表不就是填地址吗,这和语言一点关系都没有,所以在同一平台下,execl函数可以调用任意语言写的可执行程序,使其变成进程的代码。所以在execl函数看来,都是一样的文件,都是先加载到内存然后修改页表即可。

补充 3 exec函数导环境变量

        既然exec系列函数是可以调用任意的可执行文件,那如果我要传环境变量如何传呢? 也就是说exec系列函数如何传环境变量给另一个可执行文件。由于程序替换,不改变环境变量这些数据,所以执行另一个可执行文件还是能拿到原先的环境变量。如下代码,打印显示即可。

        注:test.cpp就是被编译成了test2这个可执行文件。

        exec系列函数中只要是带e的函数,就是要导入新的环境变量,经过测试,这个新的环境变量表会覆盖当前进程原有的环境变量。

四 xshell实现

        在xhell下也用了不少的命令,而随着进程的学习,我们也就可以初步实现xhell了。每次在linux下下的vim写代码,都要包含好多头文件,下面代码为了简洁,就不显示头文件了,大家到时候一个个man,熟悉熟悉man的使用以及提高看手册的能力。

        先从主函数入手。第一步是一些变量的初始化,在3 解析指令,分割字符串再解释变量保存的什么的,我们直接看2。

int main()
{while(1){//1 初始化rdir = 0;filename = NULL;2: 人机互动,接收指令  int argc = interact();if(!argc)//argc为零,无指令{continue;}//3: 分割字符串splitstring();//4 内建命令int ret = BuildCommand(argc); //5 执行普通指令if(!ret)NormalExecute();}return 0;
}

2 人机互动 接收指令

        当我们在xshell下输入指令,总会显示这一行,然后就阻塞着等待输入指令。

        所以我们的第一步是先打印bash命令行[USR+主机名+路径]$

        这个路径的获取有点绕,我是用getcwd获取放入数组,再打印的,因为如果是直接获取环境变量,这个路径是一直不变的,至于为什么不变呢?如下测试。

  printf("%s\n",getenv("PWD"));  chdir("/home");printf("%s\n",getenv("PWD"));execl("/usr/bin/pwd","pwd",NULL);

        如下,环境变量PWD并不会改变,所以我猜测chdir改变的目录是当前进程的工作目录-cwd,在进程运行时用ls /proc/+pid才可以显示。

       还有就是PWD要显示当前目录,ls显示目录下的文件信息,这两个命令用的cwd变量存的目录,根本就不用环境变量PWD,所以我猜测cd命令改变的也是工作目录,只是顺便更新了pwd环境变量。代码里我就没改这个环境变量了,就直接用个数组来保存当前路径了。

#define LEFT "["
#define RIGHT "]"
#define LABLE "# "
#define LINE_SIZE 1024
char pwd[LINE_SIZE];
const char*getusr()
{return getenv("USER");
}
const char*gethost()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,LINE_SIZE);getcwd是获取当前工作目录,并拷贝到pwd数组中,数组长度为1024
}
int interact()
{getpwd();封装成三个函数分别获取USR,Host和Pwdprintf(LEFT"%s@%s %s"RIGHT LABLE,getusr(),gethost(),pwd);接收指令,保存到command数组中,用于后面解析指令fgets(command,LINE_SIZE,stdin);//ls -a\n去除\n字符command[strlen(command) - 1] = '\0';检查重定向符号 check_command(command); 这个函数我放在3 解析指令一同说明return strlen(command)-1;
}

3 解析指令,分割字符串

        因为我们输入的ls -a -l被当成“ls -a -l”整个字符串了,所以我们需要用空格分离成一个一个字符,后面要用于execl的传参。

#define DELIM " \t" 分隔符的宏定义,包括空格和Tab键
#define OUT_RDIR 1 
#define IN_RDIR  2
#define APPEND_RDIR 3 
#define LINE_SIZE 1024
#define OPTION_SIZE 32char* filename;
int rdir = 0;
char command[LINE_SIZE];
char* argv[OPTION_SIZE];void check_command(char * pos) 
{while(*pos)  遍历指令数组{if(*pos == '<')//输入重定向{rdir = IN_RDIR;*pos++ = '\0';while(isspace(*pos)) pos++; 跳过空格 cat <    test.txtfilename = pos;break;}else if((*pos)== '>') 输出重定向  cat >test.txt{*pos++ = '\0'; if((*pos)== '>')追加重定向 cat >>test.txt{  rdir = APPEND_RDIR;*pos++ = '\0';while(isspace(*pos)) pos++;//跳过filename = pos;break;}rdir = OUT_RDIR;while(isspace(*pos)) pos++;//跳过filename = pos;            break;}else {;}pos++;}}void splitstring()
{//"ls -a -l"int i = 0;strtok函数的使用有些奇怪,第一次要传字符数组,之后的解析就传NULL空指针第二个参数是DELIM,这个参数是分隔符集合,strtok只要碰到" \t"内的字符
一次切割完成,然后赋值给argv数组。argv[i++] = strtok(command,DELIM); while(argv[i++] = strtok(NULL,DELIM));}

        如上rdir和filename记录了重定向的类型以及,重定向的文件名,所以每次循环是一次指令输入到处理,下一次输入新指令,这两个变量的信息就要清空。

4 执行内建命令

        此时我们终于可以解开内建命令的面纱了,还是那句话,真的就只是一个函数而已。

#define LINE_SIZE 1024
#define OPTION_SIZE 32int lastcode = 0; 保存的是退出码char command[LINE_SIZE];
char* argv[OPTION_SIZE]; 
char penv[OPTION_SIZE];int BuildCommand(int argc)
{if(strcmp(argv[0],"ls")==0 ) 在ls -a -l命令中加上颜色选项,我们平时输入的ls -a都是bash默认加上去的{argv[argc++] = "--color";argv[argc] = NULL;}if(strcmp(argv[0],"cd") == 0) chdir改进程的工作目录,当然不能创建子进程执行了。{chdir(argv[1]);改工作目录,并保存到pwd数组中getpwd();return 1;}else if(strcmp(argv[0],"echo")== 0) {if(strcmp(argv[1],"$?")==0)  输出退出码{printf("%d\n", lastcode);打印完后,退出码归零,所以多次echo,第二次为0lastcode = 0;  return 1;}"echo $PATH"else if(argv[1][0] == '$')  输出环境变量{printf("%s",getenv(argv[1]+1));获取环境变量并打印return 1;}}export MYVALUE=10000else if(strcmp(argv[0],"export")== 0) 设置环境变量{memcpy(penv,argv[1],strlen(argv[1]));//putenv(argv[1]);不能直接put argv存的是指向command字符数组的指针下一次输入命令就被覆盖了,那环境变量就不存在了。putenv(penv); //这种保存在一个数组内的方式,只能存一个环境变量,懒得再实现了实现主要是为了理解内建命令,不太想完完整整地造一遍 }return 0;
}

5 执行普通命令

void NormalExecute()
{//创建子进程int id = fork();if(id < 0){perror("fork:");}else if(id == 0)//子进程{if(rdir == OUT_RDIR) 用dup2函数做重定向{int fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0660);dup2(fd,1);}else if(rdir == IN_RDIR){int fd = open(filename,O_RDONLY,0660);dup2(fd,0);}else if(rdir == APPEND_RDIR){int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0660);dup2(fd,1);}//程序替换execvp(argv[0],argv); 这一步才是真正的执行指令,argv[0]是指令名称,选项都在argv数组中,直接传入即可,这就是exec函数中带p的传的是选项数组exit(2);}else//父进程{int status = 0;int ret = waitpid(id,&status,0);//阻塞等待lastcode = WIFEXITED(status);}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker Compose简单入门

Docker Compose 简介 Docker Compose 是一个编排多容器发布式部署的工具&#xff0c;提供命令集管理容器化应用的完整开发周期&#xff0c;包括服务构建&#xff0c;启动和停止。 Docker Compose 真正的作用是在一个文件&#xff08;docker-compose.yml&#xff09;中定义并运…

Arthas安装及简单使用

一. 背景介绍 Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6&#xff0c; 采用命令行交互模式&#xff0c;提供 Tab 自动补全&#xff0c;可以方便的定位和诊断线上程序运行问题。得益于 Arthas 强大且丰富的功能&#xff0c;让 Arthas 能做很多的事情&a…

超声波眼镜清洗机是智商税吗?2023年超声波清洗机比较不错推荐

随着科技的日新月异&#xff0c;我们生活中充满了各种各样的智能设备。其中&#xff0c;超声波清洗机以其独特的清洗能力&#xff0c;逐渐走进我们的生活。然而&#xff0c;对于很多人来说&#xff0c;超声波清洗机还是一个相对陌生的概念。那么&#xff0c;超声波清洗机到底是…

P9 链表 清空链表|销毁链表

目录 前言 01销毁链表 02 清空链表 测试代码 前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ …

Markdown语法入门与进阶指南

一、Markdown简介 Markdown是一种轻量级标记语言&#xff0c;创始人为约翰格鲁伯&#xff08;john Gruber&#xff09;。它允许人们使用易读易写的纯文本格式编写文档&#xff0c;然后转换成有效的XHTML&#xff08;或者HTML&#xff09;文档。这种语言吸收了很多在电子邮件中…

Spring Session介绍

Spring SessionSession储存到Redis1&#xff1a;添加依赖2&#xff1a;配置信息3:Spring Session存String3:Spring Session存对象 项目改造-Redis储存Session Spring Session Spring Session储存在Redis和取的执行流程&#xff1a; 1&#xff1a;request.getSession() 方法时&…

地隔离放大器集成电路芯片D3121,低噪声低失真双通道且外接电容小,能有效消除由线 路电阻所引起的问题及噪声

D3121 是一块对地能动冲放大器集成电路&#xff0c;该电路能有效消除由线 路电阻所引起的问题及噪声。所需外围电容小&#xff0c;便于设计时小型化的同 时可靠性不降低。广泛应用于车载音响系统内。 D3121 系列采用 DIP8 、 SOP8 、 SIP8 的封装形式封装。 主要特点&#x…

Qt应用开发(Quick篇)——矩形模块 Rectangle

一、前言 矩形模块用于用纯色或渐变填充区域&#xff0c;或者提供一个矩形边框。 二、外观 每个矩形项都可以使用使用color属性指定的纯填充色、使用gradient类型定义并使用gradient属性设置的渐变来绘制。如果同时指定了颜色和渐变效果&#xff0c;则只会生效渐变效果。 通过…

Python:核心知识点整理大全2-笔记

在本章中&#xff0c;你将学习可在Python程序中使用的各种数据&#xff0c;还将学 习如何将数据存储到变量中&#xff0c;以及如何在程序中使用这些变量。 2.1 运行 hello_world.py 时发生的情况 运行hello_world.py时&#xff0c;Python都做了些什么呢&#xff1f;下面来深入…

NFTScan | 11.27~12.03 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2023.11.20~ 2023.11.26 NFT Hot News 01/ Web3 教育平台 Open Campus 获 Binance Labs 315 万美元投资 11 月 27 日&#xff0c;Binance Labs 已向社区主导的 Web3 教育平台 Open Campu…

Selenium自动化测试工具使用方法汇总

1、设置无头浏览器模式 from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(- -headless) chrome_options.add_argument(- -disable-gpu) class XX(object): self.driver webdriv…

Python selenium自动化测试模型图解

1、线性测试 优势&#xff1a;每一个脚本都是完整独立的&#xff0c;每一个脚本对应一个测试用例 缺点&#xff1a;开发成本高&#xff0c;会有重复操作重复脚本&#xff1b;维护成本也高&#xff0c;修改重复操作的脚本时&#xff0c;要逐一进行修改。 2、模块化驱动测试 …

三、Zookeeper数据模型

目录 1、Znode兼具文件和目录两种特点 2、Znode具有原子性操作

前端小记--3.接上篇,级联组件el-cascader回显问题

在使用el-cascader这个级联组件时&#xff0c;组件的值是数组形式&#xff0c;且选中节点时&#xff0c;所返回的值中是包含选中节点的所有父节点的。 比如&#xff0c;我们选中的是“值班点1号-东门”&#xff0c;但组件实际的值是[‘值班点1号’,‘值班点1号-东门’]&#x…

[PyTorch][chapter 4][李宏毅深度学习][Gradient Descent]

前言&#xff1a; 目录: 1: 梯度下降原理 2: 常见问题 3&#xff1a; 梯度更新方案 4&#xff1a; 梯度下降限制 一 梯度下降原理 机器学习的目标找到最优的参数,使得Loss 最小 为什么顺着梯度方向loss 就能下降了。主要原理是泰勒公式。 假设损失函数为 忽略二阶导数, 当 …

分享几个电视颜色测试图形卡

介绍 本文分享几个常见的电视颜色测试图形卡和一段matlab程序&#xff0c;完成JPG转FPGA烧写文件&#xff0c;便于把彩色图片预装载到FPGA内。 电视颜色测试图形卡 一种专业检测电视显示效果的工具。它通常由一张卡片和一些色块组成&#xff0c;可以根据标准色彩空间和颜色渐…

语义分割网络FCN

语义分割是一种像素级的分类&#xff0c;输出是与输入图像大小相同的分割图&#xff0c;输出图像的每个像素对应输入图像每个像素的类别&#xff0c;每一个像素点的灰度值都是代表当前像素点属于该类的概率。 语义分割任务需要解决的是如何把定位和分类这两个问题一起解决&…

嘉里大荣物流与极智嘉再度携手,合作助力物流服务高效升级

近日&#xff0c;全球仓储机器人引领者极智嘉(Geek)与3PL知名企业嘉里大荣物流联合宣布&#xff0c;双方再度携手&#xff0c;6周内共建全新自动化订单履行中心&#xff0c;赋能国际时尚运动品牌New Balance加速B2B和B2C订单交付&#xff0c;为其客户提供更高效便捷的物流服务。…

江科大STM32学习笔记(上)

STM32F103xx 前言外设篇GPIO输出GPIO位结构GPIO模式外设的GPIO配置查看实战1&#xff1a; 如何进行基本的GPIO输入输出 OLED显示屏及调试Keil的调试模式演示 EXTI外部中断NVIC基本结构EXTI结构代码实战2&#xff1a;如何使用中断和对射式红外传感器&#xff06;旋转编码器 TIM&…

【flink番外篇】1、flink的23种常用算子介绍及详细示例(1)- map、flatmap和filter

Flink 系列文章 1、Flink 专栏等系列综合文章链接 文章目录 Flink 系列文章一、Flink的23种算子说明及示例1、maven依赖2、java bean3、map4、flatmap5、Filter 本文主要介绍Flink 的3种常用的operator&#xff08;map、flatmap和filter&#xff09;及以具体可运行示例进行说明…