From 程序员秘书
缓存一致性是一个非常关键的问题,特别是在多核处理器和直接内存访问(DMA)场景下。原因如下:
- 多核CPU与cache的缓存一致性问题:每个CPU core都有自己的cache,由于cache的写回机制,部分数据可能没有及时更新到内存,就会导致出现不同线程访问同一个变量时出现值不一致的情况。
- DMA与cache的缓存一致性问题:DMA对内存的访问不需要经过CPU,因此如果DMA修改了内存上的数据,而CPU的cache中并不知道内存已经修改了,从而会导致出现缓存不一致的情况。
因此就需要有机制和接口来确保缓存的一致性。
应对策略
- 写传播(Write Propagation):当某个CPU core里的Cache数据更新时,必须传播到其他core的Cache,以确保数据的一致性。
- 事务的串行化(Transaction Serialization):确保不同核心看到的其他core对于代码的执行顺序是一致的。通常通过引入“锁”来实现,只有获得锁的CPU core才能对数据进行更新。
- 实现uncached:针对特定硬件,特定场景不进行cached,可以理解为bypass cache,从而避免缓存一致性问题出现。
应对机制
-
缓存一致性互联(如CCI-400):在Linux+ARM 架构平台中,会通过如
CCI-400
等硬件提供互联和一致性功能,确保不同处理器和I/O设备之间的数据一致性。其通过缓存一致性协议MESI实现互联。MESI
代表四种状态:Modified
(修改)、Exclusive
(独占)、Shared
(共享)和Invalid
(无效),该协议通过状态机机制降低了总线带宽压力。 -
DMA一致性映射:Linux内核提供了
dma_alloc_coherent
等函数来申请一片uncached
的内存,这样DMA和CPU可以直接访问该内存区域,无需考虑数据一致性问题。 -
IO空间映射:对于寄存器地址空间,Linux内核可以通过
ioremap
等函数将其映射到内核空间,并配置为uncached
,以确保CPU读取的是最新的寄存器值。 -
缓存刷新和失效:在Linux内核中,可以通过部分机制(如内存屏障、锁等)来间接确保缓存的一致性。另外,某些硬件平台可能也提供了缓存刷新和失效的指令或接口,在实际应用中,应该根据具体的硬件平台和需求选择合适的机制和接口来确保缓存的一致性。
From 程序员秘书
数据缓存(DCache)刷新接口
flush_dcache_page
: 用于刷新指定页的高速缓存,确保CPU缓存与主存数据一致。通常用于直接内存访问(DMA)前的操作。flush_cache_mm
,flush_cache_range
,flush_cache_vmap
: 这些函数分别用于刷新整个内存描述符(mm_struct)关联的缓存、指定地址范围内的缓存以及虚拟映射区域的缓存。这些操作在特定的硬件交互和内存管理操作中非常有用。- 如部分ARM架构平台,如果已经支持和提供了刷新缓存的指令,我们就可以通过
内联汇编
自己封装实现一个函数。内联汇编可参见之前文章《Linux+ARM内联汇编》
=========================
From 程序员秘书
附,其他一些缓存概念和机制
sync
和 sync_file_range
-
sync
: 此系统调用会将所有修改过的缓冲区数据(包括文件系统元数据和脏页缓存)同步到磁盘上。它是一个同步操作,确保所有写操作已经完成,这对于确保数据持久性非常重要。在C程序中,可以通过调用sync()
函数实现。 -
sync_file_range
: 提供了更细粒度的控制,允许指定某个文件描述符和文件内的偏移范围进行同步。这对于大型文件的部分更新非常有用,可以减少不必要的I/O操作。使用时,需要包含<linux/fs.h>
头文件,并调用sync_file_range(fd, offset, nbytes, flags)
函数。
Inode和页缓存刷新
invalidate_inode_pages
: 用于标记inode关联的页缓存无效,通常在删除文件或重命名操作前使用,确保后续读取时不会使用到已废弃的缓存数据。writeback_invalidate_inode_pages
: 强制inode关联的脏页回写到磁盘并从页缓存中移除,适用于需要立即释放文件占用资源的场景。
Slab分配器缓存刷新
kmem_cache_flush
: 清理slab分配器管理的小对象缓存,这在模块卸载或需要清空特定缓存池时很有用。
工作队列刷新
flush_workqueue
: 刷新指定工作队列中的所有待处理任务,有助于清理由这些任务管理的状态或缓存。这对于确保任务执行的即时性和一致性很重要。
Drop Caches机制
/proc/sys/vm/drop_caches
: 通过向这个文件写入特定值(如1、2或3),可以分别丢弃页面缓存、目录项缓存(dentries)和inode缓存。这是一个非常激进的措施,通常仅在非常清楚其后果的情况下使用,以应对极端的内存压力情况。
使用注意事项
- 使用这些接口和机制时,必须谨慎,因为它们可能对系统性能产生严重影响,特别是在I/O密集型应用中。
- 对于生产环境,除非明确知道所需的效果和潜在风险,否则不推荐直接使用这些底层接口。
- 在进行任何刷新操作前,确保理解其对系统状态和性能的潜在影响,并考虑是否有必要采取其他优化措施来缓解内存或I/O问题。
From 程序员秘书
=========================