简单的分页模型

分页机制是 80x86 内存管理机制的第二部分。它在分段机制的基础上完成虚拟(逻辑)地址到物理地址的转换。为了理解分页机制,本文介绍一个简单的分页模型,虽然简单,但是对理解真正的分页模型非常有帮助。咱们开始吧!

在单纯的分段模式下,线性地址就是物理地址。

比如下面的汇编语句:

mov edx [0x1008]

这是要把内存中某个位置的值赋给 EDX,但究竟是内存的哪个位置呢?这就要看数据段描述符了。

假设描述符中的段基地址为 0x0020_0000,界限值为 0x2007,段的粒度是字节 ,那么该段的最大长度就是 0x2008(=8200)。

当访问内存的时候,用段基地址加上段内偏移 0x1008,形成线性地址 0x0020_1008,在没有开启分页的情况下,这就是物理地址。

简单的分页模型_第1张图片

一旦采用分页式内存管理,就应该把物理内存分成大小相同的页。注意,页在物理内存中的位置是有讲究的,并不是在内存中随便找个位置,说:“来 ,页从这里开始!”

事实上,页的单位一般是 4KB,即 4096 (=0x1000)字节。而且,页的起始地址和 4KB 对齐,所以,第 1 个页的物理地址是 0x0000_0000,第 2 个页的物理地址是 0x0000_1000,第 3 个页的物理地址是0x0000_2000,最后一个页的物理地址是 0xFFFFF000。

这样,可以将 4GB 的物理内存划分为 1048576 (0x 100000) 个页。很显然,页的物理地址,其低 12 位始终为全零。

问题在于,如何将长度不一的段 ,映射到大小相同的页面上呢?

内存的分配涉及段空间的分配和页的分配。如下图所示,左边是虚拟的 4GB 的内存空间,称为虚拟内存;右边呢,是实实在在的物理内存,如果大小是 4GB,就被分成 1048576 个 4KB 页面;当然,也许只有 2 GB,那就只能划分成 524288 个 4KB 页面。

当一个程序加载时,操作系统既在要左边的虚拟内存屮分配段空间,又要在右边的物理内存中分配相应的页面。因此,第一个步骤是寻找空闲的段空间,该段空间既没有被其他程序使用,也没有被同一程序内的其他段使用。假设己经成功找到并分配了一个段空间,基地址为 0x00200000,长度为 8200 字节。

页的最小尺寸是 4KB ,也就是 4096 字节。因此,8200 字节的段,需要占用 3 个页面,其中最后一个页面只用了 8 个字节。

简单的分页模型_第2张图片

作为一个具体的例子,操作系统为程序分配了一个段,段是在虚拟内存中分配的,起始地址为 0x00200000。该段有 8200字节,需要分配 3 个页面。为此,操作系统在物理内存中搜索可用的
空闲页,还真找到了,这三个页面的物理地址分别是:

0x0000_0000 起始的页面,对应虚拟内存地址 0x0020_0000-0x0020_0FFF

0x0000_1000 起始的页面,对应虚拟内存地址 0x0020_1000-0x0020_1FFF

0x0000_3000 起始的页面,对应虚拟内存地址 0x0020_2000-0x0020_2007

这里只是示例,线性地址区间和页的对应关系可以随意。

虚拟内存空间不可能用来保存任何数据,因为它是虚拟的。当操作系统加载一个程序并创建为任务时,操作系统在虚拟内存空间寻找空闲的段,并映射到空闲的页。然后,到真正幵始加载程序时,再把原本属于段的数据按页的尺寸拆开,分别写入对应的页中。

从段部件输出的是线性地址,为了根据线性地址找到页的物理地址,操作系统必须维护一张转换表,把线性地址转换成物理地址。

因为虚拟的 4GB 内存可以分成 1048576 个页,所以转换表也有 1048576 项 。每个表项占 4 字节,内容为页的物理地址。这个表格的用法是这样的:因为页的尺寸是 4K B , 故,线性地址的低 12 位可用于访问页内偏移,高 20 位可用于指定一个物理页。因此,把线性地址的高 20 位当成索引,乘以 4 ,作为表内偏移量,从表中取出一个双字(4B),就是该线性地址所对应的页的物理地址。

简单的分页模型_第3张图片
例如,执行指令
mov edx, [0x2002]

首先,要算出线性地址,用段地址 0x0020_0000 加上 0x2002,得到线性地址 0x0020_2002;然后把线性地址转换为物理地址:线性地址的高 20 位是转换表的索引,即 0x202(=514),即表格的第 514 项。如果按照字节索引,需要把 0x202 乘以 4,得到 0x808,看图,从该单元可以取出一个双字 0x0000_3000,这就是页的起始物理地址。线性地址的低 12 位是页内偏移量,用页物理地址加上页内偏移量,就是最终的物理地址。0x0000_3000 加上 0x002,得到 0x0000_3002,所以物理地址就是 0x0000_3002。

有人问,表格里面的 0x0000_3000 是怎么来的?

当程序加载时,操作系统首先在虚拟内存中分配段。然后,根据段需要分成多少页,来搜索空闲页面。当段较大时,要按页的尺寸分成好几个地址区段,操作系统用每个区段的首地址,取高 20 位 ,乘以 4 ,作为偏移最访问表格,并将分配给该区段的页的物理地址写入该表项。最后,把原本需要写入每个区段的程序数据,写到对应的页中。

注意了,在页式内存管理中,页面的管理和分配是独立的,和分段以及段地址没有关系。操作系统所要做的,就是寻找空闲页面,把它分配给需要的段,并将页的物理地址填写到映射表内。

一般来说,每个任务都可以拥有 4GB 的虚拟内存空间;同时,每个任务都有自己的页映射表,如图 16-5 所示。

简单的分页模型_第4张图片

每个任务都有自己的 4GB 虚拟内存空间,这个怎么理解呢?

考虑这样一种情景 :任务 A 有一个段 , 段基地址为 0x0005_0000,段长度为 3000 字节,操作系统为它分配了一个物理地址为 0x0800_1000 的页。过了一会儿,另一个任务 B 加载了,它也有一个段,段基地址也为 0x0005_0000,段长度 4000 字节,此时,操作系统则分配另一个不同的、物理地址为 0x0070_0000 的页。

在这种情况下,任务 A 访问线性地址 0x0005_0006,访问的其实是物理地址 0x0800_1006;在任务 B 内访问同样的线性地址 0x0005_0006,访问的其实是物理地址 0x0070_0006。

你看,线性地址都是一样的,但是被映射到了不同的物理地址上。

另一个会被质疑的问题是,每个任务都有 4GB 虚拟内存空间,而物理内存只有一个,最大也才 4GB , 根本不够分啊。事实上,的确不够分。但是,操作系统可以将暂时不用的页退避到磁盘,调入马上就要使用的页,通过这种手段来实现分页内存管理。

以上就是一个简单的分页模型,如果理解了,就会为后面学习两级页表结构打下基础。


参考资料
【1】《x86汇编语言:从实模式到保护模式》(李忠,电子工业出版社)
【2】《Linux内核完全剖析》(赵炯,机械工业出版社,2006)

你可能感兴趣的:(简单的分页模型)