
昨天看了Baron写的文档,里面介绍了Cache的细节。天啊,这么详细,都快全面了~大哥就是大哥。
看完之后,我不禁想起了我在CSDN博客上公开发表的第一篇文章,关于缓存何时需要失效刷新的分析和解释。原文章写于2016年,忍不住分享到这里。比较简单,希望对不懂缓存操作的朋友有帮助。
Baron写了许多关于安全的文章。自谦不是著名的Trustzone/TEE/安全渣。他的研究兴趣包括ARM Trustzone、TEE、各种Linux和Android安全。https://blog.csdn.net/weixin_42135087. CSDN博客地址
1.什么是缓存?
缓存的存在主要是为了解决CPU的运算速度和内存的读写速度之间的矛盾。它是介于CPU和内存之间的临时内存,容量小,但交换速度比内存快。
百度百科是这样介绍缓存的:
当CPU要读取一个数据时,先从缓存中查找,如果找到,就立即读取并发送给CPU处理。如果没有找到,可以从内存中以相对较慢的速度读取,并发送给CPU进行处理。同时可以将这个数据所在的数据块转移到缓存中,这样以后就可以从缓存中读取整个数据,而不需要调用内存。
正是这种读取机制,使得CPU读取缓存的命中率非常高(大部分CPU可以达到90%左右),也就是说,CPU下次读取的数据90%都在缓存中,只有10%左右需要从内存中读取。这大大节省了CPU直接读取内存的时间,也使得CPU在读取数据时基本上不需要等待。一般来说,CPU读取数据的顺序是先缓存再内存。
2.缓存的分类
缓存的硬件实现通常包括L1缓存(L1缓存甚至多级缓存。
对于一级缓存,有指令缓存(通常称为I-Cache)和数据缓存(通常称为D-Cache)。本文准备不讨论各级缓存之间的区别以及I-Cache和D-Cache的细节,只把所有这些实现统称为Cache。
本文仅对缓存的读写进行简要说明,并用示意图演示何时刷新缓存,何时使缓存失效。
目前我知道的一款很强的CPU:AMD ryzgenx(Thread Ripper)32核64线程,一级缓存3MB,二级缓存16MB,三级缓存128MB。有个朋友用这个芯片配置个人电脑,编译最新的Android S (12)只用了20多分钟,但是大部分公司的服务器都达不到这个性能。
对于指令缓存的I缓存和数据缓存的D缓存,D缓存的访问通常更多。下面主要以D缓存访问为例来说明指令缓存的I缓存也是同样的原理。
3.高速缓存数据访问原则
缓存读写原理
图一、缓存读写原理
写入数据时:
步骤1,CPU将数据写入缓存;
步骤2,将缓存数据转移到内存中相应的位置;
读取数据时:
步骤1,将内存中的数据转移到缓存中;
步骤2,CPU从缓存中读取数据;
在具体的硬件实现中,缓存写操作有两种方式:直写和回写:
直写
在直写式缓存中,CPU的数据总是被写入内存。如果存在对应于高速缓存中的存储器位置的数据的备份,则该备份也应该被更新,以确保存储器和高速缓存中的数据总是同步的。因此,图1中的步骤1和2将总是为每个操作执行。
回写
回写式缓存中,只将待写入的数据写入缓存,并标记缓存的相应位置,只有在需要时才会将数据更新到内存中。因此,步骤1中的图1将在每次写操作时执行,但步骤2并不总是在步骤1之后执行。
直写模式存在性能瓶颈,其性能低于回写模式。目前CPU设计基本采用缓存回写方式。
通常数据只有CPU访问,每次访问都会经过缓存,所以数据同步不会有问题。
当一个设备执行DMA操作时,该设备不通过缓存读取和写入数据,而是直接访问存储器。当设备和CPU读写同一个内存时,得到的数据可能不一致,如图2所示。
读取和写入相同内存时,设备和CPU之间的数据不一致。
图二、设备和CPU读写同一个内存时数据不一致。
CPU执行步骤1将数据A写入高速缓存,但它并不总是执行步骤2将数据A同步到存储器中,从而导致高速缓存中的数据A和存储器中的数据A’不一致。在步骤3中,当通过DMA操作时,外部设备直接从存储器访问数据,从而获得A’而不是A.
设备的DMA操作完成后,通过步骤4将数据B写入内存;但由于内存中的数据不会自动与缓存同步,所以步骤5不会执行,所以CPU在步骤3读取数据时,可能得到的是缓存中的数据B’,而不是内存中的数据B;
当CPU和外设访问同一个内存区时,如何操作缓存保证设备和CPU访问的数据一致就显得尤为重要,如图3所示。
缓存操作同步数据
图三、缓存操作同步数据
CPU执行步骤1,将数据A写入缓存。由于设备还需要访问数据A,因此它执行步骤2,通过刷新操作将数据A同步到内存中。第三步,外部设备通过DMA操作时直接从内存中访问数据A,最后CPU和设备访问相同的数据。
设备的DMA操作完成后,通过步骤4将数据B写入内存;由于CPU还需要访问数据B,所以在访问之前,缓存中的数据会被无效操作无效,所以在通过缓存读取数据时,缓存会从内存中取数据,所以CPU在步骤6读取数据时,从内存中获取更新后的数据;
4.高速缓存操作示例
4.1外设数据的DMA传输
比如在某个机顶盒平台中,存储器的加解密是在单独的安全芯片中进行的,安全芯片访问的数据是通过DMA传输的。
因此,在内存加解密之前,需要通过flush D-Cache操作将数据同步到内存中,供安全芯片访问;
加密解密完成后需要进行Invalidate D-Cache操作,保证CPU访问的数据是安全芯片加密解密的结果,而不是缓存前保存的数据;
DMA数据加密和解密示例代码:
void mem _ DMA _ desc(unsignedlongMode,unsignedlongSrcAddr,unsignedlongDestAddr,unsignedlongSlot,unsignedlongSize){.preparefordmaencryption/decryption operation.flush _ d _ cache(SrcAddr,Size);dodmaoperation,outputwillberedirecttoDestAddr.invalidateD _ d _ Cache(DestAddr,Size);返回;}4.2外设闪存的I/O
某平台nand flash的控制器也支持DMA读取。在向nand flash写入数据时,需要flash dcache来保证DMA操作的数据是真正要写入的数据,而不是内存中过期的数据。
从nand flash读取数据后需要Invalidate dcache,使缓存中的数据失效,从而保证cpu在最后一次访问时读取的是内存数据而不是缓存的结果。
通过DMA读取数据的nand flash示例代码:
static intnand _ DMA _ read(struct NAND _ dev * NAND,uint64_taddr,void*buf,size _ tlen){ intret;preparefornandflashreadddevicedmatransfer.flush _ d _ cache(descs,ndes cs * sizeof(* descs));ret=NAND _ DMA _ run(NAND,(uintptr _ t)descs);invalidate _ d _ Cache(buf,len);其他操作.returnret除了nand flash,很多硬盘也支持DMA读取。
4.3缓存和D缓存之间的转换
一般来说,缓存分为I缓存和D缓存。当执行指令时访问I缓存,当读写数据时访问D缓存。
然而,在代码处理期间,存储在外设上的指令将被视为数据。
比如一段代码存储在外设(比如nand flash或者硬盘)上,CPU想要执行这段代码,就需要先把这段代码作为数据复制到内存中,再把这段代码作为指令执行。
由于写数据和读指令分别经过D-Cache和I-Cache,所以需要同步D-Cache和I-Cache,即复制完成后,D-Cache需要先写回内存,当前I-Cache需要作废,以保证执行的是内存中更新的代码,而不是I-Cache中缓存的数据,如图4所示:
图四、复制代码后CPU执行。
CPU复制代码后执行的示例代码:
void copy _ code _ and _ execution(unsignedchar * src,unsignedchar*dest,size_tlen){.copycodefromsrcaddrtodestaddr.flush _ all _ d _ cache();invalidate _ all _ I _ Cache();jumptodesaddressforexecutionandneverreturn.printf(' failed tojumping . ');返回;}黄飞









