17-分页

来源:互联网 发布:淘宝妈妈装上衣 编辑:程序博客网 时间:2024/05/29 03:45

概述

苦苦挣扎几近半月,保护模式中的段的篇章基本结束。迈过这个坑,踏过这个坎,前方还有千千万万个坑。只要坚持,一定可以走完。

本篇开始进入新的篇章——页。

不得不说,页在现代操作系统中起着无法动遥的地位,是实现现代操作系统的重要基础(当然,段也很重要)。你完全可以不用理解这句话,因为到目前为止,我们并没有进入操作系统正题。

逻辑地址、线性地址与物理地址

咳咳咳…一下子冒出这么多概念真的好吗?忍耐,我从简说。

  • 逻辑地址,也有称作有效地址的。就是你在写 C 语言代码的时候,用到的指针里存的地址。
  • 线性地址,如果你还记得段选择子结构的话(不记得那就去把段选择子结构好好复习),其中有一个成员是 base,表示段基址。那么线性地址=ds.base+逻辑地址。(假设我用的是 ds 段寄存器)
  • 物理地址,通过某种映射方法,把线性地址映射到真实的物理内存条上的某个地址上。说的通俗点,就是通过一种计算方法,把线性地址当作参数,传到一个函数里,经过一通折腾,返回 来一个新的地址,这个新的地址就是物理地址,是真实的位于内存条上的地址。

[注] 实际上,最终计算的物理地址并不是真的就是真实物理内存上的地址,物理地址到真实内存条还有一层关系,然而对于我们来说,理解到这一层次足够了。


这里写图片描述
图1 逻辑地址到线性地址的映射

接下来,使用你的洪荒之力,把下面这张图看懂,能看懂多少是多少。


这里写图片描述
图2 逻辑地址最终转换成物理地址

至少,左半部分(Segmentation),相信你是没有问题的。右半部分(Paging),是今天的重点,我们要学的某种映射关系

没错,这里还没到转换。因为你还不知道什么叫 page 。注意,这和操作系统没有关系,这是 INTEL CPU 提供的一种机制。

页你不懂,内存块总知道是啥吧。给内存块加个限定词——有固定大小的内存块,这就页。

问题是这个固定大小,到底是多大?CPU提供了两种大小,一种是 4KB 大小的内存块,称之为小页面,还有一种是多少,不记得了,以后再说吧。请把 4KB 大声朗读 3 遍。

4KB,也就是 212字节,也就是 0x1000 字节。

为了能够方便使用内存,CPU 把4GB地址空间(0x00000000~0xffffffff)划分成了 220 个,也就是1MB个固定大小的内存块,每一块的大小都是 0x1000 字节。第 0 页的范围是 0x0000000~0x00000fff,第二页的范围是0x00001000~0x00001fff,最后一页,也就是第 2201 页的范围是0xfffff000~0xffffffff

为了能够方便的把线性地址转换成物理地址,CPU把线性地址空间中的某一页,对应到物理地址空间的某一页。下图的每个颜色块都是 4KB,表示一页。


这里写图片描述
图3 线性页到物理页的映射

如何映射

  • 页表

CPU把物理地址空间分成了若干个小页面(4KB),每个小页面都给了它一个编号,比如从编号从0~220-1(假设我的物理内存就是4GB)。写成 16 进制就是从 0x00000~0xfffff。这样,每个编号其实占用了 20 个比特位。

如果你的物理内存只有 1MB,可以分割成28个小页面,那编号就是从 0-28-1,16进制就是0x00000~0x000ff

无论如何,32位模式下,这个编号都要占用 20 个比特位。

只要知道到了小页面的编号,就一定能够计算出这些小页面的真实物理地址。比如 0 号物理页的物理地址就是 0x00000000,1 号物理页的地址是 0x000000040x000ff号的物理地址就是0x000ff000。这种计算方法很简单,只要把页面编号乘以 4KB 就行了,也就是乘以 4096,或者说左移 12 位,效果都是一样,因为每个小页面大小都是 4096 字节。

为了能让 CPU 找到这些小页面,势必要把这些小页面的“门牌号”保存起来。同样,这些“门牌号”也要保存在物理内存中,为了方便管理,再从物理内存中取出一页,来保存这些大小为 20 bit 的门牌号。

那么,一个内存页能保存多少个门牌号? 8×4KB20 吗?没有,CPU不是这么干的。

CPU 用 32 bit (4字节)来保存一个页面编号,其中高20 bit 保存编号,低12位保存页面属性。目前你还不用关心这多出来的 12 bit 保存了啥属性。我们只关心其中的 20 bit 编号。

如此一来,一页可以保存4KB4B=21222=1024 个“门牌号”.

我们把保存这种门牌号的页,称之为页表。而页表中的每一个元素占用4字节。一个页表可以保存1024个元素。如果用数组来表示页表,就是这样的

 int page_tables[1024]


这里写图片描述
图4 有一些物理页,专门用来保存页编号,称之为页表

有了这些已经足够了。接下来,就是如何把线性地址转换过去的问题。

  • 一级页表、二级页表和普通物理页

那些保存普通物理页索引号的页表,称为二级页表,而保存页表索引号的页表,称为一级页表,也叫页目录表。如图5。图中的 PDE意思是 page directory entry,即页目录表项;PTE的意思是 page table entry,即页表项。


这里写图片描述
图5 页目录、页表和普通物理页

  • 线性地址 10-10-12 分页

线性地址中保存的,就是页表中的索引号,也就是前面说的门牌号。一个线性地址是32位,它的结构是这样的。

|   31~22  |  21~12   |    11~0    | 比特|9876543210|9876543210|ba9876543210| 比特|----------|----------|------------| 占位| 一级索引 | 二级索引 |  页内偏移  | 说明

可以看到,线性地址中保存了两个索引号和一个普通物理页偏移。

有线性地址 0x12345678,对应到上面的结构就是这样的。

|   31~22  |  21~12   |    11~0    | 比特|9876543210|9876543210|ba9876543210| 比特|----------|----------|------------| 占位| 一级索引 | 二级索引 |  页内偏移  | 说明|0001001000|1101000101|011001111000| <-- 线性地址0x12345678做 10-10-12 拆分

于是我们得到第一个页表索引号,它是 0001001000 = 0x48
第二个页表的索引号,它是 1101000101 = 0x345
最后一个是页内偏移,它是 011001111000 = 0x678

假如我们已经知道一级页表(这个通常称之为页目录)的基址page_dir_tables.
那么根据一级索引,我们得到 page_dir_tables[0x48] 的值,假设 page_dir_tables[0x48] = 0x12fff067

前面说过,页表中每一项的高20位保存的都是另一个物理页的编号。

根据规则,0x12fff067 的高20位是二级页表编号,即0x12fff,于是计算得到这个物理页基址是0x12fff000.

为了能找到这一页中索引号 0x345 这个位置的值,令 int *page_tables = 0x12fff000page_tables[0x345]=0x21991067,那么这个值中告诉我们的页编号是 0x21991,换算成页基址是 0x21991000

最终我们定向到了物理页 0x21991000,这个物理页保存的也不是页面编号了,就是普通数据,它是普通页。另外,线性地址的第 3 部分,是页内偏移,把这个普通页的基址+页内偏移,最终得到了物理地址,也就是 0x21991000+0x678=0x21991678

现在回过头来,再看看图2,相信你应该能看懂了。

总结

本篇简单介绍了 10-10-12 分页,一个线性地址按照10-10-12分成三段:

第一段,一级页表索引号
第二段,二级页表索引号
第三段,普通页页内偏移

实际还有另一种2-9-9-2分页,这是以后的事情。看完这篇后,或许你还有一个疑问,就是一级页表,也就是页目录表的基址CPU是怎么知道的?

实际上在CPU中,有一个寄存器,称之为 CR3 寄存器,它保存了一级页表的基址。

2 0
原创粉丝点击