什么是TLB和PCID?为什么要有PCID?为什么Linux现在才开始使用它?

什么是TLB和PCID?为什么要有PCID?为什么Linux现在才开始使用它?

最近CPU爆出的大bug占领了各大头条,这对我们技术爱好者来说反倒是件好事。这不,原先没人关心的TLB,cache啥的开始出现在报纸上了,这是技术的春天来了吗?说不定我可以给某个纸面媒体发篇文章呢。不过其中有些不准确的信息让人颇为担心:

一些高级的芯片功能(例如 PCID)可以支持其他技术,从而减少性能影响。。。。

PCID这个在Westmere(1代酷睿,怎么知道我的电脑是酷睿几代呢?)就已经有的功能,现在相信大家所有使用中的Intel CPU都有这个特性,什么时候变成高级功能了?

关于PCID的文章很少,相信不少同学好奇它是什么?本文就来为你解惑。要介绍它,的要从TLB说起。

TLB和页表

我们在前文(Cache是怎么组织和工作的?)简单介绍了TLB,这里稍微回顾一下。

我们都知道地址分为逻辑地址(虚拟地址)、线性地址和物理地址。一个虚拟地址变换成物理地址的简单过程如下图,我就不详细说明了:

变换过程中线性地址到物理地址需要用到页表(page table)。页表由很多项组成,每一项叫一个页表项,整个页表由操作系统维护,并放置在内存中(或磁盘中)。从上文(L1,L2,L3 Cache究竟在哪里?)我们知道,一次内存访问需要数百个时钟周期,如果每次地址转换都要查看内存页表也太浪费时间了。现代计算机为了加速这一过程,引入了翻译后援缓冲器TLB。TLB可以看作页表的Cache,CPU每次转换地址都会查看TLB,如果有了就不用去取内存页表了。TLB和Cache一样,也分级,分为L1和L2,还有数据TLB和代码TLB等等。

我们知道,现代操作系统进程地址空间都是分隔开来的,同一个线性地址VA1,在进程1中会被翻译成物理地址PA1,在进程2中也许会被翻译成PA2。它们的这种对应关系是因为不同的进程有不同的页表,页表的切换是在进程切换时候把页目录指针放入寄存器CR3来实现的。假如不做任何的处理,那么在进程1切换到进程2的时候,TLB中同时存在了1和2进程的页表数据,会造成混乱。这就需要我们在进程切换的时候清除TLB,给新换入的进程留下一个干净的空间。

但这样做会造成性能的损失。空空如也的TLB必定会带来极大的TLB miss而不得不从主存调取页表,这将消耗数百个时钟周期。从前文(Cache是怎么组织和工作的?)我们还知道这样做还会进一步放大Cache的miss。我们管这种空TLB叫做cold TLB,它需要随着进程的运行warm up起来才能慢慢发挥起来效果,而在这个时候有可能又会有新的进程被调度了,而造成TLB的颠簸效应。如何做才能优化TLB的呢?

Global TLB和non-global TLB

现代OS都将地址空间分为内核空间和用户空间。内核空间ring 0访问,用户空间ring 3访问。相信这些最基本的知识大家都耳熟能详了,我就不再啰嗦了。内核空间内容基本各个进程(包括内核线程)都差不多,内核地址空间是一样的,因此对于这部分地址翻译,无论进程如何切换,内核地址空间转换到物理地址的关系是永远不变的,在进程的时候,不需要清掉。对于用户空间,各个进程的内容都不太一样,保留只会造成混乱,需要清掉。

在这种思路引导下,CPU在加载CR3的时候,只会清掉不带Global标志的用户空间页表TLB,而不会动带有global标志的内核页表项。一个新的进程会开始一个半新的TLB,效能提高不少。

按照这种思路走下去,那就要思考,有没有别的办法能够不刷新TLB呢?有办法的,那就是PCID。

PCID

PCID(进程上下文标识符)是在Westmere架构引入的新特性。简单来说,在此之前,TLB是单纯的VA到PA的转换表,进程1和进程2的VA对应的PA不同,不能放在一起。加上PCID后,转换变成VA + 进程上下文ID到PA的转换表,放在一起完全没有问题了。这样进程1和进程2的页表可以和谐的在TLB中共处,进程在它们之前切换完全不需要预热了!

所以新的加载CR3的过程变成了:如果CR4的PCID=1,加载CR3就不需要Flush TLB。

一切看起来很美好,PCID这个在多年前就有了的技术,现在已经在每个Intel CPU中生根了,那么是不是已经被广泛使用了呢?而实际的情况是Linux在2017年底才在4.15版中真正全面使用了PCID(尽管在4.14中开始部分引入PCID,见参考资料1),这是为什么呢?

TLB shootdown

PCID这么好的技术也有副作用。在它之前的日子里,Linux在多核CPU上调度进程时候,因为每次进程调度都会刷掉进程用户空间的TLB,并没有什么问题。而加入PCID之后,没有人会主动刷TLB,而进程可以在CPU核心之间调度,这样一来进程的页表项可能出现在很多CPU核心中。这时,如果我们要更改页表属性或者删除页表,光干掉本CPU的TLB项还不够,还要去清掉别的CPU核心的TLB项。而这个动作只能够通过发送IPI的方式让别的CPU核心自己干,十分耗时且复杂。

这就是TLB shootdown名字的由来。脑补一个画面,一个CPU核心坐在地上,用高射机枪打别的内核TLB里面细小的页表:

再加上PCID里面的上下文ID长度有限,只能够放得下4096个进程ID,这就需要一定的管理以便申请和放弃。

如此种种,导致Linux系统在应用PCID上并不积极,直到不得不这样做。

结论

TLB优化还有很多种方式。还有一个方法是把页表项目变少一点。现在CPU都支持64位模式,地址宽度动辄四五十位(服务器和台式机CPU不同),如果为每个支持的物理空间都分配4k页表,全页表占用的内存就对达到几百兆!!想象一下OS可以有数百个进程,这要占据多少内存,而且这些页表占用内存是不能换出到磁盘上的(知道为什么吗?)!

当然OS并不会为所有可访问地址空间都建立页表,更不会为每个进程都这样做。这只是个极端的例子,但是页表占用大量内存却是不得不考虑的问题。Intel在2MB/4MB页表的基础上,推出了1GB页表,为这个问题找到了解决方案,极大的减少了页表项。而且页表项目的减少,也对TLB miss的情况有了明显的改善,可谓一举多得。

另外ARM世界里也有和PCID类似的技术,叫做ASID,有兴趣的同学可以google一下相关资料。

其他CPU相关文章:

CPU制造的那些事之一:i7和i5其实是孪生兄弟!?

CPU制造的那些事之二:Die的大小和良品率

为什么CPU的频率止步于4G?我们触到频率天花板了吗?

为什么晶圆都是圆的不是方的?

为什么"电路"要铺满整个晶圆?

为什么Intel CPU的Die越来越小了?

为什么CPU越来越多地采用硅脂而不是焊锡散热?

为什么Intel CPU的Die越来越小了?

破茧化蝶,从Ring Bus到Mesh网络,CPU片内总线的进化之路

450mm的晶圆在哪里?

Cache其他文章:

L1,L2,L3 Cache究竟在哪里?

Cache为什么有那么多级?为什么一级比一级大?是不是Cache越大越好?

欢迎大家关注本专栏和用微信扫描下方二维码加入微信公众号"UEFIBlog",在那里有最新的文章。同时欢迎大家给本专栏和公众号投稿!

参考资料:

[1]: Linux_4.14 - Linux Kernel Newbies

用微信扫描二维码加入UEFIBlog公众号

编辑于 2018-01-08

文章被以下专栏收录