- UID
- 1043
- 精华
- 积分
- 11659
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
之前讲到AMD64的分页体系,估计很多人觉得四级分页这货会严重降低处理器操作内存的性能。如果真的每次内存的操作都要走一遍页表的话,那处理器的性能确实会被严重降低。
TLB简介
这里就要提到处理器对分页机制的缓存能力了,即地址转换旁道缓存(Translation Lookaside Buffer),也就是TLB。
在处理器将虚拟地址成功转换到物理地址后,处理器将会把虚拟地址转换到物理地址的结果保存到TLB中,以备未来的内存访问。
在TLB中,不仅包含了被转换的物理页基址,也包含了页状态,如R/W属性,NX属性,PS属性等。
当TLB被命中时,处理器将直接通过物理页基址计算得到物理地址,并比对P属性,R/W属性,U/S属性,NX属性来判断是否要产生#PF异常。
TLB缓存不存在于常规处理器缓存中,即不在任何一级,二级,三级甚至四级缓存上,TLB是独立于处理器缓存体系的一类缓存。
TLB刷新与TLB标记
之前提到过,页表基地址是保存在CR3寄存器中的,因此当mov cr3,xxx指令被执行后,处理器就必须刷新TLB来确保后续的地址转换是正确的。但不是所有的TLB都会被刷新,这是TLB标记所造成的。
通常而言,内核空间的虚拟地址到物理地址的转换不会产生变化,因此这些内存通常都带有Global标记。在页表中,一个页是否带有Global标记是最终页表项的G位来确定的。
除了Global标记外,还有ASID标记。
ASID(Address Space Identifier)标记是硬件虚拟化中的重要一环,它是一个32位的值,用于标记TLB属于哪个虚拟机。在AMD64体系下,规定宿主机的ASID为0,而虚拟机的ASID不为0。ASID的设计是为了避免不必要的TLB刷新导致的虚拟机与物理机关于地址转换性能的同时下降。在vmrun指令中,处理器将会修改CR3处理器的值为虚拟机的CR3值,但并不会刷新缓存,只是在下一次#VMEXIT之前一直使用带虚拟机ASID标记的TLB而已。
注意,Intel处理器上没有ASID机制,ASID是AMD处理器独有的。在Intel处理器上,使用16位的VPID(Virtual Processor Identifier)来标记TLB属于哪个虚拟机。
Intel处理器对TLB还有个PCID(Process-Context Identifier)标记,在同一个Process-Context下的软件将使用带同一个PCID标记的TLB缓存。注意,从2020年开始,AMD也开始有PCID了!
系统软件对TLB刷新有多种方式:
使用mov cr3,xxx指令来刷新当前ASID(VPID及PCID)下所有非Global页的缓存。
使用mov cr0,xxx指令关闭分页模式时,刷新掉全部的TLB。在虚拟机中,这种行为必须要被拦截。
使用mov cr4,xxx指令关闭PCID模式(Intel专有)或Global页模式时,刷新掉全部的TLB。
使用invpcid指令刷新掉标记了指定PCID的TLB,根据刷新类型决定刷新特定页TLB,或者该PCID下的所有TLB,或者不论PCID刷新全部TLB。(还有一种形式是刷新掉所有PCID的TLB,但不刷新Global页的TLB)但不能刷新带有其他VPID的TLB。
使用invlpg指令刷新掉特定内存地址的TLB,它无视PCID和Global标记。
使用invlpga指令刷新掉特定ASID内特定内存地址的TLB。只能在ASID=0时使用。注意这个ASID是相对的,如果宿主机中的虚拟机软件具有嵌套虚拟机的能力,则虚拟机中也可以执行这个指令来刷新掉嵌套虚拟机的TLB。
在VMCB中设置TLB控制项实现对全部TLB,指定ASID的全部TLB,指定ASID中非Global的TLB的刷新操作。vmrun指令将读取VMCB的TLB控制项并实施缓存刷新操作。
使用invvpid指令刷新掉特定标记了指定VPID的TLB,和invpcid相似,处理器根据刷新类型来刷新特定的TLB。
TLB失误
既然是缓存,就必然存在需要刷新的情况,当缓存没有被正确刷新时,就会产生缓存失误,TLB则是同理。
TLB失误通常是在系统软件在修改页表结构时没有被正确刷新导致的。在修改页表结构后,系统软件必须执行invlpg指令(Invalidate Page)来刷新这个页的TLB缓存。
需要注意的是,TLB失误是很严重的缓存错误。TLB失误可以使得系统中一个软件读写另一个软件的内存区域,甚至是操作系统的内核区域。
举例1:你修改了页表,禁止某页被写入。但是因为你没有立即刷新TLB,这个页在TLB被刷新前仍然可以被写入。
举例2:你修改了页表,你设置该页的物理地址为0x2000而非原来的0x4000。但因为你没有立即刷新TLB,这个页在TLB被刷新前读写依然指向物理地址0x4000而非0x2000。
举例3(重要!!!):你修改了页表,将某个进程的一个页面换出到了磁盘,并将磁盘中保存的内核页换入到这个物理页上。作为一个有点常识的OS编写者的你,你记得把页面设置为仅内核可访问了,或者干脆设置这个虚拟页禁止访问,但因为你没有立即刷新TLB,这个页仍然可以被用户态的进程所访问,可这个页包含的内容却不是进程的内容了,而是内核内存的内容了,于是乎用户模式进程成功读到了内核内存。
TLB分级(该章内容是基于我对通俗现代处理器架构的理解推测出来的,无论Intel还是AMD都没有在公开文档中写过这些。)
在现代处理器上,一般要求TLB的效率需要比普通缓存要更快。这样一来,TLB的生产成本甚至要比一级缓存更高,那么就有必要对TLB也进行分级。
那么分级之后,一级TLB速度最快,二级TLB其次,(可能没有三级)三级TLB就最次。
前面我们介绍过带标记的TLB,PCID,VPID和ASID。其中VPID是Intel虚拟机的TLB标记,ASID是AMD虚拟机的TLB标记,PCID是进程的TLB标记。
那么我推测现代AMD64处理器存在这么一种TLB策略:由于在上下文切换的时候在逻辑上需要刷新掉TLB,那么在一级TLB中处理器无需标记PCID,VPID和ASID,可节省一级TLB空间,即成本。每次切换上下文(比如虚拟机Guest/Host的切换,操作系统里进程间切换)的时候,处理器从二级TLB中找符合ASID/VPID/PCID的记录,并覆盖到一级TLB上。这是一种比较合理的做法。
TLB与超线程
在处理器启用超线程模式的时候,同一个物理处理器核心中的所有逻辑处理器核心使用同一套TLB。但对此设计不妥当的处理器(比如Intel处理器)就会产生安全隐患。
2018年夏季爆出的TLBleed漏洞就与此有关。 |
|