1、程序替换现象
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
printf("I am a process\n");
// 执行ls -a -l 程序,替换当前进程映像
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
// execl调用成功后下面两行永远不会执行
printf("代码运行中...\n");
printf("代码运行中...\n");
return 0;
}

调用程序替换函数后,进程没有执行下面的printf输出,而是去执行了ls命令,所以可以看出,程序替换函数execl可以用其他进程替换当前的进程
2、替换原理
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的pid为改变

替换的完整流程:
- 一个进程拥有独立的PCB、虚拟地址空间
- 调用exec函数后,操作系统从磁盘读取目标可执行程序
- 直接覆盖当前进程的虚拟地址空间里所有原代码、数据、堆栈内容
- 页面重新映射到新程序对应的物理内存
- CPU跳转到新程序的入口函数开始运行
注意 :整个过程不创建新进程,替换前后getpid() 获取的进程ID完全相同,exec调用成功后,原程序后续代码全被覆盖
exec函数返回值:
- 调用成功:无返回值,原代码直接消失;
- 调用失败:返回 - 1,回到原程序继续向下执行
3、exec函数
一共有六种以exec开头的函数:
cpp
#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[]);
六个函数命名字母有固定含义:
| 字母 | 含义 |
|---|---|
| l | list,参数以可变参数列表传递,末尾必须填NULL |
| v | vector,参数存字符串数组,数组最后一个元素为NULL |
| p | path,自动读取环境变量PATH,只需传程序名不用写完整路径 |
| e | environ,自定义环境变量,手动传入环境变量数组 |
3.1 execl
cpp
int execl(const char *path, const char *arg, ...);
- path:目标程序完整绝对路径(如
/usr/bin/ls) - arg...:可变参数列表,命令行参数依次填写,最后必须以
NULL结尾 - 示例:
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
3.2 execlp
cpp
int execlp(const char *file, const char *arg, ...);
- 带p:自动搜索PATH环境变量,第一个参数直接写程序名即可
- 传参方式和execl一致,可变参数 + NULL收尾
- 示例:
execlp("ls", "ls", "-l", NULL);
3.3 execle
cpp
int execle(const char *path, const char *arg, ..., char *const envp[]);
- l:列表传参,e:自定义环境变量
- path需要完整绝对路径
3.4 execv
cpp
int execv(const char *path, char *const argv[]);
- v:数组传参,所有命令参数提前单独放到字符串数组中
- path:依旧需要完整绝对路径
示例:
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
printf("I am a process\n");
pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("下面的代码都是子进程\n");
// 命令行参数单独写入字符串数组中
char *argv[] = {
(char *)"ls",
(char *)"-l",
(char *)"--color=auto",
NULL
};
execv("/usr/bin/ls", argv);
// execv执行失败才会执行下面,打印错误信息
perror("execv 执行失败");
exit(1);
}
// 等待子进程结束
wait(NULL);
printf("代码运行中...\n");
printf("代码运行中...\n");
return 0;
}

3.5 execvp
cpp
int execvp(const char *file, char *const argv[]);
- v:数组传参,p:自动匹配PATH
- file:只需要传程序名
- argv:参数封装数组
示例:
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
printf("I am a process\n");
pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("下面的代码都是子进程\n");
char *argv[] = {
(char *)"ls",
(char *)"-l",
(char *)"--color=auto",
NULL
};
execvp(argv[0], argv);
// execvp执行成功不会走到这里,失败才执行exit
exit(1);
}
// 父进程逻辑
wait(NULL); // 阻塞等待子进程执行完毕
printf("代码运行中...\n");
printf("代码运行中...\n");
return 0;
}
- argv0:命令名,也就是"ls"
3.6 execvpe
cpp
int execvpe(const char *file, char *const argv[], char *const envp[]);
- 集齐v+p+e,数组传参、自动PATH、自定义环境变量
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
printf("I am a process\n");
pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("下面的代码都是子进程\n");
// 命令参数数组
char *argv[] = {
(char *)"othercmd",
(char *)"-a",
(char *)"-b",
NULL
};
// 自定义环境变量数组,以NULL结尾
char *envp[] = {
(char*)"PATH=/home/me/lesson6/cmd",
NULL
};
// 使用 execvpe 替换程序,第一个参数写完整绝对路径
execvpe("/home/me/lesson6/cmd/othercmd", argv, envp);
// 执行失败才会走到这里
perror("execvpe failed");
exit(1);
}
// 父进程等待子进程结束
wait(NULL);
printf("代码运行中...\n");
printf("代码运行中...\n");
return 0;
}
cpp
// othercmd.cpp
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
int main(int argc, char* argv[], char* env[])
{
printf("I am a process\n");
for (int i = 0; argv[i]; i++)
{
cout << i << ":" << argv[i] << endl;
}
cout << "环境变量" << endl;
for (int i = 0; env[i]; i++)
{
cout << i << ":" << env[i] << endl;
}
return 0;
}

系统中的环境变量被修改成了我们自定义的环境变量,还可以用这种方式运行.cpp文件,达到了.c文件和.cpp文件混合使用的效果