
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 前情提示
-
- [1 本文内容大纲](#1 本文内容大纲)
- [4 ~> 进程程序替换](#4 ~> 进程程序替换)
-
- [4.1 对进程程序替换的朴素理解:先"见见猪跑",直接写代码](#4.1 对进程程序替换的朴素理解:先“见见猪跑”,直接写代码)
- [4.2 原理](#4.2 原理)
- [4.3 父子进程版本](#4.3 父子进程版本)
- [4.4 详细认识程序替换的函数](#4.4 详细认识程序替换的函数)
- [4.5 给进程传递新的环境变量](#4.5 给进程传递新的环境变量)
- [4.6 命名理解](#4.6 命名理解)
- [4.7 exec函数簇](#4.7 exec函数簇)
-
- [4.7.1 函数行为解释](#4.7.1 函数行为解释)
- [4.7.2 exec函数命名规则与参数形式](#4.7.2 exec函数命名规则与参数形式)
- [4.7.3 exec 函数族详解](#4.7.3 exec 函数族详解)
-
- [4.7.3.1 后缀字母含义对照表](#4.7.3.1 后缀字母含义对照表)
- [4.7.3.2 常见 exec 函数族对比](#4.7.3.2 常见 exec 函数族对比)
- [4.7.4 exec函数族图示](#4.7.4 exec函数族图示)
- 本文代码演示
- 结尾

前情提示
1 本文内容大纲

4 ~> 进程程序替换
fork()之后,父子各自执行父进程代码的一部分如果子进程就想执行一个全新的程序呢?进程的程序替换来完成这个功能!
程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中!
4.1 对进程程序替换的朴素理解:先"见见猪跑",直接写代码
我们直接写出代码来观察一下:
c
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 printf("我是父进程: pid: %d,ppid: %d\n",getpid(),getppid());
7 execl("/usr/bin/ls","ls","-a","-l",NULL);
8 printf("我正常退出了...\n");
9 return 0;
10 }
运行一下:
bash
[Alice@VM-4-17-centos 12_20_lesson21]$ ./My_myexec
我是父进程: pid: 4806,ppid: 28732
total 72
drwxrwxr-x 2 Alice Alice 4096 Dec 21 07:52 .
drwxrwxr-x 3 Alice Alice 4096 Dec 20 23:25 ..
-rw-rw-r-- 1 Alice Alice 363 Dec 21 07:21 Makefile
-rwxrwxr-x 1 Alice Alice 8960 Dec 21 07:21 myexec
-rw-rw-r-- 1 Alice Alice 5189 Dec 21 06:22 myexec.c
-rwxrwxr-x 1 Alice Alice 8568 Dec 21 06:31 My_myexec
-rw-rw-r-- 1 Alice Alice 222 Dec 21 09:11 My_myexec.c
-rwxrwxr-x 1 Alice Alice 9176 Dec 21 06:42 myproc
-rw-rw-r-- 1 Alice Alice 588 Dec 21 08:00 myproc.cc
-rw-rw-r-- 1 Alice Alice 93 Dec 20 23:30 shell.sh
-rw-rw-r-- 1 Alice Alice 42 Dec 20 23:28 text.py
[Alice@VM-4-17-centos 12_20_lesson21]$
思维导图如下所示------

朴素的理解:让进程执行一个全新的程序,我们叫做 进程替换。
前面我们介绍过fork的常规用法,现在我们知道:创建子进程,父子进程代码共享,数据写时拷贝~> 子进程没代码、没数据 ~>如果给子进程代码和数据呢?后面我们会知道,就是进程替换。像vs、gdb都是这样的原理,给代码和数据,替换进程。
4.2 原理
替换原理: 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

原理部分对应的思维导图如下所示------

运行结果如下:
bash
[Alice@VM-4-17-centos 12_20_lesson21]$ ./myexec
我是父进程: pid: 6919,ppid: 28732
我是子进程: pid: 6920,ppid: 6919
我是一个C++程序,我变成了一个进程:6920
wait child process success!
4.3 父子进程版本

4.4 详细认识程序替换的函数
其实有六种以exec开头的函数,统称exec函数:
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[]);
这部分的思维导图如下所示------

4.5 给进程传递新的环境变量
用户想给进程传递新的环境变量------

4.6 命名理解

4.7 exec函数簇
exec 函数族是一组用于 替换当前进程映像 的系统调用,通常用于在进程中加载并执行另一个程序。调用成功后,原进程的代码、数据、堆栈等会被新程序完全替换,并从新程序的入口点开始执行。
4.7.1 函数行为解释
-
调用成功时:当前进程的地址空间会被新程序覆盖,新程序从它的 main 函数(或启动代码)开始执行。由于原进程已被替换,exec 函数调用成功后不会返回原调用处。
-
调用失败时:如果由于某种原因(如程序路径错误、权限不足等)无法执行新程序,exec 函数会返回 -1,并设置相应的错误码(可通过 errno 查看)。
-
返回值特点:exec 函数只有失败时的返回值(-1),没有成功返回值。这意味着如果调用后函数返回了,那一定是出错了。
4.7.2 exec函数命名规则与参数形式
exec 函数族的命名具有一定的规律,通过后缀字母来区分参数传递方式和环境变量的处理方式:
-
选择适合的函数: 根据是否知道程序完整路径、是否需要自定义环境变量等因素选择合适的 exec 函数。
-
错误处理: 调用后应检查返回值,若为 -1 则使用
perror()或strerror(errno)输出错误信息。 -
通常与
fork()配合使用: 常见模式是先fork()创建子进程,再在子进程中调用 exec 执行新程序。
4.7.3 exec 函数族详解
4.7.3.1 后缀字母含义对照表
| 后缀字母 | 含义 | 说明 |
|---|---|---|
| l (list) | 参数以列表形式传递 | 例如 execl(),参数逐个列出,以 NULL 结尾 |
| v (vector) | 参数以数组形式传递 | 例如 execv(),参数通过字符串数组传递 |
| p (path) | 自动搜索 PATH 环境变量 | 只需提供程序名,系统会在 PATH 中查找可执行文件 |
| e (env) | 可自定义环境变量 | 允许传入一个环境变量数组,替换当前环境 |
4.7.3.2 常见 exec 函数族对比
| 函数原型 | 参数形式 | 是否搜索 PATH | 环境变量 |
|---|---|---|---|
execl() |
列表形式 | 否 | 继承当前环境 |
execv() |
数组形式 | 否 | 继承当前环境 |
execlp() |
列表形式 | 是 | 继承当前环境 |
execvp() |
数组形式 | 是 | 继承当前环境 |
execle() |
列表形式 | 否 | 自定义环境 |
execvpe() |
数组形式 | 是 | 自定义环境 |

exec调用举例如下:
c
#include <unistd.h>
int main()
{
char* const argv[] = {"ps", "-ef", NULL};
char* const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使⽤环境变量PATH,⽆需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要⾃⼰组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使⽤环境变量PATH,⽆需写全路径
execvp("ps", argv);
// 带e的,需要⾃⼰组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
4.7.4 exec函数族图示

本文代码演示
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("我是父进程: pid: %d,ppid: %d\n",getpid(),getppid());
pid_t id = fork();
if(id == 0)
{
printf("我是子进程: pid: %d,ppid: %d\n",getpid(),getppid());
sleep(1);
char *const argv[] = {
(char*)"top",
(char*)"-d",
(char*)"1",
(char*)"-n",
(char*)"3",
NULL
};
char *const env[] = {
"haha=hehe",
"HOME=/home",
"BASH=XXX",
"PATH=/usr/bin/",
NULL
};
execvp(argv[0],argv);
execlp("ls","ls","-a","-l","-n",NULL);
execv("/usr/bin/top",argv);
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL); //?????
//execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL); //?????
//execl("./myproc", "myproc", "-a", "-b", "-c", NULL); //我们没有传递环境变量!
//execle("./myproc", "myproc", "-a", "-b", "-c", NULL, env); //我们没有传递环境变量!
extern char **environ;
putenv((char*)"haha=hehe");
putenv((char*)"class=118");
execle("./myproc", "myproc", "-a", "-b", "-c", NULL, environ); //我们没有传递环境变量!
//execl("/usr/bin/bash", "bash", "shell.sh", NULL); //?????
//execl("/usr/bin/python3", "python3", "test.py", NULL); //?????
exit(1);
}
// father
pid_t rid = waitpid(id,NULL,0);
if(rid > 0)
{
printf("wait child process success!\n");
}
return 0;
}
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
【Linux进程控制(一)】进程创建是呼吸,进程终止是死亡,进程等待是重生:进程控制三部曲
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
