Linux X86在下面TLB机制

TLB - translation lookaside buffer

快表。直译为翻译后备缓冲器,也能够理解为页表缓冲。地址变换快速缓存。

因为页表存放在主存中,因此程序每次訪存至少须要两次:一次訪存获取物理地址,第二次訪存才获得数据。

提高訪存性能的关键在于依靠页表的訪问局部性。

当一个转换的虚拟页号被使用时,它可能在不久的将来再次被使用到,。

TLB是一种快速缓存,内存管理硬件使用它来改善虚拟地址到物理地址的转换速度。

当前全部的个人桌面,笔记本和server处理器都使用TLB来进行虚拟地址到物理地址的映射。使用TLB内核能够快速的找到虚拟地址指向物理地址,而不须要请求RAM内存获取虚拟地址到物理地址的映射关系。这与data cache和instruction caches有非常大的相似之处。

TLB原理

当cpu要訪问一个虚拟地址/线性地址时,CPU会首先依据虚拟地址的高20位(20是x86特定的。不同架构有不同的值)在TLB中查找。假设是表中没有对应的表项。称为TLB miss,须要通过訪问慢速RAM中的页表计算出对应的物理地址。同一时候,物理地址被存放在一个TLB表项中,以后对同一线性地址的訪问,直接从TLB表项中获取物理地址就可以,称为TLB hit。

想像一下x86_32架构下没有TLB的存在时的情况,对线性地址的訪问,首先从PGD中获取PTE(第一次内存訪问)。在PTE中获取页框地址(第二次内存訪问)。最后訪问物理地址,总共须要3次RAM的訪问。假设有TLB存在,而且TLB hit。那么仅仅须要一次RAM訪问就可以。


TLB表项

TLB内部存放的基本单位是页表条目,相应着RAM中存放的页表条目。页表条目的大小固定不变的,所以TLB容量越大,所能存放的页表条目越多。TLB hit的几率也越大。可是TLB容量毕竟是有限的,因此RAM页表和TLB页表条目无法做到一一相应。

因此CPU收到一个线性地址,那么必须高速做两个推断:

1 所需的也表示否已经缓存在TLB内部(TLB miss或者TLB hit)

2 所需的页表在TLB的哪个条目内

为了尽量降低CPU做出这些推断所需的时间,那么就必须在TLB页表条目和内存页表条目之间的相应方式做足功夫

全相连 - full associative

在这样的组织方式下。TLB cache中的表项和线性地址之间没有不论什么关系。也就是说,一个TLB表项能够和随意线性地址的页表项关联。这样的关联方式使得TLB表项空间的利用率最大。

可是延迟也可能相当的大,由于每次CPU请求,TLB硬件都把线性地址和TLB的表项逐一比較,直到TLB hit或者全部TLB表项比較完毕。特别是随着CPU缓存越来越大,须要比較大量的TLB表项,所以这样的组织方式仅仅适合小容量TLB

直接匹配

每个线性地址块都可通过模运算相应到唯一的TLB表项,这样仅仅需进行一次比較,减少了TLB内比較的延迟。可是这个方式产生冲突的几率很高。导致TLB miss的发生。减少了命中率。

比方。我们假定TLB cache共包括16个表项。CPU顺序訪问下面线性地址块:1, 17 , 1, 33。

当CPU訪问地址块1时,1 mod 16 = 1。TLB查看它的第一个页表项是否包括指定的线性地址块1,包括则命中,否则从RAM装入。然后CPU方位地址块17,17 mod 16 = 1,TLB发现它的第一个页表项相应的不是线性地址块17。TLB miss发生。TLB訪问RAM把地址块17的页表项装入TLB;CPU接下来訪问地址块1,此时又发生了miss,TLB仅仅好訪问RAM又一次装入地址块1相应的页表项。因此在某些特定訪问模式下。直接匹配的性能差到了极点

组相连 - set-associative

为了解决全相连内部比較效率低和直接匹配的冲突,引入了组相连。这样的方式把全部的TLB表项分成多个组,每一个线性地址块相应的不再是一个TLB表项。而是一个TLB表项组。CPU做地址转换时,首先计算线性地址块相应哪个TLB表项组,然后在这个TLB表项组顺序比对。依照组长度,我们能够称之为2路,4路。8路。

经过长期的project实践,发现8路组相连是一个性能分界点。8路组相连的命中率差点儿和全相连命中率差点儿一样,超过8路。组内对照延迟带来的缺点就超过命中率提高带来的优点了。

这三种方式各有优缺点,组相连是个折衷的选择。适合大部分应用环境。当然针对不同的领域,也能够採用其它的cache组织形式。

TLB表项更新

TLB表项更新能够有TLB硬件自己主动发起。也能够有软件主动更新

1. TLB miss发生后。CPU从RAM获取页表项,会自己主动更新TLB表项

2. TLB中的表项在某些情况下是无效的,比方进程切换。更改内核页表等。此时CPU硬件不知道哪些TLB表项是无效的,仅仅能由软件在这些场景下。刷新TLB。

在linux kernel软件层,提供了丰富的TLB表项刷新方法,可是不同的体系结构提供的硬件接口不同。比方x86_32仅提供了两种硬件接口来刷新TLB表项:

1. 向cr3寄存器写入值时,会导致处理器自己主动刷新非全局页的TLB表项

2. 在Pentium Pro以后,invlpg汇编指令用来无效指定线性地址的单个TLB表项无效。

TLB刷新机制

在MMU开启的情形下。线性地 址到物理地址的转换须要经过页表的查找。假设每次都这么做的话显然对系统性能有影响。因此出现了这么一个cache,用来将已经此前的查找结果保存在这个 TLB中。显然TLB由于容量的限制不可能将全部的线性地址到物理地址的转换全部容纳进去,当TLB中全部的entry都被放置满的时候。处理器会决定将 一个旧的entry替换掉。



TLB本质上就是一块cache。所以也存在cache一致性的问题。比方假设操作系统改动了页表中的某一项 的映射关系,假设该项的映射恰好保存在TLB中。那么就出现了一致性的问题。与x86下系统物理内存与处理器间的cache一致性不一样,TLB的一致性 须要系统软件出面解决,而不是硬件。x86为此提供了两种方式来解决TLB一致性的问题:

1. 更新CR3. 假设cr3寄存器被又一次载入。那么会导致整个TLB无效。OS能够通过比方: mov eax, cr3; move cr3, eax;这种指令来刷新整个TLB。还有,我们知道Linux下进程切换时,会又一次载入cr3寄存器。由于新老进程的页表项是不同样的。因此须要使 TLB无效防止出现不一致的情况。

2. x86提供了一条INVLPG指令。该指令是特权指令。操作系统能够通过该指令对TLB中的单独的某一entry进行更新。

关于这条指令的具体信息。能够 參考一下 intel 64 and IA32 Architecture Software Developer's Manual. Linux kernel中使用到该指令的样例有:
<arch/x86/mm/pgtable.c>
void set_pte_vaddr(unsigned long vaddr, pte_t pteval)
{
    ...
    /*
     * It's enough to flush this one mapping.
     * (PGE mappings get flushed as well)
     */
    __flush_tlb_one(vaddr);
}

set_pte_vaddr函数在内核中被用来直接操作页文件夹表项,里面涉及到x86线性地址映射物理地址的原理,函数在最后调用__flush_tlb_one来刷新TLB中相应的项:
<arch/x86/mm/pgtable_32.c>
static inline void __flush_tlb_one(unsigned long addr)
{
    if (cpu_has_invlpg)
        __flush_tlb_single(addr);
    else
        __flush_tlb();
}
从函数的实现能够看到。假设CPU支持INVLPG指令,那么就调用 __flush_tlb_single函数来刷新TLB中的项(详细哪一项由虚拟线性地址addr来指定)。__flush_tlb_single的实现是:

static inline void __native_flush_tlb_single(unsigned long addr)
{
    asm volatile("invlpg (%0)" ::"r" (addr) : "memory");
}
上面是一个很直观的invlpg指令使用范例。在__flush_tlb_one函数中。我们还能够看到__flush_tlb的调用,后者被用来刷新整个TLB,事实上就是本文前面提到的用mov指令倒换一下重写cr3.

在分页机制打开的情形下。当CPU要訪问一个线性地址时,首先会查找TLB,假设该线性地址的转换已经存储在TLB宾果,因此,直接访问物理地址(的确TLB它存储在VPN和PPN制图),在这一点上,你并不需要检查页文件夹页表。
假定当前的线性地址映射尚未保存TLB在,然后是一TLB miss,在这一点上,你需要找到该网页文件夹和页表项。然后结果被映射到记录TLB在。

原文地址:https://www.cnblogs.com/bhlsheji/p/5042606.html