5465 字
27 分钟
3.1内存管理概念2

3.1.3 基本分页存储管理#

固定分区会产生内部碎片,动态分区会产生外部碎片,这两种方式对内存的利用率都较低。为提高内存利用率并彻底消除外部碎片,操作系统引入了分页存储管理:将物理内存划分为若干大小相等的固定单元,称为页框(或页帧物理块);同时将进程的逻辑地址空间划分为与页框大小相同的单元,称为页面(或)。系统以页框为单位为进程分配内存。

从形式上看,分页类似于等长的固定分区,但其本质不同:固定分区按作业整体分配,而分页将进程和内存均按固定大小的单元划分,使得内存分配以页为单位进行。因此,分页管理不会产生外部碎片。然而,由于页面大小固定,当进程的某一页未被完全填满时,其对应的页框中会留下未使用的空间,形成页内碎片(内部碎片),每个进程平均产生的页内碎片约为半页。

1. 分页存储的几个基本概念#

(1) 页面和页面大小#

进程的逻辑地址空间被划分为若干页面,每个页面有一个编号,称为页号,从0开始;物理内存中的每个页框也有一个编号,称为页框号(或物理块号),同样从0开始。通过建立页号到页框号的映射,系统为进程的每个页面分配物理内存。

为便于地址转换,页面大小通常取2的整数次幂。页面大小应适中:若页面过小,则进程的页面数量增多,导致页表过长,不仅占用大量内存,还会增加地址转换开销,降低页面换入/换出的效率;若页面过大,则页内碎片增多,同样会降低内存利用率。

(2) 地址结构#

在分页存储管理中,逻辑地址由两部分组成:页号 PP 和页内偏移量 WW

以32位地址为例,若页面大小为 212B2^{12}\text{B}(4KB),则低12位 (0110 \sim 11 位)为页内偏移量,高20位 (123112 \sim 31 位)为页号,最多可支持 2202^{20}页面

(3) 页表#

为实现逻辑地址到物理地址的转换,系统为每个进程建立一张页表(Page Table)。页表是一个按页号顺序排列的数组,每个页表项记录对应页面所驻留的物理页框号,以及相关状态信息(如有效位、访问位等)。进程执行时,通过页号索引页表,即可获得该页所在的物理块号,从而完成地址映射。因此,页表的作用是实现从页号到页框号的映射

2. 基本地址变换机构#

地址变换机构的任务是将逻辑地址转换为内存中的物理地址,这一过程借助页表实现

NOTE

在页表中,页表项按页号顺序连续存放,因此页号作为索引是隐含的,无须在页表项中显式存储。

为提高地址变换速度,系统设置一个页表寄存器(PTR),存放页表在内存中的始址 FF 和页表长度 MM。由于寄存器造价昂贵,单CPU系统中通常只设置一个PTR。进程未执行时,其页表始址和长度保存在该进程的PCB中;当进程被调度执行时,操作系统将这些信息装入PTR。

设页面大小为 LL,逻辑地址 AA 到物理地址 EE变换过程如下。

  1. 根据逻辑地址计算页号 P=A/LP = \lfloor A/L \rfloor页内偏移量 W=A%LW = A \% L
  2. 判断页号是否越界,若页号 PP \ge 页表长度 MM,则产生越界中断;否则,继续执行。
  3. 根据页号 PP 查找页表,找出对应页表项中的物理块号。页表项地址 == 页表始址 F+F + 页号 P×P \times 页表项长度,取出该页表项中的物理块号 bb
  4. 计算物理地址 E=b×L+WE = b \times L + W,并据此访问内存(注意,物理地址 == 页面在内存中的始址 ++ 页内偏移量,页面在内存中的始址 == 块号 ×\times 块大小)。

上述地址变换过程由MMU硬件自动完成的。例如,若页面大小 L=1KBL = 1\text{KB},则页号2对应的物理块号 b=8b = 8,逻辑地址 A=2500A = 2500 的物理地址 EE 的计算过程如下:P=2500/1K=2P = 2500/1\text{K} = 2W=2500%1K=452W = 2500 \% 1\text{K} = 452,故物理地址 E=8×1024+452=8644E = 8 \times 1024 + 452 = 8644

若逻辑地址以二进制形式给出,则页号和页内偏移可直接通过截取高位和低位获得,效率更高。正因页面大小固定,逻辑地址可视为一维线性地址,因而仅需一个整数即可确定其位置。

页表项大小不是随意规定的,而是有所约束的。页表项大小如何确定?

页表项需能表示所有可能的物理页框号。以32位地址空间、按字节编址、页面大小为4KB为例,物理内存最多包含 232B/4KB=2202^{32}\text{B}/4\text{KB} = 2^{20} 个页框,因此需要 log2220=20\log_2 2^{20} = 20 位来表示页框号。由于内存按字节编址,为保证页表项能够指向所有页面,页表项大小至少为 20/8=3B\lceil 20/8 \rceil = 3\text{B}。但出于对齐和扩展考虑(如加入有效位、访问位等),实际系统通常将页表项设为4B。

分页管理面临两个主要挑战:

  1. 每次访存都需要进行地址转换,若转换速度不够快,则会显著降低系统性能;
  2. 页表本身占用内存空间,若页表过大,则会降低内存利用率。正是这些问题,推动了后续优化技术的发展,如快表(TLB)和多级页表。

3. 具有快表的地址变换机构#

由前述地址变换过程可知,若页表全部存放在内存中,则每次访问内存(无论是取指令还是读/写数据)至少需两次访存:第一次访问页表以获取物理块号,第二次根据形成的物理地址访问目标内容。显然,这种方式会显著降低系统性能。为此,现代地址变换机构增设了一个具有并行查找能力的高速缓冲存储器——快表(TLB),用于缓存当前活跃的若干页表项,以加速地址变换。相应地,主存中的完整页表常被称为慢表

在具有快表的分页机制中,地址变换过程如下。

  1. CPU给出逻辑地址后,硬件自动提取页号,并将其与快表中所有页号并行比较
  2. 命中(找到匹配的页号),则直接从快表中取出对应的物理块号,与页内偏移量拼接形成物理地址。此时,仅需一次访存即可。
  3. 未命中(未找到匹配的页号),则需访问内存中的页表,读取相应的页表项以获得其物理块号,再拼接形成物理地址并访问内存,共需两次访存。同时,系统会将该页表项装入快表,以便后续可能的重复访问;若快表已满,则按特定置换算法淘汰一个旧项。

在程序具有良好局部性的前提下,快表命中率通常可达90%以上,因此分页机制带来的性能开销可控制在10%以内。快表的有效性基于著名的局部性原理,这一原理将在3.2节中介绍。

4. 两级页表#

引入分页管理后,进程执行时无须将所有页面调入内存,但仍需将整个页表驻留内存。然而,页表本身可能非常庞大。以32位逻辑地址空间、页面大小为4KB、页表项大小为4B为例:页内偏移占 log24K=12\log_2 4\text{K} = 12 位,页号占20位,因此每个进程的页表最多包含 2202^{20} 个页表项,仅页表就需占用 220×4B/4KB=1K2^{20} \times 4\text{B} / 4\text{KB} = 1\text{K} 个页。若还要求页表连续存放,则显然不切实际。

解决上述问题的思路是将页表本身也视为可分页的对象,具体体现为两个方面:

  1. 对页表采用离散分配,不再要求其连续存放,而是通过一张专门的索引表记录各页表页在内存中的位置,从而消除对连续内存空间的需求;
  2. 仅将当前活跃的部分页表项调入内存,其余仍驻留磁盘,按需调入(虚拟内存的思想),从而有效缓解页表占用过多内存的问题。

不难发现,这一方案与最初引入页表以解决进程地址空间离散分配的思路如出一辙:本质上,就是为已离散化的页表再建立一层页表,称为外层页表(或页目录)。仍以上述条件为例:当采用两级分页时,将原页表按页面大小(4KB)进行分页。由于每页可容纳 4KB/4B=10244\text{KB}/4\text{B} = 1024 个页表项,而总页表项数为 2202^{20},故需 220/210=210=10242^{20}/2^{10} = 2^{10} = 1024 个页表页,对应外层页表包含1024个表项,恰好占用一页(4KB)。

两级页表是在普通页表结构上再加一层页表。

在页表的每个表项中,存放的是进程某页对应的物理块号。例如,0号页存放在1号物理块中,1号页存放在5号物理块中。在外层页表的每个表项中,存放的是某个页表分页的物理起始地址。例如,0号页表页存放在3号物理块中。

通过外层页表和内层页表的协同作用,可实现从逻辑地址到物理地址的变换。

为方便地址变换,系统增设一个外层页表寄存器(也称页目录基址寄存器),用于存放外层页表的起始地址。地址变换过程如下。

  1. 用逻辑地址中的页目录号作为索引,访问外层页表,获取对应内层页表的物理起始地址。
  2. 用逻辑地址中的二级页号作为索引,访问该页表,得到目标页面的物理块号。
  3. 将物理块号与页内偏移量拼接,形成物理地址,并据此访问内存单元。

在无快表的情况下,此过程需三次访存

对于更大的地址空间(如64位),两级页表远远不够。例如,若逻辑地址为64位、页面大小为4KB,则页号占52位。若每页仍可存放1024个页表项,则需约6级页表才能覆盖整个地址空间。然而,实际系统通常将虚拟地址限制为48位,从而采用三级或四级页表即可高效管理,既满足应用需求,又避免硬件过度复杂化。建立多级页表的根本目的,是通过按需分配页表页,避免为未使用的地址空间预留大量无用的页表项,进而显著节省内存开销。

3.1.4 基本分段存储管理#

分页管理方式是从计算机的角度出发设计的,旨在提高内存利用率和系统性能,其地址转换由硬件自动完成,对用户完全透明。而分段管理方式则从用户和程序员的实际需求出发,旨在支持方便编程、信息保护与共享、动态增长以及动态链接等高级功能。

1. 分段#

分段系统将用户进程的逻辑地址空间划分为若干大小不等的段。例如,一个进程可包含主程序段、子程序段、栈段和数据段,共划分为5个段。每个段内部从0开始编址,并分配一段连续的地址空间(段内连续,段间不要求连续)。由于地址空间被组织为多个具有明确语义的段,整个进程的逻辑地址结构呈现出二维特性:由段号段内偏移共同确定一个地址。

分段存储管理的逻辑地址由段号 SS 和段内偏移量 WW 两部分组成。在图3.12中,段号占16位,段内偏移量也占16位,则一个进程最多可拥有 216=655362^{16}=65536 个段,最大段长为64KB。

在页式系统中,逻辑地址中的页号与页内偏移量对用户透明;而在分段系统中,段号和段内偏移量必须由用户显式提供——这一工作在高级语言中由编译程序自动完成。

2. 段表#

每个进程都拥有一张段表,用于实现逻辑段到物理内存区的映射。段表中每个段对应一个段表项,记录该段在内存中的基址(起始地址)和段长(长度)。

运行时,系统通过查找段表,即可定位每段在内存中的实际位置。

由于段表项连续存放且长度相同,段号可作为隐含索引,无须额外存储。例如,在某32位分段系统中,段号为16位,段内偏移量为16位(段长字段为16位,对应最大段长64KB),物理地址为32位(可寻址4GB内存)。因此,每个段表项至少需要16(段长)+32(基址)=48位,即6B。若段表首地址为 MM,则第 KK 号段对应的段表项位于地址 M+K×6M + K \times 6 处。

3. 地址变换机构#

分段系统的地址变换过程如图3.15所示。为实现从逻辑地址到物理地址的变换,系统设置了一个段表寄存器,用于存放段表始址 FF段表长度 MM。段式存储管理的地址变换过程如下。

  1. 从逻辑地址 AA 中分离出段号 SS段内偏移量 WW
  2. 判断段号是否越界:若段号 SS \ge 段表长度 MM,则产生段号越界中断,否则继续执行。
  3. 根据段号 SS 查找段表:段表项地址 == 段表始址 F+F + 段号 S×S \times 段表项长度。取出该段表项中该段的段长 CC,若 WCW \ge C,则产生段内越界中断;否则,继续执行。
  4. 取出段表项中该段的基址 bb,计算物理地址 E=b+WE = b + W,并据此访问内存。

4. 分页和分段的对比#

分页与分段均为非连续分配方式,均需通过地址映射机构实现地址变换。然而,二者在概念上有本质区别,主要体现在以下三个方面:

  1. 页是系统管理内存的物理单位,分页的主要目的是提高内存利用率,完全由系统实现,对用户透明;段是用户程序的逻辑单位,分段的主要目的是满足编程、共享、保护等需求,由编译器根据程序结构划分,对用户可见。
  2. 页的大小固定,由系统决定。段的长度不固定,取决于程序的逻辑结构。
  3. 分页系统的地址空间是一维的,程序员只需提供单一的线性地址;分段系统的地址空间是二维的,程序员在引用地址时,必须同时指定段名(或段号)和段内偏移量。

5. 段的共享与保护#

在分页系统中,虽然也能实现共享,但远不如分段系统方便。若一段共享代码占 NN 个页框,则每个共享进程的页表中都需建立 NN 个页表项,分别指向这 NN 个页框。而在分段系统中,无论该段多大,只需在每个进程的段表中设置一个段表项指向该共享段,因此实现共享极为便捷。

为支持段共享,系统可维护一张共享段表,其中每个共享段对应一个表项,记录其段号、段长、内存起始地址、存在位、外存起始地址以及共享进程计数 count。当某进程不再使用该段时,count 减1;仅当 count=0 时,系统才回收该段所占的内存空间。值得注意的是,同一共享段在不同进程中可以具有不同的段号,各进程通过各自的段号即可访问同一物理段。

不能被修改的代码称为可重入代码(或纯代码),它允许多个进程并发执行。为确保共享代码的完整性,系统通常将此类代码置于只读段中。同时,每个进程需配备私有的局部数据区,专门用于存放执行过程中可能变化的数据,从而有效避免对共享代码段的任何写操作。

与分页管理类似,分段系统的保护机制主要包括两类:

  1. 地址越界保护,将逻辑地址中的段号与段表长度比较,若段号 \ge 段表长度,则产生越界中断;将段内偏移与段表项中的段长比较,若偏移 \ge 段长,则同样触发越界中断。
  2. 存取控制保护,通过段表项中的访问权限位防止非法访问。

3.1.5 段页式存储管理#

分页存储管理能有效提高内存利用率,而分段存储管理能反映程序的逻辑结构,并有利于段的共享与保护。将二者各自的优势有机结合,便形成了段页式存储管理方式。

在段页式系统中,进程的地址空间首先被划分为若干逻辑段,每段拥有唯一的段号;随后,每个段被进一步划分为若干大小固定的。内存空间的管理仍采用分页方式,即划分为与页面大小相同的物理块,内存分配以物理块为单位。

进程的逻辑地址由三部分组成:段号 SS页号 PP页内偏移量 WW

为实现地址变换,系统为每个进程建立一张段表,每个段对应一个段表项,记录该段的页表始址页表长度(段号作为段表索引,无须显式存储)。每个段还对应一张页表,其每个页表项记录对应页的物理块号(页号作为页表索引,亦无须存储)。此外,系统设置一个段表寄存器,用于存放当前进程段表的起始地址和段表长度,既用于段表寻址,也用于段号越界检查。

NOTE

在段页式存储管理中,每个进程仅有一张段表,但可能有多张页表(每个段一张)。

段页式存储管理的地址变换过程如下。

  1. 根据逻辑地址中的段号 SS,通过段表寄存器定位段表,并查得该段对应页表的起始地址;
  2. 系统利用页号 PP 访问该页表,获取物理块号
  3. 将物理块号与页内偏移量 WW 拼接,形成物理地址

在无快表的情况下,每次基于逻辑地址的内存访问需三次访存。为加速查找,可引入快表(TLB),其表项包含关键字(段号页号)及对应的物理块号和保护信息。

对用户而言,程序仍按段组织,页的划分由系统自动完成且完全透明。正因如此,段页式管理的地址空间是二维的——这与分段系统一致,而分页机制对用户不可见。

3.1.6 本节小结#

本节开头提出的问题的参考答案如下。

  1. 为什么要进行内存管理?

    在单道程序系统中,系统一次仅运行一个程序,内存分配极为简单——只需将全部内存(或固定分区)分配给当前进程。引入多道程序设计后,多个进程并发执行并共享主存。若缺乏有效管理,则不同进程的地址空间可能重叠或相互覆盖,导致数据被意外修改,破坏程序正确性,严重影响并发可靠性。因此,为支持多道程序安全、高效地并发运行,必须进行内存管理。

  2. 多级页表解决了什么问题?又会带来什么问题?

    多级页表解决了逻辑地址空间较大时,单级页表过长、占用内存过多的问题。通过分级组织页表,系统只需将当前使用的各级页表驻留内存,从而既减少内存占用,又避免对大块连续内存的需求。然而,采用多级页表也会带来性能开销:在无快表(TLB)的情况下,一次地址变换需逐级访问多级页表,导致多次内存访问,进而增加访存延迟。

无论是段式、页式还是段页式管理,读者只需掌握三个关键问题:

  1. 逻辑地址结构
  2. 页(段)表项结构
  3. 寻址过程

搞清这三点,便掌握了各类存储管理方式的核心机制。再次提醒注意区分逻辑地址结构与表项结构。

评论