计算机体系结构中,内存管理是操作系统最核心的职能之一,也是系统架构设计师考试中每年必现的高频考点。许多考生学到这里会陷入一种模糊感:知道有段式、有页式、有段页式,但说不清楚三者之间究竟差在哪里,更谈不上在考场上快速完成地址转换的计算题。这篇文章的目标,就是帮你彻底打通从段式到页式再到段页式的完整脉络,让你拿到任何一道地址转换题目都能稳准快地算出答案。
要理解内存管理机制,首先需要回到问题的原点:为什么需要虚拟内存?早期的计算机系统中,程序直接操作物理内存地址。这种裸机编程方式存在着三个致命缺陷。第一,地址空间冲突,两个程序可能被加载到同一块物理内存区域,一旦覆盖就会导致运行崩溃。第二,程序必须完整驻留内存才能运行,内存容量直接决定了能跑多大的程序。第三,程序的逻辑地址空间被物理内存的碎片化切割得支离破碎,程序员不得不在编码阶段就操心内存布局。虚拟内存技术的诞生,本质上是用一层抽象映射解决了这三个问题:每个进程拥有独立的虚拟地址空间,操作系统通过内存管理单元将虚拟地址动态转换为物理地址,程序只需要关心自己的逻辑地址,底层物理内存的分配和回收完全由系统托管。这一思想是理解段式、页式、段页式三种管理方式的共同基础。
从硬件的角度看,虚拟内存的运转离不开内存管理单元MMU。MMU位于CPU与主存之间的数据通路上,每一次内存访问请求都要经过它的地址转换。MMU的核心工作就是查表:根据当前进程的页表或段表,把程序发出的虚拟地址翻译成物理总线上的实际地址。如果查表过程中发现目标页面不在内存中,就会产生缺页中断信号,通知操作系统介入处理。这种软硬件协同的设计模式贯穿内存管理的全部环节,是理解后续所有机制的出发点和落脚点。
段式存储管理的基本思想是:按照程序的逻辑结构来划分内存。一个程序在逻辑上天然由代码段、数据段、堆栈段等模块组成,段式管理将每一个逻辑模块映射为内存中的一个连续区域,称为段。每个段有独立的段基址和段长度,段的长度由程序本身决定,可以各不相同。
在段式管理下,程序的虚拟地址由段号和段内偏移量两部分组成。以十六位地址为例,高四位可能表示段号,低十二位表示段内偏移。操作系统维护一张段表,段表的每一个表项记录了一个段的基址和长度。CPU发出逻辑地址后,MMU根据段号在段表中查到该段的物理起始地址,然后加上段内偏移量,就得到了实际的物理地址。这一过程在硬件层面通常由段寄存器协助完成,现代CPU的段描述符表正是这一思想的硬件实现。
段式管理的一个天然优势在于信息共享。两个不同的进程如果需要共享同一个代码库,只需让它们的段表项指向同一块物理内存区域即可。这种共享方式在语义上非常清晰,共享的是完整的功能单元而非碎片化的内存页。段式管理的另一个重要特性是保护,每个段表项中通常包含访问权限控制信息,可以标注该段是只读、可读写还是只执行。代码段设为只读可执行,数据段设为可读写不可执行,堆栈段设为可读写。任何越权访问都会触发段异常,操作系统随即终止越权进程。
段式管理虽然直观,但它有一个致命的弱点:段的大小不一,经过反复分配和回收后,物理内存中会出现大量无法利用的小碎片,即所谓的外部碎片。当系统需要加载一个新段时,即使空闲内存总量足够,但没有一块连续区域能容纳它,就需要执行紧凑操作,将已分配的各段整体搬家来腾出连续空间。紧凑操作需要暂停所有正在运行的进程、逐段复制内存内容并修正所有相关的地址引用,是一个极为耗时的全局操作。
页式存储管理采用了一种完全不同的思路:不再根据程序的逻辑结构来切分,而是将程序的逻辑地址空间和物理内存都切割成固定大小的小块。逻辑空间的小块称为页面,物理内存的小块称为页框,页面和页框的大小完全相同,通常为四KB或者八KB。这种设计消除了外部碎片的问题,因为任何页面都可以装入任何空闲的页框,不需要管页框在物理内存中的位置是否连续。
页式管理的核心数据结构是页表。每个进程拥有一张独立的页表,页表的索引是逻辑页号,页表项中记录的是对应的物理页框号。地址转换的过程为:CPU给出的逻辑地址被拆分为页号和页内偏移两部分,页号作为索引在页表中查到物理页框号,然后将页框号与页内偏移拼接,就得到了物理地址。整个过程完全由硬件MMU在访问内存的瞬间自动完成,对上层程序完全透明。
第一个被忽视但考试常考的问题是:页表本身也存在存储开销。假设虚拟地址空间为三十二位,页面大小为四KB,那么页号占二十位,也就是说页表最多可以有一百万个表项。如果每个表项占四个字节,一张完整的页表就要占用四MB的内存,而且每个进程都需要一张。在六十四位地址空间下,这个开销将大到无法承受。解决这个问题的方法是引入多级页表:页号被进一步拆分为多段,例如拆分页目录索引和页表索引。最高级的页目录表只有一页大小,它的表项指向第二级的页表。如果某一段虚拟地址空间根本没有被使用,对应的二级页表就不需要分配,从而大幅节省了页表的内存占用。三级页表和四级页表在六十四位系统中已经成为标准做法。
与多级页表紧密配合的是TLB快表。TLB是嵌入在MMU内部的一个小型高速缓存,用来缓存最近使用过的页表项。由于程序具有局部性原理,指令和数据访问通常集中在少数几个页面上,因此TLB的命中率可以高达百分之九十九以上。当TLB命中时,地址转换在单时钟周期内即可完成;当TLB未命中时,MMU需要逐级遍历页表,耗时可能是几十甚至上百个时钟周期。考试中常见的陷阱是把TLB误认为是存储页表的内存区域,实际上TLB是页表的缓存副本。
页式管理虽然解决了外部碎片问题,但也引入了一个新问题:内部碎片。因为页面大小是固定的,而程序的实际大小未必正好是页面大小的整数倍。最后一个页面可能只用了一部分,但其对应的整页物理页框都要分配给它,那多出来的部分就浪费掉了。这是固定分区方案不可回避的代价,页面越小内部碎片越少但页表越大,反之亦然,这是一个经典的时空权衡问题。
页式管理的另一个关键机制是请求调页。系统并不要求程序的所有页面一开始都加载到内存中,只有那些即将被访问的页面才被调入。当程序访问一个不在物理内存中的页面时,MMU会产生一个缺页中断,操作系统的缺页处理程序接管控制权,从磁盘上将所需的页面调入内存,更新页表后重新执行触发缺页的指令。这一机制的巧妙之处在于,缺页发生时指令尚未完成执行,操作系统处理完缺页后可以让CPU从断点处继续,整个过程对程序本身是透明的,仿佛内存空间是无限大的。
段式管理和页式管理各有优劣。段式管理符合程序的逻辑结构、便于共享和保护,但存在外部碎片和段大小不灵活的问题;页式管理碎片少、空间利用率高,但牺牲了逻辑结构信息,不便于共享和保护。段页式存储管理正是取两者之长、避两者之短的产物。
在段页式管理下,程序的逻辑地址被分解为三个部分:段号、段内页号和页内偏移。地址映射需要经过两级查表:第一步,根据段号在段表中找到该段的页表起始地址;第二步,根据段内页号在该段的页表中找到物理页框号;第三步,将物理页框号与页内偏移拼接得到最终的物理地址。段页式管理的精妙之处在于,它在段的逻辑视角之下融合了页的物理视角,让程序员看到的是有意义的段,而操作系统在底层用页来分配物理内存,巧妙地规避了外部碎片问题。
在段页式管理下,地址转换的计算是考试中的重头戏。设段号为S,段内页号为P,页内偏移为W,段表给出该段的页表基址为B,页表中第P项的物理页框号为F,页面大小为Z,那么最终的物理地址为F乘以Z加上W。这个计算虽然步骤多,但只要把三层拆分和两次查表的关系理清楚,就绝对不会出错。建议考场上遇到这种题目在草稿纸上画出箭头关系图:段号箭头指向段表,段表项箭头指向页表基址,段内页号箭头指向页表项,页表项箭头指向物理页框,物理页框拼接页内偏移得到最终物理地址。
段页式管理在现代操作
本篇完!