目录
1、先看代码 && 现象
2、解释原理
3、进程替换的作用
4、使用所有的替换方法,并且认识函数参数的含义
1、先看代码 && 现象
代码:
#include <stdio.h>
#include <unistd.h>int main()
{printf("testexec ... begin!\n");execl("/usr/bin/ls","ls","-l","-a",NULL);printf("testexec ... end!\n");return 0;
}
现象:
2、解释原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec系列的函数并不创建新进程,所以调用exec前后该进程的id并未改变。
本质:就是将当前程序的代码和数据进行替换。
补充:站在被替换的进程的角度:本质就是这个程序被加载到了内存中了!
补充一个问题:这个过程是怎么加载的呢?
答:OS直接调用 exec* 系列的函数,这类函数就是类似于一种 Linux 上的加载函数。
补充:exec* 系列的函数,执行完毕之后,后面的代码就都被替换了。
注:exec 系列的函数的返回值是可以不关心的,因为如果替换成功,则不会执行原本后面的代码,如果替换失败,就会执行原本后面的代码,所以只需要看原本后面的代码有没有被执行,就能判断是否替换成功了。
3、进程替换的作用
- 让子进程执行父进程代码的一部分
- 让子进程去执行一个全新的程序(用 exec* 系列的函数) -- 注:此时不仅是数据需要写时拷贝,连代码都需要进行写时拷贝。
进程替换:
4、使用所有的替换方法,并且认识函数参数的含义
替换函数
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
拿 execl 做例子,来详解一下:
path:所要运行的程序的路径(找到它)。
后面的参数的传参方式:在命令行中,是怎么执行这些命令的,那就怎么传参。
ex:
execl("/usr/bin/ls","ls","-a","l",NULL); -- 注:语法要求必须以 NULL 结尾。
总结:第一个参数是在问你“你想执行谁?”,后面的参数是在问你“你想怎么执行它?”
int execlp(const char *file, const char *arg, ...);
补充:const char *file:用户可以不传要执行的文件的路径(但是文件名要传),就是直接告诉 exec* ,我要执行谁就行了。
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]); -- 注:只有这个是真正的系统调用,其他 5 个都是用C语言封装了 execve 后的库函数。
进程替换函数:
补充一个问题:有一个系统调用 execve 就够用了,为什么还要有其他 5 个库函数?
答:为了支持不同的应用场景。
补充:函数解释
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1。
- 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
- l(list) : 表示参数的传参方式采用列表。
- v(vector) : 表示参数的传参方式采用数组。
- p(path) : 表示系统会自动在环境变量 PATH 中查找这个你要执行的程序。
- e(env) : 表示整体替换所有的环境变量。-- 注:1、可以用全新的环境变量(自定义)给子进程 2、用老的环境变量给子进程(比如environ) 3、将老的环境变量修改一部分再给子进程
补充:putenv ,该函数可用于往当前环境变量中添加新的环境变量(具体用法看 man 手册)。
注:程序替换也可以替换成我们自己写的程序。