操作系统(八):内存管理

整理《Operating System Concepts》 第七版第八章内存管理部分,内容均为原书和中文版翻译的摘录,其中原书摘录部分由我 按个人理解简化、翻译为中文,可能存在一些不准确之处

背景

基本硬件

  • CPU 通常在一个 CPU 时钟周期内完成对内置寄存器的访问,对内存的访问需多个 CPU 时钟周期,在缺少数据来完成正在执行的指令时,CPU 需要 暂停(stall) ,通常在 CPU 和内存之间添加高速缓存(cache)以协调速度差异。
  • 要确保操作系统不被用户进程访问、用户进程不被其他用户进程访问,需要每个进程拥有独立的地址空间。通过 基地址寄存器(base register)界限地址寄存器(limit register) 可对进程内存加以保护,基地址寄存器存储进程可访问的最低合法 物理 内存地址,界限地址寄存器决定从该地址开始的,可访问的内存范围大小。CPU 硬件对 用户模式 下产生的 每一个 地址和寄存器指明的地址区间比较,用户模式下的程序试图访问操作系统/其他用户进程内存的行为会触发陷阱(trap),操作系统会将其视为致命错误处理。
  • 基地址寄存器和界限地址寄存器能且仅能由操作系统的特权指令加载,因为仅有操作系统在内核模式下执行,故避免了用户程序修改这两个寄存器。

地址绑定

  • 程序执行前需将所需部分二进制数据调入内存并置于进程空间内,执行过程中根据操作系统采用的内存管理方案可能还会有内存/硬盘之间的换入换出移动。在磁盘上等待调入内存的进程构成了 输入队列(input queue)
  • 多数系统允许用户进程存放在物理内存任意位置,原程序中的地址通常用符号来表示, 编译器 会将这些符号地址 绑定(bind) 到可重定位的地址, 链接程序加载程序 再将这些可重定位的地址绑定成绝对地址。绑定是从一个地址空间到另一个地址空间的映射。将指令和数据绑定到内存地址可能有如下几种情况:
    • 编译时(compile time) :在编译时已经知道进程将在物理内存中的驻留地址,则可生成 绝对代码(absolute code)
    • 加载时(load time) :若编译时尚不了解进程将位于物理内存中具体哪个位置,则编译器需生成 可重定位代码(relocatable code) ,如 “从这个模块开始的第 14 个字节”。此时,最后绑定延迟到加载时进行。
    • 执行时(execution) :若进程在执行时可在内存段间移动,则绑定需要在执行时进行,此种方案需要特定硬件,绝大多数通用操作系统采用这种方式。
  • 从源码到二进制内存镜像的过程如下图所示,图片截取自李文生老师《操作系统概念》PPT。

逻辑地址和物理地址

  • CPU 生成的地址为 逻辑地址(logical address) ,加载到 内存地址寄存器(memory-address register) 中的地址为 物理地址(physical address) 。编译时和加载时的地址绑定方法生成的逻辑地址和物理地址相同,执行时的地址绑定方案生成的逻辑地址和物理地址不同。通常称逻辑地址为 虚拟地址(virtual address)
  • 逻辑地址空间(logical address space) 为由程序所生成的所有逻辑地址的集合, 物理地址空间(physical address space) 为与这些逻辑地址对应的物理地址集合。
  • 运行时从虚拟地址到物理地址的映射由硬件设备 内存管理单元(memory-management unit,MMU) 完成。一个最简单的 MMU 方案是将用户进程产生的地址加上 重定位寄存器(relocation register) 的值作为最终的物理内存地址。
  • 用户进程绝不会看到真正的物理地址,一个地址在内存中比较、使用均基于虚拟地址,只有该地址作为内存地址,如执行加载/存储时才需要做到物理空间的地址映射。总而言之,用户进程只产生逻辑地址,且认为地址空间从 0 开始,而使用对应内存地址前必须由 MMU 做物理地址映射。

动态加载、链接与共享库

  • 动态加载(dynamic loading) :所有子程序均以可重定位的方式保存在磁盘上,仅主程序装入内存并执行,当且仅当某个子程序被需要时才会装载进内存。此种方法设计程序主要是用户程序开发者的责任。其优势在于不被使用的子程序绝不会加载,若程序中有较多代码用于处理异常,动态加载会特别有效。
  • 动态链接库(dynamically linked library) :部分操作系统只支持 静态链接(static linking) ,即加载程序将操作系统提供的语言库与其他目标模块一起合并到最终的二进制程序镜像中,所有程序均有一份所需系统库的副本;而动态链接将系统库的加载延迟到运行时,用户程序对系统库的引用留有 存根(stub) ,存根指用于定位内存驻留库程序的一小段代码。当执行存根时,若所需的系统库已经驻留在内存中,则存根使用已有的系统库,否则将系统库装入内存。最终存根均会将系统库地址替换自身并执行系统库,此时所有使用某个库的进程只需要一个库代码副本。
  • 动态链接为库更新带来方便,库版本更新后,引用该库的程序会自动使用新版本而无需重新链接。多个版本的库也可以同时装入内存,程序根据自己所需的版本信息确定使用哪个副本。此类系统也称为 共享库(shared libraried)

交换

  • 内存中的进程可暂时从内存中 交换(swap)备份存储(backing store) 上,等到再次需要执行时调回。有时称为 滚出(roll out)滚入(roll in)
  • 备份存储通常为足够大的快速磁盘,以容纳所有用户程序的内存镜像副本、并提供对内存镜像的直接访问。交换系统上下文切换时间较长,为有效使用 CPU,通常使每个进程每次执行获取的时间片比交换时间长。
  • 为了 只交换用户进程真正使用的内存空间 (如一个用户进程当前可能只使用了 10MB,但其最多可能使用 200 MB),用户进程需要告诉系统其内存需求情况以减少交换时间。
  • 换出进程时,进程必须完全处于空闲状态。考虑如下场景:一个正在等待 I/O 操作的进程 P 即将被换出,若 I/O 操作异步访问 P 进程内存中的缓冲区,或是 I/O 设备正忙,I/O 操作在排队等待,此时换出进程 P,换入进程 P’ 会导致 I/O 操作已经属于 P’ 的内存。解决方案:
    • 不能换出有待处理 I/O 的进程
    • I/O 操作只能使用操作系统缓冲区,仅当进程在内存中执行时才发生操作系统缓冲和进程内存缓冲之间的数据转移
  • 上述标准交换在目前的操作系统中使用不广泛,交换需要很长时间并且只能提供很少的执行时间。对上述交换方式的一种修正在很多 UNIX 系统中得到使用:当且仅当系统负荷过高,内存吃紧时进行交换。早期缺乏高级硬件的个人计算机通过此种交换可同时运行多个进程,如 MS Windows 3.1,该系统内存不足时将老进程交换到磁盘上,当且仅当用户再次选择执行该进程时才再次换入。

覆盖(补充)

  • 多道程序设计环境下可通过 覆盖(Overlays) 技术扩充内存。由程序员实现,不需要操作系统的特殊支持。其思想为:将程序划分为若干个功能上相对独立的程序段,按程序逻辑结构让不会同时执行的程序段共享所有程序段均会使用到的内存。
  • 覆盖方式的内存管理仅始终保留一个程序 在任何时候 都需要的数据。
  • 与交换的区别:
    • 覆盖对程序员要求较高,程序员必须十分清楚程序的逻辑结构,明确规定各程序段的执行和覆盖顺序以及设计实现覆盖驱动模块,而交换技术对用户透明
    • 覆盖在同一进程/作业内进行,而交换在进程与进程之间进行。

连续内存分配

内存保护

  • 内存分为操作系统驻留区域和用户进程驻留区域,操作系统通常位于低内存(因为中断向量通常位于低内存)。 连续内存分配(contiguous memory allocation) 可使内存中每个进程占有连续的内存区域。
  • 通过重定位寄存器和界限地址寄存器可实现内存保护,重定位寄存器含有最低 物理地址 值,界限地址寄存器含有 逻辑地址 的范围值。重定位寄存器机制也允许操作系统动态改变,若某操作系统服务(如某个驱动程序)不常使用,则内存中不必保留其代码和数据,这类代码称为 暂时(transient) 操作系统代码,它们根据需要调入/调出,可在程序执行时动态改变操作系统大小。重定位寄存器和界限地址寄存器的硬件支持如下图所示。

内存分配

  • 最简单的内存分配方法:将内存分为多个 固定 大小的 分区(partition) ,每个分区只能容纳一个进程,多道程序的程度受分区数限制。当一个分区空闲时可以从输入队列选择一个进程调入空闲分区,进程终止时释放该分区。此种方式最初被 MFT(F:Fixed,IBM OS/360)使用,目前已被淘汰。
  • 对 MFT 的推广是 MVT(V:Variable),即 可变分区(variable partition) 方案。操作系统维护一个表以记录哪些内存可用和哪些内存已被占用。初始时所有内存均可用于用户进程,可视作一大块可用内存,称为 孔(hole) 。新进程到来时需要查找足够大的孔,并从该孔为进程分配所需内存,孔内剩余内存可分配给其他进程。随着进程的到来和离开,内存中会分散着大小不同的孔。进程终止时释放的内存会形成新孔,若新孔和其他孔相邻,则这些相邻的孔可以合并为一个大孔。此时系统可以检查是否有进程在等待分配内存空间,以及新合并的内存空间是否满足该进程的需求。
  • 上述 MVT 方法是通用 动态存储分配问题(dynamic storage allocation problem) 的一种情况,从内存中的一组孔中选择一个空闲孔有如下三种常用方法,其中执行时间、空间利用方面最差适应方法最差,空间利用方面首次适应和最佳适应相近,但首次适应更快:
    • 首次适应(First fit) :分配寻找到的第一个足够大的孔,查找可以从任何位置(如内存开始位置或上次首次适应结束的位置)开始,一旦找到足够大的空闲孔就停止;
    • 最佳适应(Best fit) :分配 最小 的足够大的孔,此方式必须查找整个表(若表内孔位置记录按孔的大小排序则不需要查找整个表);
    • 最差适应(Worst fit) :分配 最大 的孔,同样需要查找整个表,产生的孔比最佳适应方法产生的孔价值更大。

碎片

  • 随着进程装入和移出内存,空闲内存空间被分为散落的小段,产生 外部碎片问题(external fragmentation) :所有可用内存空间之和满足一个/多个进程的请求,但这些可用内存并不连续。上述首次适应和最佳适应两种不同方法导致的碎片的数量也不同,对于不同的系统两者各有优劣,分配方向(从空闲块的顶端还是模块开始分配内存)也会对碎片数量产生影响。
  • 50% 规则 :对采用首次适应方法的统计表明,假定有 N 个块已被分配,无论采用什么优化,都可能有 0.5N 个块为外部碎片,即 1/3 的内存无法被使用。
  • 维护一个小孔的开销比小孔本身可能更大,例如一个需要 2046B 空间的进程被分配了大小为 2048B 的孔,剩余的 2B 小孔维护的开销比 2B 大得多。因此通常采用 固定大小的块 而不是字节作为分配单元。此时进程被分配的空间通常大于所需空间,分配给进程的块中使用不到的空间被称为 内部碎片(internal fragmentation)
  • 解决外部碎片问题的方法:
    • 紧缩(compaction) :移动内存内容使所有空闲空间合并为一整块。紧缩仅可在重定位是动态的、且在运行时重定位的情况下可用。紧缩根据采用的合并算法不同,需要的开销大小也不同,最简单的合并算法将所有进程移动到内存的一端,空闲的孔移动到内存另一端以生成大孔,其开销较大;
    • 允许一个进程占有的内存地址空间非连续,只要有物理内存就可以为进程分配:分页、分段、分段+分页。

分页

分页(Paging) 允许进程物理地址空间非连续,内存和备份存储均按页划分,页大小相同。在前述连续内存分配中,内存中的数据换入/换出到备份存储时,备份存储中也会存在类似的碎片问题,而分页避免了这一点。传统的分页由硬件处理,最近的设计(64位机)结合了硬件和操作系统。 在第八章中,需要假定自己尚未了解第九章的内容,并且进程在执行前已经把所需要的全部数据分页加载到内存中

分页方法

  • 将物理内存分为固定大小的块,称为 帧(frame) ,将逻辑内存分配同样大小的块,称为 页(page) 。备份存储页分为同样固定大小的块,进程执行时将执行所需的页从备份存储中调入可用的内存帧中。其硬件支持如下图所示。
  • CPU 生成的每个逻辑地址分为两部分: 页号(page number)页偏移(page offset) ,记为 pd 。页号为 页表(page table) 的索引,页表包含每页位于物理内存的基地址,页 p 在页表中对应的基地址加上页偏移量 d 即为该逻辑地址映射的物理地址。
  • 页大小由硬件决定,通常为 2 的次幂,根据计算机结构不同,每页大小从 512B ~ 16MB 不等。页大小为 2 的幂可直接将逻辑地址的 2 进制表示划分为 pd
  • 分页也是一种动态重定位,每个逻辑地址由分页硬件绑定为对应的物理地址。采用分页技术不会产生外部碎片,但可能有内部碎片(进程所需内存不足一页,或要求的内存大小不是页的整数倍则最后一帧有内存空闲)。
  • 目前页大小通常为 4 ~ 8KB,有的系统支持更大页,有的 CPU 内核支持多种页大小。页的大小受如下因素制约:
    • 在进程大小和页大小无关的前提下,可以假设每个进程平均有半页内部碎片,因此更小的页会带来更少的内部碎片;
    • 页表对于页和物理内存中的帧的对应关系记录需要一定开销,并且该开销随着页大小的增大而减小,页表中每个条目通常占 4B(可变)。
  • 分页的重要特点是 用户视角内存实际物理内存 的分离,通过地址转换硬件将用户视角下的逻辑地址转换为物理地址。用户程序将内存作为一整块处理,而实际物理内存中进程可能分布在各个独立的帧中。用户进程无法访问其页表规定之外的内存,进程可见的页表仅包含进程拥有的页面记录。
  • 操作系统使用 帧表(frame table) 维护物理内存的分配细节(已被占用的帧、可用帧等),帧表的每个条目对应一帧,并标明该帧是否空闲,若占用则被哪个(些)进程的哪个页占用等。操作系统同时 为每个进程维护一个页表的副本 ,当一个进程可被分配 CPU 时,CPU 调度程序可根据该副本定义硬件页表(用户进程运行在用户模式,若进行系统调用,操作系统需要使用进程的页表副本来获取进程逻辑地址映射的物理地址)。

硬件支持

  • 最简单的页表硬件实现方法将页表作为一组专用寄存器。
  • 当代计算机允许页表非常大,因此页表放置在内存中,并设置 页表基寄存器(page-table base register,PTBR) 指向页表,改变页表的位置仅需要修改此寄存器。此种做法的缺陷在于访问一个字节需要两次内存访问(一次用于在内存的页表中查找页号对应的条目,一次用于获取目标字节)。
  • 转换表缓冲区(translation look-aside buffer,TLB) 是针对上述问题的专用快速硬件缓冲(关联存储器),其条目由键和值组成。TLB 查找速度快且造价昂贵,通常仅有 64 ~ 1024 个条目。TLB 和页表一起使用时,TLB 仅包含最近使用过的页表条目,查询流程如下:
    • CPU 产生逻辑地址,从逻辑地址提取出页号交付 TLB,TLB 将页号和存储的键比对,若寻找到相同键则结束流程;
    • 请求的页码不在 TLB 中,即 TLB 失效(TLB miss) ,此时需要在页表中查询。在页表中查询到与页号对应的帧号后,将页号和帧号增加到 TLB 中。若 TLB 中条目已满,则操作系统使用某种策略替换掉已有的一个条目,例如 最近最少使用替换(LRU) 或随机替换等。TLB 中有的条目是永久驻留的(不允许从 TLB 中被替换),通常内核代码在 TLB 中的条目固定。
  • 有的 TLB 在每个 TLB 条目中存储了 地址空间标识符(address-space identifier,ASID) ,该项用于唯一标识进程,为进程提供地址空间保护。TLB 解析虚拟页号时必须确保当前运行进程的 ASID 和 TLB 中条目对应的 ASID 匹配,否则视作 TLB 失效。除了内存空间保护,ASID 还使 TLB 能够同时包含多个不同进程的记录。如果 TLB 不支持每个条目有独立的 ASID,那么一旦有新页表被选择(例如进程的换入/换出),TLB 就需要被全部 刷新(flushed) 或删除,防止 TLB 中存在无效的条目(页号地址无效的条目,如上一个进程留下来的无效物理地址)导致被换入的进程使用错误的地址转换。
  • 页号在 TLB 中被查找到的百分比为 命中率(hit ratio)有效内存访问时间(effective memory-access time) 的计算需要根据 TLB 的命中率加权。例如查找 TLB 需要 20ns,内存访问需要 100ns,命中率 80%,则有效内存访问时间为 0.8 x 120 + 0.2 x 220 = 140ns 。需要注意的是, TLB 查询早于内存中页表的查询,只有 TLB 查询结束并且没有查询到帧号时才会开始在页表中的查询 ,此前计算机组成原理中讲过的 TLB 有错误。

保护和共享

  • 分页情况下内存保护通过每个帧对应的保护位实现,这些保护位通常保存在页表中。常见类型的位有:
    • 可读写/只读位:产生地址引用时除了在页表中查找对应的帧码,还需要检查保护位验证是否有对只读页进行了写操作,若有则向操作系统产生硬件陷阱。扩展这种方法可提供更细致的保护。
    • 有效/无效位:该位有效表示与之相关的页属于当前进程的逻辑地址空间,为合法页,否则与之相关的页不属于当前进程的逻辑地址空间。使用该位可以捕获到非法地址,操作系统通过对该位的设置可允许/禁止进程对某页的访问。
  • 一个进程很少会使用到所有分配的地址空间,页表为地址范围内的所有页都建立一个条目是浪费的行为(并且每个进程有一个页表副本),表中的多数条目不会被使用,而这些条目却占据可用地址空间。有些系统提供了 页表长度寄存器(page-table length register,PTLR) 表示页的大小,该寄存器的值可用于验证逻辑地址是否处于进程的有效范围内。
  • 分页存储可以 共享 公共代码,这对于分时系统非常重要。例如一个多用户系统,每个用户均执行一个文本编辑器,若代码不支持共享,则每个用户需要维护一个文本编辑器的副本;若代码是 可重入代码(reentrant code)纯代码(pure code) ,则这部分代码可以被共享。可重入代码是不能自我修改的代码,它们在执行期间从不改变因此多个进程可以同时执行这部分代码。当然,除了共享的代码,每个进程还有自己的寄存器副本和数据存储。要实现共享,代码必须能够重入,并且可重入代码的只读性需要操作系统强制实现。(如果你了解 Haskell,可重入代码和 Haskell 里的 pure code 有类似的特性,后者不会对环境产生副作用,每次执行仅根据参数确定结果)

页表结构

  • 层次页表(Hierarchical Paging) :当代计算机支持非常大的逻辑地址空间,此时页表本身将非常大。例如 32 位逻辑地址空间的计算机系统,若页大小为 4KB,则页表需要包含 2^20 个条目,即使每个条目在页表中仅需要 4B 存储,整个页表也需要 4MB 物理地址空间存储(每个进程还需要独立维护一个副本)。因为内存采取分页管理,页表的大小超过了一个页面的大小,因此需要将页表划分到足够小以便一页能够容纳。一种可行的方式是二级分页算法:例如上述 32 位逻辑地址系统,可将 20 位页号划分为 10 位的外部页表页码 p1 和 10 位的页表偏移量 p2,其具体的映射方式如下图所示。此种方案也称为 向前映射页表(forward-mapped page table) 。对于 64 位体系结构,层次结构并不合适,例如 64 位 UltraSPARC 体系结构使用 7 级分页,几乎已经是内存访问极限。
  • 哈希页表(Hashed Page) 以虚拟页码作为哈希值,每个条目包括一个链表用于处理碰撞。链表中的每个元素包含三个域:虚拟页码,对应帧号以及指向链表中下一个元素的指针。此种方式的地址映射过程如下:将虚拟页号哈希到表中某个条目,若该条目对应的链表存在元素,则按顺序比较直到找到对应的元素。哈希页表的一个变种是 群集页表(clustered page table) ,它的每个条目包括多页信息,因此一个条目存储了多个物理页帧的映射,这对于 稀疏(sparse) 地址空间非常有效,稀疏地址空间种的地址引用通常不连续并且散布在整个地址空间。
  • 反向页表(inverted page table) :每个进程均维护一个相关页表,这个进程使用到的每个页在其持有的页表里有一项,或者每个虚拟地址在页表里都有一项而不论这个虚拟地址是否有效,此时每个页表会有很多项,这些表会消耗大量的内存,而其目的仅仅是追踪物理内存如何使用。反向页表中,每个真实的内存页/帧存在一个条目,该条目包括引用该物理帧的虚拟页号以及拥有该页的进程信息。所以整个系统只有一个页表,每个物理内存帧仅有一条相应的条目。
  • 一种简化的反向页表实现(IBM RT 采用):系统每个虚拟地址对应一个三元组 <pid | page numbe r | offset>,反向表中每个条目为 <pid | page number>,需要内存引用时,操作系统查找反向页表寻找匹配,若匹配找到则产生物理地址,否则认为产生了非法地址访问。这种方案减少了存储每个页表所需的内存空间,但增加了查找页表所需要的时间。反向页表按照物理地址排序,而查找依据虚拟地址,所以可能需要查找整个表来寻找匹配。可以使用 哈希页表 限制页表条目或加入 TLB 来改善。此外,采用反向页表的系统很难共享内存,因为每个物理帧只对应一个虚拟页条目。对于这种情况,可以允许页表的没一个条目仅包含一个虚拟地址到共享内存地址的映射,这时对未被映射虚拟地址的引用会导致页错误(page fault)。

分段

  • 采用分页管理导致用户视角的内存和实际物理地址内存分离,对于装载/写入操作必须将虚拟内存映射到实际的物理内存。 分段(segmentation) 支持另一种用户视角,其逻辑地址空间由一些段组成,每个段有自己的编号和长度,用户通过段号和段内偏移(与分页的区别在于,分页管理中用户只指定一个虚拟地址,由硬件将虚拟地址拆分为页码和偏移,这些工作对程序员透明)来指定地址。
  • 编译用户程序时,编译器会自动根据输入的程序源码构造段(代码段、静态区、堆、栈等),编译时链接的库可能被分配不同的段,加载程序时装入这些段并分配段号。
  • 用户可以通过二维地址(段号、偏移)来引用存储单元,但实际物理内存仍然为一维的序列。操作系统通过 段表(segment table) 实现将二维用户定义地址映射到一维物理地址,段表的每个条目对应一个段,存储着该段的段号、段基地址(段在内存中开始的物理地址)和段界限(段的长度),对于某段超出段长的地址引用访问会导致硬件陷阱的触发。
  • 段表在内存中的位置由 段基址寄存器(segment table base register,STBR)段长度寄存器(segment table length register,STLR) 指定,当段号 s 满足 s < STLR 时该段合法。
  • 分段会导致不连续的空闲内存空间以及外部碎片,从空闲内存为进程分配段也存在类似 连续内存分配 中的问题,有首次适应和最佳适应两种分配方式。
  • 段可以被共享,多个进程可以通过相同段号共享同一个段。段的保护可通过类似页表中的保护位实现,段表中的每一条条目有一个合法位(为 0 表示不合法),同时也可以支持读/写/执行权限。

实例:Intel Pentium

  • 奔腾结构允许一个段的大小最大为 4GB,每个进程最多可持有 16K 个段。进程逻辑地址空间分为两部分,第一部分最多由 8K 个段组成,此部分私有;第二部分最多由 8K 个段组成,此部分可以被所有进程共享。第一部分的信息保存在 本地描述符表(local descriptor table,LDT) 中,第二部分信息保存在 全局描述符表(global descriptor table,GDT) 中,这两个表中每个条目占 8B 空间,包括一个段的详细信息(段基址、段界限)。
  • 逻辑地址格式: <selector | offset>,其中选择器(selector)是一个 16 位的数,前 13 位表示段号,第 14 位表示该段在 GDT 还是 LDT 中,最后两位表示保护信息,支持四级保护。偏移量(offset)是 32 位的数,表示段内偏移量。奔腾 CPU 中有 6 个段寄存器,允许一个进程同时访问 6 个段,同时还有 6 个 8B 微程序寄存器保存相应的来自于 LDT 或 GDT 的描述符,它们使奔腾不必在每次内存引用时从内存读取描述符。
  • 奔腾结构允许页的大小为 4KB 或 4MB,对于 4KB 的页,奔腾使用二级分页方案,32 位线性地址划分为 10、10、12 三块,类似前述的二级分页的例子。最高 10 位引用 页目录(page directory) 的条目,中间 10 位指向内部页表,最后 12 位为 4KB 页面内的偏移。页目录中每个条目有一个 PageSize 标志,若该标识被设置则代表页帧大小 4MB,此时跳过中间 10 位对内层页表的查询,直接使用后 22 位指向 4MB 页帧内偏移。奔腾的页表还可以交换到磁盘上,通过页目录条目的无效位表示该条目对应的页表位于内存还是磁盘。若页表在磁盘上,则可使用条目中剩下的 31 位标明页表在磁盘的具体位置以调入内存。
  • 奔腾体系结构上运行的 Linux 系统使用 6 个段(内核代码段、内核数据段、用户代码段、用户数据段、任务状态段 TSS 以及默认的 LDT 段)。Linux 中默认的 LDT 段被所有进程共享,如果一个进程需要自己的 LDT ,则它可以生成一个新的 LDT 来代替默认值。此外,每个进程有自己的 TSS 以存储上下文切换中的硬件上下文,每个进程也有自己的页表。
  • 奔腾体系结构上运行的 Linux 仅使用了四级保护中的两种,用于区分内核模式和用户模式。Linux 采用三级分页方案(全局目录-中间目录-页表-偏移),而奔腾采用二级分页模式,此时 Linux 的 “中间目录” 大小为 0,因此等价于奔腾的二级分页。

专栏目录:计算机理论基础
此专栏的上一篇文章:操作系统(七):死锁
此专栏的下一篇文章:操作系统(九):虚拟内存

参考资料:《操作系统概念 英文第七版》,恐龙书,英文名《Operating System Concepts》,作者 Abraham Silberschatz、Peter Baer Galvin、Greg Gagne

原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章原始出处(http://blog.forec.cn/2017/01/03/os-concepts-8/) 、作者信息(Forec)和本声明。

分享到