如何在Linux中刷新地址空间区域的CPU缓存?

时间:2014-03-27 23:31:18

标签: c linux linux-kernel x86 arm

我感兴趣的是仅针对地址空间区域刷新缓存(L1,L2和L3),例如从地址A到地址B的所有缓存条目。是否存在在Linux中执行此操作的机制,来自用户还是内核空间?

5 个答案:

答案 0 :(得分:10)

检查此页面以获取linux内核中可用的刷新方法列表:https://www.kernel.org/doc/Documentation/cachetlb.txt

  

Linux下的缓存和TLB刷新。大卫·米勒

有一系列范围刷新功能

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 }

答案 1 :(得分:7)

这适用于ARM。

GCC提供__builtin___clear_cache 应该syscall cacheflush。但是它可能有caveats

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

此博文Caches and Self-Modifying Code可能会有所帮助。

答案 2 :(得分:4)

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

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

实际上,x86提供了另外一条可能有用的指令:CLWBCLWB将数据从缓存刷新到内存而不会将其清除,使其保持干净但仍然缓存。

另请注意,这些指令是缓存一致的。它们的执行将影响系统中所有处理器(处理器内核)的所有高速缓存。

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

如果我正确理解,在这方面要解释ARM要困难得多。与IA-32处理器系列相比,ARM系列处理器的一致性要低得多。您可以拥有一个具有全功能缓存的ARM,另一个完全没有缓存的ARM。此外,许多制造商可以使用定制的MMU和MPU。因此,最好对某些特定的ARM处理器模型进行推理。

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

最后请注意,不要混淆内存缓存和TLB。它们都是缓存,但以不同的方式组织并用于不同的目的。 TLB仅缓存虚拟和物理地址之间最近使用的转换,但不缓存该地址指向的数据。

与内存缓存相比,TLB并不一致。请注意,因为刷新TLB条目不会导致从内存缓存中刷新适当的数据。

答案 3 :(得分:3)

有几个人对clear_cache表示疑虑。下面是一个手动过程,用于驱逐缓存,该缓存效率低,但可以从任何用户空间任务(在任何操作系统中)实现。

PLD / LDR

可以通过 mis - 使用pld指令来驱逐缓存。 pld将获取缓存行。为了逐出特定的内存地址,您需要知道缓存的结构。例如,cortex-a9有一个4路数据缓存,每行8个字。高速缓存大小可配置为16KB,32KB或64KB。这是512,1024或2048行。这些方法总是对低地址位无关紧要(因此顺序地址不会发生冲突)。因此,您将通过访问memory offset + cache size / ways来填补新的方式。因此对于皮质-a9来说,每4KB,8KB和16KB。

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

请参阅:Programmatically get the cache line size?

例如,如果要驱逐 0x12345 ,则行从 0x12340 开始,对于16KB循环缓存, 0x13340上的pld 0x14340 0x15340 0x16340 将以这种方式逐出任何值。可以应用相同的原则来驱逐L2(通常是统一的)。迭代所有高速缓存大小将驱逐整个高速缓存。您需要分配一个与缓存大小相同的未使用内存来驱逐整个缓存。这可能对L2来说非常大。不需要使用pld,而是需要完全内存访问(ldr/ldm)。对于多个CPU(线程缓存逐出),您需要在每个CPU上运行驱逐。通常,L2对所有CPU都是全局的,所以它只需要运行一次。

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

Context swich

可以通过在某些ARM CPU和特定操作系统上使用MMU欺骗操作系统来规避缓存。在* nix系统上,您需要多个进程。您需要在进程之间切换,操作系统应该刷新缓存。通常,这仅适用于较旧的ARM CPU(不支持pld的CPU),其中OS应刷新缓存以确保进程之间不会发生信息泄漏。它不可移植,需要您对操作系统有很多了解。

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

答案 4 :(得分:1)

在x86中,您可以使用它刷新整个缓存层次结构

native_wbinvd()

在arch / x86 / include / asm / special_insns.h中定义。如果您看一下它的实现,它只是调用WBINVD指令

static inline void native_wbinvd(void)
{
        asm volatile("wbinvd": : :"memory");
}

请注意,您需要处于特权模式才能执行 WBINVD X86指令。这与 CLFLUSH x86指令相反,该指令清除单个缓存行,并且不需要调用方处于特权模式。

如果您查看x86 Linux内核代码,则只会看到该指令的极少数(在撰写本文时为6位)。这是因为它会减慢该系统上运行的所有实体的速度。想象一下,在具有100MB LLC的服务器上运行它。此指令将意味着将整个100+ MB从缓存移到RAM。此外,我还注意到该指令是不间断的。因此,它的使用可能会严重影响RT系统的确定性,例如

(尽管最初的问题询问如何清除特定的地址范围,但我认为有关清除整个缓存层次结构的信息对某些读者也很有用)