读书笔记
🛎️《Operating System:Three Easy Pieces》第二十三章 VAX/VMS虚拟内存系统
00 分钟
2023-1-3
2023-3-28
type
status
date
slug
summary
tags
category
icon
password
Property
Mar 28, 2023 12:55 AM
数字设备公司(DEC)在 20 世纪 70 年代末推出了 VAX-11 小型机体系结构。VMS 是软件创新的很好例子,用于隐藏架构的一些固有缺陷。尽管操作系统通常依靠硬件来构建高效的抽象和假象,但有时硬件设计人员并没有把所有事情都做好。在 VAX 硬件中,我们会看到一些例子,也会看到尽管存在这些硬件缺陷,VMS 操作系统如何构建一个有效的工作系统。

内存管理硬件

VAX-11 为每个进程提供了一个 32 位的虚拟地址空间,分为 512 字节的页。因此,虚拟地址由 23 位 VPN 和 9 位偏移组成。此外,VPN 的高两位用于区分页所在的段。该系统是分页和分段的混合体
地址空间的下半部分称为“进程空间”,对于每个进程都是唯一的。
在进程空间的前半部分(称为 P0)中,有用户程序和一个向下增长的堆
在进程空间的后半部分(P1),有向上增长的栈。
地址空间的上半部分称为系统空间(S),尽管只有一半被使用。受保护的操作系统代码数据驻留在此处,操作系统以这种方式跨进程共享。
VMS 设计人员的一个主要关注点是 VAX 硬件中的页大小非常小(512 字节)。由于历史原因选择的这种尺寸,存在一个根本性问题,即简单的线性页表过大。因此,VMS 设计人员的首要目标之一是确保 VMS 不会用页表占满内存。
系统通过两种方式,减少了页表对内存的压力:
  • 首先,通过将用户地址空间分成两部分,VAX-11 为每个进程的每个区域(P0 和 P1)提供了一个页表。因此,栈和堆之间未使用的地址空间部分不需要页表空间。。一个基址寄存器保存该段的页表的地址,界限寄存器保存其大小(即页表项的数量)。
  • 其次,通过在内核虚拟内存中放置用户页表(对于 P0 和 P1,因此每个进程两个),操作系统进一步降低了内存压力。因此,在分配或增长页表时,内核在段 S 中分配自己的虚拟内存空间。如果内存受到严重压力,内核可以将这些页表的页面交换到磁盘,从而使物理内存可以用于其他用途。

一个真实的地址空间

补充:为什么空指针访问会导致段错误 你现在应该很好地理解一个空指针引用会发生什么。通过这样做,进程生成了一个虚拟地址 0: int *p = NULL; // set p = 0 *p = 10; // try to store value 10 to virtual address 0 硬件试图在 TLB 中查找 VPN(这里也是 0),遇到 TLB 未命中。查询页表,并且发现 VPN 0 的条目被标记为无效。因此,我们遇到无效的访问,将控制权交给操作系统,这可能会终止进程(在 UNIX 系统上,会向进程发出一个信号,让它们对这样的错误做出反应。但是如果信号未被捕获,则会终止进程)。
notion image
代码段永远不会从第 0 页开始。相反,该页被标记为不可访问,以便为检测空指针(null-pointer)访问提供一些支持。
内核虚拟地址空间(即其数据结构和代码)是每个用户地址空间的一部分。在上下文切换时,操作系统改变 P0 和 P1 寄存器以指向即将运行的进程的适当页表。但是,它不会更改 S 基址和界限寄存器,并因此将“相同的”内核结构映射到每个用户的地址空间。
内核映射到每个地址空间,如果操作系统收到用户程序(例如,在 write()系统调用中)递交的指针,很容易将数据从该指针处复制到它自己的结构。操作系统自然是写好和编译好的,无须担心它访问的数据来自哪里。
如果内核完全位于物理内存中,那么将页表的交换页切换到磁盘是非常困难的。如果内核被赋予了自己的地址空间,那么在用户应用程序和内核之间移动数据将再次变得复杂和痛苦。通过这种构造(现在广泛使用),内核几乎就像应用程序库一样,尽管是受保护的。
操作系统不希望用户应用程序读取或写入操作系统数据或代码。因此,硬件必须支持页面的不同保护级别才能启用该功能。VAX通过在页表中的保护位中指定CPU访问特定页面所需的特权级别来实现此目的。因此,系统数据和代码被设置为比用户数据和代码更高的保护级别。试图从用户代码访问这些信息,将会在操作系统中产生一个陷阱,并且可能会终止违规进程。

页替换

VAX 中的页表项(PTE)包含以下位:一个有效位,一个保护字段(4 位),一个修改(或脏位)位,为 OS 使用保留的字段(5 位),最后是一个物理帧号码(PFN)将页面的位置存储在物理内存中。敏锐的读者可能会注意到:没有引用位(no reference bit)!因此,VMS 替换算法必须在没有硬件支持的情况下,确定哪些页是活跃的。 开发人员也担心会有“自私贪婪的内存”(memory hog)——一些程序占用大量内存,使其他程序难以运行。到目前为止,我们所看到的大部分策略都容易受到这种内存的影响。例如,LRU 是一种全局策略,不会在进程之间公平分享内存。

分段的 FIFO

防止有“自私贪婪的内存”(memory hog)—— 一些程序占用大量内存, 使其他程序难以运行和在没有硬件支持的情况下,确定哪些页是活跃的。
每个进程都有一个可以保存在内存中的最大页数,称为驻留集大小(Resident Set Size,RSS)。每个页都保存在 FIFO 列表中。当一个进程超过其 RSS 时,“先入”的页被驱逐。决定了进程不能占用过大内存。
纯粹的 FIFO 并不是特别好。为了提高 FIFO 的性能,VMS 引入了两个 二次机会列表( second-chance list,两个队列),页在从内存中被踢出之前被放在其中。具体来说, 是全局干净页空闲列表脏页列表。当进程 P 超过其 RSS 时,将从其每个进程的 FIFO 中移除一个页。如果干净(未修改),则将其放在干净页列表的末尾。如果脏(已修改),则 将其放在脏页列表的末尾。
如果另一个进程 Q 需要一个空闲页,它会从全局干净列表中取出第一个空闲页。但是, 如果原来的进程 P 在回收之前在该页上出现页错误(不在物理内存中,在磁盘中),则 P 会从空闲(或脏)列表中回收,从而避免昂贵的磁盘访问。这些全局二次机会列表越大,分段的 FIFO 算法越接近 LRU

页聚集

通过聚集,VMS将大批量的页从全局脏列表中分组到一起,并将它们一举写入磁盘(从而使它们变干净)。聚集用于大多数现代系统,因为可以在交换空间的任意位置放置页,所以操作系统对页分组,执行更少和更大的写入,从而提高性能。
补充:模拟引用位 事实证明,你不需要硬件引用位,就可以了解系统中哪些页在用。事实上,在 20 世纪 80 年代早期,Babaoglu 和 Joy 表明,VAX 上的保护位可以用来模拟引用位[BJ81]。其基本思路是:如果你想了解哪些页在系统中被活跃使用,请将页表中的所有页标记为不可访问(但请注意关于哪些页可以被进程真正访问的信息,也许在页表项的“保留的操作系统字段”部分)。当一个进程访问一页时,它会在操作系统中产生一个陷阱。操作系统将检查页是否真的可以访问,如果是,则将该页恢复为正常保护(例如,只读或读写)。在替换时,操作系统可以检查哪些页仍然标记为不可用,从而了解哪些页最近没有被使用过。 这种引用位“模拟”的关键是减少开销,同时仍能很好地了解页的使用。标记页不可访问时,操作系统不应太激进,否则开销会过高。同时,操作系统也不能太被动,否则所有页面都会被引用,操作系统又无法知道踢出哪一页。

其他漂亮的虚拟内存技巧

VMS 有另外两个现在成为标准的技巧:按需置零和写入时复制。我们现在描述这些惰性(lazy)优化。

按需置零(demand zeroing)

利用按需置零,当页添加到你的地址空间时,操作系统会在页表中放入一个标记页不可访问的条目。如果进程读取或写入页,则会向操作系统发送陷阱。在处理陷阱时,操作系统注意到(通常通过页表项中“保留的操作系统字段”部分标记的一些位),这实际上是一个按需置零页。此时,操作系统会完成寻找物理页的必要工作,将它置零,并映射到进程的地址空间。如果该进程从不访问该页,则所有这些工作都可以避免,从而体现按需置零的好处。

写时复制(copy-on-write,COW)

如果操作系统需要将一个页面从一个地址空间复制到另一个地址空间,不是实际复制它,而是将其映射到目标地址空间,并在两个地址空间中将其标记为只读。如果两个地址空间都只读取页面,则不会采取进一步的操作,因此操作系统已经实现了快速复制而不实际移动任何数据。
但是,如果其中一个地址空间确实尝试写入页面,就会陷入操作系统。操作系统会注意到该页面是一个 COW 页面,因此(惰性地)分配一个新页,填充数据,并将这个新页映射到错误处理的地址空间。该进程然后继续,现在有了该页的私人副本。
COW 有用有一些原因。任何类型的共享库都可以通过写时复制,映射到许多进程的地址空间中,从而节省宝贵的内存空间。
在 UNIX 系统中,由于 fork()和 exec()的语义,COW 更加关键。fork()会创建调用者地址空间的精确副本。对于大的地址空间,这样的复制过程很慢,并且是数据密集的。更糟糕的是,大部分地址空间会被随后的exec()调用立即覆盖,它用即将执行的程序覆盖调用进程的地址空间。通过改为执行写时复制的 fork(),操作系统避免了大量不必要的复制,从而保留了正确的语义,同时提高了性能。

参考


评论
Loading...