同一DMA缓冲区上多地址映射的同步问题
AArch64平台DMA内核模块开发问题
我正在AArch64系统上编写一个使用DMA API的简单内核模块,暂未添加错误处理,核心步骤如下:
// 获取按PAGE_SIZE对齐的空闲页(kmalloc()不按页对齐) buffer = __get_free_pages(GFP_KERNEL | GFP_DMA, size_in_pages); // 为设备映射DMA缓冲区 dma_address = dma_map_single(&dma_device, buffer, size_in_byte, DMA_TO_DEVICE); // dma_map_single()后缓冲区归设备所有,同步回CPU以便写入 dma_sync_single_for_cpu(&dma_device, dma_address, size_in_byte, DMA_TO_DEVICE);
随后通过以下代码将物理地址重映射到用户态:
remap_pfn_range(vma, vma->vm_start, PFN_DOWN(dma_address) + vma->vm_pgoff, size, vma->vm_page_prot);
应用程序会修改该内存区域,之后调用以下代码同步缓冲区到设备并触发DMA传输:
dma_sync_single_for_device(&dma_device, dma_address, size_in_byte, DMA_FROM_DEVICE);
技术问题
dma_sync_single_for_device()会刷新缓存,该操作是否包含应用程序通过remap_pfn_range()创建的用户态虚拟映射写入的数据,还是仅包含原始内核态映射buffer的数据?若仅包含后者,当前代码将无法正常工作。dma_sync_single_for_device()是否包含所有必要的内存屏障?- 是否需要通过
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);将虚拟映射设置为非缓存?
问题解答
会包含用户态写入的数据。
dma_sync_single_for_device()操作的是物理页对应的缓存行,内核态虚拟地址buffer和用户态映射的虚拟地址最终指向同一组物理页,缓存是与物理地址绑定而非虚拟地址,所以只要数据写入了对应物理页的缓存,同步操作就会把这些数据刷到内存,供DMA设备读取。是的,
dma_sync_single_for_device()已经包含了必要的内存屏障。在AArch64架构下,该函数会根据需求生成dmb(数据内存屏障)或dsb(数据同步屏障)指令,确保CPU所有未完成的内存操作(包括用户态写入)都在DMA设备访问前完成,同时避免指令重排导致的顺序问题。建议设置为非缓存(或写直达)。如果用户态映射是缓存的,应用程序写入的数据可能仅停留在CPU缓存中,虽然
dma_sync_single_for_device()会刷新缓存,但频繁写入会带来额外的一致性维护开销;更关键的是,若同步操作遗漏,DMA可能读取到旧数据。使用pgprot_noncached()可以彻底避免缓存一致性问题,简化同步逻辑,在AArch64这种多缓存层级的架构上,非缓存映射能确保用户态写入直接落内存,降低出错概率。
内容的提问来源于stack exchange,提问作者Domso
相关产品推荐
相关产品推荐

