目录
[Part1. exec介绍](#Part1. exec介绍)
[Part2. exec 系列函数分类](#Part2. exec 系列函数分类)
[Part3. execl](#Part3. execl)
[Part4. execlp](#Part4. execlp)
[Part5. execv](#Part5. execv)
[Part6. execvp](#Part6. execvp)
[Part7. execle](#Part7. execle)
[Part8. execve](#Part8. execve)
[Part9. exec相关补充](#Part9. exec相关补充)
[Part10. 结语](#Part10. 结语)
前言
Linux系统中,我们可以进行程序的替换,对于他的理解是非常重要的,接下来来跟随小编的视角来看看吧。
let's go!!!!!!!!
Part1. exec介绍
在 Linux 中,fork() 只能创建一个和父进程代码相同的子进程。如果想让子进程去运行另一个全新的程序,就需要使用 exec 系列函数。
exec 的作用不是创建新进程,而是:
把当前进程的代码段、数据段、堆、栈替换成新程序,让这个进程转而运行另一个程序。
所以:
• fork():创建新进程
• exec():替换进程运行的程序
Linux 中运行一个新程序,通常是:
fork() + exec()
先复制出一个子进程,再让子进程执行别的程序。
Part2. exec 系列函数分类
#include <unistd.h>
exec 不是一个单一函数,而是一组函数:
execl
execlp
execle
execv
execvp
execve
它们的区别可以按三个维度记忆:
维度 含义
l 以可变参数列表传递命令行参数
v 以字符串数组传递命令行参数
p 自动在 PATH 环境变量中查找程序
e 可以传入自定义环境变量
Part3. execl
int execl(const char* path, const char* arg, ...);
参数说明• path:程序的完整路径
• arg:第一个参数,通常是程序名本身
• ...:后续参数,构成命令行参数列表
• 最后必须以 NULL 结尾
示例
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
对应命令:
ls -l -a
注意点1. 第一个参数是完整路径:/usr/bin/ls
2. 第二个参数是 argv0,一般写程序名:"ls"
3. 参数依次往后传
4. 最后必须传 (char*)NULL
5. const char* 传 NULL 时建议强转:(char*)NULL
Part4. execlp
int execlp(const char* file, const char* arg, ...);
execlp 和 execl 的区别是:
函数 是否自动搜索 PATH
execl 否,必须写完整路径
execlp 是,可以直接写程序名
示例
execlp("ls", "ls", "-l", NULL);
系统会在环境变量 PATH 指定的目录中查找 ls,比如:
/usr/bin/ls
/bin/ls
所以 execlp("ls", ...) 能运行,而 execl("ls", ...) 通常会失败。
Part5. execv
int execv(const char* path, char* const argv\[\]);
execv 不使用可变参数列表,而是把参数放到一个字符串数组里。
示例
char* argv\[\] = {"ls", "-l", "-a", NULL};
execv("/usr/bin/ls", argv);
等价于:
ls -l -a
适合参数数量动态变化的场景。
Part6. execvp
int execvp(const char* file, char* const argv\[\]);
execvp = execv + p
也就是:
• 用数组传参
• 自动搜索 PATH
示例
char* argv\[\] = {"ls", "-l", NULL};
execvp("ls", argv);
Part7. execle
int execle(const char* path, const char* arg, ..., char* const envp\[\]);
execle 可以给新程序传入自定义环境变量。
示例
char* envp\[\] = {"HOME=/root", "PATH=/usr/bin", NULL};
execle("/usr/bin/ls", "ls", "-l", NULL, envp);
新程序运行时,就能拿到这些环境变量。
Part8. execve
int execve(const char* path, char* const argv\[\], char* const envp\[\]);
execve 是真正的系统调用,其他 exec 函数通常在底层调用 execve。
参数 说明
path 程序完整路径
argv 命令行参数数组
envp 环境变量数组
Part9. exec相关补充
如果 exec 成功,当前进程的代码已经被替换,原来的代码不会继续执行。
exec 成功,不返回。
失败时只有 exec 失败时,才会返回 -1,并设置 errno。
execl("/usr/bin/ls", "ls", "-l", NULL);
perror("execl failed");常见失败原因:
• 文件不存在
• 没有执行权限
• 路径错误
• 参数格式错误
• 内存不足
为什么最后要加 NULL
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);NULL 的作用是:
标记参数列表结束。因为 execl 是可变参数函数,编译器不知道参数有多少个,必须用 NULL 告诉它参数结束了。
exec 和 fork 的配合
cpppid_t pid = fork(); if (pid == 0) { // 子进程 execl("/usr/bin/ls", "ls", "-l", NULL); perror("execl"); exit(1); } else if (pid > 0) { // 父进程 wait(NULL); }执行流程:
1. fork() 创建子进程
2. 子进程调用 execl()
3. 子进程的代码被替换成 ls
4. ls 开始运行
5. 父进程 wait() 回收子进程
exec 前后进程身份不变
调用 exec 后,虽然程序换了,但:
• PID 不变
• PPID 不变
• 用户 ID 不变
• 进程组不变
• 大部分文件描述符继续有效
所以 exec 不是"创建新进程",而是"同一个进程运行新程序"。
exec 常用示例
示例 1:运行 ls
execl("/usr/bin/ls", "ls", "-l", NULL);示例 2:运行 pwd
execlp("pwd", "pwd", NULL);示例 3:运行 echo
execlp("echo", "echo", "hello", "world", NULL);示例 4:运行自定义程序
execl("./a.out", "a.out", NULL)
Part10. 结语
这篇文章我们认识并知道了Linux程序替换,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~
