• 【Linux】进程替换|exec系列函数


    一、看一看单进程版的进程替换

    #include
    #include
    #include
    
    int main()
    {
        printf("before : I am a process, pid : %d,ppid : %d\n",getpid(),getppid());
    
        execl("/usr/bin/ls", "ls" , "-a" , "-l", NULL);
    
        printf("after : I am a process, pid : %d,ppid : %d\n",getpid(),getppid());
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    通过代码和现象可以看出,执行了execl函数之后,其之后的代码不再被执行。
    而是执行了 ls -a -l 这条指令。

    二、进程替换的原理

    在这里插入图片描述

    CPU首先执行父进程的代码,打印出before语句后,接下来执行execl系统调用。

    ls -a -l也是一个文件,放在磁盘中,execl将ls -a -l文件的代码和数据进行替换,把调用execl系统调用的进程的数据和代码替换掉!

    替换后,execl之后的代码不再被执行。

    注意:进程替换仅仅是替换掉进程的数据和代码,并没有创建新的进程。所以进程的id没有改变

    补充说明:

    • 1.程序替换成功后,exec 系列的函数后面的代码不会被执行。
    • 2.如果程序替换失败,才有可能执行后续的代码;并且,替换成功没有返回值,替换失败才有返回值。

    三、多进程版——验证各种程序替换接口

    exec系列函数

    在这里插入图片描述

    execl

    int execl(const char *path, const char *arg, ...);
    
    • 1

    exec系列是进程替换的系统调用系列函数,l代表的就是list的意思。

    就是链式的。

    具体使用方法:

    execl("/usr/bin/ls", "ls" , "-a" , "-l", NULL);
    
    • 1

    第一个参数就是执行的命令文件所在的路径。
    第二个参数往后开始,就是要执行什么命令!
    平常执行的命令是这样的:ls -a -l ,现在传参给execl后,只需要把空格变成逗号即可,并在最后加上一个NULL即可!
    在这里插入图片描述

    execl第一个参数要传路径的原因:
    想要执行一个程序,就必须先找到这个程序!

    execlp

    int execlp(const char *file, const char *arg, ...);
    
    • 1

    p代表的就是PAHT环境变量。
    代表的就是从自己的环境变量中查找路径。

    所以,标准写法如下:

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

    这样写,不带路径,执行该函数时系统会去环境变量中查找该命令所在路径。

    当然,也可以这样写:

    execlp("/usr/bin/ls", "ls" , "-a" , "-l", NULL);
    
    • 1

    这样写对编译器更好,也不用编译器自己去找了。

    这里有个问题:

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

    第一个ls和第二个ls一样吗?

    答案是不一样的,前面说过,要执行一个程序,必须先找到该程序。
    第一个ls其实就是程序所在的路径,只不过该函数可以通过PATH环境变量帮助我们找到ls这个命令。

    在这里插入图片描述
    也就是说通过环境变量和第一个ls,就能找到ls所在的路径:/usr/bin/ls

    而第二个ls是我该怎么执行ls这个命令。

    总结:第一个ls是找到ls这个命令在哪,第二个ls表示的是该怎么执行ls这个命令。

    execv

    这里的v就代表vector,也就是顺序表。

    int execv(const char *path, char *const argv[]);
    
    • 1

    传递的第二个参数就由list变成了字符串指针数组

    在这里插入图片描述
    核心的传参方式如下:

    char* const argv[] = {"ls","-a","-l",NULL};
    execv("/usr/bin/ls",argv);
    
    • 1
    • 2

    第一个参数是路径,表示操作系统需要去哪个路径下执行命令,第二个参数是字符串指针数组。经过操作系统层面,会将数组的每一个字符串提取出来传递给第一个参数。

    因为一个可执行程序肯定也有main函数,也就是说将argv作为参数传给了main函数,main函数就知道怎么执行该命令了。

    execvp

    与execlp类似,

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

    第一个参数传递的是文件路径,但是由于有环境变量的存在,使得CPU执行该系统调用时,不费什么劲就能找到file文件。

    char* const argv[] = {"ls","-a","-l",NULL};
    execvp("ls",argv);
    
    • 1
    • 2

    第一个ls表明所需要执行的指令所在路径。

    tips

    注意:execl函数不仅能调用系统提供的函数,还能调用用户自己的命令,也就是还能通过execl函数调用其他可执行程序。

    execl("./otherExe","otherExe",NULL);
    
    • 1

    因为不管是什么语言,都能夸语言调用!!!
    因为无论是可执行程序还是脚本,本质都是进程!!!

    是进程,都能进行进程进程替换,都能用exec系列函数调用!

    所以可以通过该调用方法,来调用可执行程序。

    验证execv可以将命令传递给其他可执行程序。

    otherExe.c文件
    int main(int argc,const char* argv[])
    {
        int i = 0;
        for(;i<argc;i++)
        {
            printf("%s\n",argv[i]);
        }
        return 0;
    }
    
    test.c
    int main()
    {
        //多进程版进程替换
        char* const argv[] = {"ls","-a","-l",NULL};
        pid_t id = fork();
        if(id == 0)
        {
            //child
            printf("before : I am a child process, pid : %d,ppid : %d\n",getpid(),getppid());
            execvp("./otherExe",argv);
    
            printf("after : I am a child process, pid : %d,ppid : %d\n",getpid(),getppid());
            exit(0);
        }
        //father   
        sleep(3);
        int status;
        pid_t ret = waitpid(id,&status,WNOHANG);
        if(ret == id)
        {
            printf("wait success! wait pid is : %d \n",id);
        }   
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述
    执行test可执行程序后,会将argv参数传递给otherExe可执行程序,然后打印出来。

    总结:通过exec系列函数,可以调用其他的可执行程序。


    问题2:进程替换时,环境变量会被替换吗?

    答案是并不会。

    当我们在test.c中调用otherExe.c时,并没有将环境变量传递给otherExe函数。

    在这里插入图片描述

    但是通过进程替换,仍然可以看到,otherExe函数仍然可以打印出环境变量!!!

    在这里插入图片描述

    otherExe.c文件如下:
    
    printf("这是命令行参数\n");
    int i = 0;
    for(;argv[i];i++)
    {
        printf("%s\n",argv[i]);
    }
    printf("这是环境变量信息\n");
    
    i = 0;
    for(;env[i];i++)
    {
        printf("%s\n",env[i]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结论:环境变量不会被进程替换给替换掉,进程替换只是替换进程的代码和数据。
    环境变量会随着继承关系从父进程继承下来。
    环境变量也是数据,创建子进程的时候就已经继承下来了。


    在bash进程中导入环境变量:

    export xxx=xxx;
    
    • 1

    bash中导入环境变量,同样会被子进程继承下来。

    如果不想从bash中导入,而是从某一个进程中导入环境变量,则使用一个系统调用:putenv

    int putenv(char *string);
    
    • 1

    哪个进程调用该函数,就向哪个进程中导入环境变量。

    此后,所有该父进程的子进程都会继承该环境变量下来。

    所以,从bash开始,只要导了环境变量,越往下,环境变量会越来越多。


    execle

    int execle(const char *path, const char *arg,
                      ..., char * const envp[]);
    
    • 1
    • 2

    最后一个参数是环境变量数组,也就是当前进程的环境变量表。

    在这里插入图片描述

    在库的声明中,有一个environ变量,使用该变量也能将环境变量传给execle函数。

    通过调用其他可执行程序,就能打印出环境变量了。

    execve

    int execve(const char *path, char *const argv[], char *const envp[]);
    
    • 1

    同样,只是比execv多了一个参数,该参数可以传environ,也就是把当前进程的环境变量传过去即可。


    实际上,execve才是真正的系统调用,其他的exec*函数最终都是调用execve,所以execve在man手册的第二节,也就是系统调用那节,其他函数在man手册第三节。

    在这里插入图片描述

    四、总结

    这篇文章重点讲解exec系列函数。

  • 相关阅读:
    自动驾驶学习笔记(七)——感知融合
    刷题 | 迷宫求解
    (附源码)ssm停车位共享系统app 毕业设计 041534
    自动控制一些知识
    Pinpoint--基础--03--安装部署
    Vuex的搭建与使用
    【牛客编程题】python入门103题(输入&类型,字符串&列表&字典&元组,运算&条件&循环,函数&类&正则)
    查询表中指定列数据
    Javaweb之Vue生命周期的详细解析
    【目标检测】SPP-Net中候选区域在原图和feature map之间的映射关系
  • 原文地址:https://blog.csdn.net/w2915w/article/details/134382499