Liunux进程(七):程序控制篇(中)

前面我们学习了 Linux 进程的创建、终止与等待,学会使用 fork 创建子进程,并用 wait/waitpid 回收僵尸进程。但 fork 出来的子进程默认和父进程运行同一份代码,很多场景下我们希望子进程去执行一个完全不同的外部程序,比如在代码里执行 ls、gcc 等命令。进程替换(exec 系列函数)就是用来解决这个需求的核心机制。

目录

一、进程替换概念

[二、exec 系列函数](#二、exec 系列函数)

总览

命名规律

execl

execlp

[execv /execvp(参数放数组)](#execv /execvp(参数放数组))

[execle /execve(自定义环境变量 e)](#execle /execve(自定义环境变量 e))

[三、exec 原理](#三、exec 原理)

四、什么时候用进程替换?

五、总结


一、进程替换概念

我们先用 fork() 创建子进程:

fork 之后,子进程复制父进程的地址空间(代码段、数据段、堆、栈等) ,父子进程跑一样的程序,只是 pid 不同。

很多场景我们不想要子进程继续执行父进程的代码,而是想让子进程去跑一个全新的可执行 程序 (比如在代码里调用 ls、gcc、python)。

进程替换(exec) : 在不创建新进程、不改变 PID 的前提下,把当前进程的代码段、数据段、堆、栈全部清空,加载一个新的可执行文件到这个进程中,从新程序的入口开始执行。

结论:

PID 不变!exec 不是创建新进程,只是 "换程序";fork 才是创建新进程。

exec 调用成功后,后面的代码不会执行,直接被新程序覆盖。

exec 失败才会返回 -1,继续执行原来代码。

文件描述符默认继承(默认不会关闭)。

fork + exec fork 创建子进程 → 子进程调用 exec 替换成别的程序,父进程等待子进程退出(wait/waitpid)。


二、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,参数放到字符串数组 argv

p:path,会去环境变量 PATH 里搜索程序(不用写绝对路径)

e:env,自己传入环境变量数组,不继承父进程环境变量

表格

函数 路径查找 参数形式 环境变量
execl 必须绝对路径 列表 继承
execlp PATH 搜索 列表 继承
execle 绝对路径 列表 自定义
execv 绝对路径 数组 继承
execvp PATH 搜索 数组 继承
execve 绝对路径 数组 自定义

execve 是系统调用,剩下 5 个都是库函数,底层封装 execve。

参数规则:argv[0] 是程序名(惯例,可随便写,一般和程序同名),真正参数从 argv 1 开始,数组最后必须以 NULL 结尾。

execl

execl:需要写程序完整路径,参数一个个传,NULL 收尾

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        return 1;
    }
    if(pid == 0)
    {
        // 子进程:替换为 ls 程序
        // /bin/ls 程序路径, argv0:ls, argv1:-l, argv2:-a, NULL结束
        execl("/bin/ls", "ls", "-l", "-a", NULL);

        // 如果execl成功,这行永远跑不到!只有失败才执行
        perror("execl failed");
    }
    else
    {
        // 父进程等待子进程结束
        wait(NULL);
        printf("子进程执行完毕,父进程继续\n");
    }
    return 0;
}

子进程 PID 不变,地址空间被 ls 程序覆盖,执行 ls -l -a。 如果路径写错,execl 返回 - 1,触发 perror 打印错误。

结果如下

execlp

execlp 不需要写绝对路径,会去 PATH 里面找命令,写命令名即可

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(pid == 0)
    {
        // 直接写ls,不用/bin/ls
        execlp("ls", "ls", "-l", NULL);
        perror("execlp");
    }
    else
    {
        wait(NULL);
    }
    return 0;
}

结果如下

execv /execvp(参数放数组)

适合参数数量动态变化的场景,把参数放到 char * 数组

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(pid == 0)
    {
        char* argv[] = {"ls", "-l", "-h", NULL};
        // execv 需要绝对路径
        // execv("/bin/ls", argv);

        // execvp 自动搜PATH
        execvp("ls", argv);
        perror("execvp");
    }
    else
    {
        wait(NULL);
    }
    return 0;
}

结果如下

execle /execve(自定义环境变量 e)

execle /execve 可以给新程序指定一套全新环境变量,不再继承父进程的

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    pid_t pid = fork();
    if(pid == 0)
    {
        char* env[] = {"MYNAME=process_replace", "AGE=20", NULL};
        // execle:列表参数 + 自定义环境变量
        execle("/bin/env", "env", NULL, env);
        perror("execle");
    }
    else
    {
        wait(NULL);
    }
    return 0;
}

结果如下


三、exec 原理

  1. 用户调用 exec 系列函数,陷入内核,调用 execve 系统调用
  2. 内核校验可执行文件(ELF)
  3. 释放当前进程原有地址空间(代码段、数据、堆)
  4. 把新 ELF 文件加载到进程虚拟地址空间,建立页表映射
  5. 初始化栈、参数 argv、环境变量 env
  6. 修改 pc 寄存器,跳转到新程序入口点执行

四、什么时候用进程替换?

  1. shell 命令行:bash 就是父进程,输入命令 → fork 子进程 → exec 替换成对应程序
  2. 服务器程序:主进程负责监听,fork 子进程调用别的工具程序
  3. 程序内部调用外部脚本、编译器、第三方可执行文件

五、总结

进程替换不会创建新进程,进程 PID 保持不变,只是清空当前进程地址空间、加载新的 ELF 可执行程序。exec 一共 6 个函数,依靠后缀 l/v/p/e 区分传参方式、路径查找规则和环境变量继承方式,其中 execve 是真正的系统调用,其余都是封装库函数。

exec 成功之后后续代码不会执行,只有调用失败才会返回继续执行原有代码,一般配合 fork 使用:fork 创建子进程,子进程调用 exec 替换程序,父进程使用 wait/waitpid 等待回收。exec 在 Shell 解释器、服务程序调用外部工具等场景广泛使用。

结合上一部分内容,fork(创建进程) + exec(替换程序) + wait/waitpid(资源回收)共同构成 Linux 多进程开发最基础、最重要的一套组合。

相关推荐
分布式存储与RustFS26 分钟前
工作流脚本和依赖 JAR 散在各节点本地盘:用 DolphinScheduler 资源中心接到 RustFS
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
俊俊几年的28 分钟前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
星恒随风9 小时前
MySQL 从零入门(五):MySQL 内置函数详解——日期、字符串、数学函数与实战案例
linux·数据库·笔记·sql·mysql
唐墨12314 小时前
linux kernel 比较数据大小的宏定义
linux·算法
程序开发阿元14 小时前
密码不进工具:一个独立开发者的服务器凭据管理办法
linux·ssh
承渊政道16 小时前
Fooocus本地AI绘图教程:Windows部署、低门槛生图与远程访问
人工智能·windows·cpolar·fooocus·生图
分布式存储与RustFS17 小时前
群晖 NAS 被 C3RB3R 锁了:本地备份一起被加密,RustFS 对象锁为什么删不掉
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
真好啊又活了一天17 小时前
服务器和虚机配置IPV6
运维·服务器·github
zixu_star17 小时前
定时任务是什么?从日常自动化到 AI 应用,软件如何自动执行任务?
运维·人工智能·自动化