DMA
和 Cache
一致性问题相关知识点
DMA
Cache
pgprot_noncached
关于代码深入分析见DMA 相关概念以及 arm 实现
DMA
DMA(Direct memory access)
直接内存访问是一种硬件机制,它允许 外围设备
和 主内存
之间直接传输它们的 I/O
数据,而不需要 CPU
的参与。使用这种机制可以大大提高与设备通信的吞吐量
DMA
方式的数据传输由 DMA 控制器 (DMAC)
控制,在传输期间 CPU
可以并发地执行其他任务,当 DMA
结束后, DMAC
通过中断通知 CPU
数据传输已经结束,然后由 CPU
执行相应的中断服务程序进行后续处理
Cache 和不一致性问题
Cache
即高速缓冲存储器,是一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。
假设 DMA
针对内存的目的地址和 Cache
缓存的对象没有重叠区域, DMA
和 Cache
之间就相安无事,但是,如果有重叠呢,经过 DMA
操作, Cache
缓存对应的内存的数据已经被修改,而 CPU
本身并不知道,它仍然认为 Cache
中的数据仍然还是内存中的数据,以后访问 Cache
映射的内存时,它仍然使用陈旧的 Cache
数据,这就会发生 Cache
与 内存
之间数据 不一致性
的错误。
最简单的方法是直接禁止 DMA 目标地址范围内内存的 Cache 功能
,当然这是牺牲性能的,但却高可靠。
Cache
带来的高效率与 一致性问题
需要平衡
只要 Cache
的空间与主存空间在一定范围内保持适当比例的映射关系, Cache
的命中率还是相当高的。一般规定 Cache
与内存的空间比为 4:1000
,即 128kB Cache
可映射 32MB
内存; 256kB Cache
可映射 64MB
内存。在这种情况下。命中率都在 90%
以上。至于没有命中的数据, CPU
只好直接从内存获取。获取的同时,也把它拷进 Cache
。
工作模式
cache
有两种工作模式
wirte through
,CPU
对主存
写数据时,不经过cache
直接写到内存,此时对于写的实现比较简单,如果系统只用写穿模式的话,cache
则变成了读缓存write back
,CPU
写入数据时,不直接将数据写入内存,而是写入cache
,当cache
数据被替换出去或者系统做cache flush
时才写回主存
Cache 接口
Flush
,把Cache
内容写回Main Memory
, 当Cache
为Write through
, 不需要Flush
Invalidate
,把Cache
内容直接丢掉不要
Cache 使用场景
当有 DMA
在使用 Main Memory
的时候,一般要用到 cache
的处理。因为 DMA
在访问 Main Memory
时是不经过 cache
的。比较典型的比如在 Ethernet
, wireless
, USB
等驱动中, DMA
会操作 descriptors
和 packet buffers
,驱动需要实现如下:
- 如果
Driver
使用descripter
和packet buffer
的地址都是cache
的地址,那么Driver
在读 descripter
里一些状态比如Owned by CPU/DMA
,有没有收到包时,要对descripter
当前结构里的内容做cache invalidate
,收到packet
后,也要对packet buffer
做cache invalidate
Driver
在写 descripter
里一些状态比如Owned by DMA
,要发送包时,要对descripter
当前结构里的内容做cache flush
,发送packet
时,也要对packet buffer
做 cache flush
- 有些
Driver
会对descripter
使用uncache 地址
,那么上面两种情况里invalidate/flush
就不用做了。一般很少会对packet buffer
也用uncache 地址
的,因为对packet
内容的处理将会很频繁,使用uncache
会很慢。而descripter
一般由于结构比较小,如果也使用cache
地址的话,做invalidate/flush
的时间消耗可能会比uncache
的还要多。
DMA 映射
因此在 DMA
是否使用 cache
的问题上,可以根据 DMA
缓冲区期望保留的的时间长短来决策。根据 DMA
缓冲区期望保留的时间长短,区分两种类型的 DMA
映射:
一致性 DMA 映射 (Coherent DMA buffers)
一致性 DMA 映射
申请的缓存区不使用 cache,因此可以保持 cache 一致性。一致性映射具有很长的生命周期,在这段时间内占用的映射寄存器,即使不使用也不会释放。生命周期为该驱动的生命周期流式 DMA 映射
流式 DMA 映射
实现比较复杂。生命周期比较短,而且使用 cache,需要处理一致性问题。一些硬件对流式映射有优化。建立流式 DMA 映射
,需要告诉内核数据的流动方向DMA
从外设读取数据到供处理器使用时,可先invalidate
操作。这样将迫使处理器在读取cache
中的数据时,先从内存中读取数据到缓存,保证缓存和内存中数据的一致性DMA
向外设写入由处理器提供的数据时,可先writeback
操作。这样可以DMA
传输数据之前先将缓存中的数据写回到内存中- 如果不清楚
DMA
操作的方向,也可先同时进行invalidate
和writeback
操作。操作的结果等同于invalidate
和writeback
操作效果的和。
一致性 DMA 映射
dma_alloc_coherent()
首先分配一组连续的物理页用作后续 DMA
操作的缓冲区,然后在软件层面将该段物理地址空间重新映射到非缓存的虚拟地址空间,具体来说在页目录和页表项中关闭了这段映射区间上的 cache 功能,使得 cache 的一致性问题不再成为问题。因为关闭了 cache,失去了高速缓存功能,所以一致性映射在性能上打了折扣。
流式 DMA 映射
在 流式 DMA 映射
场合, DMA
传输通道所使用的缓冲区往往不是由当前驱动程序自身分配的,而且往往每次 DMA
传输都会重新建立一个 流式映射的缓冲区
。此外,由于无法确定外部模块传入的 DMA
缓冲区的映射情况,所以设备驱动程序必须小心地处理可能会出现的 cache 一致性问题
。
需要注意的是,在某些平台上,比如 ARM
,** CPU
的读 / 写用的是不同的 cache(读用的是 cache
,写则用的是 write buffer
),所以建立 流式 DMA 映射
需要指明数据在 DMA
通道中的流向,以便由内核决定是操作 cache
还是 write buffer
。**
pgprot_noncached
arch:arm
pgprot_noncached()
是一个宏,它实际上禁止了相关页的 cache 和写缓冲 (write buffer), 另外一个稍微少的一些限制的宏是:
pgprot_writecombine(prot)
它则没有禁止写缓冲
#define pgprot_noncached(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)