3.6 有关实现的问题 Implementation Issues

3.6.1 Operating System Involvement with Paging 与分页有关的工作

与分页有关的四个阶段:

  1. 进程创建时
  2. 进程执行时
  3. 发生缺页中断时
  4. 进程退出时

进程创建时

  1. 操作系统需要决定进程初始的程序和数据空间有多大,并为他们创建页表。

  2. 操作系统需要在内存中为页表分配空间并完成初始化。

  3. 当进程被换出时,页表不需要保留在内存中,但是当进程运行时,页表必须在内存中。

  4. 操作系统应当在 swap 中分配空间,以便当进程被换出时,swap 有可以容纳该进程的空间。

    不同的操作系统有不同的策略,linux 默认不在 swap 中分配空间,可能发生 oom 导致进程被杀死。

  5. 操作系统应当用程序正文和数据空间对 swap 进行初始化,当进程发生缺页中断时,可以调入需要的页面。

    传统操作系统在创建进程时,先将其加载到 swap 中(完成初始化,并且在 swap 中保留该进程的空间),然后再将其加载到内存中。

  6. 部分操作系统可以直接从磁盘上的可执行文件对程序正文进行分页,以节省磁盘空间和初始化时间。

    现代操作系统就是这样做的,直接从磁盘上的可执行文件做映射,无需复制到 swap

  7. 操作系统必须把所有有关页表和磁盘 swap 的信息存储在进程表中。

调度进程执行时

重置 MMU 和 TLB,加载新进程的页表。在进程初始化时,可以把部分或者全部页面加载进内存,减少缺页中断的次数。

发生缺页中断时

  1. 读取硬件寄存器,找到发生缺页中断的虚拟地址,计算对应的物理地址,在磁盘上定位对应的页面。
  2. 寻找可用的页帧,如果有必要,可以使用页面替换算法换出部分老页面,然后加载需要的页面。
  3. 回退 PC 计数器,重新执行程序。

进程退出时

  1. 操作系统必须释放进程的页表、页面和页面在磁盘上占用的空间。
  2. 如果有共享的页面,只有最后一个进程退出时,才能释放内存和磁盘空间。

3.6.2 Paging Fault Handling 缺页中断处理

  1. 硬件陷入内核,在栈上保存 PC 寄存器。大多数机器将当前指令的各种状态信息保存在特殊的 CPU 寄存器。
  2. 启动一个汇编代码程序保存通用寄存器和其他易失信息,避免操作系统被破坏。
  3. 操作系统需要确定发生中断的虚拟页面,一般来说一个硬件寄存器包含了这个信息,如果没有的话,需要读取 PC 寄存器分析出虚拟地址。
  4. 得知发生中断的虚拟地址后,操作系统检查地址是否有效以及访问保护是否一致。如果异常(访问了无效的地址或者尝试写一个只读的页面),操作系统向进程发送对应的信号杀死进程。否则,系统寻找空闲页帧或者执行页面替换算法。
  5. 如果被选择的页帧是一个脏页,需要将其写回磁盘。此时会发生一次上下文切换,挂起中断处理程序,调度其他进程先运行,直到磁盘 IO 结束。在此期间,被选中的页框会被标记为繁忙,防止被其他进程占用。
  6. 一旦页框清理完成,操作系统找到所需页面的磁盘地址,调度磁盘 IO 加载页面到内存。IO 过程中,产生缺页中断的进程仍然被挂起,如果有其他可运行的用户进程,则选择另一个用户进程运行。
  7. 磁盘中断发生后,说明页面加载完毕,页表更新为,页帧被标记为正常状态。
  8. 恢复发生缺页中断的以前的状态,PC 寄存器重新指向这条指令。
  9. 调度引发缺页中断的进程,操作系统返回调用它的汇编程序。
  10. 汇编程序恢复寄存器和其他状态信息,返回用户空间继续执行。

3.6.3 Instruction Backup 指令备份

考虑一个指令 MOVE.L#(A1), 2(A0) 占用 6 个字节,如下:

为了重启这条指令,操作系统必须知道指令的起始地址,但是引发 page fault 时 PC 寄存器的值不确定,可能是指令引发的也可能是操作数引发的,所以操作系统无法确定指令的起始位置。

在每条指令执行前,将 PC 寄存器中的内容复制到一个内部隐藏的寄存器中,这样在恢复指令时就可以找到指令的起始位置了。

3.6.4 Locking Pages in Memory 锁定内存中的页面

  1. 操作系统为 DMA 分配页面作为缓冲区
  2. 设备开始 DMA 传输
  3. 恰好全局策略下的页面替换算法将缓冲区的页面换出
  4. DMA 继续写入数据到缓冲区
    DMA 将数据写入了其他进程的页面,导致数据损坏。

解决方案:

  1. pinning:锁定 IO 缓冲区,确保 IO 期间页面不会被换出
  2. 内核缓冲区:在内核缓冲区中完成 IO 操作,然后将数据拷贝到用户空间

3.6.5 Backing Store 后备存储

在磁盘上划分一个分区,直接映射物理地址,不安装任何文件系统,消除了文件系统的开销(将文件偏移转为物理地址)。

当系统启动时,交换分区为空,内存中保留分区的起始和大小。进程启动时,在交换区中保留一个和这个进程相同大小的交换分区,进程结束后释放这个空间。

进程表中记录该进程交换区的磁盘地址,页面在交换区中的地址 = 进程在交换区中的起始地址 + 页面在虚拟地址空间的偏移量。但是在进程启动时,必须初始化交换区。

  1. 进程启动时,将整个进程映像复制到 swap 中,以便随时将页面换入
  2. 进程启动时,将整个进程装入内存,在需要时换出

笔者不是很明白为什么需要去初始化,像是历史原因。唯一能想到的好处是,每个进程都有对应的 swap 空间,不会出现需要换出页面但是没有空间的问题。在现代操作系统中,应当是按需分配,只要需要换出页面时,才在 swap 中为其分配空间。

进程在启动后,占用的内存空间会不断增大,对应的在 swap 的空间也要随之扩增,应当为程序、数据、堆栈各自划分 swap,允许这些swap 分区在磁盘上的占用多余一个块。

如果在程序启动时,不做任何初始化,只在页面换出时为其分配空间,那么就需要在内存中记录每个页面对应的磁盘地址,好处是进程不必固定于任何 swap。

在特殊情况下,可能 swap 不可用或者空间不够,可以利用文件系统中的事先定位的文件。

在 windows 中,可以利用进程对应的可执行文件,当需要换出程序页面时,只需要丢掉即可,需要换入时再从磁盘文件中读取。

3.6.6 Separation of Policy and Mechanism 策略和机制的分离

将策略从机制中分离出来,使得用户态进程可以参与一部分内存管理。

以 Mach 为例,Mach 的内存管理主要分为以下三个部分:

  1. MMU

    与机器相关的代码,负责地址转换

  2. 内核的缺页中断处理程序

    平台无关代码,负责处理缺页中断,包含大部分分页机制

  3. 用户态的页面调度程序

    部分内存管理策略

内核捕获缺页中断,发送消息给用户态页面调度程序,然后由面调度程序从磁盘中找到需要的页面并加载到自己的地址空间中,然后通知缺页中断处理程序页面的位置。缺页中断处理程序从页面调度程序的地址空间中取消该页面的映射,然后请求 MMU 将页面放到用户地址空间中正确的位置,之后重新启动用户进程。

页面替换算法的位置:

  1. 用户态的页面调度程序

    无法访问页面的 RW 位,需要某种机制提供页面的信息。

  2. 内核的缺页中断处理程序中

    只用告诉页面调度程序换出和载入的页面

这中策略和机制分离的内存管理模式

  • 优点:模块化和扩展性
  • 缺点:内核态和用户态,系统模块之间的通信开销
posted @ 2025-12-16 00:25  DantalianLib  阅读(5)  评论(0)    收藏  举报