☰
Linux 进程替换 与 exec
2026/10/17 6:52:39 网站建设 项目流程

1. 引言

Linux 系统编程中,进程、文件与 Shell 三者紧密交织。很多初学者在学完fork之后,会对exec系列函数、环境变量传递、文件打开机制产生一连串疑问:exec到底会不会创建新进程?环境变量什么时候传给子进程?打开文件时内容真的全部读进内存吗?

本文基于课件笔记整理,用大白话 + 考点的方式,帮你把「Shell 与进程」「文件核心原理」「进程替换 exec」「exec 六大函数」这四块内容一次讲透,并附上考试高频考点与代码示例。

Shell、环境变量 argv env

  1. bash/shell 本身就是一个进程!

    你敲指令,shell 的做法:fork创建子进程,子进程exec去执行你输入的命令。

  2. 进程的两个数组:

  • char *argv[]:命令行参数数组(程序名、传入参数)
  • char *env[]:环境变量数组,每个进程都自带一份环境变量表
  1. export myval=111111

    export 作用:把 shell 里的局部变量,放进 env 环境变量表。

    fork 创建子进程的时候,子进程会继承父 shell 的环境变量。

  2. shell 环境变量从哪里来?

    用户登录系统,启动 shell 进程,shell 自动读取家目录下的.bash_profile,里面写好的export命令,自动加载环境变量。
    .bash_profile:家目录里的一个配置文本文件,开头带点代表隐藏文件,登录自动读取

  3. putenv():C 库函数,在程序内部新增 / 修改当前进程的环境变量。

文件的核心原理(大白话 + 考点)

共识原理

  1. 文件 = 内容 + 属性

    内容:里面存的数据(txt 文字、代码)

    属性:文件名、大小、权限、创建时间、位置等信息。

  2. 文件分成两大类:已经打开的文件、存磁盘没打开的文件

  3. ✅打开的文件:谁打开?进程

一句话:打开文件这件事,一定是某个进程做的。这一块本质就是进程和文件的关系。

进程和打开的文件:1 个进程可以打开 n 个文件(1:n)

文件一旦被打开,要加载到内存,磁盘上的文件本体不动,内存建立内核对象来管理这个打开的文件。

内核里面怎么管理打开的文件:

内核会为每一个被打开的文件创建一个内核结构体(文件打开对象),里面保存这个打开文件的各种属性,用链表串起来管理。

c

运行

struct file { // 文件的各种属性 struct file *next; // 链表指针,把所有打开文件串起来 };
  1. 没打开的文件:安静躺在**磁盘(硬盘)**上。

    重点:磁盘上文件数量巨大,核心需求是快速增删查改,快速找到文件,这就是目录 / 文件系统干的活。

磁盘上整个文件不会一次性全部读到内存!只是一部分信息先进内存

1. 打开文件(open)的时候

当进程调用 open 打开磁盘文件:

  1. 内核在内存创建一个struct file(打开文件对象),记录:文件偏移、权限、状态等

  2. 加载文件的元数据(属性):大小、权限、位置

    ✅这些是先进内存的

    ❌文件里面全部内容,不会一次性读到内存

类比:你打开一本书,只是先拿到这本书的封面、目录信息,不会一次性把整本书全部复印到桌上。

2. 什么时候才把文件内容读到内存?

等到你调用read()读文件的时候:

操作系统才会把需要用到的那一小块数据,从磁盘拷贝到内存缓冲区。

也就是用到哪块,加载哪块(惰性加载)。

3. 区分两个概念(考试重点)

  • 磁盘上的文件:永久存在硬盘,关掉电脑还在
  • 打开文件的内核对象(struct file):在内存,进程 close 或者进程退出,这个对象就销毁消失

4. 容易混淆的点

课件原话:文件一旦被打开,必须先被加载到内存

课件这句话的真实含义:

代表这个文件已经打开的管理信息(struct file)加载进内存

不是说整个文件全部数据全部搬进内存。

6.2 进程替换

核心一句话(最重要考点)

exec 程序替换,不会创建新进程!只把当前进程的代码、数据全部换成磁盘上新程序的内容,PID 全程不变。


1. 关键问题逐个讲

问题①:程序替换有没有创建新的进程?

❌没有新建进程

  • PID(进程编号)不变,task_struct这个进程基本描述结构体保留不变
  • 只有mm_struct(虚拟地址空间管理结构)里面的旧代码、堆、栈全部清空,加载磁盘上新程序(ELF 可执行文件)

类比:房间不变(房间号 PID 不变),把房间里面旧家具全部清空,搬进一套全新家具。

问题②:环境变量什么时候给进程?exec 的时候会换掉环境变量吗?

课件原话:创建子进程的时候,子进程就继承环境变量。exec 替换不会覆盖环境变量。

流程:

  1. 父进程 fork → 子进程诞生,直接复制父进程的环境变量

  2. 子进程调用 exec 做程序替换

  3. 替换代码、栈、堆,但是环境变量保留不变

    ✅ 新程序拿到的,还是 fork 继承来的那套环境变量

只有你手动调用带 env 参数的 execve,才可以主动改成新环境变量。

问题③:exec 函数返回值规则(必考)

exec成功的时候,没有返回值!

只有调用失败的时候,才会返回 - 1,继续执行 exec 后面的代码。

大白话:

exec 一旦替换成功,原来程序剩下的代码直接丢掉,再也跑不到。

只有找不到程序、权限不足等失败场景,才会回来继续跑后面代码。

问题④:CPU 怎么找到新程序入口地址?

磁盘上 Linux 可执行程序是ELF 格式

ELF 文件头部里面写好了程序入口地址。

exec(加载器)读取 ELF 头,拿到入口地址,让 CPU 从这个地址开始跑新程序代码。


2. 整体流程(结合 fork+exec,就是 shell 干的事)

  1. shell(父进程)调用 fork,创建子进程

  2. 子进程和父进程一模一样,PID 不同,继承环境变量

  3. 子进程调用 exec,程序替换

    • 子进程 PID 不变,但是代码换成 ls/top 等磁盘上的程序
    • 环境变量保留
  4. 子进程运行新程序,结束后父进程 wait 回收

3. 课件里的代码片段

c

运行

printf("before: I am a process, pid: %d, ppid:%d\n", getpid(), getppid()); // 程序替换,运行ls命令 execl("/usr/bin/ls", "ls", "-a", "-l", NULL); // 如果execl成功,下面这行永远不会执行!

4. 50 行自定义 shell(课件最后目标)

自定义 shell 核心逻辑:

循环:

  1. 读取用户输入命令

  2. fork 创建子进程

  3. 子进程 exec 执行用户输入的程序

  4. 父进程 wait 等待子进程执行结束

    循环等待下一条命令。

考点背诵精简版

  1. exec不创建新进程,PID 不变,task_struct 不变,替换虚拟地址空间里面代码数据
  2. fork 时继承环境变量;exec 默认保留原有环境变量,不会自动替换
  3. exec 成功:无返回,后续代码不执行;失败返回 - 1,继续执行后面代码
  4. Linux 可执行文件 ELF,文件头部保存程序入口地址,exec 加载器读取入口。

exec 六大函数课件

先记住口诀:l、v、p、e

  • l:list 列表传参,一个个写参数,最后必须补NULL
  • v:vector 数组传参,把全部参数放进一个字符串数组
  • p:path,会自动去PATH环境变量里面搜索程序,不用写完整路径
  • e:environment,自己手动传入新的环境变量数组,替换默认环境变量

6 个 exec 函数:

  1. execl:l 列表传参,需要写完整路径,沿用旧环境变量
  2. execlp:l 列表传参,带 p,自动搜 PATH,不用写全路径
  3. execle:l 列表传参,带 e,手动给新环境变量
  4. execv:v 数组传参,需要写完整路径,沿用旧环境变量
  5. execvp:v 数组传参,带 p,自动搜 PATH,不用写全路径
  6. execve:v 数组传参,带 e,手动传入环境变量(系统调用,其余 5 个都是库函数,底层都调用 execve)

逐个看图里的例子

① execl(l:列表传参)

c

运行

execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
  • 第一个参数:程序完整路径/usr/bin/ls,告诉系统去哪里找这个程序
  • 后面一串:命令参数,按顺序写,最后必须以 NULL 结尾
  • 参数含义:ls -a -l,和终端敲命令一模一样
  • 没有 p:必须写全路径;没有 e:继承原来进程的环境变量

② execv(v:数组传参)

c

运行

char *const myargv[] = {"ls", "-l", "-a", NULL}; execv("/usr/bin/ls", myargv);
  • 把所有命令参数放到字符串数组myargv,数组末尾放 NULL
  • 适合:参数数量不确定、动态构造参数的场景
  • 同样,需要写完整程序路径,沿用旧环境变量

③ execlp(带 p,PATH 搜索)

c

运行

execlp("ls", "ls", "-a", "-l", NULL);

✅ 好处:不用写 /usr/bin/ls 完整路径!

它会自动读取PATH环境变量,挨个去 PATH 里面的目录找 ls 程序。

就像你在 shell 直接敲 ls,不用写全路径。


核心考点

  1. 只要 exec 替换成功,后面代码一律不执行,只有失败才返回 - 1 继续跑下面代码
  2. 参数数组argv,最后一个元素必须是 NULL,标记参数结束,漏写会报错
  3. extern char **environ;:全局环境变量,exec 不带 e 的函数,都会默认使用这个环境变量
  4. 只有带e的 execle /execve,才可以自己传入全新环境变量,覆盖原来的
extern char **environ;

术语解释

extern:声明变量,告诉编译器这个变量已经在别的地方定义好了,直接拿来用。

environ是 Linux 系统自带的全局环境变量表。

类型char**:字符串指针数组,存放一堆"key=value"格式的环境变量字符串

核心重点:带e的 exec(execle/execve)

字母e= environment,可以自己传给新程序一套全新的环境变量

不带 e:沿用原来进程的全局environ环境表

带 e:直接用你写的环境数组,覆盖旧环境,不是追加,是整体换掉!

1. 课件里的例子

c

运行

// 自己造一套全新环境变量数组,最后必须NULL结尾 char *const myenv[] = { "MYVAL=1111", "MYPATH=/usr/bin/XXX", NULL }; // execle 末尾把myenv传进去 execle("./otherExe", "otherExe", "-a", "-w", "-v", NULL, myenv);
大白话:

运行otherExe这个程序的时候,它拿到的环境变量只有 MYVAL、MYPATH 这两个。

原来进程里的 PATH、HOME 全部消失,被你写的myenv整套替换。

⚠️考点:是覆盖,不是添加!不是在旧环境基础上加这两个变量,直接换成全新一套。

两种给子进程环境变量的方式对比:

  1. fork 继承 + putenv:父进程先 putenv 增加环境变量,fork 子进程,子进程拿到【旧环境 + 新增变量】(追加)
  2. execle/execve 传自定义 env 数组:exec 替换程序,直接给一套全新环境,全部覆盖

2. 6 个 exec 函数分类(课件下方 man 手册截图)

c

运行

// 库函数(底层全部调用execve系统调用) 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 execvpe(const char *file, char *const argv[], char *const envp[]); // 真正的系统调用! int execve(const char *filename, char *const argv[], char *const envp[]);

一句话:execve 是内核原生系统调用,剩下 6个都是 C 库封装出来的函数,底层最终都会调用 execve

字母口诀复习

  • l:list,参数一个个罗列,可变参数,末尾 NULL
  • v:vector,参数打包成字符串数组
  • p:PATH,自动去 PATH 环境变量搜索程序,不用写完整路径
  • e:environment,手动传入自定义环境变量数组

3. 思考题:exec 能不能运行我们自己写的程序?

✅可以!不只是能跑ls、top这种系统命令。

只要是编译好的 ELF 可执行文件,不管是系统自带,还是你自己写代码编译出来的./otherExe,exec 都可以加载运行。

完整流程演示

  1. 你写一段 C 代码,编译生成可执行文件otherExe
  2. 在主程序里面调用execle("./otherExe", "otherExe", "-a", NULL, myenv);
  3. 执行后,当前进程代码直接替换成otherExe,并且otherExe只会看到你给的myenv这组环境变量

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询