文章目录
- 0.目的
- 1.页表结构
- [2. 地址转换的例子](#2. 地址转换的例子)
- 3.分页机制的性能问题
- [4. Translation Look-aside Buffer (TLB)](#4. Translation Look-aside Buffer (TLB))
0.目的
怎么有效地实现这种转换机制? 前面已经提到了有页表,但页表怎么来实现?怎么能实现高效?怎么能实现的更加节省空间?
操作系统和计算机硬件相互配合才能共同完成的目标,所以把页表专门拎出来做更深入的讲解。
1.页表结构
先看看页表结构,页表其实是大数组,一般来说它的索引就是它的页号,索引所对应页表项的内容主要存的就是帧号(frame number), 对应关系看起来好像比较直观。CPU 会查这个页表在什么地方,它的起始地址在哪?通过 page number 算出它的 index,寻址到对应的页表项,把相应 frame number 给取出来,那么就可以把 frame number 叠加上 offset,形成物理地址。
除了 frame number 之外,还有一些 bit,bit 是一些特殊的用途,可以表示这个页表项是否是一个合法的页表项,什么叫合法,就是这个页对应的物理页在内存中是否存在。逻辑地址空间可能很大,也意味着有可能有一部分的逻辑地址空间可能没有对应到物理空间,这时候它的存在属性就应该不存在了,若存在属性 1代表存在,0 代表不存在,可能是0,它不存在。当然还有其他属性,比如说代表这个页读或者写的情况,写过、读过还是没有读、没有写等等,这些属性都是有一定的表示。
2. 地址转换的例子
首先看看内存空间的大小,首先是逻辑地址空间,逻辑空间一共有16 bit, 意味着它有64K 的地址空间,但是物理地址空间只有32 KB,可看出来逻辑地址空间和物理地址空间是不一致的,它们之间不对等,但是每一个业内的偏移是一样的,因为每一页的大小是一样的,页大小和页帧大小 都是1K ,1024B。
在这种情况下,逻辑页的两个地址,(4,0)(3,1023)代表什么意思?(4,0)就是说逻辑页号是4,页内偏移是0。(3,1023)是逻辑页号是3,页内偏移是1023,这种情况下它对应的物理页地址是多少?
- 首先 CPU 要找这个地址,先看第一个(4,0),代表页号是4,查页表,第四项有标志位和相应帧号,帧号看是0,但是需要注意它的标志位这个红色的那个0什么意思呢?就是代表当前物理页帧是否存在?如果0代表不存在,1代表存在,这里面标识的是0,这个页对应的物理页页帧在内存中不存在,没有映射关系,所以说如果 CPU 访问这个地址,它应该产生异常,就是内存访问异常。
- 看第二个(3,1023),页号是3,业内偏移是1023。页号3表示第四项,页表里面第四项的驻留位就是存在位是1,代表页号对应的物理叶帧确实在物理内存中存在。对应的页帧号是多少?可以看看那里面查出来的二进制是00100,也就是它的页帧号是4,可以映射出它的页帧对应的物理页帧号是4,页帧内的偏移和页的偏移是一样的,偏移是1023,那就形成了物理页帧的地址,它的页帧号是4,页帧内的偏移是1023,运算出来可以看到,指向了物理空间种(4,1023)这个位置。
这就是整个映射过程,可以看到页表项里面也会出现两种情况:
- 一种情况是说物理页帧存在,可以找到对应的映射地址。还有一种情况是物理页帧不存在,又会产生内存访问异常。一旦出现异常之后,操作系统来做进一步处理,一般情况下,如果是因为非法访问,就直接把进程给杀死。
- 那另一方面,页表在使能页机制之前,页表要建立好,页表建立过程是操作系统来完成。这是页表访问的例子。
3.分页机制的性能问题
看起来好像这个机制也是比较完善的,可以完成从逻辑地址到物理地址转换,那有没有问题?其实这里面主要存在的问题就是空间代价问题,第二个时间开销问题。
如果说直接看刚才那幅图没有任何问题,如果要在计算机系统里面把这个机制给实现起来,需要考虑这两个问题,希望空间占用得越小越好,希望执行的速度越快越好。这两个问题是不是都可以很好地解决呢?
举个例子,比如说现在计算机寻址系统越来越大,现在用到的计算机很多是64 位计算机,也意味着它寻址空间是 2 64 2^{64} 264,如果说这里一个页的大小只有1024,也就说1K,这么大一个寻址空间,要建多大的页表才能够把这个映射关系表示完呢?答案是 2 54 2^{54} 254次方。这是多大的page table 项?其实容量是相当大。
-
根据上述例子,现在一般计算机没有这么大的空间,完全无法存下一个页表,那也就没法去完成这个对应关系,第一个问题就出来, 这个页表有可能由于逻辑地址空间很大,导致对应的页表也很大, 这是一个。第二个在计算机系统里面,可以跑多个应用程序,有多个应用程序都在内存中,而为了有效地实现地址空间隔离,其实需要每一个运行的程序都有自己的一个页表。那如果有 n 个程序,对应有 n 页表,那也意味着页表所占空间要 n 份,这其实也很耗空间,第一个问题就是页表可能会导致占的空间很大。
-
第二个访问效率问题,既然页表空间很大,那很明显整个页表组织结构不能放在 CPU 里面,因为 CPU 空间很小,CPU 里面有 cache ,最多几兆,但是现在这个页表按照刚才那种设计,相当大 ,CPU 放不下,那就需要把页表放内存里面。如果页表放到内存里面,每一次寻址访问一个内存,就要访问一次页表,这个页表本身也在内存里面,那意味着要去做一次内存寻址,要访问两次内存,那这个开销也是很大的。
所以说这两个问题就需要去想出一些对策,来有效地解决或者缓解时间和空间上的问题。那么对于计算机系统而言,一般来说解决时间空间问题有两种办法:
- 一种办法是缓存,希望把一些最常用的数据或者最常用的内容缓存到离 CPU 很近的地方。就像前面讲到内存管理系统中 cache 一样,把最常的东西放 cache 里面,可以提升访问速度。
- 第二个通过一种间接的方式,通过间接方式可以把一个很大的空间拆成比较小的空间,叫间接方式,后面可以看到,通过多级页表机制可以有效地缓解页表占用空间过大的一个问题。
4. Translation Look-aside Buffer (TLB)
通过两种方法来解决页表空间和时间问题,首先讲时间,这个时间叫 TLB,就是在 CPU 里面的 MMU(MMU 是内存管理单元),内存管理单元里面有一个叫做 TLB(translation look-aside Buffer),这是一个缓存,缓存页表里的内容。
上图可以看到 TLB 是一个特殊的一块区域,位于CPU 内部。里面包含两项,一个 p 值,一个 f 值,p 值是 key,f 是 value,key 和 value 就形成 TLB 表项,而 TLB 表项本身是用相关存储器来实现,相关存储器是快速查询的存储器,它的速度很快,可以并发地进行查找,但是实现的代价,容量都是有限的,实现代价很大,所以容量是有限的,只有有限的个数,那么可以把当前经常用到的页表项放到 TLB 里面去,这样就可以提升访问速度,这时候就不需要查页表了,当 CPU 得到一个逻辑地址的时候,首先根据这个 p 去查这个 TLB,如果 TLB 里面存在这个 p 这个 key 的话,那么很容易给得到这个 f,因为它们保存在一个TLB 项里面,那么有了 f 之后,加上 offset, 就可以直接得到物理地址,就可以去找内存中对应这个地址的内容,那就避免了一次页表访问。
如果出现 TLB 访问不到的情况,那么CPU 会去查页表,因为 TLB 表项里面已经不存在了,那不得不查页表,在页表里面应该有对应的那么一项,如果这一项里面存在位值是1的话,那么就会把这一项里面的 frame number 号取回来,取到 TLB 里面,用 TLB 表项来存储缓存这个值。那么可以看到,最常用的一些访问会放在 TLB 表项里面,不常用的就没有了,那这时候可以使得 TLB 的访问效率很高。
会不会 TLB 出现缺失情况会很大呢?
TLB 缺失不会很大,为什么那么说,因为一个页,一般来说现在32位系统里面一个页是4K,那么要访问4K 次,如果每一个地址都要访问的话,这访问4K 次才会引起一次TLB的缺失,这个其实还是可以接受的程度。就是说通过某种机制可以使得TLB的缺失尽量小,那这个机制什么呢?写程序的时候需要注意,尽量写出程序有一定的局部性,访问的局部性,把平时的这些访问集中在一个区域里面,这样可以有效地减少 TLB 的缺失,这当然是编程的时候需要注意的问题。 通过这种方式使得可以尽量地避免对内存页表访问,从而可以使得整个寻址的开销得到极大降低。
-
另外还需要注意,TLB 缺失之后,要从页表里把这项取出来,再存到 CPU 里面去,那这个过程是由操作系统软件来做,还是 CPU 硬件直接就做完了?
这跟 CPU 本身的特征有关系的,如果现在是X86 CPU, TLB MISS 之后从页表中取对应项到 TLB 中去的这个过程,完全硬件来完成,不需要操作系统参与,这是一种情况。但是对于另一类 CPU,比如说 Linux,那么它这个过程是由操作系统来完成,这是由软件来实现的,这两种情况都存在。