
主存系统管理详解:从分区分段到虚拟内存
本文系统介绍操作系统主存管理的核心概念、早期分区与分段管理、现代分页与虚拟内存机制、页表结构、TLB、页面置换算法等内容,是计算机系统 III 软件部分的重要课程内容。
[迁移说明] 本文最初发布于
blog.zzw4257.cn,现已迁移并在本站进行结构化整理与增强。
主存系统管理详解
1. 系统基础与核心名词
1.1 操作系统与主存管理的关系
操作系统(OS)是连接用户/应用与硬件的中介,负责资源管理与调度。主存(内存)管理是OS最核心的功能之一,目标是:
- 高效分配/回收内存资源
- 保护各进程空间安全隔离
- 支持多进程/多线程并发
- 提供虚拟内存、共享内存等抽象
没有良好的主存管理,无法实现多道程序设计、分时系统、进程隔离等现代操作系统特性。
1.2 核心名词解释
操作系统的主存管理离不开一系列基础概念的理解,这些名词不仅是后续所有机制的基础,也是理解系统设计取舍的关键。
**进程(Process)是操作系统资源分配和调度的基本单位。每个进程拥有独立的虚拟地址空间、代码、数据、堆和栈等资源。进程的所有运行状态、调度信息、内存映射等都被记录在进程控制块(PCB, Process Control Block)**中。PCB是内核管理进程的核心数据结构。
**线程(Thread)**是进程内的基本执行单元。一个进程可以包含一个或多个线程,线程之间共享进程的地址空间和资源,但每个线程有独立的程序计数器、寄存器和栈。多线程模型提升了程序的并发性和资源利用率,但也带来了同步与安全的挑战。
**系统调用(System Call)**是用户程序请求操作系统服务的唯一合法途径。通过系统调用,用户进程可以访问文件、分配内存、创建进程等。系统调用通常伴随用户态与内核态的切换,是操作系统安全与隔离的基础。
**上下文切换(Context Switch)**是操作系统在多任务环境下切换CPU执行对象(进程或线程)的过程。切换时,操作系统需要保存当前执行对象的全部状态(如寄存器、程序计数器、内存映射等),并恢复下一个对象的状态。上下文切换是实现多道程序设计和分时系统的关键,但频繁切换会带来性能开销。
进程间通信(IPC, Inter-Process Communication)是不同进程之间交换数据和同步协作的机制。常见的IPC方式包括管道、消息队列、信号、套接字和共享内存(Shared Memory)。其中,共享内存允许多个进程映射同一块物理内存区域,数据交换高效,但需要额外的同步机制保证一致性。
**信号量(Semaphore)和互斥锁(Mutex)**是并发程序中常用的同步与互斥工具。信号量是一种整型计数器,用于控制对共享资源的访问数量,既可实现互斥,也可实现同步。互斥锁则专注于保护临界区,确保同一时刻只有一个线程进入。
在并发环境下,多个进程或线程竞争资源时,若每个都持有部分资源并等待其他资源释放,可能导致死锁(Deadlock)。死锁是系统设计和实现中必须警惕的问题,通常需要通过资源分配策略、检测与恢复机制来避免或解决。
这些基础名词相互关联,共同构成了操作系统主存管理的理论与实践基础。理解它们的内涵和联系,是深入学习主存管理机制的前提。
1.3 多道程序设计与分时系统
操作系统为了提升资源利用率和用户体验,引入了多道程序设计(Multiprogramming)和分时系统(Timesharing/Multitasking)的思想。
多道程序设计允许多个进程同时驻留在内存中,CPU在等待某个进程I/O时,可以切换到其他进程继续执行,从而避免CPU空闲。分时系统则进一步通过频繁的进程切换,让每个用户都感觉自己独占系统资源,实现了“同时多用户交互”的效果。
实现多道程序设计和分时系统,必须依赖主存管理的隔离与保护机制。为此,操作系统采用了双模式操作(Dual-mode operation),即用户模式和内核模式。特权指令只能在内核模式下执行,用户进程通过系统调用进入内核模式,确保系统安全。
1.4 内存管理的基本需求
主存管理的目标不仅仅是分配和回收内存,更要满足以下核心需求:
- 安全性:防止进程间非法访问,保护操作系统和其他进程的数据不被破坏。
- 隔离性:每个进程拥有独立的虚拟地址空间,互不干扰。
- 共享性:支持进程间安全高效地共享内存区域(如共享库、共享内存IPC)。
- 高效性:最大化内存利用率,减少碎片,提升系统整体性能。
这些需求推动了主存管理机制从简单的分区、分段,发展到现代的分页与虚拟内存体系。
1.5 内存单位与常用换算
💡 常用内存单位与典型换算
- 1KB = = 1024B
- 1MB = = 1024KB
- 1GB = = 1024MB
- 4KB = B(常见页/帧大小)
- 32位虚拟地址空间:B = 4GB
- 64位虚拟地址空间:B(实际实现远小于理论上限)
📝 页大小与偏移位数
- 4KB页:页内偏移12位()
- 2MB页:页内偏移21位()
ℹ️ 典型架构页表分级
- x86-64:4级页表(PGD/P4D/PUD/PMD/PTE),页大小4KB
- RISC-V Sv39:3级页表,虚拟地址39位,页大小4KB,偏移12位,VPN分三段(9+9+9)
2. 早期主存管理:分区与分段
2.1 分区(Partition)管理
早期操作系统采用分区管理方式对主存进行分配。分区管理的核心思想是将物理内存划分为若干区域,每个区域分配给一个进程。
2.1.1 固定分区(Fixed Partition)
- 内存在系统启动时被划分为若干固定大小的分区,每个分区只能容纳一个进程。
- 优点:实现简单,分区表静态维护。
- 缺点:存在严重的内部碎片(分区大于进程实际需求时,剩余空间无法利用),分区数量限制了多道程序度。
2.1.2 可变分区(Variable Partition)
- 内存不预先划分,进程到来时按需分配一块连续空间。
- 优点:减少内部碎片,提高内存利用率。
- 缺点:随着进程的创建和释放,内存中会出现许多不连续的空闲块(Holes),导致外部碎片。
- 典型分配算法:
- 首次适应(First-fit):从头开始找到第一个足够大的空闲块。
- 最佳适应(Best-fit):找到最小但足够大的空闲块。
- 最差适应(Worst-fit):找到最大的空闲块。
- 外部碎片可通过**紧缩(Compaction)**合并,但代价高昂。
2.2 分段(Segmentation)
分段管理是一种更贴近程序逻辑结构的内存分配方式。
- 程序的逻辑地址空间被划分为若干有独立意义的段(如代码段、数据段、堆、栈等)。
- 每个段大小可变,独立分配。
- 逻辑地址格式:
<段号, 段内偏移>。 - 操作系统为每个进程维护一个段表(Segment Table),每项记录段的物理基址(Base)和长度(Limit)。
- 地址转换时,MMU根据段号查段表,基址+偏移得到物理地址,并检查越界。
- 分段方便了模块化、共享和保护,但仍有外部碎片。
ℹ️ 分区与分段的关系
- 分段可视为可变分区的逻辑升级,强调程序结构和保护。
3. 分页与虚拟内存
3.1 分页(Paging)基本原理
分页机制的引入,彻底改变了主存管理的方式。分页将物理内存划分为固定大小的块(帧,Frame),将进程的逻辑地址空间划分为同样大小的块(页,Page)。每个进程的页面可以分散地加载到物理内存的任意空闲帧中,无需连续。
- 页(Page):进程虚拟地址空间的固定大小块,常见大小为4KB。
- 帧(Frame):物理内存的固定大小块,与页大小一致。
- 地址结构:逻辑地址被分为页号(Page Number)和页内偏移(Offset)。如4KB页,偏移为12位。
- 地址转换:CPU生成逻辑地址,MMU通过页表将页号映射到物理帧号,拼接偏移得到物理地址。
- 碎片问题:分页消除了外部碎片,但可能产生内部碎片(最后一页未被完全利用)。
📝 分页地址转换示例
- 32位虚拟地址,4KB页:20位页号 + 12位偏移
- 64位虚拟地址,4KB页:52位页号 + 12位偏移(实际实现远小于理论上限)
3.2 页表(Page Table)机制
页表是分页系统的核心数据结构,用于记录虚拟页号到物理帧号的映射。
- 页表项内容:
- 物理帧号
- 有效位(Valid Bit):页面是否在内存中
- 读/写/执行权限位
- 脏页位(Dirty Bit):页面是否被修改
- 访问位(Accessed/Referenced Bit):页面是否被访问过
- 单级页表:结构简单,但大进程下空间消耗大。
- 多级页表:分层索引,节省空间。典型如x86-64的四级页表、RISC-V Sv39的三级页表。
- 页表过大问题:如32位地址空间、4KB页,一个进程需4MB页表。多级页表、哈希页表、倒排页表等结构应运而生。
ℹ️ 典型多级页表结构
- x86-64: 4级(PGD→P4D→PUD→PMD→PTE)
- RISC-V Sv39: 3级(VPN[2]→VPN[1]→VPN[0]→PTE)
3.3 TLB(Translation Lookaside Buffer)
TLB是硬件实现的高速缓存,用于存放最近使用的页表项,极大加速地址转换。
- TLB命中:直接获得物理帧号,无需访问内存页表。
- TLB未命中:需访问内存中的页表,查到后将结果载入TLB。
- 上下文切换与ASID:进程切换时需刷新TLB,或用ASID(地址空间标识符)区分不同进程的页表项。
- TLB命中率直接影响有效访问时间(EAT)。
3.4 虚拟内存与请求分页
虚拟内存允许进程的地址空间大于实际物理内存,按需将页面调入内存。
- 请求分页(Demand Paging):程序启动时只加载部分页面,访问缺页时才从磁盘调入。
- 缺页异常(Page Fault):MMU检测到页面不在内存时触发,操作系统负责分配物理帧、调入页面、更新页表。
- 虚拟内存的意义:提升多道程序度,支持大进程运行,简化编程模型。
4. 页表结构与典型架构
4.1 多级页表结构详解
随着虚拟地址空间的扩大,单级页表的空间消耗和连续性要求变得不可接受。多级页表(Hierarchical Page Table)通过分层索引,有效节省了内存空间,并支持稀疏分布的虚拟地址。
- 分级思想:将页表本身也分页,每一级页表只在需要时分配。
- 优点:
- 显著减少页表占用的物理内存
- 支持大虚拟空间和稀疏分布
- 便于操作系统管理和扩展
- 缺点:
- 地址转换过程需多次内存访问(每级一次),但TLB可极大缓解
4.2 x86-64 四级页表结构
x86-64架构采用四级页表,支持48位虚拟地址空间(理论64位,实际48位)。
| 层级 | 名称 | 位宽 | 数量 | 作用 |
|---|---|---|---|---|
| 1 | PGD | 9 | 512 | 页全局目录 |
| 2 | PUD | 9 | 512 | 上级目录 |
| 3 | PMD | 9 | 512 | 中级目录 |
| 4 | PTE | 9 | 512 | 页表项 |
| 偏移 | 12 | 4096 | 页内偏移 |
- 虚拟地址结构:
[PGD 9][PUD 9][PMD 9][PTE 9][Offset 12],共48位。 - 每级页表512项,每项8字节。
- 支持4KB、2MB、1GB等多种页大小(大页/巨页)。
📝 x86-64虚拟地址分解
- 0x0000_7fff_ffff_ffff
- PGD: 47-39, PUD: 38-30, PMD: 29-21, PTE: 20-12, Offset: 11-0
4.3 RISC-V Sv39 三级页表结构
RISC-V Sv39模式采用三级页表,支持39位虚拟地址空间。
| 字段 | 位宽 | 说明 |
|---|---|---|
| VPN[2] | 9 | 一级页表索引 |
| VPN[1] | 9 | 二级页表索引 |
| VPN[0] | 9 | 三级页表索引 |
| Offset | 12 | 页内偏移 |
- 虚拟地址结构:
[VPN2 9][VPN1 9][VPN0 9][Offset 12],共39位。 - 每级页表512项,每项8字节。
- 支持4KB、2MB、1GB页。
ℹ️ Sv39页表遍历流程
- 取VPN[2]索引根页表,找到下一级页表物理地址
- 用VPN[1]索引二级页表,找到下一级页表物理地址
- 用VPN[0]索引三级页表,得到最终PTE
- 拼接物理帧号与Offset,得到物理地址
4.4 其他页表结构简述
- 哈希页表(Hashed Page Table):用哈希函数定位页表项,适合超大虚拟空间。
- 倒排页表(Inverted Page Table):每个物理帧对应一项,记录其属于哪个进程的哪一页,节省空间但查找慢。
5. 请求分页与页面置换
5.1 请求分页(Demand Paging)机制
请求分页是现代虚拟内存系统的核心机制。它允许进程的虚拟地址空间远大于物理内存,只有实际访问到的页面才会被加载到内存。
- 按需加载:程序启动时只加载必要的页面,其他页面在首次访问时触发缺页异常(Page Fault),由操作系统负责调入。
- 缺页异常处理流程:
- 进程访问未在内存中的页面,MMU检测到无效页表项,触发缺页异常。
- 操作系统检查访问是否合法。
- 若合法,分配空闲物理帧,将页面从磁盘(或后备存储)读入内存。
- 更新页表,恢复进程执行。
- 优点:极大提升了多道程序度和内存利用率,支持大进程和更多进程并发。
- 风险:频繁缺页会导致性能下降,甚至系统颠簸(Thrashing)。
5.2 页面置换算法(Page Replacement Algorithms)
当物理内存已满且发生缺页时,操作系统必须选择一个页面置换出去,为新页面腾出空间。页面置换算法的优劣直接影响系统性能。
- FIFO(先进先出):最早进入内存的页面最先被置换,简单但可能出现Belady异常。
- Optimal(最优算法):理论上置换未来最长时间不会被访问的页面,实际不可实现,仅作性能上界参考。
- LRU(最近最少使用):置换最近最久未被访问的页面,接近最优但实现复杂。
- Second-Chance/Clock:FIFO的改进,结合引用位,常用高效的近似LRU算法。
- 增强型Second-Chance:结合引用位和修改位,优先置换未被访问且未被修改的页面。
📝 LRU近似实现
- 额外引用位算法:每页维护8位,定期右移,统计访问频率。
- Second-Chance:FIFO队列+引用位,引用过的页面获得“第二次机会”。
5.3 系统颠簸(Thrashing)与工作集模型
- 系统颠簸(Thrashing):当分配给进程的物理帧不足以容纳其活跃工作集时,频繁发生缺页和页面置换,导致CPU大部分时间都在等待I/O,系统性能急剧下降。
- 工作集(Working Set)模型:
- 定义:进程在最近一段时间内频繁访问的页面集合。
- 工作集窗口Δ的选择影响工作集大小。
- 若所有进程的工作集总和大于物理帧数,系统极易发生颠簸。
- 缓解措施:
- 动态调整进程分配帧数
- 暂停或换出部分进程
- 估算和监控工作集大小,合理分配内存
6. Linux内存管理实现
6.1 关键数据结构
Linux 内核通过一系列数据结构实现对进程虚拟内存和物理内存的高效管理。
mm_struct:每个用户进程拥有一个 mm_struct,描述其整个虚拟地址空间。主要字段包括:
pgd:指向进程的页全局目录(Page Global Directory),即多级页表的根。mmap:指向虚拟内存区域(VMA)链表,每个 VMA 描述一段具有相同属性的虚拟内存(如代码段、堆、栈、mmap 区域等)。- 统计信息:如 task_size、total_vm、locked_vm、data_vm、exec_vm、stack_vm。
- 各类锁:如 page_table_lock(保护页表结构)、mmap_lock(保护 VMA 结构)。
vm_area_struct (VMA):描述一段连续的虚拟内存区域,包含起止地址、权限、映射类型(匿名/文件)、是否共享等。
6.2 页表与缺页处理
- Linux 支持多级页表(如 x86-64 的四级页表、ARM/RISC-V 的三级页表),每个进程的页表以 mm_struct 的 pgd 字段为根。
- 当进程访问未映射的虚拟地址时,MMU 触发缺页异常(Page Fault),内核通过 do_page_fault 入口处理。
- do_page_fault 主要流程:
- 获取出错地址和访问类型。
- 查找对应的 VMA,判断访问是否合法。
- 获取 mmap_lock 读锁,保护内存映射结构。
- 调用 __handle_mm_fault 进行实际的页面分配、加载或 COW 操作。
- 根据返回结果处理 OOM、信号等特殊情况。
- 释放 mmap_lock。
- __handle_mm_fault 负责页表遍历(Page Table Walk)、分配物理页帧、从后备存储加载页面、更新页表项等。
- 支持 Copy-on-Write(COW):fork 时父子进程共享只读页面,写时才复制,极大提升 fork 效率。
6.3 物理内存分配器
Linux 内核采用多种分配器协同管理物理内存:
Buddy System(伙伴系统):
- 按 2 的幂次分配和合并内存块,适合大块、连续内存分配。
- 优点:合并/分裂高效,减少外部碎片。
- 缺点:分配的块必须是 2 的幂次,存在内部碎片。
Slab Allocator(Slab 分配器):
- 面向对象缓存,适合频繁分配/释放的小对象(如进程描述符、内核数据结构)。
- 优点:减少内部碎片,提高分配速度,对象可预初始化。
- Slab 被划分为 Full/Partial/Empty 三种状态,便于管理。
SLUB/SLAB/SLOB:Linux 内核支持多种 Slab 分配器实现,默认使用 SLUB。
6.4 典型内存管理机制
- 虚拟内存与请求分页:Linux 支持虚拟内存和按需分页,进程只需关心虚拟地址空间,物理内存由内核动态分配和回收。
- 页面置换:内核实现了多种页面置换算法(如 LRU 近似),通过页帧回收机制(如 kswapd、direct reclaim)维持内存压力下的系统稳定。
- 内存映射(mmap):支持将文件或匿名内存映射到进程虚拟空间,实现高效的文件 I/O 和进程间共享内存。
- 内存保护与隔离:通过页表权限位、VMA 属性等机制,防止非法访问和破坏。
6.5 64位系统与大内存支持
- 64 位 Linux 系统支持更大的虚拟地址空间和物理内存,页表层级更多(如 x86-64 四级页表)。
- 支持大页(Huge Page)、透明大页(THP)等机制,提升大内存场景下的性能。
6.6 参考源码与文档
7. 重点总结与易错点
7.1 重点回顾
- 主存管理经历了分区、分段、分页、虚拟内存等多阶段演进,目标始终是高效、安全、灵活地分配和隔离内存资源。
- 分区/分段解决了早期多道程序设计的需求,但碎片问题严重。
- 分页机制消除了外部碎片,配合虚拟内存和请求分页极大提升了系统多道程序度和内存利用率。
- 多级页表、TLB、页面置换算法等机制是现代操作系统高效主存管理的关键。
- Linux 内存管理通过 mm_struct、VMA、Buddy System、Slab Allocator 等实现了虚拟内存、按需分页、页面置换、内存保护等功能。
7.2 常见易错点与面试高频考点
⚠️ 常见易错点与陷阱
- 分页只消除外部碎片,内部碎片依然存在。
- 分段和分页可以结合使用(如 x86 早期架构),但现代主流系统多以分页为主。
- 多级页表节省空间但增加了地址转换的访问次数,TLB 命中率对性能影响极大。
- 页面置换算法实际多用 LRU 近似算法,最优算法仅作理论参考。
- Linux Buddy System 适合大块内存分配,Slab Allocator 适合小对象频繁分配。
- Copy-on-Write(COW)是 Linux fork() 高效实现的关键。
- 系统颠簸(Thrashing)本质是物理帧数不足以容纳所有进程的工作集。
- 虚拟内存空间远大于物理内存,实际分配和映射由操作系统动态管理。
7.3 典型面试与考研真题提示
- 解释分页和分段的区别与优缺点。
- 画出多级页表的地址分解与转换流程。
- 说明 TLB 的作用及其对系统性能的影响。
- 举例说明页面置换算法的原理与实际应用。
- 描述 Linux 内存分配器(Buddy/Slab)的工作机制和适用场景。
- 分析系统颠簸产生的原因及其缓解措施。
7.4 推荐学习与查阅资料
- 《Operating System Concepts》(Silberschatz 等)
- 《深入理解计算机系统》(CSAPP)
- Linux 内核源码(mm/ 目录)
- Linux mm_struct 源码↗
- do_page_fault (ARM64)↗
- __handle_mm_fault↗