文章目录
5.1 页表
5.1.1 页表介绍
虽然我们上一篇也简单提过页表,感觉页表也很简单,就是把内存分页之后,在进程中逻辑内存是连续的,存到物理内存中是离散的,所以一个表来记录这个关系。
这个表的结构为:
页号 | 页框号 | 保护 |
---|---|---|
0 | 5 | R |
1 | 1 | R/W |
2 | 3 | R/W |
3 | 6 | R |
通过逻辑地址的页号,查找到对应物理地址的页框号。
这就是页表的作用。
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。进程未执行时,页表的起始地址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内存会把它们存放到页表寄存中。
5.1.2 页表项
是不是觉得页表就这么简单??
通过我需要用一篇文章来介绍就明白,这个页表不简单了,那不简单在哪里???
我们是不是忘记计算页表项的个数了?
按照我们现代操作系统分页的大小一般是4K,然后现在的操作系统内存是4G,(当然目前内存肯定是16G,32G了,不过我们就按课本上的,按4G来计算。)
然后通过计算,一共有多少个页表项:4G/4K = 1M = 1,048,576个。
wakao,1百万个页表项,如果是16G,那就是4百万个页表项。
如果是遍历查找,这需要查到明年(这是夸张的手法,哈哈哈)。
那一个页表项有多大?
页表项的作用就是为了能查到这1百万个页表项,所以只要范围能在1百万以内就可以了。
上面我们计算的页表项个数为:220 = 需要20个二进制位来这么多个内存块号 = 需要3个字节就可以了
为了内存对齐访问,一般会扩展到4个字节,也就是这个页表项 大小为:220 * 4 = 4M字节存储。
每个进程需要4M来存储页表项,如果很多个进程呢??那不得上天了。
正因为页表项很大,不方便查找和储存,所以才需要开始研究页表的结构。
(这里就有人抬杠了,说不是挺方便查找的,用数组的方式,但是用了数组的方式容易查找,就不容易存储啊,又有人说用链表结构来存储,链表结构是容易存储,但是不容易查找,所以我们还是到后面的章节来学习一波,下一节就见分晓)
5.2 页表结构
哈工大老师说的有道理,正因为有多个解的时候,才需要算法,因为页表很大,不要存储和查询,才需要研究一下页表存储的结构,和查询的算法。下面我们就来学习学习。
5.2.1 多级页表
通过上面的分析,我们知道了页表会很大,达到1百万个条目,如果一个条目为4字节,大小达到4M。那如果按照数组的方式储存,我们需要连续开辟4M的大空间,我们前面学习了分页,就是为了把连续大空间给拆小,现在又这样,明显不对。
那我们是不是可以借助分页的思想,进行再分页,没错多级页表就是这种思想。
我们现在就按二级页表来分析一下,虽然linux使用的是三级页表,不过我们先学习二级页表。
我们一个页为4K,4K=212,所以我们页偏移只需要12位即可。
我们从上面分析到4G内存,一个页4K,我们需要220个表。我们现在分为二级页表,也就是第一次为210,第二级也为210。
这是我画的一个二级页表,画的比较大,也是因为要符合比例吧。
最左边的是页目录号,也成顶级页表,一共有1024个,每个4byte,刚好是4k,一个页,就是这么凑巧,其实并不是凑巧,就是这么设计的。
二级页表,也是一共有1024个,每个为4byte,存储的是4M的偏移,4M是怎么来的??
因为页大小为4k,个数刚好是1024个,相乘就刚好4M,一个二级页表管理4M,那一共有1024个,管理的就是4G,这就是二级页表的来历。
举个例子,如果我们要找到逻辑地址为0x00403004的物理地址是多少:
我们转换成二进制:01,0000000011,000000000100
对应上面的多级页表:PT1=1,PT2=3,Offset=4。
我们就按上面的图来看吧,PT1=1就相当于我们找到了页号为1的页面,然后继续在这个页面中找到3的位置,上面我没画,就看是3吧。那相当于我们找到的页框号为1027个,计算出地址为:(1024+3)*4k = 4,206,592 然后在加上偏移就等于 4,206,596。
为啥这个逻辑地址跟物理地址一样呢??其实是我们PT1和PT2的索引是一样,所以求出来的结果一样,其实实际上,这两个应该是没有关系的。
如果该页面不在内存中的,页表项中其实中一位表示"在不在内存中"的意思(页表项位还是很多剩余的),如果不在,这个位为0。然后会引发缺页中断。如果在的话,就直接访问物理地址了。
实际上,我们的进程虽然管理内存有4G,但是我们程序是分段的,代码段一般都是在最低(0~4M),数据段会紧跟着代码段(4M-8M),然后就是最顶端的堆栈段了4M,按照这个二级页表,我们这个进程运行的话,主需要4*4K=16K的页表内存就够了,最后一个是页目录表。如果是单级页表就需要4M,这差别很大了。
5.2.2 哈希页表
处理大于32位地址空间的常用方法是使用哈希页表(好像我没见过,哈哈哈),采用虚拟页码作为哈希值(应该一级的页码)。
哈希表的每一个条目都包括一个链表,处理哈希碰撞的。
该算法工作如下:虚拟地址的虚拟页码到哈希表中查找,查找的话,判断是否存在链表,如果有链表就需要一个链表一个链表的查找,看看是否陪匹配虚拟页码,如果匹配就取出响应的页框码,通过这页框码计算出物理地址。
这个就不画图的,就是一个哈希的使用,比较简单。
5.2.3 倒置页表
我们上面学习的多级页表,是从虚拟地址出发,然后采用分级分页,最后才映射到物理地址。如果虚拟地址过大于物理地址,就会导致页表项很多,然后其实映射到物理页还是一样的,所以我们把这种关系进行倒置,这就是倒置页表。
倒置页表,是以物理内存为中心,把物理内存进行划分成页,如果一个4G的内存,4K的页,就分为220个页表项,注意:这里是整个系统有220个页表项,不像虚拟内存,有n个进程,就有n*220个页表项。
如果这样存储的话,那虚拟地址怎么映射到物理地址???
这个虚拟地址的定义也跟我们之前不一样了,每个虚拟地址为一个三元组:<进程id, 页码, 偏移>
而每个导致页表的条目为二元组<进程id, 页码>。
当发生内存引用时,由<进程id, 页码>组成的虚拟地址被提交到内存子系统,然后搜索整个倒置页表来寻找匹配,如果匹配,通过这个条目i(因为物理地址是有序),在加上偏移即可得到物理地址。
这个图就是工作的过程,通过条目i*4K+offset来计算物理地址,然后找到对应的内存。
这个倒置页表有啥缺点呢?
很明显嘛,搜索过程就是一个缺点,一遍一遍的搜索全表肯定慢,要想提高这个速度,有两种方法。
第一种:可以使用一个哈希表,就是上面所描述的,直接用哈希表找到了条目i。
第二种:就是后面要介绍的TLB快表。
5.3 快表
因为有多级页表的存在,如果是二级页表,我们访问一次物理地址,需要3次内存访问,第一次访问页目录表,第二次通过页目录表,访问页表,第三次才访问物理地址,按这种访问速度,确实比之前的慢了很多。
5.3.1 局限性原理
看了王道论坛的操作系统,在3.1.8具有快表的地址变换机构一节中,提到了局限性原理。我看着感觉挺不错的。
时间局限性:如果执行了程序中某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(程序中存在大量的循环)
空间局限性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多程序在内存中都是连续存放的)
5.3.2 快表机制
通过上面的局限性分析,我们就想到不用每次都去访问多级页表,而是可以在添加一个小的表,这个表就是转换表缓冲区(Translation Look-aside Buffer, TLB)或快表。
现代的TLB查找硬件是指令流水线的一部分,基本上不添加任何性能负担。为了能够在单步的流水线中执行搜索,TLB不应大,通常它的大小在32~1024之间。有些CPU采用分开的指令和数据地址的TLB。
TLB与页表一起使用的方法如下:当CPU产生一个逻辑地址后,它的页码就会发送到TLB。如果TLB中有这个页码的缓存,那就可以直接取出帧码,然后就根据偏移,就可以找到了物理地址。因为TLB是在寄存中,访问速度很快,这样获取几乎没有浪费时间。
如果页码不在TLB中(称为TLB未命中),这时候操作系统一般会出现缺页中断,现在就需要访问页表,通过页码找到对应的帧码,然后在访问物理内存。另外,会将页码和帧码添加到TLB中,这样下次再访问的时候就可以快速找到。页表存储在内存中,需要找到内存的时候,相对会比TLB慢很多,如果是多级页表,速度会更慢。
如果TLB满的话,我们就需要使用淘汰机制,淘汰机制都是相同的,可以使用LRU或者随机替换啥的。
上面画的图就是这样。
5.3.3 访问速率
上面我们也提到访问TLB的速度比访问页表的速度快,那我们现在就来看看加上了快表之后速率怎么算?
我们使用TLB,比较感兴趣的就是TLB的命中率,根据5.3.1的局限性原理上面,我们知道程序运行时,访问的内存页码都是附近的,所以TLB的命中率很高,起码能到达90%。
我们就按90%的命中率来计算,访问TLB的时间为20ns,访问内存的时间为100ns。
按照公式:
有
效
访
问
时
间
=
命
中
率
∗
(
T
L
B
+
内
存
访
问
时
间
)
+
(
1
−
命
中
率
)
∗
(
T
L
B
+
2
∗
内
存
访
问
时
间
)
有
效
访
问
时
间
=
命
中
率
∗
(
T
L
B
+
内
存
访
问
时
间
)
+
(
1
−
命
中
率
)
∗
(
2
∗
内
存
访
问
时
间
)
/
/
直
接
同
时
访
问
有效访问时间 = 命中率 * (TLB + 内存访问时间) + (1-命中率)*(TLB + 2*内存访问时间) 有效访问时间 = 命中率 * (TLB + 内存访问时间) + (1-命中率)*(2*内存访问时间) // 直接同时访问
有效访问时间=命中率∗(TLB+内存访问时间)+(1−命中率)∗(TLB+2∗内存访问时间)有效访问时间=命中率∗(TLB+内存访问时间)+(1−命中率)∗(2∗内存访问时间)//直接同时访问
这个公式是快报和页表不能同时查询,如果可以同时查询,后面访问内存的时间可以去掉TLB。
有效访问时间 = 0.9 * 120ns + 0.1 * 220ns = 130ns
如果没有快表,那时间为 2 * 100ns = 200ns。(这个是一级页表的情况)
所以快表的存在也提高了CPU访问物理内存的效率。
5.4 总结
这一篇,介绍了页表和多级页表还有快表,再加上前面的分段,现在操作系统的内存管理已经出来了,下一篇就开始介绍段页结合的实际内存管理了。