我对清除缓存(L1、L2 和 L3)只针对地址空间的某个区域感兴趣,例如从地址 A 到地址 B 的所有缓存条目。在 Linux 中,是否有一种机制可以从用户或内核空间进行此操作?
我对清除缓存(L1、L2 和 L3)只针对地址空间的某个区域感兴趣,例如从地址 A 到地址 B 的所有缓存条目。在 Linux 中,是否有一种机制可以从用户或内核空间进行此操作?
请查看此页面,以获取Linux内核中可用清除方法的列表:https://www.kernel.org/doc/Documentation/cachetlb.txt
在Linux下进行缓存和TLB清除。David S. Miller
这里有一组范围清除函数。
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
例如,在ARM中 - 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
,它应该执行syscall cacheflush
。但是它可能有注意事项。
重要的是Linux提供了一个系统调用(特定于ARM)来刷新缓存。您可以查看Android/Bionic flushcache 以了解如何使用此系统调用。但我不确定在调用时Linux会给出什么样的保证或者它是如何通过内部工作实现的。
这篇博客文章缓存和自修改代码可能会有所帮助。
cacheflush
的行为,你应该直接调用它。调用一个具有较弱行为保证的内置函数,因为它目前恰好是在你想要的更强函数之上实现的,似乎不是一个好主意。 - Peter Cordesvoid clflush_cache_range(void *vaddr, unsigned int size)
的函数,该函数用于清除缓存范围。此函数依赖于CLFLUSH
或CLFLUSHOPT
指令。建议检查处理器是否实际支持它们,因为理论上它们是可选的。
CLFLUSHOPT
是弱排序的。 CLFLUSH
最初被指定为仅由MFENCE
排序,但是所有实现它的CPU都具有与写入和其他CLFLUSH
指令强排序相关的功能。英特尔决定添加一个新指令(CLFLUSHOPT
),而不是更改CLFLUSH
的行为,并更新手册以保证未来的CPU将强制执行CLFLUSH
。出于这个目的,在使用任何一种指令之后,应该MFENCE
,以确保在负载从基准测试中加载之前完成刷新(不仅是存储)。CLWB
。 CLWB
将数据从缓存刷新到内存而不会(必须)使其失效,使其保持清洁但仍然被缓存。clwb
在SKX上像clflushopt
一样使其失效,但是
还要注意,这些指令是高速缓存一致的。它们的执行将影响系统中所有处理器(处理器核心)的所有高速缓存。_mm_clflushopt
的内部函数)并在您的用户空间应用程序中创建自己的void clflush_cache_range(void *vaddr, unsigned int size)
(但在实际使用之前不要忘记检查它们的可用性)。
最后需要注意的是,不要混淆内存缓存和TLB。它们都是缓存,但组织方式和服务的目的不同。TLB缓存最近使用的虚拟和物理地址之间的转换,但不缓存由这些地址指向的数据。
而且,与内存缓存不同,TLB不是一致的。要小心,因为刷新TLB条目不会导致从内存缓存中刷新相应的数据。
有几个人对clear_cache
表示担忧。下面是一种手动清除缓存的方法,虽然效率不高,但在任何用户空间任务(在任何操作系统中)中都可以实现。
可以通过错误地使用pld
指令来清除缓存。 pld
将获取一个缓存行。为了清除特定的内存地址,您需要了解缓存的结构。例如,cortex-a9具有4路数据缓存,每行8个字。缓存大小可配置为16KB、32KB或64KB。因此,它有512、1024或2048个行。路始终不重要于较低的地址位(因此顺序地址不会冲突)。因此,您将通过访问memory offset + cache size / ways
来填充新的路径。对于cortex-a9,这是每4KB、8KB和16KB。
在“C”或“C ++”中使用ldr
很简单。您只需要适当地调整数组大小并访问即可。
请参见:Programmatically get the cache line size?
例如,如果要清除0x12345,则行从0x12340开始,并且对于16KB循环缓存,对0x13340、0x14340、0x15340和0x16340的pld
将清除该路径中的任何值。相同的原理可以应用于清除L2(通常是统一的)。迭代所有缓存大小将清除整个缓存。您需要分配一个未使用的内存来清除整个缓存。对于L2而言,这可能非常大。pld
不需要使用,但需要完全访问内存(ldr/ldm
)。对于多个CPU(线程化缓存驱逐),您需要在每个CPU上运行驱逐。通常,L2对所有CPU都是全局的,因此只需运行一次即可。
NB: 此方法仅适用于LRU(最近最少使用)或轮询缓存。 对于伪随机替换,您将需要写入/读取更多数据以确保清除,具体数量高度与CPU相关。 ARM随机替换基于一个LFSR,其位数取决于CPU,范围在8-33位之间。 对于一些CPU,默认为轮询模式,而对于其他CPU,则默认为伪随机模式。 对于一些CPU,Linux内核配置将选择模式。ref: CPU_CACHE_ROUND_ROBIN 然而,对于更新的CPU,Linux将使用启动加载程序和/或硅片的默认值。 换句话说,如果您需要完全通用,或者您必须花费大量时间可靠地清除缓存,则值得尝试并使clear_cache
操作系统调用起作用(请参见其他答案)。
在某些ARM CPU和特定操作系统上,可以通过欺骗使用MMU的操作系统来绕过缓存。在*nix系统上,您需要多个进程。您需要在进程之间切换,并且操作系统应刷新缓存。通常,这只适用于旧的ARM CPU(不支持pld
),操作系统应刷新缓存以确保进程之间没有信息泄漏。它不可移植,并且需要您对操作系统有很多了解。
大多数显式缓存清除寄存器都受限于系统模式,以防止进程之间的拒绝服务类型攻击。一些漏洞利用可以尝试通过查看其他进程已经清除的行来获得信息(这可以提供关于其他进程正在访问的地址的信息)。使用伪随机替换会使这些攻击更加困难。
native_wbinvd()
这在 arch/x86/include/asm/special_insns.h 中有定义。如果你查看它的实现,它只是调用了 WBINVD 指令。
static inline void native_wbinvd(void)
{
asm volatile("wbinvd": : :"memory");
}
wbinvd
本身不可中断,因此它对中断延迟非常不利。这几乎总是错误的解决方案,除了性能实验或其他实验性或玩具用途。此外,它会刷新所有核心上的所有缓存。 - Peter Cordes