【Linux】进程的程序替换

简介: 【Linux】进程的程序替换

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

从特定的标准输入当中获取对应的命令行输入,把对应的数据放在缓冲区中



执行可执行程序后即可显示命令行

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

相关文章
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
250 67
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
138 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
108 20
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
86 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
118 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
84 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
82 0
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
7月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
241 4
|
7月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
352 5