【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        //child
 11        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        //child
 11        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        //child
   11        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)
      {
        //child
       execvp(argv[0],argv);
        exit(1);
     }
      int status=0;
       waitpid(id,&status,0);
   }
  }

动图演示

在这里插入图片描述

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

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

相关文章

【三维几何学习】从零开始网格上的深度学习-2:卷积网络CNN篇(Pytorch)

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 从零开始网格上的深度学习-2:卷积网络CNN篇引言一、概述1.1 卷积操作简述1.2 网格上的面卷积二、核心代码2.1 面卷积2.2 网络框架三、基于CNN的网格分类3.1 分类结果3.2 全部代码引言…

FPGA之时钟规划图解

目录 一、前言 二、时钟规划概念 三、时钟规划的模块 四、时钟规划之时钟单元布局 4.1 BUFG 4.2 BUFH 4.3 BUFR 4.4 BUFIO 五、时钟规划之时钟单元走线 5.1 BUFG->BUFH 5.2 BUFR->FF 5.3 BUFIO->FF 一、前言 对于vivado这类使用verilog语言的进…

《Netty》从零开始学netty源码(七)之NioEventLoop.selectStrategy

NioEventLoop是一个事件轮询器&#xff0c;在它的run方法中其实是一个for死循环&#xff0c;不断重复三个过程&#xff1a;1. 获取IO事件&#xff0c;2. 处理IO事件&#xff0c;3. 处理任务队列中的task&#xff0c;而SelectStractegy就是用于第一步获取IO事件&#xff0c;它的…

css:使用filter和backdrop-filter实现高斯模糊效果

背景 今天接到一个需求是&#xff0c;使用高斯模糊的效果对一个页面进行模糊处理&#xff0c;正好借这个机会来整理一下 css3 中高斯模糊的两个 API API介绍 filter 说明&#xff1a; 该 API 是一个过滤器&#xff0c;不仅能实现高斯模糊&#xff0c;还有很多比如颜色偏移、…

接口文档包含哪些内容?怎么才能写好接口文档?十年测试老司机来告诉你

目录 接口文档结构 参数说明 示例 错误码说明 语言基调通俗易懂 及时更新与维护 总结 那么我们该如何写好一份优秀的接口文档呢&#xff1f; 接口文档结构 首先我们要知道文档结构是什么样子的。接口文档应该有清晰明确的结构&#xff0c;以便开发人员能快速定位自己需…

经典文献阅读之--Dynamic-VINS(动态点滤除VINS)

0. 简介 现在的SLAM算法在静态环境中表现良好&#xff0c;但在动态环境中很容易失败。最近的工作将基于深度学习的语义信息引入到SLAM系统以减轻动态对象的影响。然而&#xff0c;在资源受限的机器人的动态环境中应用鲁棒定位仍然具有挑战性。所以《RGB-D Inertial Odometry f…

ES+Redis+MySQL,这个高可用架构设计太顶了!

一、背景 会员系统是一种基础系统&#xff0c;跟公司所有业务线的下单主流程密切相关。如果会员系统出故障&#xff0c;会导致用户无法下单&#xff0c;影响范围是全公司所有业务线。所以&#xff0c;会员系统必须保证高性能、高可用&#xff0c;提供稳定、高效的基础服务。 …

vue笔记

第一个Vue应用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevice-…

【零基础入门前端系列】—动画和弹性盒模型(二十四)

【零基础入门前端系列】—动画和弹性盒模型&#xff08;二十四&#xff09; 一、概念 动画是使元素从一种样式逐渐变化为另一种样式&#xff0c;你可以改变任意多的样式任意多的次数。 请用百分比来规定变化发生的时间&#xff0c;或用关键词from和to&#xff0c;等同0%和10…

购物清单(蓝桥杯C/C++省赛)

目录 1 问题描述 2 文件的读取格式 3 代码实现 1 问题描述 小明刚刚找到工作&#xff0c;老板人很好&#xff0c;只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦&#xff0c;但又不好推辞。 这不&#xff0c;XX大促销又来了&#xff01;老板…

项目实战典型案例26——nacos的命名空间名称和id不一致带来的思考

nacos的命名空间名称和id不一致带来的思考一&#xff1a;背景介绍Nacos命名空间相关知识点思考总结一&#xff1a;背景介绍 项目用的naocs做的配置中心和服务发现。由于开发环境和本地环境使用的都是同一个命名空间&#xff0c;我们多个服务相互调用的时候&#xff0c;由于开发…

若依分离版下拉框动态加载

最近在学习使用若依分离版框架&#xff0c;想要实现下拉框动态加载另一张表的数据&#xff0c;于是参考【字典数据-字典名称】的实现方式&#xff0c;成功试下下拉框动态加载&#xff0c;做下记录 涉及表格&#xff1a;his_user&#xff08;用户表&#xff09;-- 用户管理&…

【linux】:进程概念

文章目录 冯诺依曼体系结构一&#xff1a;操作系统二: 进程总结冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 冯诺依曼体系如下图&#xff1a; 那么输入设备有哪些呢&#xff1f…

常见的Web安全漏洞:SYN攻击/CSRF/XSS

一、SYN攻击&#xff08;属于DOS攻击&#xff09; 什么情况下被动方出现SYN_RCVD状态?(flood攻击服务) 客户伪造 ip 端口&#xff0c; 向服务端发送SYN请求。完成2次握手&#xff0c;第三次服务端 等待客户端ACK确认&#xff0c;但由于客户不存在服务端一直未收到确认&#…

内含18禁~~关于自学\跳槽\转行做网络安全行业的一些建议

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。所以可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。…

金三银四,我猜你需要这套网络安全工程师面试题合集

2023年已经开始了&#xff0c;先来灵魂三连问&#xff0c;年初定的目标是多少&#xff1f;薪资能涨吗&#xff1f;女朋友能找到吗&#xff1f; 好了&#xff0c;不扎大家的心了&#xff0c;接下来进入正文。 由于我之前写了不少网络安全技术相关的文章和回答&#xff0c;不少…

过来人告诉你:Java学到什么程度可以找工作?

大部分初次学习Java的同学都非常关注自己学到什么程度可以找工作就业&#xff0c;因为学习的目的一方面在于掌握知识、提高技能&#xff0c;另一方面就是就业谋生。今天笔者就来跟大家聊一聊一下Java学习到什么地步可以面试找工作。任何企业&#xff0c;不论大小&#xff0c;对…

exe反编译为.py文件

介绍公司以前的一个exe包&#xff0c;我们需要查看里面python源码&#xff0c;但是以前的py源码文件找不到&#xff0c;所以只能反编译&#xff0c;介绍一下反编译的过程。首先准备&#xff1a;pyinstxtractor.py这个文件&#xff0c;网上很多&#xff0c;自己下载准备查看二进…

十八、动画与canvas

1.RequestAnimationFrame 早期定时动画 setTimeout和setInterval不能保证时间精度&#xff0c;第二个参数只能保证何时将代码添加到浏览器的任务队列 requestAnimationFrame(cb)的cb在浏览器重绘屏幕前调用 function updateProgress(){const div document.getElementById(d…

昨天某读者拿到华为OD岗位offer,今天来分享一下经验,包含华为OD机试

来自读者投稿&#xff0c;已经拿到华为 OD 开发岗位 offer&#xff0c;询问了一些问题&#xff0c;下面是他的一些经验。 文章目录华为 OD 投递简历华为 OD 机试分数OD 机试通过之后&#xff0c;收到综合测评OD 技术面&#xff08;时长 1 小时左右&#xff09;主管/HR 面试&…
最新文章