
一 预热
1 进程虚拟地址空间
这是操作系统给每个进程营造的 "独立内存假象",从低地址 到高地址大致分为以下区域:
- 代码段(.text)
- 内容 :编译后的机器指令(如
main函数、系统调用)。 - 属性 :只读、可执行。
- 特点 :多个进程可共享(如运行多个
ls命令共用同一份代码),防止程序意外修改自身逻辑。
- 内容 :编译后的机器指令(如
- 常量区(.rodata)
- 内容 :字符串常量(如
"hello world")、const全局常量。 - 属性 :只读。
- 关键点 :图中明确指出
char *str = "hello world"中的字符串存于此,不可直接修改,否则会触发段错误。
- 内容 :字符串常量(如
- 数据段(.data)与 BSS 段
- .data 段 :存放已初始化 的全局变量、静态变量(
static)。 - .bss 段 :存放未初始化的全局变量、静态变量。程序加载时由系统自动清零。
- 共同点:生命周期与程序共存,位于高地址下方,堆和栈之间。
- .data 段 :存放已初始化 的全局变量、静态变量(
- 堆(Heap)
- 方向 :向上增长(地址升高)。
- 管理 :由程序员手动分配 / 释放(
malloc/free),用于动态内存。
- 栈(Stack)
- 方向 :向下增长(地址降低)。
- 管理:编译器自动管理,存放局部变量、函数参数、返回地址。
- 特点:空间有限(通常 8MB),溢出会导致段错误。
二、 关键机制:fork 与 写时复制(COW)
解析Linux 下 fork() 系统调用的底层优化逻辑:
- 传统误区
- 误以为
fork()会立刻完整复制父进程的所有物理内存(如代码、数据、堆、栈)。 - 弊端 :耗时、浪费内存(子进程常随后执行
exec替换镜像,复制的内容无效)。
- 误以为
- 真实实现:写时复制(Copy-On-Write, COW)
- fork 瞬间 :不复制任何物理内存 。
- 内核只创建子进程的 PCB(进程控制块)和页表。
- 父子进程共享同一份物理内存页面。
- 内核将这些共享页面的权限标记为只读。
- 读操作:父子进程任意读取共享页面,数据一致,无开销。
- 写操作(触发 COW) :
- 当任一进程(父 / 子)尝试写入共享页面时,CPU 触发写保护异常(Page Fault)。
- 内核介入:分配新的物理页,复制原页内容,修改页表指向新页,并恢复可写权限。
- 结果:此时父子进程拥有独立内存,数据不再互通,实现了 "按需复制"。
- fork 瞬间 :不复制任何物理内存 。
三、 关键细节
- "不是内存,是进程地址空间 / 虚拟地址空间!"
- 这是核心重点:程序看到的地址是虚拟地址,需经过页表映射才能找到物理内存。每个进程都有独立的 4GB(32 位)或更大虚拟地址空间,互不干扰。
- 常量特性
- 常量区(字符串)具有常性,只要进程地址空间存在,它就一直存在于内存中,且不可修改。
- 堆栈相对而生
- 堆向上涨,栈向下涨,两者之间是空闲空间。栈溢出可能会覆盖堆数据(堆栈碰撞),导致程序崩溃。
四、 总结
阐述了 Linux 内存管理的两大基石:
- 分层布局:清晰的虚拟地址空间划分(代码 / 数据 / 堆 / 栈)保证了内存访问的有序性和安全性。
- 惰性复制 :COW 机制极大提升了
fork的效率,是现代操作系统优化进程创建的核心手段。
二 正式进入虚拟地址空间
一、 核心实验现象:进程独立性的铁证

1. 地址与数值的矛盾
- 现象 :父子进程中,全局变量
g_val的地址 (0x601054)完全相同,但数值不同(父进程 100,子进程 101/103/105)。 - 核心结论 :
- 打印出的地址 不是物理地址 ,而是 虚拟地址。
- 物理地址对用户完全不可见,由操作系统通过页表隐藏。
- 这证明了 虚拟地址空间是进程独有的,每个进程都有独立的 4GB 虚拟地址蓝图。
二、 内存管理架构:虚拟 vs 物理
1. 虚拟地址空间(用户视角)
- 32 位布局 :
- 高地址 1G:内核空间(系统共享,用户态无权访问)。
- 低地址 3G :用户空间(进程私有),包含:
- 代码段:只读,共享。
- 数据段 / BSS:全局 / 静态变量。
- 堆 :向上增长,
malloc申请区域。 - 共享区:动态库、内存映射文件。
- 栈:向下增长,局部变量 / 函数调用。
2. 物理内存(内核视角)
- 物理内存是稀缺资源,被所有进程共享。
- 映射机制 :进程看到的虚拟地址必须通过 页表 转换才能找到物理内存。
- 图中关键点 :
g_val在虚拟地址中是0x601054,在物理内存中对应0x12345678。
三、 核心机制:fork 与 写时复制(COW)

这是图中最核心的技术点,涵盖了从创建到隔离的全过程:
1. 进程创建的流程(OS 自动完成)
Linux 创建进程的流程是:先创建内核数据结构(PCB / 页表),再按需加载代码和数据。
- 支持延迟加载 :如果不立即执行,可以先创建好进程控制块(
task_struct),等真正要运行时再加载代码和数据。
2. 父子进程的 "共享与拷贝"
- fork 瞬间 :
- 代码共享:父子进程执行同一段代码(只读)。
- 数据共享 :内核不立即复制 物理内存,而是让父子进程共享 同一份物理页,并将页表权限标记为 只读。
- 写时复制(触发条件) :
- 当父子进程中任意一个尝试修改 共享数据(如子进程改
g_val)时,CPU 触发 缺页中断(Page Fault)。 - 内核介入:分配新的物理页,复制原页内容,修改页表指向新页,恢复可写权限。
- 结果:父子进程从此拥有独立内存,数据不再互通,实现了 "进程独立性"。
- 当父子进程中任意一个尝试修改 共享数据(如子进程改
3. 图中的关键异常:缺页中断引起内存二次申请
- 当进程访问的虚拟地址不在物理内存中(如写时复制触发),会触发缺页中断。
- 内核会进行 二次申请:分配新的物理页,建立新的映射。这就是 "深拷贝" 的发生时刻。
四、 动态内存分配:malloc 的本质
- 图中标注 :
malloc(1024); new XXX(); - 本质 :
malloc并没有立刻分配物理内存,而是在 进程虚拟地址空间 中申请了一段连续的 虚拟地址。 - 延迟分配:真正的物理内存分配,发生在第一次访问该地址(触发缺页中断)的时候。
五、 内核数据结构与进程控制块(PCB)
展示进程的内核态管理:
struct task_struct:- 也就是 PCB(进程控制块)。
- 包含进程的所有元数据(PID、状态、优先级、文件描述符表、信号处理函数等)。
struct mm_struct:- 管理进程的虚拟地址空间。
- 图中下方的结构体字段:
total_vm、locked_vm、start_code、end_code、start_brk、start_stack等,定义了地址空间的起止范围和属性。
- 父子进程的关系 :
- 子进程创建时,以父进程为模板。
- 子进程会拷贝父进程的
task_struct,但会独属于自己的虚拟地址空间和页表。
六、 核心结论与 OS 规定
- 进程独立性的保证 :
- 虚拟地址空间和页表,每个进程自有一套。
- 代码只读(父子不影响),数据写时复制(修改后不影响)。
- OS 规定 :
- 父子进程共享只读代码,互不影响。
- 若对共享数据进行修改,则发生 "写时复制",变为深拷贝,进程彻底独立。
七、 总结
用户态代码 到 内核态内存管理 的完整链路:
- 程序视角 :看到的是虚拟地址(如
0x601054)。 - 内核视角:通过页表映射到物理内存。
- fork 机制:利用写时复制(COW)优化了进程创建开销。
- 最终结果:实现了进程间的完美隔离(独立性)。
三 理解虚拟地址空间和区域划分
一、 核心概念:什么是虚拟地址空间?
1. 核心比喻:OS = 大富翁,内存 = 大饼
- 物理内存的局限性:物理内存大小有限(如 10 亿字节),无法直接给每个进程完整的 "大饼"。
- 虚拟地址空间的本质 :
- OS 给每个进程画了一张独立的 "大饼蓝图"(虚拟地址空间)。
- 进程认为自己独占了所有内存(如 "私生子" 各自独立),实际上 OS 只按需分配真正的物理内存("饼")。
struct mm_struct的作用 :- 它是进程的内存管理对象,描述了该进程的虚拟地址空间全貌。
- 管理逻辑:先描述(建立虚拟地址空间蓝图),再组织(通过页表映射物理内存)。
- 按需分配:OS 不会一次性给足,而是进程要用多少,就批多少虚拟地址,再动态映射物理内存。
2. 进程独立性的比喻
- "私生子" 比喻:每个进程都认为自己是 OS 的独子,拥有完整的地址空间。
- 实际上 OS 只是为每个进程提供了独立的 虚拟地址蓝图,物理内存则由 OS 统一调度、共享。
二、 区域划分:如何理解虚拟地址空间中的区域划分?
1. 核心比喻:分桌子
- 问题:多人共用一张大桌子(物理内存),容易混乱。
- 解决方案:OS 将虚拟地址空间(桌子)划分为不同区域(代码区、数据区、堆、栈等),每个区域有专属的起止地址。
2. 区域划分的代码实现逻辑
- 类比
struct deskop(桌面区域结构体) :- 定义区域的起始和结束地址(如
int xs_start, int xs_end)。 - 例如
struct deskop d = {1, 50, 50, 100}表示一个区域。
- 定义区域的起始和结束地址(如
- 计算机语言的实际应用 :
- 用
struct描述虚拟地址区域,记录区域的权限 (可读 / 可写 / 可执行)和范围。 - 区域内的地址是连续且有效的,进程可以直接使用,无需关心物理内存的具体位置。
- 用
3. 区域划分的目的
- 隔离与保护:不同区域(如代码区 vs 数据区)权限不同,防止越界访问或修改。
- 高效管理 :按区域管理内存,方便进行缺页中断 、权限检查等操作。
三、 关键机制:页表与权限管理
1. 页表的核心作用
- 它是 虚拟地址 → 物理地址 的翻译官。
- 图中展示了 页表项 的结构:
- 虚拟地址 :
0x601054 - 映射的物理地址 :
0x11223344 - 标志位:包含权限(读 / 写 / 执行)、是否存在、是否被修改等。
- 虚拟地址 :
2. 权限管理:为什么代码和常量是只读的?
- 问题 :为什么全局变量、
static变量生命周期是全局的?为什么字符串和代码是只读的? - 答案 :由页表标志位 强制限制。
- 代码段(.text) :页表标记为 只读(Read-Only) + 可执行(Execute)。
- 常量区(.rodata) :页表标记为 只读。
- 效果 :如果进程尝试修改代码或常量,CPU 会触发 段错误(Segmentation Fault),因为页表禁止了写操作。
3. 生命周期与区域的关系
- 全局 / 静态变量 :存放在 数据段 /.bss 区,生命周期与程序一致,因此是 "全局的"。
- 字符串常量 / 代码 :存放在 只读区,生命周期与进程一致,且禁止修改。
四、 硬件支撑:MMU(内存管理单元)
1. MMU 的角色
- 硬件 :集成在 CPU 内部,负责实时完成虚拟地址到物理地址的转换。
- 核心功能 :
- 检查页表中的权限标志位。
- 拦截非法操作(如向只读区域写数据)。
- 处理缺页中断(当物理内存缺失时,触发 OS 介入)。
2. 实战案例:修改字符串的崩溃过程
- 代码 :
char *s = "helloworld"; *s = 'H'; - 过程 :
"helloworld"存放在 只读区 ,页表标记为 只读。- 执行
*s = 'H'时,CPU 通过 MMU 检查权限。 - 发现是写操作 ,但目标区域是只读。
- MMU 触发 权限异常 ,操作系统终止进程,输出 段错误。
五、 总结:内存管理的三层架构
这张图串联起了从 用户逻辑 到 硬件实现 的完整链路:
- 用户层 :通过
struct mm_struct描述虚拟地址空间,按区域划分使用。 - 内核层:通过页表建立虚拟地址与物理内存的映射关系,管理权限。
- 硬件层 :CPU 内的 MMU 负责实时地址转换和权限检查,保障内存安全。
💡 核心启示
这张图的精髓在于 "虚拟化" 与 "隔离":
- 虚拟化:OS 创造了 "独占内存" 的假象(虚拟地址空间)。
- 隔离:通过区域划分和页表权限,严格保护进程间、进程与内核间的内存安全。
四 虚拟地址空间设置的意义
一、 开篇核心:为什么需要虚拟地址空间?
图中左上角明确给出了 3 个根本原因,是理解所有后续内容的前提:
- **保护物理内存(安全层面)**虚拟地址空间 + 页表机制,会拦截进程的非法访问(如越界、修改只读代码 / 常量),从根源上避免进程直接操作物理内存,防止恶意 / 误操作破坏系统。
- 无序变有序(内存布局层面) 物理内存是碎片化的(图中右侧物理内存的零散块),但虚拟地址空间给进程呈现了连续、完整的 4G(32 位)地址蓝图,让程序无需关心物理内存的实际排布,实现了 "逻辑连续、物理离散"。
- **解耦进程管理与内存管理(架构层面)**进程管理只需要维护虚拟地址空间的布局,内存管理只需要维护物理内存的分配,两者通过页表解耦,大幅提升了系统的可扩展性和管理效率。
关键细节
- 虚拟连续 vs 物理不连续:虚拟地址空间的区域是连续的,但对应到物理内存是零散的,通过页表完成映射。
- 堆与栈的相对生长:堆向上、栈向下,中间是空闲虚拟地址空间,避免了相互覆盖。
二、 内核数据结构:虚拟地址空间的 "管理者"

Linux 内核中管理虚拟地址空间的三层数据结构,是理解内存管理的核心:
1. task_struct(进程控制块 PCB)
- 是进程的 "身份证",包含进程所有元数据(PID、状态、文件描述符、信号等)。
- 其中的
struct mm_struct *mm指针,指向该进程的虚拟地址空间管理结构体,是进程与内存的连接点。
2. mm_struct(虚拟地址空间总控)
- 是整个进程虚拟地址空间的总描述符
- 核心作用:
- 记录虚拟地址空间各区域的起止地址 (如
start_code/end_code对应代码段)。 - 维护
vm_area_struct链表 / 红黑树,管理所有虚拟区域。 - 关联页表,完成虚拟地址到物理地址的映射。
- 记录虚拟地址空间各区域的起止地址 (如
3. vm_area_struct(虚拟区域结构体)
- 是虚拟地址空间中每个独立区域的描述符,对应图中右侧的代码段、数据段、堆、栈等彩色块。
- 每个
vm_area_struct描述一个连续的虚拟地址范围,包含:- 区域的起始 / 结束地址、权限(读 / 写 / 执行)。
- 前后指针
vm_prev/vm_next,形成链表,用于遍历所有区域。 - 关联的
mm_struct,归属到对应进程。
- 核心特性:
- 虚拟地址空间的宏观划分,由这些结构体串联而成。
mmap系统调用会创建新的vm_area_struct,用于文件映射、共享内存等。
三、 虚拟地址 → 物理地址的映射
图中右侧完整展示了映射逻辑:
- 虚拟地址空间 :进程看到的连续 4G 地址,由
mm_struct和vm_area_struct管理。 - 页表:是虚拟地址到物理地址的 "翻译表",每个进程有独立的页表,存储在内核空间。
- 物理内存:实际的硬件内存,是零散的物理页框,由内核统一分配管理。
- 映射流程 :
- 进程访问虚拟地址 → CPU 通过 MMU(内存管理单元)查页表 → 找到对应物理地址 → 访问物理内存。
- 若页表中无对应映射,触发缺页中断 ,内核动态分配物理页并更新页表(
malloc的延迟分配原理)。
四、 补充关键知识点
-
malloc的本质malloc(100)只是在虚拟地址空间的堆区 申请了一段连续的虚拟地址(如0x10000000),不会立即分配物理内存,真正的物理内存分配发生在第一次访问该地址(触发缺页中断)时。 -
区域权限的意义 每个
vm_area_struct都有独立的权限标志:- 代码段:只读 + 可执行,防止程序修改自身代码。
- 常量区:只读,防止修改字符串常量(如
char *s = "hello"; *s = 'H'会触发段错误)。 - 数据段 / 堆 / 栈:可读写,用于存储变量。
-
进程独立性的保障 每个进程有独立的
mm_struct、vm_area_struct链表和页表,因此即使虚拟地址相同,也会映射到完全不同的物理内存,实现了进程间的内存隔离。
五、 总结
这张图完整打通了 Linux 内存管理的全链路:
- 设计层面:虚拟地址空间解决了物理内存的安全、布局、管理三大问题。
- 布局层面:清晰的区域划分,规范了程序的内存使用。
- 内核层面 :
task_struct→mm_struct→vm_area_struct的三层结构,实现了虚拟地址空间的精细化管理。 - 映射层面:页表 + MMU 完成虚拟地址到物理地址的翻译,保障了进程独立性和内存安全。