开发者
资源
【Linux】进程程序替换-转载
【Linux】进程程序替换-转载
发表于2024/07/07
1702
 

想要弄明白为什么要有进程程序替换,我们首先要知道父进程创建子进程的目的是什么?想要子进程完成什么样的任务? 

 

想让子进程执行父进程代码的一部分。 

让子进程想办法,加载磁盘上的指定程序,然后执行新程序对应的代码和数据。(这就是我们所讲的进程程序替换) 

一.什么是进程程序替换 

进程程序替换顾名思义,就是将其他程序替换过来继续执行,主要是通过exec* 这类函数来帮助我们替换,直接来学这类函数。 

 

二.怎样实现 

进程程序替换函数其实不止一个函数,而是由六个以execl开头的函数构成的函数族,如下图: 

 image.png

 

其中,最常用的就是execl,我们就用execl为例讲解。我们理解了一个函数的使用方法和原理,对其他相关函数的学习就会起到事半功倍的效果。 

 

execl函数 

      int execl(const char *path, const char *arg, ...);后边的“...”是可变参数列表,意为可以传入多个参数。 

 

 

 要完成进程替换需要哪些工作? 

 

我们要找到要替换的程序所在路径,即找到。execl的第一个参数的作用就是传入替换程序的具体路径。 

然后,我们要找到程序的执行方法,就是在命令行中如何执行,就要如何传入。除第一个外,其他参数都是用来传入执行方法的。执行方法传入完毕后,以null结尾。 

实例: 

 

   execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);  

 

单进程程序替换完整代码 

#include<stdio.h> 

#include<unistd.h> 

int main() 

    printf("start.......\n"); 

    execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL); 

    printf("end.......\n"); 

  

 执行: 

 

 

 父子进程程序替换完整代码 

代码: 

 

#include<stdio.h> 

#include<unistd.h> 

#include<sys/types.h> 

#include<sys/wait.h> 

int main() 

    pid_t id=fork(); 

    if(id==0) 

    { 

        printf("start.......\n"); 

        execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL); 

        printf("end.......\n"); 

  

    } 

    else 

    { 

        int status=0; 

        pid_t fd=waitpid(id,&status,0); 

        if(fd==id) 

        { 

            printf("pid:%d,exist code:%d,sig code:%d\n",fd,(status>>8)&0xFF,status&0x7F); 

  

        } 

    } 

运行结果:  

 image.png

 

基于execl谈程序替换原理 

1.单进程 

我知道,此刻的大家心里一定有诸多的问题,比着急,我们一一探讨。 

 

首先来看这张我们非常熟悉的图: 

 image.png

 

想必大家学到现在,这张图都不陌生了。 

 

程序替换的原理就是:把物理内存中原来的代码和数据替换成目标程序的代码和数据,也就是说:调用execl之后的代码和数据都被替换掉了,有可能还要发生映射关系的改变。 

 

程序替换的本质就是:将指定的代码和数据,加载到指定的位置,然后覆盖自己的代码和数据。 

 

为什么在单进程的运行结果中,第一条打印语句执行了,而第二条打印语句没有执行? 

 

因为在execl语句之后的代码和数据,在物理内存中被目标程序的代码和数据替换掉了(也就是覆盖掉了),接下来执行的是目标程序的代码和数据,因为这个原因,第二条print语句没有被执行。 

 

进程替换的时候,有没有创建新的进程? 

 

没有,进程替换仅仅将物理内存中的代码和数据做了替换。虚拟内存和PCB表没有发生任何改变。  

 

关于返回值问题,为什么程序替换成功,没有返回值;程序替换失败返回值为-1? 

 

 不需要,因为:exec类函数只要返回了,就一定是失败的。 

 

成功的情况下,就和接下来的代码无关了(不会再执行下面关于返回值的判断了),返回值也就变得毫无意义。 

失败的情况下,仍然执行接下来的代码,可能仍然需要利用返回值做某些判断,这时的返回值仍然有意义。 

2.多进程 

 在理解单进程程序替换的基础上,多进程的程序替换就容易理解了。 

 

我们先要达成一个共识:写时拷贝这种机制,不仅仅是对数据,对代码也有写时拷贝策略。 

 image.png

 

子进程被创建,子进程继承父进程的PCB,虚拟内存,页表,并和父进程共同使用一份代码和数据。一方对代码和数据进行修改时,为该方创建一份新的内存空间,供其修改。这就是写时拷贝。 

 

假如:子进程要对代码进行修改,操作系统为其分配一段新的内存空间,让其存放新的代码和附带数据,依旧保持内存的独立性。 

 

所以,依旧还是两个进程在运行,并未产生新的进程。  

 

 三.其他程序替换类函数(除execl) 

 

除execl外,其余五个函数的返回值问题,所属头文件,作用等等和execl都是相同的,差异主要体现在参数列表方面。详解如下: 

 image.png

 1.execlp 

程序替换类函数的函数名称共同部分为:exec。然后后面再加不同的字母,显然这些字母代表着不同的含义: 

 

l:list。表示将参数逐个传入。 

 

p:path。如何找到程序的功能。带有p字符的函数,不用传入程序所在的具体路径,只要传入程序名,函数会自动在PATH里的路径下,进行可执行程序的查找。 

 

 想必大家都知道execlp函数的使用方法了吧 

 

如下: 

 

#include<stdio.h> 

#include<unistd.h> 

int main() 

    printf("start.......\n"); 

    execlp("ls","ls","-a","-l",NULL); 

    printf("end.........\n"); 

  

在execl函数的参数中,有两个“ls”,冲突吗?不冲突,第一个是告诉系统我要执行谁,一个是告诉系统我要如何执行。 

 

运行一下 

 image.png

  

2.execv 

v:vector,可以将执行参数放入数组中,进行同一传递。而不用使用可变参数方案。 

 

#include<stdio.h> 

#include<unistd.h> 

int main() 

    char *const argv_[]={ 

        "ls", 

        "-a", 

        "-l", 

        "--color=auto" 

    }; 

    printf("start.......\n"); 

    execv("/usr/bin/ls",argv_); 

    printf("end.........\n"); 

  

运行一下: 

 image.png

  

 

3.execle 

e:传入自定义环境变量,即可以查询系统中的环境变量。 

 

代码实例:这次我们需要创建3个文件,makefile,mybin.c,getenv.cc 

 

makefile: 

 

.PHONY:all 

all:my.out mybin 

  

my.out:getenv.cc 

    g++ getenv.cc -o my.out -std=c++11 

mybin:mybin.c 

    gcc mybin.c -o mybin -std=c++11 

.PHONY:clean  

clean: 

    rm -f my.out mybin 

getenv.cc 

 

#include<stdio.h> 

#include<unistd.h> 

#include<stdlib.h> 

int main() 

    printf("start.......\n"); 

    putenv("myenv=1111122222");//作用为:向环境变量中导入自定义的环境变量。 

    execl("./mybin","mybin",NULL,environ); 

  

mybin.c 

 

#include<stdio.h> 

#include<stdlib.h> 

int main() 

      printf("PATH:%s\n",getenv("PATH")); 

      printf("PWD:%s\n",getenv("PWD")); 

      printf("myenv:%s\n",getenv("myenv")); 

  

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

  

4.execvp和execvpe 

这两个函数其实就是前面我们所说的几个函数传参方式糅杂在一起而已,所以这里就不在讲了。 

 

5.总结 

 

image.png

 

刚刚好我们一共提到了6个函数,其实这些函数都是C语言封装的函数,这些函数都是由一个叫execve的函数封装而来。execve这个函数是系统调用接口,感兴趣的可以自己查一下。 

 

四.如何利用程序替换函数调用自己写的程序 

我们自己写的程序也是可执行程序,理论上也是可以使用程序替换函数进程程序替换的。 

 

接下来,我们验证一下:创建三个文件:makefile,mybin.c getenv.cc 

 

makefile: 

 

.PHONY:all 

all:my.out mybin 

  

my.out:getenv.cc 

    g++ getenv.cc -o my.out -std=c++11 

mybin:mybin.c 

    gcc mybin.c -o mybin -std=c++11 

.PHONY:clean  

clean: 

    rm -f my.out mybin 

 mybin.c 

 

#include<stdio.h> 

#include<unistd.h> 

int main() 

    printf("start.......\n"); 

    execl("./mybin","mybin",NULL); 

     

  getenv.cc 

 

#include<stdio.h> 

int main() 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

      printf("hello world\n"); 

  

运行一下: 

 image.png

 

其实,无论是什么语言写的程序,只要是可执行程序,都可以使用进程替换函数执行。 

 

五.制作一个简单的shell程序 

代码: 

 

#include <stdio.h> 

#include <unistd.h> 

#include <stdlib.h> 

#include <sys/types.h> 

#include <sys/wait.h> 

#include <assert.h> 

#include<string.h> 

#define NUM 1024 

char lineCommand[NUM]; 

char *myargv[24]; 

int main() 

     

    while (1) 

    {    

        printf("用户名@主机名 当前路径# "); 

        //printf("%s@%s %s####",getenv("USER"),getenv("HOST"),getenv("PWD")); 

        fflush(stdout);//刷新缓冲区 

        char *s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin); 

        assert(s != nullptr); 

        (void)s; 

        lineCommand[strlen(lineCommand)-1]=0; 

        myargv[0]=strtok(lineCommand," ");//根据空格分割字符串。 

        int i=1; 

        while(myargv[i]=strtok(NULL," ")); 

  

        pid_t fd=fork();//创建新进程 

        if(fd==0) 

        { 

            execvp(myargv[0],myargv);//子进程进行程序替换 

            printf("命令非法,请重新输入\n"); 

             

        } 

        waitpid(fd,NULL,0); 

  

  

    } 

    return 0; 

  

运行一下  

 image.png

 

 写到最后,因水平有限,难免会有错误,请各位指正!! 

———————————————— 

 

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 

                         

原文链接:https://blog.csdn.net/2301_79181030/article/details/139578689 

收藏举报
Level 1
0
帖子
0
粉丝
0
获赞