7操作系统概念(第10版)

主内存

内存管理

分页分段虚拟内存页面置换

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章 主内存(内存管理)

一、导读

1.1 本章学习目标

理解内存管理的基本功能:地址绑定、逻辑地址与物理地址的转换

掌握连续内存分配方案及其碎片问题

深入理解分页(Paging)机制的原理和页表结构

掌握多级页表和TLB(Translation Lookaside Buffer)的设计思想

理解分段(Segmentation)的概念及其与分页的区别

掌握虚拟内存的实现机制:按需调页和页面置换算法

了解工作集模型和抖动(Thrashing)现象

理解内存映射文件的概念和应用

1.2 为什么本章如此重要

内存管理是操作系统最核心的子系统之一,直接影响系统的性能、安全性和可用性。现代操作系统通过虚拟内存技术为每个进程提供了一个独立的、连续的地址空间抽象,使得程序员不需要关心物理内存的实际布局。这种抽象不仅简化了编程,还实现了进程间的内存隔离——一个进程无法访问另一个进程的内存,这是系统安全的基础。

从性能角度看,内存管理的设计直接影响程序的执行效率。页表的大小和结构影响地址翻译的速度,TLB的命中率决定内存访问的延迟,页面置换算法的选择影响缺页中断的频率。一个设计良好的内存管理系统可以将虚拟内存的开销降到几乎不可感知的程度,而设计不当则可能导致严重的性能问题(如抖动)。

从系统资源利用角度看,虚拟内存使得系统可以运行比物理内存更大的程序,通过按需加载和页面置换实现了内存的超售(overcommit)。交换空间(Swap Space)作为物理内存的扩展,允许系统在内存紧张时将不活跃的页面换出到磁盘。这些技术极大地提高了系统的多道程序度和资源利用率。

1.3 核心问题引导

为什么每个进程都"认为"自己独占整个内存?这种抽象是如何实现的?

分页和分段各解决了什么问题?为什么现代系统主要使用分页?

TLB是什么?为什么它对系统性能如此重要?

页面置换算法中,为什么LRU比FIFO更好?LRU如何实现?

什么是抖动?如何检测和预防抖动?

写时复制(Copy-on-Write)技术如何优化fork()的性能?

内存映射文件与普通文件I/O有什么区别?性能优势在哪里?


二、核心概念详解

2.1 地址绑定与内存保护

程序从源代码到执行需要经历编译、链接、加载和执行四个阶段。地址绑定是将程序中的符号地址转换为物理地址的过程,可以在不同阶段完成。编译时绑定生成绝对代码,如果起始地址改变则需要重新编译。加载时绑定生成可重定位代码,加载时根据加载地址计算绝对地址。执行时绑定允许程序在运行时移动,需要硬件支持(如MMU)。

逻辑地址(Logical Address)是CPU生成的地址,也称为虚拟地址(Virtual Address)。物理地址(Physical Address)是内存单元的实际地址。内存管理单元(MMU, Memory Management Unit)是负责将逻辑地址转换为物理地址的硬件。最简单的MMU使用基址寄存器(Base Register)和限址寄存器(Limit Register),逻辑地址加上基址得到物理地址,同时检查是否超过限址以实现保护。

内存保护确保进程只能访问自己的地址空间。当进程A试图访问进程B的内存时,MMU检测到地址越界并触发异常,操作系统终止违规进程。这种硬件级别的隔离是进程安全和系统稳定的基础。

2.2 分页机制

分页(Paging)将物理内存划分为固定大小的帧(Frame),将逻辑内存划分为相同大小的页(Page)。页和帧的大小通常是2的幂次方,在512字节到8MB之间,常见大小为4KB。分页通过页表(Page Table)实现逻辑地址到物理地址的转换。

逻辑地址被分为两部分:页号(Page Number, p)和页内偏移(Page Offset, d)。页号作为页表的索引,查找对应的页表项(Page Table Entry, PTE),PTE包含该页对应的物理帧号。物理地址由帧号和页内偏移拼接而成。页内偏移在转换过程中不变,因为页和帧大小相同。

页表存储在内存中,每次内存访问需要两次内存操作:一次读取页表项,一次访问实际数据。为减少开销,使用TLB(Translation Lookaside Buffer)缓存最近使用的页表项。TLB是一种关联存储器(Associative Memory),可以并行搜索所有条目,访问时间远小于普通内存。TLB命中时直接使用缓存的帧号,未命中时从内存页表加载并更新TLB。

页表项包含多个控制位:有效/无效位(Valid bit)标识该页是否在物理内存中;保护位(Protection bits)标识该页的访问权限(读/写/执行);修改位(Dirty bit)标识该页是否被修改过;引用位(Reference bit)标识该页最近是否被访问过。这些控制位用于内存保护和页面置换。

2.3 多级页表与TLB

随着地址空间的增大,传统页表变得非常大。例如,32位地址空间、4KB页面大小时,页表有2^20=1M个条目,每个条目4字节,页表本身需要4MB。对于64位地址空间,问题更加严重。多级页表(Multilevel Page Table)将页表分层组织,只分配实际使用的部分,减少内存占用。

两级页表将页号分为两部分:页目录索引和页表索引。页目录的每个条目指向一个页表,页表的每个条目指向一个物理帧。如果某些地址范围没有使用,对应的页目录项为空,不需要分配页表。在32位系统中,两级页表将4MB的页表减少到几KB(仅分配使用的部分)。

x86-64架构使用四级页表(PGD → PUD → PMD → PTE),支持48位虚拟地址空间(256TB)。每级使用9位索引,加上12位偏移,共48位。反向映射(Reverse Mapping)技术允许操作系统快速找到映射到某个物理帧的所有虚拟页,用于页面置换和内存回收。

TLB的组织方式影响地址翻译的性能。完全关联TLB可以缓存任意页号的翻译,但硬件复杂度高。组关联TLB折中了性能和复杂度。TLB的条目数通常为64到1024个,命中率对性能影响巨大。TLB未命中(TLB Miss)的代价从几个周期(L1页表缓存命中)到数百个周期(多级页表遍历)不等。

2.4 分段机制

分段(Segmentation)将内存按逻辑单元划分,每个段对应一个逻辑实体(如代码段、数据段、栈段、堆段等)。与分页的固定大小不同,段的长度可变,由段的逻辑功能决定。

分段地址由段号(Segment Number)和段内偏移(Offset)组成。段表(Segment Table)的每个条目包含段基址(Base Address)和段长度(Limit)。地址翻译时,用段号查找段表获得基址和长度,检查偏移是否超过长度(保护检查),然后将基址加偏移得到物理地址。

分段的优势在于:符合程序员对内存的逻辑视图(代码、数据、栈分离);便于共享(多个进程共享同一个代码段);便于保护(不同段可以有不同的访问权限)。分段的缺点是产生外部碎片(段长度可变导致空闲空间分散),需要紧凑(Compaction)技术回收碎片。

现代系统通常结合分页和分段。x86架构支持分段和分页的组合:先通过段表将逻辑地址转换为线性地址,再通过页表将线性地址转换为物理地址。但在64位模式下,x86-64实际上禁用了分段(段基址固定为0),只使用分页。


三、深入分析与讨论

3.1 页面置换算法

当发生缺页中断(Page Fault)且物理内存已满时,操作系统需要选择一个页面换出(可能写入磁盘),为新页面腾出空间。页面置换算法的选择直接影响缺页中断的频率和系统性能。

FIFO(First-In-First-Out)置换最先进入内存的页面。实现简单(使用队列),但可能换出频繁使用的页面。FIFO存在Belady异常——增加物理帧数反而增加缺页次数,这是因为FIFO不考虑页面的使用频率。

OPT(Optimal)置换换出未来最长时间不会被使用的页面。这是理论最优算法,缺页次数最少,但无法实现(需要预知未来的页面访问序列)。OPT用作评估其他算法性能的上界。

LRU(Least Recently Used)置换最近最少使用的页面。基于局部性原理——最近使用的页面很可能再次使用。LRU没有Belady异常,性能接近OPT。但LRU的实现需要硬件支持:计数器方案为每个页面维护一个使用计数器,每次访问更新所有计数器,开销大;栈方案维护一个页面使用栈,每次访问将页面移到栈顶,栈底就是最久未使用的页面。

Clock算法(二次机会算法)是LRU的近似实现,开销更低。每个页面关联一个引用位,页面排列成环形链表。置换时检查当前页面的引用位:如果为1则清零并移到下一个,如果为0则换出。这给了页面一次"第二次机会"。增强型Clock算法进一步考虑修改位,优先换出未修改的页面(不需要写回磁盘)。

3.2 工作集模型与抖动

工作集(Working Set)是进程在一段时间窗口内实际访问的页面集合。工作集模型基于局部性原理:进程在一段时间内只访问少量页面,且访问模式随时间变化。工作集大小WSS(t, delta)表示在时间t、窗口delta内的不同页面访问数。

如果为进程分配的物理帧数少于其工作集大小,进程将频繁发生缺页中断——刚换入的页面很快又被换出,系统大部分时间在处理缺页中断而非执行程序,这种现象称为抖动(Thrashing)。抖动的特征是CPU利用率急剧下降,系统吞吐量接近零。

预防抖动的方法包括:工作集模型——为每个进程分配足够的帧以容纳其工作集,如果总需求超过物理内存则挂起某些进程;页面频率控制——监控缺页频率,超过阈值则增加分配帧数或挂起进程;预防性调页——在缺页发生前预加载可能需要的页面。


四、实践案例

4.1 Linux内存管理架构

Linux使用四级页表(在5.11+内核中扩展为五级以支持更大的地址空间),支持多种页面大小(4KB、2MB、1GB)。Linux的虚拟内存区域(VMA, Virtual Memory Area)管理进程的地址空间,每个VMA描述一段连续的虚拟地址范围及其属性(读/写/执行、共享/私有)。

Linux的页面置换使用增强型Clock算法的变体,维护活跃页面链表和非活跃页面链表(Active/Inactive List)。新加载的页面进入活跃链表,不活跃使用的页面迁移到非活跃链表,置换时从非活跃链表选择。这种设计近似LRU但开销更低。

Linux的OOM Killer(Out-of-Memory Killer)在系统内存极度紧张时启动,选择一个进程终止以释放内存。选择标准基于进程的OOM评分(基于内存使用量、运行时间和nice值)。

4.2 内存映射文件的应用

内存映射文件(Memory-Mapped File)通过mmap()系统调用将文件直接映射到进程的地址空间,文件内容可以通过内存访问的方式读写,无需read()/write()系统调用。映射后,文件偏移量对应虚拟地址偏移,访问映射区域触发缺页中断时,内核从磁盘加载对应的文件块到物理页。

内存映射文件的优势在于:减少了用户空间和内核空间之间的数据拷贝;利用操作系统的页面缓存机制,多个进程映射同一文件时共享物理页;可以利用虚拟内存管理自动处理文件的换入换出。数据库系统(如MySQL、MongoDB)广泛使用内存映射文件来管理数据存储。


五、常见误区与注意事项

虚拟内存不等于无限内存:虚拟地址空间的大小受处理器架构限制(32位系统为4GB,64位系统理论上为16EB)。虚拟内存也不意味着可以运行任意大的程序——如果工作集超过物理内存太多,系统会陷入抖动。

分页消除了外部碎片但不消除内部碎片:每个页面的最后一部分可能未完全利用,产生内部碎片。页越大,内部碎片越严重;页越小,页表越大。4KB是常见的折中选择。

TLB不是越大越好:TLB的访问时间与大小相关,完全关联TLB需要并行比较所有条目,条目数增加导致硬件复杂度和功耗增加。实际TLB大小是命中率和访问延迟的折中。

LRU的近似算法可能性能差距很大:Clock算法虽然开销低,但在某些访问模式下(如循环扫描大数组)性能远差于真正的LRU。增强型Clock算法通过考虑修改位部分弥补了这一差距。

共享内存页的COW机制需要注意:fork()后父子进程共享页面,任一方写入时触发COW复制。如果子进程紧接着exec(),COW是浪费的。Linux的vfork()避免了这种浪费(子进程直接在父进程地址空间执行直到exec或exit)。


六、本章小结

本章详细讲解了内存管理的核心技术。分页通过固定大小的页消除了外部碎片,是现代系统的主流内存管理方案。多级页表和TLB解决了大地址空间下页表过大和地址翻译效率的问题。分段按逻辑单元组织内存,提供更自然的视图但产生外部碎片。虚拟内存通过按需调页实现了比物理内存更大的地址空间,页面置换算法的选择直接影响系统性能。工作集模型帮助理解程序的内存访问模式,预防抖动是内存管理的重要目标。内存映射文件简化了文件I/O并提高了效率。


七、思考题与练习

解释逻辑地址和物理地址的区别,描述MMU如何进行地址翻译。

比较分页和分段的优缺点,说明为什么现代系统主要使用分页。

计算一个32位系统使用两级页表时的页表结构,分析内存占用。

实现LRU页面置换算法,分析其时间复杂度和空间复杂度。

解释TLB的工作原理,分析TLB命中率对系统性能的影响。

描述抖动现象的产生原因和检测方法,讨论预防策略。

分析内存映射文件的工作原理,比较其与传统文件I/O的性能差异。

研究Linux的Active/Inactive List页面置换策略,分析它如何近似LRU。

7/10主内存
0%