# 写在前面
在 `Windows` 平台下,我们可以通过双击运行可执行程序,让这个可执行程序成为一个进程;
在 `Linux` 平台,我们可以通过 `./` 运行,让一个可执行程序成为一个进程。
但是,如果我们本来就运行着一个程序(进程),我们如何在这个进程内部启动一个外部程序,由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 `exec 函数族`实现。
[exec 函数族](https://baike.baidu.com/item/exec%E5%87%BD%E6%95%B0%E6%97%8F/3489348?fr=aladdin&fromtitle=EXEC&fromid=9077756),顾名思义,就是一簇函数,在 `Linux` 中,并不存在 `exec()` 函数,`exec` 指的是一组函数,一共有 6 个
```c
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
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[]);
```
其中只有 `execve()` 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。`exec` 函数族提供了六种在进程中启动另一个程序的方法。`exec` 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种`exec`函数以执行一个外部程序。当进程调用一种`exec`函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始(main函数)执行。调用 `exec` 函数被并不会创建新进程,所以调用 `exec`族函数前后该进程的`pid` 不变
将当前进程的 `.text`,`.data`等替换为所要加载的程序的`.text`,`.data`等,然后让进程从新的`.text`第一条执行开始执行。

本文主要介绍`execl` 与 `execlp` 两个函数,通过这两个函数基本可以实现所有`exec`族函数的功能
# execl
作用:自己指定待执行程序路径
函数原型:int execl(const char *path, const char *arg, ...);
参数:
- path:待执行的外部程序的路径
- arg:要执行的外部程序所需要的参数
- 在参数末尾要添加一个哨兵NULL,代表参数结束
返回值:
exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回,而且,exec 函数族下面的代码执行不到。只有调用失败了,它们才会返回 -1,失败后从原程序的调用点接着往下执行。
## 案例
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
} else if(pid == 0) {
printf("This is child, will print hello world\n");
//execl("a.out",NULL);错误写法
execl("a.out", "a.out",NULL); //通过execl调用a.out,输出hello world
printf("execl exit\n"); //如果execl函数正常退出则不会执行下面的内容
perror("execl");
exit(0);
} else {
wait(NULL);
printf("This is parent\n");
}
return 0;
}
```
程序执行结果如图,事实正如分析。

# execlp
函数原型:int execlp(const char *file, const char *arg, ...);
借助 `PATH` 环境变量找寻待执行程序
## 案例
创建子进程,让其执行`ls -lh`命令
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
} else if(pid == 0) {
printf("This is child, will print ls-lh\n");
execlp("ls", "ls","-l","-h",NULL);
printf("execl exit\n");
perror("execl");
exit(0);
} else {
wait(NULL);
printf("This is parent\n");
}
return 0;
}
```

execv() 和 execl() 的用法基本是一样的,无非将列表传参,改为用指针数组.
```c
#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
} else if(pid == 0) {
printf("This is child, will print hello world\n");
//execl("a.out", "a.out",NULL);
char *arg[]={"ls","ls", "-a", "-l", "-h", NULL};
execv(arg)
printf("execl exit\n"); //如果execl函数正常退出则不会执行下面的内容
perror("execl");
exit(0);
} else {
wait(NULL);
printf("This is parent\n");
}
return 0;
}
```
一张总结图

进程替换:exec 函数族