进程程序替换和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和Pwd
    printf(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 32

char* 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.txt
            filename = 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 32

int 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,第二次为0
            lastcode = 0;  
            return 1;
        }
        "echo $PATH"
       else if(argv[1][0] == '$')  输出环境变量
        {
            printf("%s",getenv(argv[1]+1));获取环境变量并打印
            return 1;
        }
    }

       export MYVALUE=10000

    else 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);
       
    }
}

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

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

相关文章

(C语言)计算n的阶乘

要求使用双精度 #include<stdio.h> double factorial(int n) {if(n 1)return 1;return n * factorial(n-1); } int main() {int n ;double res;scanf("%d",&n);res factorial(n);printf("%lf",res); return 0; } 运行截图&#xff1a; 注&am…

oops-framework框架 之 界面管理(三)

引擎&#xff1a; CocosCreator 3.8.0 环境&#xff1a; Mac Gitee: oops-game-kit 注&#xff1a; 作者dgflash的oops-framework框架QQ群&#xff1a; 628575875 回顾 在上文中主要通过oops-game-kit大家了一个新的模版项目&#xff0c; 主要注意项是resources目录下的两个文…

Python Opencv实践 - Yolov3目标检测

本文使用CPU来做运算&#xff0c;未使用GPU。练习项目&#xff0c;参考了网上部分资料。 如果要用TensorFlow做检测&#xff0c;可以参考这里 使用GPU运行基于pytorch的yolov3代码的准备工作_little han的博客-CSDN博客文章浏览阅读943次。记录一下自己刚拿到带独显的电脑&a…

卷积神经网络(CNN):艺术作品识别

文章目录 一、前言一、设置GPU二、导入数据1. 导入数据2. 检查数据3. 配置数据集4. 数据可视化 三、构建模型四、编译五、训练模型六、评估模型1. Accuracy与Loss图2. 混淆矩阵3. 各项指标评估 一、前言 我的环境&#xff1a; 语言环境&#xff1a;Python3.6.5编译器&#xf…

继承 多态 拆箱装箱 128陷阱 枚举类

继承 在java里一个类只能继承一个类&#xff0c;但可以被多个类继承&#xff1b;c里一个类可以继承多个类&#xff1b; 子类可以使用父类的方法&#xff1b; 在java中&#xff0c;Object是所有类的父类&#xff1b; equals方法比较的是对象是否指向同一个地方&#xff0c;这个方…

原生横向滚动条 吸附 页面底部

效果图 /** 横向滚动条 吸附 页面底部 */ export class StickyHorizontalScrollBar {constructor(options {}) {const { el, style } optionsthis.createScrollbar(style)this.insertScrollbar(el)this.setScrollbarSize()this.onEvent()}/** 创建滚轴组件元素 */createS…

Windows下打包C++程序无法执行:无法定位程序输入点于动态链接库

1、问题描述 环境&#xff1a;CLionCMakeMinGW64遇到问题&#xff1a;打包的exe无法运行&#xff0c;提示无法定位程序输入点于动态链接库。 2、解决思路 ​ 通过注释头文件的方式&#xff0c;初步定位问题是因为使用了#include <thread> 多线程库引起的。而且exe文件…

外包干了2个月,技术倒退2年。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;20年通过校招进入深圳某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

如何创建maven项目的多模块项目

Maven多模块项目是指一个Maven项目中包含多个子模块&#xff0c;每个子模块又是一个独立的Maven项目&#xff0c;但它们之间可以存在依赖关系。Maven多模块项目可以方便地管理多个子模块的依赖和构建过程&#xff0c;同时也可以提高项目的可维护性和可扩展性。创建maven项目的父…

ChatGPT发布一年后,搜索引擎的日子还好吗?

导读&#xff1a;生成式AI&#xff0c;搜索引擎的终结者还是进化加速器 ChatGPT发布刚刚一年&#xff0c;互联网世界已经换了人间。 2023年&#xff0c;以ChatGPT和大模型为代表的生成式AI浪潮对全球互联网、云计算、人工智能领域都带来巨大冲击。而且生成式AI在各行各业的应用…

深入理解JVM虚拟机第二十七篇:详解JVM当中InvokeDynamic字节码指令,Java是动态类型语言么?

😉😉 学习交流群: ✅✅1:这是孙哥suns给大家的福利! ✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 🥭🥭3:QQ群:583783824 📚📚 工作微信:BigTreeJava 拉你进微信群,免费领取! 🍎🍎4:本文章内容出自上述:Sp…

[ROS2] --- ROS diff ROS2

1 ROS存在的问题 一旦Ros Master主节点挂掉后&#xff0c;就会造成整个系统通信的异常,通信基于TCP实现&#xff0c;实时性差、系统开销大对Python3支持不友好&#xff0c;需要重新编译消息机制不兼容没有加密机制、安全性不高 2 ROS and ROS2架构对比 ROS和ROS2架构如下图所…

Redis实战篇笔记(最终篇)

Redis实战篇笔记&#xff08;七&#xff09; 文章目录 Redis实战篇笔记&#xff08;七&#xff09;前言达人探店发布和查看探店笔记点赞点赞排行榜 好友关注关注和取关共同关注关注推送关注推荐的实现 总结 前言 本系列文章是Redis实战篇笔记的最后一篇&#xff0c;那么到这里…

如何使用cpolar内网穿透工具实现公网SSH远程访问Deepin

文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统&#xff0c;专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致&#xff0…

卷积神经网络(CNN):乳腺癌识别.ipynb

文章目录 一、前言一、设置GPU二、导入数据1. 导入数据2. 检查数据3. 配置数据集4. 数据可视化 三、构建模型四、编译五、训练模型六、评估模型1. Accuracy与Loss图2. 混淆矩阵3. 各项指标评估 一、前言 我的环境&#xff1a; 语言环境&#xff1a;Python3.6.5编译器&#xf…

(C语言)交换变量

在主函数中定义两个双精度变量x,y&#xff0c;并输入值&#xff0c;编写一个函数实现交换变量x,y&#xff0c;要求函数的参数是指针类型&#xff0c;并编写一个主函数进行调用。 #include<stdio.h> void swap(double *x,double *y) {double t;t *x;*x *y;*y t;} int …

Web前端 ---- 【vue】vue 组件传值(props、全局事件总线、消息的订阅与发布)

目录 前言 父子组件 父传子 子传父 全局事件总线 什么叫全局事件总线 如何创建全局事件总线 如何在组件上获取到这个全局vc对象 最常用的创建全局事件总线 兄弟组件 消息订阅与发布 安装 使用 爷孙组件 前言 在上篇文章我们介绍了父子组件之间的传值通信&#xff…

软件平台架构设计与技术管理之道笔记

软件平台架构设计与技术管理之道笔记 认知 领导软件平台各方面的工作&#xff0c;对技术底蕴、思维模式、决策能力、工作风格、文化铸造等方面都有极高的要求&#xff0c;可以称之为“领域智慧”。认知盲区的代价是巨大的&#xff0c;“不知”比“不会”的后果更严重&#xf…

【VRTK】【VR开发】【Unity】10-连续移动

课程配套学习资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【概述】 连续移动与瞬移有如下不同: 连续移动不容易打断沉浸对于新手或者不适应者来说更容易晕动 我对玩家的建议:连续移动前后左右可以用摇杆,转向用自己…

是时候重估荣耀了

文 | 智能相对论 作者 | 叶远风 在更换董事长后&#xff0c;荣耀的上市计划总算落定。 除了“借壳”被否认&#xff0c;外界对荣耀所有上市的猜想基本都被印证&#xff0c;此外CEO赵明明确表示会在境内上市。 在三年的长途奔袭后&#xff0c;毫无疑问荣耀来了到一个重要关口…