原文链接https://lwn.net/Articles/694800/

控制对Cache的访问

cpu对内存的访问一直以来都会通过L1/L2/L3缓存来加速,我们都知道当你打算严肃地去考察性能问题时,各级缓存的命中率一直是一个重要的指标。而一个进程的缓存命中率在很大程度上又和它在各级缓存中所占的空间大小正相关。由于缓存本身是socket范围上的共享资源,一个进程的缓存命中率不仅取决于它自己的行为,同时也受和它共同运行的其他进程影响,这使得工程师们很难将一个特定进程的缓存命中率维持在理想的值上。

Intel自Haswell开始的cpu已经搭载了它们的缓存控制技术。Intel把它称为Cache Allocation Technology,缩写为CAT。在本届LinuxCon 2016上,来自Intel的Yu Fenghua向大家汇报了将这一技术集成进Linux内核的进度情况。

那么CAT具体是怎么工作的呢?在一个普通的Intel X86 cpu上,对于一个N-way set associative cache(N路组相联缓存)来说,当一个64B的cache line被带入LLC时,它的一部分虚拟地址决定了它会被hash到哪个行(或者说set)中,具体的映射算法Intel保密没有公开。进入一个set之后,放到哪个way(或者说列)上就按照正常的淘汰算法来进行,而在一个开启了CAT的cpu上,这时会有一个掩码(Intel称它为CBM,Cache Bit Mask)介入,决定这个新cache line只能放到哪几个way上、只能在哪几个way的范围之内进行淘汰。这就控制住了一个进程可以污染的cache范围。为了使用方便,这个掩码并不会直接使用,而是通过和一个名为CLOSID(Class of Service ID)的值联系在一起。Intel希望集成这一特性的OS在上下文切换时把进程对应的CLOSID写到一个特定的MSR寄存器里,这样CAT就流畅地跑起来了。

话虽如此,当Linux集成一个新特性的时候,考虑的可不仅仅是让它跑起来这么简单。硬件厂商对于一个新特性的想法会续地变化,一个通用操作系统的架构必须能够在一定范围内适应这种变化,它既要做好抽象,把细节屏蔽掉,使得这个框架能够适应不同厂商的相似特性;又不能抽象得太过份,以至于某些特性无法得到支持。在集成CAT这个问题上我们再一次看到了类似的故事上演。最初的一版patch是基于cgroup写的,经过几轮review之后,社区的反对意见集成在cgroup这个事情本身– CAT可以有两种配置风格, 一种是像上文说的那样,以进程或者进程组为中心配置,这时使用cgroup是没问题的;另一种是以cpu为中心配置,即我们指定给某一个cpu分配某些cache,跑在它上边的东西,不管是进程的用户态代码,还是陷入内核后执行的内核代码,还是中断处理函数,都遵守相同的分配规则。这时就无法和cgroup协调起来了,因为整个cgroup的设计都是围绕着进程视角来实现的。另外,CAT的掩码作用范围是在socket级别的,这就意味着操作系统可以允许一个进程跑在不同的socket上时使用不同的掩码,cgroup抽象由于没有办法描述cpu,同样抑制了这种用法。

放弃cgroup这个抽象之后,又有人提出了ioctl风格的接口,响应者寥寥。大家都不愿意再往ioctl这个大杂烩里加东西了。

最新一版的代码以新提出的kernfs抽象为基础,这是一个脱胎于sysfs的新的虚拟文件系统,cat的专属目录会出现在/sys/fs/resctrl下。在它之下有三个虚拟文件:tasks、cpus以及schema。tasks文件包含了被schema中的掩码控制的进程PID。cpus则包含被这些掩码控制的cpu编号。那么当一个特定进程跑在一个特定cpu上,而它俩在schema中的配置冲突了该听谁的呢?目前的策略是以cpu为中心的配置优先。

Yu Fenghua的演讲最后提到了这一新特性具体的配置方法,这可以在patchset中找到;同时他的slides里也提到了一些性能测试,从中可以看到对于一些cache竞争严重的场景CAT确实会有很大作用。