大家好,我叫徐锦桐,个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家访问。
一、前言
从虚拟地址到物理地址需要进行多级的页表转换,64位操作系统一般是3~5级,如果每次都要进行这个页表转换那会带来性能上的消耗。
如果我们将已知的虚拟地址和物理地址映射关系存到一个缓存表中,那么如果再次用到这个映射关系就能直接查表来找到虚拟地址对应的物理地址了,这就是TLB表,也叫快表。
但是会发生一个歧义,就是两个进程有相同的虚拟地址,但是映射到了不同的物理地址,到时候查表的时候就会发生歧义。
二、页表查找简述
我们知道MMU是将虚拟地址转换为物理地址,然后其中的映射关系存储到页表中,而页表又是分级的。
64位操作系统一般有3~5级页表,这里我们以4级页表为例,分别是PGD、PUD、PMD、PTE四级页表。在硬件上有一个页表基地址寄存器,它存储PGD页表的首地址,MMU根据这个寄存器从PGB表开始查,最后查到PTE,然后生成最后的物理地址。
这个就是相当于找你家的具体位置一样,先找你的省份,然后是市级…。当下次再找你家的具体位置时,还要这么来一次,这非常的麻烦。但是如果第一次查完之后,把你的信息记录下来,比如说一个名字对应你家的具体位置,下次找的时候直接查表就行了。但是如果遇到同名的咋办,这就遇到了歧义,解决办法下面我再说。
三、TLB本质
TLB本质就是一个告诉缓存。TLB缓存虚拟地址和其映射的物理地址。
TLB根据虚拟地址查找cache,也只能根据虚拟地址查找。硬件存在TLB后,虚拟地址转到到物理地址的过程发生了变化。
虚拟地址首先传给TLB确认是否命中cache,如果cache hit就直接可以得到其对应的物理地址。否则,就是一级一级的查页表。
(操作系统虽然是64位,64位是非常大的一个数,我们目前一般用不多这么多,所以硬件厂商为了设计简单或节约成本,所以64位CPU寻址范围并不是64位,这里以48位的进行举例)
四、解决TLB中的歧义
在进程切换的时候我们可以将整个TLB失效,切换的进程肯定不会命中TLB,但是会导致性能损失,如果我们进程切换的非常频繁,可能TLB表就没咋用。
那怎么尽可能避免flush TLB(这里的flush理解成使无效的意思)呢。如果我们能区分不同的进程TLB表就好了。
Linux操作系统为了区分不同的进程,会给每个进程一个独一无二的进程ID,我们也可以除了通过虚拟地址查表外,通过对比一个ID来区分不同进程的TLB表项。
所以,TLB添加一项ASID(Address Space ID)的匹配。ASID就类似进程ID一样,用来区分不同进程的TLB表项。这样在进程切换的时候就不需要flush TLB。但是仍然需要软件管理和分配ASID。
五、管理ASID
ASID和进程ID是不一样的。ASID一般是8或16 bit,所以只能区分256或65536个进程,但是进程ID的取值范围非常的大。我们不能将ASID和进程ID一一对应,我们必须给每个进程分配一个ASID。
Linux管理进程,每个进程会有个tast_struct结构,这里存储的进程的基本信息,我们可以把分配给这个进程的ASID存储到这里。页表基地址寄存器如果有空闲位也可以用来存储ASID。
当进程切换时候,可以将页表基地址和ASID(可以从task_struct获得)共同存储在页表基地址寄存器中。当查找TLB的时候,可以对比虚拟地址和ASID和TLB表中的是否一样来区分不同进程TLB表。 如果TLB miss就需要查页表了,然后再缓存到TLB中,同时缓存当前的ASID。
六、up
内核空间和用户空间是分开的,并且内核空间是所有进程共享的。
内核空间是共享的,那么进程A切换进程B的时候,如果进程B访问的地址位于内核空间,我们完全可以用进程A缓存的TLB表。但是现在因为ASID不一样,所以导致TLB miss。
我们针对内核空间这种全局共享的映射关系称为global映射。针对每个进程的映射成为non-global映射。 所以,我们在最后一级页表引入一个bit(non-global(nG)bit)代表是不是global映射。
当映射关系缓存到TLB的时候,将nG bit也缓存下来。这时候,我们的TLB表中有虚拟地址和物理地址的映射关系、ASID、nG bit。当判断是否命中TLB时,先比较虚拟地址,当找到表中的虚拟地址后,然后再看是不是global映射,如果是,就直接TLB hit,这时候就不用比较ASID了。如果不是global映射,最后还需要比较ASID。