在前两期,我们讲述了X姐利用缓存一致性原理,晋升P9并且设法避免了被老板毕业的故事,链接在此:
软硬件融合技术内幕 基础篇 (9) ——大厂高P毕业背后的隐情 (上)
软硬件融合技术内幕 基础篇 (10) ——大厂高P毕业背后的隐情 (中)
原来,在以Intel至强处理器为代表的多核处理器中,存在着缓存一致性机制,缓存一致性机制的核心,叫做“写传播”和“串行化”。
所谓的写传播,指的是某个CPU,向某个物理地址写入一个值的时候,需要通知其他CPU,如果自己的缓存中,有指向这个物理地址的值,需要处理这个事件;
如图,图中有多个CPU的L2缓存均映射到RAM中的某个单元0x80053020,此时CPU 2对该单元进行了写操作,CPU的缓存控制器会将这个操作同步到其他所有CPU。
CPU0,CPU63等CPU中,有L2缓存映射到了0x80053020,接收到这一同步操作的信息的时候,会更新自己的缓存。而CPU1并没有L2缓存映射到这一地址,自然也不会做缓存更新的动作。
而所谓的串行化,是指这样的行为:
如对某个内存单元的物理地址进行了多次写入,该写入行为在向各个CPU进行写传播时,其先后次序是一致的。
如下图:
图中,红色箭头代表CPU2向0x80053020写入了一个数值,假定为0x55AAAA55。而蓝色箭头代表CPU 3向同一个地址0x80053020写入了一个数值,假定为0xAA5555AA。
假定CPU2比CPU3先一步完成写入动作,那么,首先,RAM中这个地址的内容会发生两次变化:
第一次为CPU2的写入结果:0x55AAAA55
第二次为CPU3的写入结果:0xAA5555AA
并且,需要保证,红色虚线箭头和蓝色箭头代表的两次写传播次序,生效时间与CPU写入行为一致,也就是红色箭头代表的CPU 2写入在前,蓝色箭头代表的CPU 3写入再后。这样,RAM中以及各个CPU中对应的Cache内容是一致的,均为0xAA5555AA。
如果写传播次序对于每个CPU核而言,并没有严格实现强一致,那么,后果是可想而知的——
在某些CPU看来,L2 Cache中的内容是0x55AAAA55,程序读取这个地址的时候,读取到的是错误的数据(也就是所谓的脏数据),这将导致程序运行的结果是错误的。
进一步地,对于内存的读写,除了CPU以外,还有一种可能性——外设直接读写系统的内存,也就是所谓的DMA(Direct memory access)。
我们在前文中介绍过,对于网卡和磁盘等外设,有可能以包的方式或块的方式进行数据的读写,如果让CPU逐个字(word)的方式,在内存和外设之间进行数据的搬运,就如同原始的钢铁工厂中,手工搬运煤炭、石灰和铁矿石等原料一样效率低下。当然,进入了蒸汽机时代后,钢铁工厂利用了自动化传送带这一发明,免去了人力搬运的劳动,形成了所谓的“煤钢复合体”,提升了生产力,从而实现了工业化的开端。
实际上,早在8086时代,工程师们就赋予了计算机外设实现DMA的能力。外设可以向DMA控制器发出DMA请求,DMA会暂时封锁总线,允许外设对指定的内存地址进行直接读写。当然,这块地址是系统预留,并在初始化外设的时候告知了外设。
以网卡(以太网适配器)收包为例,网卡接收到数据包以后,会将数据包通过DMA方式写入操作系统驱动为网卡申请的mbuf内存,写入完毕后,网卡发生msi中断,操作系统在msi中断处理程序中获取到mbuf内存地址(物理地址),转换为逻辑地址后,对数据包内容进行分类处理。显然,由于mbuf地址指向的内存内容被网卡修改过,有可能会与CPU Cache内缓存的内容不一致。
这个问题最简单粗暴的办法,当然是禁用Cache了 (误)。在Linux中有一个接口,叫dma_alloc_coherent,可以分配no cache的内存给DMA使用。显然,这会造成CPU访问数据的性能较为低下。
因此,软件维护cache一致性的方式也随之而来。在Linux下,还有一个接口叫做dma_cache_sync,用以实现cache同步,也就是在CPU读写有可能被DMA修改的缓冲区时,读之前同步cache,或写之后同步cache,保证cache和RAM的内容一致。
当然,最先进的方式,是在硬件设计中搞定这件事情。在x86_64架构中,已经定义了这种缓存一致性(详见Volume 3,Chap 11,Memory Cache Control),并使用CCI(Cache Conherence Interconnect)硬件来实现。但是,ARM处理器并没有对DMA的缓存一致性做要求,工程师们有可能需要使用软件处理的方式来实现缓存一致性。这在IO和网络的性能上,就会有显著的体现了。
当然,缓存只是计算机性能的一个助推器。下一期,我们将为大家介绍决定计算机性能的根本——