自学内容网 自学内容网

【Linux】进程程序替换

程序替换

如今我们已经了解了fork出来的子进程的完整生命周期了,也曾经在认识fork时提到过,命令行(以bash为例)的实现中大量运用到了fork函数,大多数指令都是bash通过创建子进程去执行该命令的bash就像一个任务处理平台,接受用户下达的指令,然后通过创建子进程并将该指令交由子进程完成。既能完成用户下达的命令,又能确保自生不会因指令下达有误而出错导致bash出问题。
同时,bash作为内核的解释器,将使用者的命令翻译给核心(kernel)处理,可以在外层(shell)就将恶意,错误的请求拒接,保证了内核的安全,所以这种设计是很优秀的。

而子进程要执行父进程下派的指令就需要依赖exec系列函数进行程序替换。

exec函数命令理解

exec系列函数有六个,都为库函数;个数虽然有点多,但是只要结合名字理解还是很好记忆的。
exec
实际上一共有七大程序替换函数,除了上面的六个库函数,还有一个系统级别的替换函数——execve,而上面六个都是对execve的封装,程序替换真正依靠的是execve
execve

函数名参数格式是否需要指明路径是否使用当前环境变量
execl列表
execlp列表
execle列表不是,必须自己组装
execv数组
execvp数组
execvpe数组不是,必须自己组装
execve数组不是,必须自己组装
  • execve是系统级函数

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • llist : 表示参数采用列表组织
  • vvector: 表示参数用数组组织
  • ppath : 自动搜索环境变量PATH,即不需要指明路径,只需要文件名即可
  • eenv : 表示自己维护环境变量

注意:p的替换函数可以不用带路径,只需要程序名即可;但是所替换的程序的所处路径必须在PATH环境变量中,否则无法执行。

函数解释:

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的时候返回值,而没有成功的返回值

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

替换原理

  • 注意,fork创建子进程后才进行替换,所以调用exec前后该进程的id并未改变。

替换原理

七大替换函数

以下替换函数的头文件都为#include <unistd.h>

带l,也就是参数以列表形式组织的替换函数中,会发现第三个参数为...,这是可变参数列表

可变参数列表
实际上我们早就接触过可变参数列表——printf

printf
可变参数列表允许不同类型,不限个数的参数。
可变参数列表
为配合exec的演示,采用下面代码演示。

演示模板:

int main()
{
    char* const argv[]=
    {
        "mytest",
        NULL
    };

    char* const env[]=
    {
        "Myvalue=1030",
        NULL
    };

    pid_t pid = fork();
    
    if(pid<0)
    {
        perror("fork");
        exit(-2);

    }
    else if(pid==0)
    {
        //child
        int exid;
       //exid= execle("./mytest","mytest",NULL,env);//*不带选项执行,但NULL一定记得要有
       exid = execvpe("./mytest",argv,env);//虽然是PATH,但是指定路径也行。
       //exid = execve("./mytest",argv,env);//系统调用,其余六个exec函数都是库函数,都是对execve的封装

       printf("execle failed exid:%d\n",exid);
       exit(exid);//-1

    }
    //father
    int status=0;
    pid_t rid=waitpid(pid,&status,0);
    if(rid>0)
    {
        printf("waitpid success pid:%d\n",rid);
        if(WIFEXITED(status))
        {
            printf("exit code:%d\n",WEXITSTATUS(status));
        }
        else
        {
            printf("kill by sig:%d\n",status&0x7f);
        }
    }
    return 0;
}

execl

int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的路径,如ls/usr/bin/ls
  • 2:待替换程序的名称,如 ls
  • 3:待替换程序的选项,如-l -a
    • 注意可变参数列表处必须以NULL结尾,即使替换的程序不带选项也必须要有NULL。

使用:

 exid=execl("/usr/bin/ls", "ls","-l", NULL);

execl

execlp

 int execlp(const char *file, const char *arg, .../* (char  *) NULL */);                 

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的名称,如ls
  • 2:待替换程序的名称,如 ls
  • 3:待替换程序的选项,如-l -a
    • 注意可变参数列表处必须以NULL结尾,即使替换的程序不带选项也必须要有NULL。

带p的能够自己查找路径,只需要提供替换程序的名称即可,比较好用。

使用:

exid=execlp("ls", "ls","-l", NULL);//推荐

execlp

execv

int execv(const char *pathname, char *const argv[]);

参数2所需的指针数组:

        char*const myargv[]=//ls -l
        {
            "ls",
            "-l",
            NULL
        };

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的路径,如ls/usr/bin/ls
  • 2:包含所替换程序选项的指针数组,如char*const myargv[]
    • 该指针数组也必须以NULL结尾。

使用:

exid=execv("/usr/bin/ls", myargv);

execv

execvp

int execvp(const char *file, char *const argv[]);

参数2所需的指针数组:

        char*const myargv[]=//ls -l
        {
            "ls",
            "-l",
            NULL
        };

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的名称,如ls
  • 2:包含所替换程序选项的指针数组,如char*const myargv[]
    • 该指针数组也必须以NULL结尾。

使用:

exid=execvp("ls", myargv);

execvp

剩余的三个程序替函数都需要自己配置环境变量,为了更好演示,采用另一个演示模板;同时,替换的程序换成我们自己所写的 mytest.cpp

mytest

#include<unistd.h>
#include<sys/types.h>
#include <iostream>

using namespace std;



int main(int argc,char*const argv[],char* const env[])
{
    printf("This is mytest pid:%d ppid:%d\n",getpid(),getppid());
    printf("Myvalue=%s\n",getenv("Myvalue"));
    exit(0);
}

演示模板:

extern char** environ;

int main()
{
    pid_t pid=fork();
    
    if(pid<0)
    {
        perror("fork");
        exit(-1);
    }

    if(pid==0)               
    {
        //child
        char*const myargv[]=//ls -l
        {
            "ls",
            "-l",
            NULL
        };
        
        int exid;
        //exid=execl("/usr/bin/ls", "ls","-l", NULL);
        //exid=execlp("ls", "ls","-l", NULL);//推荐
        //exid=execv("/usr/bin/ls", myargv);
        exid=execvp("ls", myargv);

        printf("exec failed exid:%d\n",exid);
        exit(-1); // 如果子进程有此退出码,说明替换失败
    }

    int status=0;
    pid_t rid=waitpid(pid,&status,0);
    if (rid > 0)
    {
        printf("waitpid success pid:%d\n", rid);
        if (WIFEXITED(status))
        {
            printf("exit code:%d\n", WEXITSTATUS(status));
        }
        else
        {
            printf("kill by sig:%d\n", status & 0x7f);
        }
    }
    return 0; 
}

execle

int execle(const char *pathname, const char *arg, ...    /* (char *) NULL*/, char *const envp[] );                      

返回值:

  • 失败返回-1,成功没有返回值。

自设环境变量表,也要以NULL结尾。

char* const env[]={"Myvalue=1030",NULL};

参数:

  • 1:待替换程序的路径,如mytest./mytest
    • mytest为自己所写程序;mytest在当前路径下
  • 2:待替换程序的名称,如mytest
  • 3:待替换程序的选项:此处为自己的程序,没有选项可带,但是可变参数列表一定要以NULL结尾。
  • 4:要替换的环境变量表;本质也是指针数组。
    • execle
    • man手册有点bug,应该是四个参数

使用:

exid= execle("./mytest","mytest",NULL, env);//可以不带选项执行,但NULL一定记得要有

exele
可以看到在替换的程序中的环境变量确实有我们传进去的Myvalue

execvpe

int execvpe(const char *file, char *const argv[], char *const envp[]);                     
    char* const argv[]=
    {
        "mytest",
         NULL
    };

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的名称
  • 2:存放对应替换程序所使用的命令行参数的指针数组,如char*const myargv[]
    • 该指针数组也必须以NULL结尾。
  • 3:所要传入的环境变量表,以NULL结尾

注意:虽然第一个参数可以是名称,但这只是对于已处于环境变量PATH路径中的程序而言,如果替换的程序的路径不在PATH中,请使用绝对路径。

使用:

 exid = execvpe("./mytest",argv,env);//虽然是PATH,但是指定路径也行。
  • mytest不在环境变量PATH中

execvpe

execve

 int execve(const char *pathname, char *const argv[], char *const envp[]);                

返回值:

  • 失败返回-1,成功没有返回值。

参数:

  • 1:待替换程序的路径
  • 2:存放对应替换程序所使用的命令行参数的指针数组,如char*const myargv[]
    • 该指针数组也必须以NULL结尾。
  • 3:所要传入的环境变量表,以NULL为结尾

使用:

exid = execve("./mytest",argv,env);

execve
以上就是进程替换的相关函数,关系如下:
exec

了解这些以后,再加上之前的知识,我们也可以做一个简易版的bash命令行。


原文地址:https://blog.csdn.net/Mesar33/article/details/143535426

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!