type
status
date
slug
summary
tags
category
icon
password
Property
Apr 15, 2023 03:24 PM
有时候人们会说,操作系统有两种方法,来解决大多数空间管理问题。
第一种是将空间分割成不同长度的分片,就像虚拟内存管理中的分段。遗憾的是,这个解决方法存在固有的问题。具体来说,将空间切成不同长度的分片以后,空间本身会碎片化(fragmented),随着时间推移,分配内存会变得比较困难。
因此,值得考虑第二种方法:将空间分割成
固定长度
的分片。在虚拟内存中,我们称这种思想为分页
,可以追溯到一个早期的重要系统,Atlas[KE+62, L78]。分页不是将一个进程的地址空间分割成几个不同长度的逻辑段(即代码、堆、段),而是分割成固定大小的单元,每个单元称为一页。相应地,我们把物理内存看成是定长槽块的阵列,叫作页帧(pageframe)。每个这样的页帧包含一个虚拟内存页。我们的挑战是:关键问题:如何通过页来实现虚拟内存 如何通过页来实现虚拟内存,从而避免分段的问题?基本技术是什么?如何让这些技术运行良好,并尽可能减少空间和时间开销?
一个简单的例子 🌰
在这个例子中,有 8 个页帧(由 128 字节物理内存构成,也是极小的)
为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为
页表
(page table)。页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置。页表是一个
每进程的数据结构
(如果在上面的示例中运行另一个进程,操作系统将不得不为它管理不同的页表,因为它的虚拟页显然映射到不同的物理页面(除了共享之外)。
现在,我们了解了足够的信息,可以完成一个地址转换的例子。设想拥有这个小地址空间(64 字节)的进程正在访问内存:具体来说,注意从地址
<virtual address>
到寄存器 eax
的数据显式加载(因此忽略之前肯定会发生的指令获取)。为了转换(translate)该过程生成的虚拟地址,我们必须首先将它分成两个组件:
虚拟页面号
(virtual page number,VPN
)和页内的偏移量
(offset)。物理帧号
(PFN)(有时也称为物理页号,physical page number 或 PPN)我们可以通过用 PFN 替换 VPN 来转换此虚拟地址,然后将载入发送给物理内存。
偏移量保持不变
(即未翻译),因为偏移量只是告诉我们页面中的哪个字节是我们想要的。
页表存在哪里
页表可以变得非常大,比我们之前讨论过的小段表或基址/界限对要大得多,我们没有在 MMU 中利用任何特殊的片上硬件,来存储当前正在运行的进程的页表,而是将每个进程的页表存储在
内存
中。现在让我们假设页表存在于操作系统管理的物理内存中,很多操作系统内存本身都可以虚拟化,因此页表可以存储在操作系统的虚拟内存中(甚至可以交换到磁盘上)。因此,页表一般放在
内存
中。列表中究竟有什么
页表就是一种数据结构,用于将虚拟地址(或者实际上,是虚拟页号)映射到物理地址(物理帧号)。因此,任何数据结构都可以采用。最简单的形式称为
线性页表
(linear page table),就是一个数组。操作系统通过虚拟页号(VPN)检索该数组,并在该索引处查找页表项
(PTE),以便找到期望的物理帧号(PFN)。现在,我们将假设采用这个简单的线性结构。至于每个 PTE 的内容:
有效位
(valid bit)通常用于指示特定地址转换是否有效。
例如,当一个程序开始运行时,它的代码和堆在其地址空间的一端,栈在另一端。所有未使用的中间空间都将被标记为无效(invalid),如果进程尝试访问这种内存,就会陷入操作系统,可能会导致该进程终止。因此,有效位对于
支持稀疏地址空间
至关重要。通过简单地将地址空间中所有未使用的页面标记为无效,我们不再需要为这些页面分配物理帧,从而节省大量内存。保护位
(protection bit),表明页是否可以读取、写入或执行。同样,以这些位不允许的方式访问页,会陷入操作系统。
存在位
(present bit)表示该页是在物理存储器还是在磁盘上(即它已被换出,swapped out)。
脏位
(dirty bit)也很常见,表明页面被带入内存后是否被修改过。
参考位
(reference bit,也被称为访问位,accessed bit)有时用于追踪页是否被访问,也用于确定哪些页很受欢迎,因此应该保留在内存中。
分页:也很慢
要获取所需数据,系统必须首先将虚拟地址转换为正确的物理地址。因此,在从地址 117 获取数据之前,系统必须首先从进程的页表中提取适当的页表项,执行转换,然后从物理内存中加载数据。
为此,硬件必须知道当前正在运行的进程的页表的位置。现在让我们假设一个页表基址寄存器(page-table base register)包含页表的起始位置的物理地址。为了找到想要的 PTE的位置,硬件将执行以下功能:
一旦知道了这个物理地址,硬件就可以从内存中获取 PTE,提取 PFN,并将它与来自虚拟地址的偏移量连接起来,形成所需的物理地址。具体来说,你可以想象 PFN 被 SHIFT左移,然后与偏移量进行逻辑或运算,以形成最终地址。
对于每个内存引用(无论是取指令还是显式加载或存储),分页都需要我们执行一个额外的内存引用,以便
首先从页表中获取地址转换
。工作量很大!额外的内存引用开销很大,在这种情况下,可能会使进程减慢两倍或更多。
如果不仔细设计硬件和软件,页表会导致系统运行速度过慢,并占用太多内存。内存追踪
补充:数据结构——页表
现代操作系统的内存管理子系统中最重要的数据结构之一就是页表(page table)。通常,页表存储虚拟—物理地址转换(virtual-to-physical address translation),从而让系统知道地址空间的每个页实际驻留在物理内存中的哪个位置。由于每个地址空间都需要这种转换,因此一般来说,系统中每个进程都有一个页表。页表的确切结构要么由硬件(旧系统)确定,要么由 OS(现代系统)更灵活地管理。
一段循环赋值 c 代码:
编译:
反编译后的汇编:
第一条指令将
零值
(显示为$0x0)移动到数组位置的虚拟内存地址,这个地址是通过取%edi
的内容并将其 加上%eax乘以4
来计算的。%edi
保存数组的基址
,而%eax
保存数组索引(i
)。第二条指令增加保存在
%eax
中的数组索引(i++)
第三条指令将该寄存器的内容与十六进制值 0x03e8 或十进制数 1000 进行
比较
(i<1000
)。如果比较结果显示两个值不相等(这就是 jne
指令测试)第四条指令跳回到循环的顶部。
假设一个大小为
64KB
的虚拟地址空间
。我们还假定页面大小
为 1KB
。- 页表:物理地址 1KB(1024)
- 代码段:虚拟地址 1KB,大小 1KB,VPN=1,映射到物理页 4(VPN 1->PFN 4)
- 数组:4000 字节(1000X
4
),int占4字节
,我们假设它驻留在虚拟地址 40000 到 44000(不包括最后一个字节)。(VPN 39 → PFN 7), (VPN 40 → PFN 8), (VPN 41 → PFN 9), (VPN 42 → PFN 10)
当它运行时,
每个指令
将产生两个内存引用:一个访问页表
以查找指令
所在的物理框架,另一个访问指令
本身将其提取
到 CPU 进行处理另外,在
mov 指令
的形式中,有一个显式的内存引用,这会首先增加另一个页表访问
(将数组虚拟地址转换为正确的物理地址),然后时数组访问本身。图 18.7 展示了前 5 次循环迭代的整个过程。
左边虚拟地址
和右边实际物理地址
。小结
- 我们引入了分页(paging)的概念
- 优点:
- 不会导致外部碎片,因为分页(按设计)将内存划分为固定大小的单元。
- 它非常灵活,支持稀疏虚拟地址空间。
- 两个可能的问题:
- 会导致较慢的机器(有许多额外的内存访问来访问页表)
- 内存浪费(内存被页表塞满而不是有用的应用程序数据)
参考
上一篇
《Operatring System:Three Easy Pieces》第十九章 分页:快速地址转换(TLB)
下一篇
《Operatring System:Three Easy Pieces》第十七章 空闲空间管理
- 作者:GJJ
- 链接:https://blog.gaojj.cn/article/blog-20
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。