如何在Linux中刷新CPUcaching区域的地址空间?

我只对一个地址空间区域(例如,从地址A到地址B的所有caching项)刷新caching(L1,L2和L3)感兴趣。有没有一种机制可以在Linux中从用户或内核空间?

在这个页面查看linux内核中可用的刷新方法列表: https : //www.kernel.org/doc/Documentation/cachetlb.txt

在Linux下缓存和TLB刷新。 大卫S.米勒

有一套范围冲洗功能

2) flush_cache_range(vma, start, end); change_range_of_page_tables(mm, start, end); flush_tlb_range(vma, start, end); 

3)void flush_cache_range(struct vm_area_struct * vma,unsigned long start,unsigned long end)

 Here we are flushing a specific range of (user) virtual addresses from the cache. After running, there will be no entries in the cache for 'vma->vm_mm' for virtual addresses in the range 'start' to 'end-1'. 

你也可以检查函数的实现 – http://lxr.free-electrons.com/ident?a=sh;i=flush_cache_range

例如,在手臂 – http://lxr.free-electrons.com/source/arch/arm/mm/flush.c?a=sh&v=3.13#L67

  67 void flush_cache_range(struct vm_area_struct *vma, unsigned long start, unsigned long end) 68 { 69 if (cache_is_vivt()) { 70 vivt_flush_cache_range(vma, start, end); 71 return; 72 } 73 74 if (cache_is_vipt_aliasing()) { 75 asm( "mcr p15, 0, %0, c7, c14, 0\n" 76 " mcr p15, 0, %0, c7, c10, 4" 77 : 78 : "r" (0) 79 : "cc"); 80 } 81 82 if (vma->vm_flags & VM_EXEC) 83 __flush_icache_all(); 84 } 

这是为ARM。

GCC提供了__builtin___clear_cache应该做系统调用cacheflush 。 然而,它可能有其警告 。

这里重要的是Linux提供了一个系统调用(特定于ARM)来刷新缓存。 你可以检查Android / Bionic flushcache如何使用这个系统调用。 不过,我不确定Linux给你什么样的保证,或者它是如何通过内部工作来实现的。

这篇博客文章缓存和自修改代码可能会有所帮助。

在x86版本的Linux中,您还可以找到一个函数void clflush_cache_range(void *vaddr, unsigned int size) ,用于刷新缓存区域。 该功能依赖于CLFLUSHCLFLUSHOPT指令。 我会建议检查你的处理器实际上是否支持它们,因为理论上它们是可选的。

CLFLUSHOPT是弱有序的。 CLFLUSH最初被指定为仅由MFENCE命令,但实现它的所有CPU都以强排序的方式执行。 写入和其他CLFLUSH指令。 英特尔决定添加一个新的指令( CLFLUSHOPT ),而不是改变CLFLUSH的行为,并更新手册以保证未来的CPU将按照强烈的顺序实现CLFLUSH 。 对于这个用途,你应该在使用之后使用MFENCE ,以确保在你的基准测试(不仅仅是商店)负载之前完成冲洗。

实际上x86提供了一个更有用的指令: CLWBCLWB将缓存中的数据刷新到内存中,而不会将其清除,并保持缓存。

还要注意,这些指令是缓存一致的。 它们的执行会影响系统中所有处理器(处理器核心)的所有缓存。

所有这三个指令都可以在用户模式下使用。 因此,您可以在您的用户空间应用程序中使用汇编程序并创建自己的void clflush_cache_range(void *vaddr, unsigned int size) (但在实际使用之前,不要忘记检查它们的可用性)。


如果我正确理解,就这方面来说,推理ARM要困难得多。 ARM处理器系列与IA-32处理器家族的一致性要差得多。 你可以拥有一个具有全功能高速缓存的ARM,另一个完全没有高速缓存。 此外,许多制造商可以使用定制的MMU和MPU。 所以最好推理一些特定的ARM处理器模型。

不幸的是,看起来几乎不可能对刷新某些数据所需的时间进行任何合理的估计。 这个时间受到很多因素的影响,包括刷新的缓存行数,指令的无序执行,TLB的状态(因为指令采用虚拟地址作为参数,但是缓存使用物理地址),系统中CPU的数量,系统中其他处理器上的内存操作的实际负载,以及该范围内的多少行实际上被处理器缓存,最后是CPU,内存,内存控制器和内存总线的性能。 结果,我认为在不同的环境和不同的负载下执行时间会有很大的不同。 唯一合理的方法是测量系统上的冲洗时间,并用与目标系统类似的负载进行测量。


最后,请不要混淆内存缓存和TLB。 他们都是缓存,但是以不同的方式组织起来,服务于不同的目的。 TLB缓存最近使用的虚拟地址和物理地址之间的翻译,但不包括那些地址指向的数据。

与内存缓存相比,TLB并不一致。 要小心,因为刷新TLB条目不会导致从内存缓存中清除适当的数据。

有几个人对clear_cache表示疑虑。 以下是一个手动过程,用于驱逐高效缓存,但可能来自任何用户空间任务(在任何操作系统中)。


PLD / LDR

有可能通过pld指令来驱逐高速缓存。 该pld将获取缓存线。 为了驱逐一个特定的内存地址,你需要知道你的缓存的结构。 例如,cortex-a9有一个4行数据缓存,每行8个字。 缓存大小可配置为16KB,32KB或64KB。 那就是512,1024或2048行。 这些方式对低地址位来说总是微不足道的(所以顺序地址不会冲突)。 所以你将通过访问memory offset + cache size / ways来填充新的memory offset + cache size / ways 。 所以,对于cortex-a9,每4KB,8KB和16KB。

在'C'或'C ++'中使用ldr很简单。 您只需要适当调整数组的大小并访问它。

请参阅:以编程方式获取缓存行大小?

例如,如果要驱逐0x12345 ,行开始于0x12340 ,对于16KB循环缓存, 0x13340,0x14340,0x153400x16340上的pld将驱逐任何值。 驱逐L2(通常是统一的)可以使用同样的原则。 遍历所有的缓存大小将驱逐整个缓存。 您需要分配一个未使用的内存大小的缓存驱逐整个缓存。 L2可能相当大。 pld不需要被使用,而是一个完整的内存访问( ldr/ldm )。 对于多个CPU(线程缓存逐出),您需要在每个CPU上运行逐出。 通常L2对于所有的CPU都是全局的,所以它只需要运行一次。

NB:这种方法只适用于LRU (最近最少使用)或循环缓存。 对于伪随机替换,您将不得不写入/读取更多的数据以确保逐出,确切的数量是高度CPU特定的。 ARM随机替换是基于一个8-33位的LFSR,取决于CPU。 对于某些CPU,默认为循环 ,其他的默认为伪随机模式。 对于几个CPU,Linux内核配置将选择该模式。 ref: CPU_CACHE_ROUND_ROBIN但是,对于较新的CPU,Linux将使用引导装载程序和/或芯片的默认值。 换句话说,如果您需要完全通用,或者您将需要花费大量时间来可靠地清除缓存,那么尝试使clear_cache OS调用起作用(请参阅其他答案)是值得的。

上下文swich

通过在某些ARM CPU和特定的操作系统上使用MMU欺骗操作系统,可以规避高速缓存。 在* nix系统上,您需要多个进程。 您需要在进程之间切换,操作系统应刷新缓存。 通常情况下,这只适用于较旧的ARM CPU(不支持pld CPU),操作系统应该刷新缓存以确保进程之间没有信息泄露。 这不是可移植的,需要您了解很多关于您的操作系统。

大多数显式缓存刷新寄存器都被限制在系统模式下,以防止进程之间的拒绝服务类型攻击。 一些攻击可以通过查看哪些行被其他进程驱逐(这可以提供另一个进程正在访问什么地址的信息)来尝试获取信息。 伪随机替换这些攻击更加困难。