深入浅出计算机组成原理学习笔记:第四十讲
一、简单页表
1、页表的概念
想要把虚拟内存地址,映射到物理内存地址,最直观的办法,就是来建一张映射表。这个映射表,能够实现虚拟内存里面的页,到物理内存里面的页的一一映射。
这个映射表,在计算机里面,就叫作 页表(PageTable)。
页表这个地址转换的办法,会把一个内存地址分成 页号(Directory)和 偏移量(Offset)两个部分。这么说太理论了,我以一个32位的内存地址为例,帮你理解这个概念。
1、其实,前面的高位,就是内存地址的页号。后面的低位,就是内存地址里面的偏移量。
2、做地址转换的页表,只需要保留虚拟内存地址的页号和物理内存地址的页号之间的映射关系就可以了。
3、同一个页里面的内存,在物理层面是连续的。以一个页的大小是4K比特(4KiB)为例,我们需要20位的高位,12位的低位。
2、内存地址转换,的三个步骤:
总结一下,对于一个内存地址转换,其实就是这样三个步骤:
1. 把虚拟内存地址,切分成页号和偏移量的组合;
2. 从页表里面,查询出虚拟页号,对应的物理页号;
3. 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。
3、一个页表需要多大的空间
不知道你算出的数字是多少?32位的内存地址空间,页表一共需要记录2^20个到物理页号的映射关系。这个存储关系,就好比一个2^20大小的数组。
一个页号是完整的32位的4字节(Byte),这样一个页表就需要4MB的空间。听起来4MB的空间好像还不大啊,毕竟我们现在的内存至少也有4GB,服务器上有个几十GB的内存和很正常。
不过,这个空间可不是只占用一份哦。我们每一个进程,都有属于自己独立的虚拟内存地址空间。这也就意味着,每一个进程都需要这样一个页表。
不管我们这个进程,是个本身只有几KB大小的程序,还是需要几GB的内存空间,都需要这样一个页表。
如果你用的是Windows,你可以打开你自己电脑上的任务管理器看看,现在你的计算机里同时在跑多少个进程,用这样的方式,页表需要占用多大的内存。
这还只是32位的内存地址空间,现在大家用的内存,多半已经超过了4GB,也已经用上了64位的计算机和操作系统。这样的话,
用上面这个数组的数据结构来保存页面,内存占用就更大了。那么,我们有没有什么更好的解决办法呢?你可以先仔细思考一下。
4、简单页表存在的问题
一个页号是完整的32位的4字节(Byte),这样一个页表就需要 4MB的空间。 不过,这个空间可不是只占用一份哦。我们每一个进程,都有属于自己独立的虚拟内存地址空间。这也就意味着,每一个进程都需要这样一个页表。不管我们这个进程,是个本身只有几KB大小的程序,还是需要几GB的内存空间,
二、多级页表
1、为什么我们不用哈希表而用多级页表呢?
我们先来看一看,一个进程的内存地址空间是怎么分配的。在整个进程的内存地址空间,通常是“两头实、中间空”。在程序运行的时候,内存地址从顶部往下,不断分配占用的栈的空间。
而堆的空间,内存地址则是从底部往上,是不断分配占用的。
所以,在一个实际的程序进程里面,虚拟内存占用的地址空间,通常是两段连续的空间。而不是完全散落的随机的内存地址。而多级页表,就特别适合这样的内存地址分布
2、多级页表
多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,
那么多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数
Linux 用的正是四级页表来管理内存页,如下图所示,虚拟地址被分为 5 个部分,前 4 个表项用于选择页,而最后一个索引表示页内偏移。
3、页表树
事实上,多级页表就像一个多叉树的数据结构,所以我们常常称它为 页表树(Page Table Tree)。因为虚拟内存地址分布的连续性,树的第一层节点的指针,
很多就是空的,也就不需要有对应的子树了。所谓不需要子树,其实就是不需要对应的2级、3级的页表。找到最终的物理页号,就好像通过一个特定的访问路径,
走到树最底层的叶子节点。
4、一个4级的多级页占用多少空间
以这样的分成4级的多级页表来看,每一级如果都用5个比特表示。那么每一张某1级的页表,只需要2^5=32个条目。如果每个条目还是4个字节,那么一共需要128个字节。
而一个1级索引表,对应32个4KiB的也就是16KB的大小。一个填满的2级索引表,对应的就是32个1级索引表,也就是512KB的大小。
我们可以一起来测算一下,一个进程如果占用了1MB的内存空间,分成了2个512KB的连续空间。那么,它一共需要2个独立的、填满的2级索引表,也就意味着64个1级索引表,
2个独立的3级索引表,1个4级索引表。一共需要69个索引表,每个128字节,大概就是9KB的空间。比起4MB来说,只有差不多1/500。
5、多级页表存在的问题
不过,多级页表虽然节约了我们的存储空间,却带来了时间上的开销,所以它其实是一个“以时间换空间”的策略。原本我们进行一次地址转换,
只需要访问一次内存就能找到物理页号,算出物理内存地址。但是,用了4级页表,我们就需要访问4次内存,才能找到物理页号了。
我们在前面两讲讲过,内存访问其实比Cache要慢很多。我们本来只是要做一个简单的地址转换,反而是一下子要多访问好多次内存。
对于这个时间层面的性能损失,我们有没有什么更好的解决办法呢?那请你一定要关注下一讲的内容哦!
三、总结延伸
好了,这一讲的内容差不多了,我们来总结一下。
我们从最简单的进行虚拟页号一一映射的简单页表说起,仔细讲解了现在实际应用的多级页表。多级页表就
像是一颗树。因为一个进程的内存地址相对集中和连续,所以采用这种页表树的方式,可以大大节省页表所
需要的空间。而因为每个进程都需要一个独立的页表,这个空间的节省是非常可观的。
在优化页表的过程中,我们可以观察到,数组这样的紧凑的数据结构,以及树这样稀疏的数据结构,在时间
复杂度和空间复杂度的差异。另外,纯粹理论软件的数据结构和硬件的设计也是高度相关的。