Linux 之 【进程替换】(execl、execlp、execle、execv、execvp、execve)

目录

1.进程替换原理

2.替换函数

2.1execl

2.2execlp

2.3execle

2.4execv

2.5execvp

2.6execve

2.7总结


1.进程替换原理

  • 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)
  • 子进程通过调用一种exec函数可以执行另一个程序:当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换**,操作系统修正进程地址空间、页表等数据结构之后** ,该进程开始执行新程序。
  • 调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
  • 多子进程的程序替换实行写时拷贝,不影响父进程,单进直接进行替换
  • 所以得出一个结论,数据段是可写的,通过写时拷贝机制实现进程间隔离;代码段是只读的,exec()时直接被新程序替换
  • 编译器形成的可执行程序是有格式的,如ELF,可执行程序的表头存储了程序的入口

注意,代码和数据是从磁盘文件加载到内存的,进程替换时,操作系统将新程序的代码和数据替换到原代码和数据处,空间不够就扩容

2.替换函数

2.1execl

复制代码
int execl(const char *path, const char *arg, ...);

execl 函数用于执行指定路径的程序(参数1)

并将后续一系列独立的字符串作为命令行参数传递给该程序 (后续的可变参数)

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
        execl("/usr/bin/ls", "ls", "-a", "-l", NULL);    
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了ls程序,并成功执行了ls -a -l 命令

所以参数1传入的是所替换程序的完整路径

参数二传入的是命令行参数,注意:最后传入 NULL
执行一个程序,首先需要找到该程序,然后决定如何执行该程序,需要涵盖哪些选项

所以参数1:定位要执行的程序

参数2及后续:告诉程序做什么
值得注意的是,如果execl函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。

2.2execlp

复制代码
int execlp(const char *file, const char *arg, ...);

execlp 函数用于执行PATH路径下的程序(参数1)

并将后续一系列独立的字符串作为命令行参数传递给该程序 (后续的可变参数)

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
         execlp("ls", "ls", "-a");    
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了ls程序,并成功执行了ls -a 命令

所以参数1传入的是所替换程序的路径,参数二传入的是命令行参数,注意:最后传入 NULL
值得注意的是,

  1. 如果execlp函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。
  2. 参数1可以只传入程序名,系统自动到PATH路径下寻找
  3. 参数1也可以传入完整的路径,此时execl与execlp功能一致

2.3execle

复制代码
int execle(const char *path, const char *arg,..., char * const envp[]);

execle 函数用于执行指定路径下的程序(参数1)

并将后续一系列独立的字符串作为命令行参数传递给该程序 (后续的可变参数)

最后将envp(环境变量表)传递给该程序 (最后一个参数)

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
         char* const env[] = {"MY=11111111111", "X=22222222222", NULL};                                                                                                                
        execle("/usr/bin/env", "env", NULL, env);
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了env程序,并成功执行了env命令

所以参数1传入的是所替换程序的路径,参数二传入的是命令行参数,注意:最后传入 NULL

参数三传入了自定义环境变量表env
值得注意的是,

  1. 如果execle函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。
  2. execle函数参数1与参数2的规则与execl一致
  3. 参数三意义不是传递全局变量environ(即父进程的环境变量表),因为子进程一旦被创建就会自动继承父进程的环境变量,进程替换时,如果不传入自定义环境变量表,替换后的子进程的环境变量表仍然与父进程保持一致,如果传入了自定义环境变量表,原环境变量表将被覆盖

2.4execv

复制代码
int execv(const char *path, char *const argv[]);

execv 函数用于执行指定路径下的程序(参数1)

并将用数组保存的命令行参数传递给该程序

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
         char* const _argv[] = {"ls", "-a", "-l", NULL};
         execv("/usr/bin/ls", _argv); 
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了ls程序,并成功执行了ls -a -l 命令

所以参数1传入的是所替换程序的路径,参数二传入的是数组名

注意:数组最后一个元素是NULL
值得注意的是,如果execv函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。
execv 的本质就是 "数组版的 execl"

2.5execvp

复制代码
int execvp(const char *file, char *const argv[]);

execvp 函数用于执行PATH路径下的程序(参数1)

并将用数组保存的命令行参数传递给该程序 (参数2)

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
         char* const _argv[] = {"ls", "-a", "-l", NULL};
         execvp("ls", _argv);     
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了ls程序,并成功执行了ls -a 命令

所以参数1传入的是所替换程序的路径,参数二传入的是数组名

注意:数组最后一个参数是 NULL
值得注意的是,

  1. 如果execvp函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。
  2. 参数1可以只传入程序名,系统自动到PATH路径下寻找
  3. 参数1也可以传入完整的路径,此时execvp与execv功能一致
  4. 参数2使用数组保存的命令行参数,注意最后一个参数为NULL
    execv 的本质就是 数组版的 execlp

2.6execve

复制代码
int execvpe(const char *file, char *const argv[],char *const envp[]);

execve 函数用于执行指定路径下的程序(参数1)

并将用数组保存的命令行参数传递给该程序 (参数2)

最后将envp(环境变量表)传递给该程序 (最后一个参数)

复制代码
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程执行ls -a -l命令    
        printf("子进程执行任务:\n");    
         char* const env[] = {"MY=11111111111", "X=22222222222", NULL};
         char* const _argv[] = {"env", NULL};
         execve("/usr/bin/env", _argv, env);        
        exit(errno);    
    }    
    else if(id > 0)    
    {    
        printf("父进程等待子进程:\n");                                                                                                                                                      
        int status = 0;    
        pid_t ret = waitpid(id, &status, 0);//阻塞等待    
        if(ret < 0)    
        {    
            printf("等待失败\n");    
            exit(errno);    
        }    
        else    
        {    
            printf("等待成功,子进程退出码:%d\n", WEXITSTATUS(status));    
        }    
    }    
    return 0;
}

如上图结果所示,子进程的代码和数据替换成了env程序,并成功执行了env命令

所以参数1传入的是所替换程序的路径,参数二传入的是命令行参数,注意:最后传入 NULL

参数三传入了自定义环境变量表env
值得注意的是,

  1. 如果execve函数成功执行,那么这意味着进程被完全替换,原代码不再执行;如果执行失败时才返回-1并继续执行原进程代码。
  2. execve函数参数1与参数2的规则与execv一致
  3. 参数三意义不是传递全局变量environ(即父进程的环境变量表),因为子进程一旦被创建就会自动继承父进程的环境变量,进程替换时,如果不传入自定义环境变量表,替换后的子进程的环境变量表仍然与父进程保持一致,如果传入了自定义环境变量表,原环境变量表将被覆盖

2.7总结

  • char* const p 的特点是 p 的指向无法改变,exec系列的部分函数中设计该类指针的数组,这就能够保证函数内部无法修改数组元素的指针指向
  • exec系列函数通过替换把可执行程序导入了内存,起到了代码级别的加载器效果
  • 不同语言形成的可执行程序最终会成为进程,所以一旦指明了路径与执行命令,exec系列函数就能替换并运行不同语言形成的可执行程序
  • 显然,exec系列函数中l、c不能同时存在
函数名 参数传递形式 程序文件查找方式 环境变量传递 核心特点与适用场景
execl 列表 (可变参数) 需提供完整路径 继承当前环境 最基础形式,需指定全路径。
execlp 列表 (可变参数) 可在PATH环境变量中搜索文件名 继承当前环境 只需提供文件名,方便调用系统命令。
execle 列表 (可变参数) 需提供完整路径 自定义环境变量数组 可完全控制新进程的环境。
execv 数组 (指针数组) 需提供完整路径 继承当前环境 适合命令行参数已构建在数组中的情况。
execvp 数组 (指针数组) 可在PATH环境变量中搜索文件名 继承当前环境 execv 的便捷版,无需完整路径。
execve 数组 (指针数组) 需提供完整路径 自定义环境变量数组 唯一的系统调用,功能最基础,其他函数均基于此实现。

函数解释

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

命令理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
相关推荐
乌蒙山连着山外山2 小时前
linux中查询多个匹配字段
java·linux·服务器
乘凉~2 小时前
在Ubuntu上部署并使用xianyu-auto-reply
linux·运维·ubuntu
学不完的路路路2 小时前
Makefile文件编写-Linux-Ubuntu系统
linux·运维·ubuntu
coding record2 小时前
linux 上安装Matlab,case: Matlab2020a,或者有效时间过期
linux·运维·服务器
冉佳驹2 小时前
Linux ——— sudo权限管理和GCC编译工具链的核心操作
linux·makefile·make·gcc·sudo·.phony
爱跑马的程序员2 小时前
Kernel i2c 设备驱动详细讲解
linux·安卓·内核驱动
love混世_魔王3 小时前
VIM经典命令系列之数字递增、递减
linux·编辑器·vim·verilog vim插件·vim使用技巧·vim高效编程
PAQQ3 小时前
ubuntu 22.04 更新cmake版本
linux·运维·ubuntu
陈苏同学3 小时前
[ 已解决 ] Cursor ssh 打开文件夹目录后,聊天框不能 chat 卡住
linux·服务器·ssh