【Linux】进程控制(三):进程程序替换机制与替换函数详解

引言

当我们使用fork()创建子进程时,发现了一个有趣的场景------子进程仿佛是父进程的"克隆体",执行着完全相同的代码。就像影分身术,所有分身都在做同样的动作。

  • 但想象一下这样的需求:一个厨师分身去做编程,另一个分身去画画。如果每个分身只能做和本体一样的事情,那多进程的价值就大打折扣了!
  • 这就是我们面临的核心问题:fork()给了我们"复制"的能力,却没有赋予"改变"的自由。子进程被束缚在父进程的代码逻辑中,如何让它摆脱这种束缚,去执行一个全新的程序呢?

这时,我们需要一个"变身术"------进程程序替换!这个神奇的能力让一个进程可以"脱胎换骨",在保持外壳(进程ID、环境等)不变的情况下,完全替换内部执行的程序。



目录

一、替换原理

fork创建子进程后,子进程最初执行的是和父进程相同的程序(但可能执行不同的代码分支)。子进程通常要调用一种exec函数来执行另一个完全不同的程序。

当进程调用exec函数时:

  1. 用户空间完全替换:原进程的用户空间代码和数据被完全清除
  2. 内存管理重构:操作系统释放原进程占用的所有物理内存页
  3. 新程序加载:从磁盘读取新程序的代码段、数据段等
  4. 映射关系重建 :在页表中建立虚拟地址→新物理页的映射

(注意:虚拟地址范围不变,但指向的物理页内容全新)

  1. 进程身份不变:PCB、PID、文件描述符表等保持不变
  2. 全新开始执行:从新程序的启动例程(如main函数)开始执行

整个过程不创建新进程 ,只是在现有进程框架内完成"程序内容"的彻底替换。

思考:子进程程序替换后,会影响父进程的代码和数据吗?

  • 子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此子进程进行程序替换后不会影响父进程的代码和数据。

二、替换函数

其实替换函数有六种exec开头的,我们统称为exec函数,所以让我们先来通过表格整体了解一下这6个函数。

函数名 核心特点 路径/文件名要求 参数传递方式
execl 基础 exec,列表传参 需传入程序绝对/相对路径(如 /bin/ls 可变参数列表(以 NULL 结尾)
execlp 带 PATH 搜索的 execl 可直接传程序名(如 ls 可变参数列表(以 NULL 结尾)
execle 可自定义环境变量的 execl 需传入程序绝对/相对路径 可变参数列表(NULL 后接 envp)
execv 数组传参版 execl 需传入程序绝对/相对路径 argv[] 数组(以 NULL 结尾)
execvp 带 PATH 搜索的 execv 可直接传程序名 argv[] 数组(以 NULL 结尾)
execvpe PATH 搜索 + 自定义环境(GNU 扩展) 可直接传程序名,自动从 PATH 查找 argv[] + envp[](均以 NULL 结尾)

接下来让我们一个一个来了解这些替换函数!


2.1 execl

  • 第一个参数是执行程序的路径
  • 第二个参数是可变参数列表
  • 以NULL结尾
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	printf("交换前执行, pid: %d, ppid: %d\n", getpid(), getppid());
	execl("/usr/bin/ls", "ls", "-a", "-i","-l", NULL);
	printf("交换后执行, pid: %d, ppid: %d\n", getpid(), getppid());
	                                                                            
	return 0;
}

2.2 execlp

  • 第一个参数是要执行程序的名字
  • 第二个参数是可变参数列表,表示你要如何执行这个程序
  • 以NULL结尾。
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
    printf("父进程 pid: %d\n", getpid());

    pid_t id = fork();
    if (id == 0)  // 子进程
    {
        printf("子进程(pid:%d)执行ls命令前\n", getpid());
        // execlp执行ls -a -l,成功则后续代码不执行
        execlp("ls", "ls", "-a", "-l", NULL);
        
        // 仅execlp失败时执行以下代码
        perror("execlp执行失败");
        exit(1);
    }

    // 父进程:等待子进程并解析状态
    int status;
    wait(&status);  // 阻塞等待子进程退出

    if (WIFEXITED(status))
    {
        int code = WEXITSTATUS(status);
        printf("\n子进程(pid:%d)正常退出,退出码:%d\n", id, code);
    }
    else
    {
        printf("子进程异常退出\n");
    }

    return 0;
}

2.3 execle

  • 第一个参数是要执行程序的路径
  • 第二个参数是可变参数列表,表示你要如何执行这个程序
  • 并以NULL结尾
  • 第三个参数是你自己设置的环境变量。
cpp 复制代码
char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

2.4 execv

  • 第一个参数是要执行程序的路径
  • 第二个参数是一个指针数组
  • 数组当中的内容表示你要如何执行这个程序,数组以NULL结尾
cpp 复制代码
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);

2.5 execvp

  • 第一个参数是要执行程序的名字
  • 第二个参数是一个指针数组
  • 数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。
cpp 复制代码
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

2.6 execvpe

  • 第一个参数是要执行程序的名字
  • 第二个参数是一个指针数组
  • 数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。
  • 第四个参数是自己维护的环境变量
cpp 复制代码
char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2025", NULL };
execve("./mycmd", myargv, myenvp);

函数解释

这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。

如果调用出错,则返回-1。

也就是说,exec系列函数只要返回了,就意味着调用失败。


三、execve

事实上,只有execve才是真正的系统调用,其它五个函数最终都是调用的execve,所以execve在man手册的第2节,而其它五个函数在man手册的第3节,也就是说其他五个函数实际上是对系统调用execve进行了封装,以满足不同用户的不同调用场景的。

下图为exec系列函数族之间的关系:


总结


✨ 坚持用 清晰易懂的图解 + 代码语言, 让每个知识点都 简单直观 !

🚀 个人主页不呆头 · CSDN

🌱 代码仓库不呆头 · Gitee

📌 专栏系列

💬 座右铭 : "不患无位,患所以立。"

相关推荐
Forsete几秒前
LINUX驱动开发#9——定时器
linux·驱动开发·单片机
JY.yuyu6 分钟前
Docker常用命令——数据卷管理 / 端口映射 / 容器互联
运维·docker·容器
独自破碎E19 分钟前
【BISHI15】小红的夹吃棋
android·java·开发语言
森G21 分钟前
七、04ledc-sdk--------makefile有变化
linux·c语言·arm开发·c++·ubuntu
驱动探索者26 分钟前
linux mailbox 学习
linux·学习·算法
lpruoyu1 小时前
【Docker进阶-06】docker-compose & docker swarm
运维·docker·容器
China_Yanhy1 小时前
入职 Web3 运维日记 · 第 8 日:黑暗森林 —— 对抗 MEV 机器人的“三明治攻击”
运维·机器人·web3
艾莉丝努力练剑1 小时前
hixl vs NCCL:昇腾生态通信库的独特优势分析
运维·c++·人工智能·cann
酉鬼女又兒2 小时前
每天一个Linux命令_printf
linux·运维·服务器
虾说羊2 小时前
docker容器化部署项目流程
运维·docker·容器