linux cache 一致性

一块mem或者外设寄存器可能会被CPU和另外一个master 去访问,导致data 不一致的问题。


工程中一般有两种情况:

(1)寄存器地址空间。寄存器是CPU与外设交流的接口,有些状态寄存器是由外设根据自身状态进行改变,这个操作对CPU是不透明的。有可能这次CPU读入该状态寄存器,下次再读时,该状态寄存器已经变了,但是CPU还是读取的cache中缓存的值。但是寄存器操作在kernel中是必须保证一致的,这是kernel控制外设的基础,IO空间通过ioremap进行映射到内核空间。ioremap在映射寄存器地址时页表是配置为uncached的。数据不走cache,直接由地址空间中读取。保证了数据一致性。

这种情况kernel已经保证了data的一致性,应用场景简单。

(2)DMA缓冲区的地址空间。DMA操作对于CPU来说也是不透明的,DMA导致内存中数据更新,对于CPU来说是完全不可见的。反之亦然,CPU写入数据到DMA缓冲区,其实是写到了cache,这时启动DMA,操作DDR中的数据并不是CPU真正想要操作的。

这种情况是,CPU 和DMA 都可以异步的对mem 进行操作,导致data不一致性。

对于cpu 和 dma 都能访问的mem ,kernel有专业的管理方式,分为两种:1. mem 硬件上uncache 2. 使用过程中,通过flush cache 保证data 一致性。

通用DMA层主要分为2种类型的DMA映射:

(1)一致性映射,代表函数:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

一般驱动使用多,申请一片uncache mem ,这样无需考虑data 一致性。代码流程:对page property,也就是是kernel页管理的页面属性设置成uncache,在缺页异常填TLB时,该属性就会写到TLB的存储属性域中。保证了dma_alloc_coherent映射的地址空间是uncached的。

dma_alloc_coherent首先对分配到的缓冲区进行cache刷新,之后将该缓冲区的页表修改为uncached,以此来保证之后DMA与CPU操作该块数据的一致性。

(2)流式DMA映射,代表函数:
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction dir)
void dma_unmap_single(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)
void dma_sync_single_for_device(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir)

int dma_map_sg(struct device *, struct scatterlist *, int, enum dma_data_direction);
void dma_unmap_sg(struct device *, struct scatterlist *, int, enum dma_data_direction);

流式,这个定义应该是基于数据的方向流,来操作cache,失效或者清除,所以称为流式。 

dma_map_single为例,包括DMA_FROM_DEVICE,设备通过DMA写到mem,CPU 读data时,需要做invalid无效。方向为DMA_TO_DEVICE,则v7_dma_clean_range写回该段地址cache。保证了cache数据一致性

当只有一个缓冲区要被传输的时候,第一步:使用dma_map_single函数映射它.这句话的实质是说,mem中的data流向变动时,需要flush cache。比如,mem一直由CPU操作,这时mem要通过DMA送给sdio,那么必须通过这个API来传送mem,API实质是根据流向flush cache。第二步:返回值是总线地址,把它传递给设备。第三步:当传输完毕后,使用dma_unmap_single函数删除映射。到此cache driver 自成一体,很简单了。

流式DMA映射的几条原则:
*缓冲区只能用于这样的传送,即其传送方向匹配于映射时给定的方向。
*一旦缓冲区被映射,它将属于设备,而不是处理器。
直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。
*在DMA处于活动期间内,不能撤销对缓冲区映射,否则会严重破坏系统的稳定性。

驱动程序需要不经过撤销映射就访问流式DMA缓冲区的内容,有如下调用:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);
将缓冲区所有权交还给设备:
void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);


可以看出来,LDD3讲到,流式DMA映射对于CPU何时可以操作DMA缓冲区有严格的要求,只能等到dma_unmap_single后CPU才可以操作该缓冲区。究其原因,是因为流式DMA缓冲区是cached,在map时刷了下cache,在设备DMA完成unmap时再刷cache(根据数据流向写回或者无效),来保证了cache数据一致性,在unmap之前CPU操作缓冲区是不能保证数据一致的。因此kernel需要严格保证操作时序。当然kernel也提供函数dma_sync_single_for_cpu与dma_sync_single_for_device,可以在未释放时操作缓冲区,很明显这2个函数实现中肯定是再次进行刷新cache的操作保证数据一致性。
DMA的2种类型映射都分析完了,很清晰的看出一致性映射与流式DMA映射核心区别就是在于缓冲区页表映射是否为cached,一致性映射采用uncached页表保证了CPU与外设都可以同时访问。
不过这些都是内核为驱动开发者已经封装好的接口函数,驱动开发者并不需要关心cache问题,只需要按照LDD3的规定调用这些接口即可。这也就是为什么在驱动中很少见到cache操作,内核代码将cache操作做到对驱动不透明了。
这也让我想起了在开发网卡驱动时,DMA描述符的分配是一致性映射,是因为DMA描述符需要CPU与设备同时操作。而数据收发缓冲区分配是流式的,随用随分配,用完释放后CPU才可以操作数据!

到目前为止,我所接触到的内核TLB映射,做了uncached映射的只有2个:寄存器空间(ioremap)和一致性DMA缓冲区(dma_alloc_coherent),其他地址空间都是cached,来保证系统性能。


  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值