比较有特色的网站,淘宝客做网站怎么做,北京做网站多少钱,咸阳 网站建设1. TLB介绍
TLB是Translation Lookaside Buffer的简称#xff0c;可翻译为“地址转换后援缓冲器”#xff0c;也可简称为“快表”。
简单地说#xff0c;TLB就是页表的Cache#xff0c;属于MMU的一部分#xff0c;其中存储了当前最可能被访问到的页表项#xff0c;其内…1. TLB介绍
TLB是Translation Lookaside Buffer的简称可翻译为“地址转换后援缓冲器”也可简称为“快表”。
简单地说TLB就是页表的Cache属于MMU的一部分其中存储了当前最可能被访问到的页表项其内容是部分页表项的一个副本。
处理器在取指或者执行访问memory指令的时候都需要进行地址翻译即把虚拟地址翻译成物理地址。
而地址翻译是一个漫长的过程需要遍历几个level的Translation table从而产生严重的开销。
为了提高性能我们会在MMU中增加一个TLB的单元把地址翻译关系保存在这个高速缓存中从而省略了对内存中页表的访问。 TLB存放了之前已经进行过地址转换的查询结果。 这样当同样的虚拟地址需要进行地址转换的时候我们可以直接在 TLB 里面查询结果而不需要多次访问内存来完成一次转换。
TLB其实本质上也是一种cache既然是一种cache其目的就是为了提供更高的performance。而与我们知道的指令cache和数据cache又又什么不同呢 1.指令cache解决cpu获取main memory中的指令数据的速度比较慢的问题而设立 2.数据cache解决cpu获取main memory中的数据的速度比较慢的问题而设立
Cache为了更快的访问main memory中的数据和指令而TLB是为了更快的进行地址翻译而将部分的页表内容缓存到了Translation lookasid buffer中避免了从main memory访问页表的过程。
2. TLB的转换过程
TLB中的项由两部分组成
标识区:存放的是虚地址的一部数据区:存放物理页号、存储保护信息以及其他一些辅助信息
对于数据区的辅助信息包括以下内容
有效位(Valid)对于操作系统所有的数据都不会加载进内存当数据不在内存的时候就需要到硬盘查找并加载到内存。当为1时表示在内存上为0时该页不在内存就需要到硬盘查找。引用位(reference):由于TLB中的项数是一定的所以当有新的TLB项需要进来但是又满了的话如果根据LRU算法就将最近最少使用的项替换成新的项。故需要引用位。同时要注意的是页表中也有引用位。脏位(dirty):当内存上的某个块需要被新的块替换时它需要根据脏位判断这个块之前有没有被修改过如果被修改过先把这个块更新到硬盘再替换否则就直接替换。 下面我们来看一下当存在TLB的访问流程
当CPU收到应用程序发来的虚拟地址后首先去TLB中根据标志Tag寻找页表数据假如TLB中正好存放所需的页表并且有效位是1说明TLB命中了那么直接就可以从TLB中获取该虚拟页号对应的物理页号。假如有效位是0说明该页不在内存中这时候就发生缺页异常CPU需要先去外存中将该页调入内存并将页表和TLB更新假如在TLB中没有找到就通过上一章节的方法通过分页机制来实现虚拟地址到物理地址的查找。如果TLB已经满了那么还要设计替换算法来决定让哪一个TLB entry失效从而加载新的页表项。
引用位、脏位何时更新? 如果是TLB命中那么引用位就会被置1当TLB或页表满时就会根据该引用位选择适合的替换位置 如果TLB命中且这个访存操作是个写操作那么脏位就会被置1表明该页被修改过当该页要从内存中移除时会先执行将该页写会外存的操作保证数据被正确修改。
3. 如何确定TLB match
我们选择Cortex-A72 processor来描述ARMv8的TLB的组成结构以及维护TLB的指令 A72实现了2个level的TLB
绿色是L1 TLB包括L1 instruction TLB48-entry fully-associative和L1 data TLB32-entry fully-associative。黄色block是L2 unified TLB它要大一些可以容纳1024个entry是4-way set-associative的。当L1 TLB发生TLB miss的时候L2 TLB是它们坚强的后盾
通过上图我们还可以看出对于多核CPU每个processor core都有自己的TLB。 假如不做任何的处理那么在进程A切换到进程B的时候TLB和Cache中同时存在了A和B进程的数据。
对于kernel space其实无所谓因为所有的进程都是共享的
对于A和B进程它们各种有自己的独立的用户地址空间也就是说同样的一个虚拟地址X在A的地址空间中可以被翻译成Pa而在B地址空间中会被翻译成Pb如果在地址翻译过程中TLB中同时存在A和B进程的数据那么旧的A地址空间的缓存项会影响B进程地址空间的翻译
因此在进程切换的时候需要有tlb的操作以便清除旧进程的影响具体怎样做呢
当系统发生进程切换从进程A切换到进程B从而导致地址空间也从A切换到B这时候我们可以认为在A进程执行过程中所有TLB和Cache的数据都是for A进程的一旦切换到B整个地址空间都不一样了因此需要全部flush掉
这种方案当然没有问题当进程B被切入执行的时候其面对的CPU是一个干干净净从头开始的硬件环境TLB和Cache中不会有任何的残留的A进程的数据来影响当前B进程的执行。
当然稍微有一点遗憾的就是在B进程开始执行的时候TLB和Cache都是冰冷的空空如也因此B进程刚开始执行的时候TLB miss和Cache miss都非常严重从而导致了性能的下降。
我们管这种空TLB叫做cold TLB它需要随着进程的运行warm up起来才能慢慢发挥起来效果而在这个时候有可能又会有新的进程被调度了而造成TLB的颠簸效应。
我们采用进程地址空间这样的术语其实它可以被进一步细分为内核地址空间和用户地址空间。
对于所有的进程包括内核线程内核地址空间是一样的因此对于这部分地址翻译无论进程如何切换内核地址空间转换到物理地址的关系是永远不变的其实在进程A切换到B的时候不需要flush掉因为B进程也可以继续使用这部分的TLB内容上图中橘色的block。
对于用户地址空间各个进程都有自己独立的地址空间在进程A切换到B的时候TLB中的和A进程相关的entry上图中青色的block对于B是完全没有任何意义的需要flush掉。
在这样的思路指导下我们其实需要区分global和local其实就是process-specific的意思这两种类型的地址翻译因此在页表描述符中往往有一个bit来标识该地址翻译是global还是local的同样的
在TLB中这个标识global还是local的flag也会被缓存起来。有了这样的设计之后我们可以根据不同的场景而flush all或者只是flush local tlb entry。
4. 多核的TLB操作
完成单核场景下的分析之后我们一起来看看多核的情况。进程切换相关的TLB逻辑block示意图如下 在多核系统中进程切换的时候TLB的操作要复杂一些主要原因有两点
其一是各个cpu core有各自的TLB因此TLB的操作可以分成两类一类是flush all即将所有cpu core上的tlb flush掉还有一类操作是flush local tlb即仅仅flush本cpu core的tlb。
另外一个原因是进程可以调度到任何一个cpu core上执行当然具体和cpu affinity的设定相关从而导致task处处留情在各个cpu上留有残余的tlb entry。
我们了解到地址翻译有global各个进程共享和local进程特定的的概念因而tlb entry也有global和local的区分。
如果不区分这两个概念那么进程切换的时候直接flush该cpu上的所有残余。
这样当进程A切出的时候留给下一个进程B一个清爽的tlb而当进程A在其他cpu上再次调度的时候它面临的也是一个全空的TLB其他cpu的tlb不会影响。
当然如果区分global 和local那么tlb操作也基本类似只不过进程切换的时候不是flush该cpu上的所有tlb entry而是flush所有的tlb local entry就OK了。
5. PCID
按照这种思路走下去那就要思考有没有别的办法能够不刷新TLB呢
有办法的那就是PCID。
PCID进程上下文标识符是在Westmere架构引入的新特性。简单来说在此之前TLB是单纯的VA到PA的转换表进程1和进程2的VA对应的PA不同不能放在一起。
加上PCID后转换变成VA 进程上下文ID到PA的转换表放在一起完全没有问题了。
这样进程1和进程2的页表可以和谐的在TLB中共处进程在它们之前切换完全不需要预热了
所以新的加载CR3的过程变成了如果CR4的PCID1加载CR3就不需要Flush TLB。
6. TLB shootdown
一切看起来很美好PCID这个在多年前就有了的技术现在已经在每个Intel CPU中生根了那么是不是已经被广泛使用了呢
而实际的情况是Linux在2017年底才在4.15版中真正全面使用了PCID尽管在4.14中开始部分引入PCID见参考资料1这是为什么呢
PCID这么好的技术也有副作用。
在它之前的日子里Linux在多核CPU上调度进程时候因为每次进程调度都会刷掉进程用户空间的TLB并没有什么问题。
如果支持PCID的话TLB操作变得很简单或者说我们没有必要去执行TLB的操作因为在TLB的搜索的时候已经区分各个进程这样TLB不会影响其他任务的执行。
在单核系统中这样的操作确实能够获得很好的性能例如场景为A—B—A如果TLB足够大TLB再两个进程中反复切换极大的提升了性能。
但是在多核系统重如果CPU支持PCID并且在进程切换的时候不flush tlb那么系统中各个CPU中的TLB entry则保留各个进程的TLB entry当在某个CPU上一个进程被销毁了或者该进程修改了自己的页表的时候就必须将该进程的TLB从系统中请出去。
这时候不仅仅需要flush本CPU上对应的TLB entry还需要flush其他CPU上和该进程相关的残余。
而这个动作就需要通过IPI实现从而引起了系统开销此外PCID的分配和管理也会带来额外的开销。
再加上PCID里面的上下文ID长度有限只能够放得下4096个进程ID这就需要一定的管理以便申请和放弃。
如此种种导致Linux系统在应用PCID上并不积极直到不得不这样做。
7. 结论
TLB的引入解决了分页机制的性能问题但是如何提高TLB的性能问题但是如何提高TLB的命中确成为一个新的技术难题对于X86提供了PCID的方式而ARM采用的ASID技术但是对于现在日益复杂的应用场景这些都未能彻底的解决这些问题。
参考资料
https://zhuanlan.zhihu.com/p/492184589?utm_id0