最近忙着看IBM PowerVM的文档,博文更新有点慢,因为PowerVM这个东西对我而言比较新,所以一时半会也没有什么心得,好在context switch比较多一点,比如今天下午又切到x86上的VM实现上。
这篇博文简单总结一下TLB的东西,可能很多网友都熟悉这个了,我把它记录在下面,作为一个备忘录。
TLB的全称是Translation Lookaside Buffer,翻译成中文有时是后备转换缓存,可能还有其他叫法,总之这个东西是用来保存线性地址到物理地址转换用的。因为在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中使用到该指令的例子有:
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中对应的项:
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中存放的是VFN和PFN的映射),此时不需要去查页目录页表了。
如果当前线性地址的映射尚没有保存在TLB中,那么就发生了一次TLB miss,此时需要查找页目录和页表项,然后把映射结果记录到TLB中。
阅读(1823) | 评论(0) | 转发(0) |