【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

📌 专栏系列

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

相关推荐
有谁看见我的剑了?2 小时前
Linux ssh连接超时时间学习
linux·学习·ssh
Ronin3052 小时前
【Linux网络】多路转接poll
linux·网络·io·多路转接·poll
allk552 小时前
Android 性能优化深水区:电量与网络架构演进
android·网络·性能优化
旧梦吟2 小时前
脚本网页 linux内核源码讲解
linux·前端·stm32·算法·html5
wanhengidc4 小时前
物理服务器与云服务器的不同之处
运维·服务器·网络·游戏
ZFJ_张福杰4 小时前
【技术深度】金融 / 钱包级 Android 安全性架构(毒APP)
android·安全·金融·架构·签名证书
kaoa0004 小时前
Linux入门攻坚——58、varnish入门
linux·运维·服务器
安当加密4 小时前
通过ASP认证系统作为 RADIUS 认证服务器:解决异地办公登录安全问题的实践方案*
运维·服务器·安全
gaize12134 小时前
服务器的用途:多元化应用与无限可能
运维·服务器