操作系统导论:分页
分页:介绍
有时候人们会说,操作系统有两种方法,来解决大多数空间管理问题。第一种是将空间分割成不同长度的分片
,就像虚拟内存管理中的分段
。遗憾的是,这个解决方法存在固有的问题。具体来说,将空间切成不同长度的分片以后,空间本身会碎片化(fragmented)
,随着时间推移,分配内存会变得比较困难。
第二种方法 :将空间分割成固定长度的分片。在虚拟内存中,我们称这种思想为分页。
分页
不是将一个进程的地址空间分割成几个不同长度的逻辑段(即代码、堆、段)
,而是分割成固定大小的单元,每个单元称为一页
。相应地,我们把物理内存看成是定长槽块的阵列
,叫作页帧(page frame)
。每个这样的页帧
包含一个虚拟内存页。
1.1 一个简单例子
图 1.1 展示了一个只有 64 字节的小地址空间,有 4 个 16 字节的页(虚拟页 0、1、2、3)。真实的地址空间肯定大得多,通常 32 位有 4GB 的地址空间,甚至有 64 位
物理内存,如图 1.2 所示,也由一组固定大小的槽块组成。从图中可以看出,虚拟地址空间的页放在物理内存的不同位置。图中还显示,操作系统自己用了一些物理内存。64 字节的地址空间在 128 字节的物理内存中
通过完善的分页方法,操作系统能够高效地提供地址空间的抽象,不管进程如何使用地址空间。例如,我们不会假定堆和栈的增长方向,以及它们如何使用。
另一个优点是分页提供的空闲空间管理的简单性。例如,如果操作系统希望将 64 字节的小地址空间放到 8 页的物理地址空间中,它只要找到 4 个空闲页。也许操作系统保存了一个所有空闲页的空闲列表(free list),只需要从这个列表中拿出 4 个空闲页。
为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置。页表因此具有以下 4 个条目:(虚拟页 0→物理帧 3)、(VP 1→PF 7)、(VP 2→PF 5)和(VP 3→PF 2)。
为了转换(translate)
该过程生成的虚拟地址
,我们必须首先将它分成两个组件:虚拟页面号(virtual page number,VPN)
和页内的偏移量(offset)
。对于这个例子,因为进程的虚拟地址空间是 64 字节,我们的虚拟地址总共需要 6 位(26 =64)。因此,虚拟地址可以表示如下:
在该图中,Va5 是虚拟地址的最高位,Va0 是最低位。因为我们知道页的大小(16 字节),所以可以进一步划分虚拟地址,如下所示:
页面大小为 16 字节,位于 64 字节的地址空间。因此我们需要能够选择 4 个页,地址的前 2 位就是做这件事的。因此,我们有一个 2 位的虚拟页号(VPN)。其余的位告诉我们,感兴趣该页的哪个字节,在这个例子中是 4 位,我们称之为偏移量。
当进程生成虚拟地址时,操作系统和硬件必须协作,将它转换为有意义的物理地址。例如,让我们假设上面的加载是虚拟地址 21:
将“21”变成二进制形式,是“010101”,因此我们可以检查这个虚拟地址,看看它是如何分解成虚拟页号(VPN)和偏移量的:
因此,虚拟地址“21”在虚拟页“01”(或 1)的第 5 个(“0101”)字节处。通过虚拟页号,我们现在可以检索页表
,找到虚拟页 1 所在的物理页面。在上面的页表中,物理帧号(PFN)
(有时也称为物理页号,physical page number或PPN)是7(二进制111)。因此,我们可以通过用PFN替换VPN来转换此虚拟地址,然后将载入发送给
物理内存`(见图 1.3)。
1.2 页表存在哪里
页表可以变得非常大,比我们之前讨论过的小段表或基址/界限对要大得多。
一个 20 位的VPN
意味着,操作系统必须为每个进程管理2^20个地址转换(大约一百万)
,也就是 100 万个虚拟页(VPN)转换为物理页(PFN)。假设每个页表格
条目(PTE
)需要 4 个字节,来保存物理地址转换和任何其他有用的东西,每个页表
(100 万个地址转换的对应信息)就需要巨大的4MB内存
!这非常大。现在想象一下有100个进程
在运行:这意味着操作系统会需要400MB内存
,只是为了所有这些地址转换!即使是现在,机器拥有千兆字节的内存,将它的一大块仅用于地址转换,这似乎有点疯狂。
由于页表如此之大,我们没有在 MMU(Memory Management Unit 内存管理单元)中利用任何特殊的片上硬件,来存储当前正在运行的进程的页表,而是将每个进程的页表存储在内存中。
一小组地址转换例子:内核物理内存中的页表
1.3 页表中究竟有什么
页表就是一种数据结构,用于将虚拟地址(或者实际上,是虚拟页号)映射到物理地址(物理帧号) 因此,任何数据结构都可以采用。最简单的形式称为线性页表(linear page table),就是一个数组。操作系统通过虚拟页号(VPN) 检索该数组,并在该索引处查找页表项(PTE),以便找到期望的物理帧号(PFN)。
至于每个 PTE 的内容,我们在其中有许多不同的位,值得有所了解。有效位(valid bit)通常用于指示特定地址转换是否有效。
我们还可能有保护位(protection bit)
,表明页是否可以读取、写入或执行
。同样,以这些位不允许的方式访问页,会陷入操作系统。
存在位(present bit)
表示该页是在物理存储器还是在磁盘上(即它已被换出,swapped out)。当我们研究如何将部分地址空间交换(swap)到磁盘,从而支持大于物理内存的地址空间时,我们将进一步理解这一机制。交换允许操作系统将很少使用的页面移到磁盘,从而释放物理内存。脏位(dirty bit)也很常见,表明页面被带入内存后是否被修改过。
参考位(reference bit,也被称为访问位,accessed bit)
有时用于追踪页是否被访问,也用于确定哪些页很受欢迎,因此应该保留在内存中。
1.4 分页:也很慢
要获取所需数据,系统必须首先将虚拟地址(21)转换为正确的物理地址(117)。因此,在从地址 117 获取数据之前,系统必须首先从进程的页表中提取适当的页表项,执行转换,然后从物理内存中加载数据。为此,硬件必须知道当前正在运行的进程的页表的位置
要获取所需数据,系统必须首先将虚拟地址(21)转换为正确的物理地址(117)。因此,在从地址 117 获取数据之前,系统必须首先从进程的页表中提取适当的页表项,执行转换,然后从物理内存中加载数据。
为此,硬件必须知道当前正在运行的进程的页表的位置。现在让我们假设一个页表基址寄存器(page-table base register)包含页表的起始位置的物理地址。为了找到想要的 PTE 的位置,硬件将执行以下功能:
一旦知道了这个物理地址,硬件就可以从内存中获取 PTE,提取 PFN,并将它与来自虚拟地址的偏移量连接起来,形成所需的物理地址。
最后,硬件可以从内存中获取所需的数据并将其放入寄存器 eax。程序现在已成功从内存中加载了一个值!
对于每个内存引用(无论是取指令还是显式加载或存储),分页都需要我们执行一个额外的内存引用,以便首先从页表中获取地址转换。工作量很大!额外的内存引用开销很大,在这种情况下,可能会使进程减慢两倍或更多。
1.5 内存追踪
在结束之前,我们现在通过一个简单的内存访问示例,来演示使用分页时产生的所有内存访问。
以下是生成的汇编代码:
第一条指令将零值(显示为 $0x0)移动到数组位置的虚拟内存地址,这个地址是通过取 %edi 的内容并将其加上 %eax 乘以 4 来计算的。因此,%edi 保存数组的基址,而 %eax 保存数组索引(i)。我们乘以 4,因为数组是一个整型数组,每个元素的大小为 4 个字节。
第二条指令增加保存在 %eax 中的数组索引,第三条指令将该寄存器的内容与十六进制值 0x03e8 或十进制数 1000 进行比较。如果比较结果显示两个值不相等(这就是 jne 指令测试),第四条指令跳回到循环的顶部。
版权声明: 本文为 InfoQ 作者【小白钊钊】的原创文章。
原文链接:【http://xie.infoq.cn/article/bfc802314d528bfb4277a6baf】。文章转载请联系作者。
评论 (1 条评论)