Linux内存管理(18)DMA mapping机制分析

作者 by adtxl / 2022-02-21 / 暂无评论 / 522 个足迹

转载于公众号LoyenWang

1. 概述

DMA(Direct Memory Access):直接存储器访问;

先看问题的引入:

image.png

  • Non-DMA:CPU直接与设备进行数据交互,CPU的负载会随着数据的读写而增加;
  • DMA:CPU不参与数据的直接传输,DMA Controller负责Device与Memory之间的数据搬运,并以中断信号的形式通知CPU;
  • 可以看出,使用DMA的最大优点是可以提高CPU的使用率;

2. address mapping

DMA涉及三种地址空间:

image6853c89ed2aae48c.png

  • CPU虚拟地址:CPU使用的地址空间;
  • CPU物理地址:CPU使用的虚拟地址通过MMU转换成物理地址;
  • 总线地址:设备使用的地址空间;

CPU与Device看待地址的空间不一样,看几个示例:

image20856b88a0b87e5e.png

  1. A:Host bridge负责将Bus address映射到CPU的物理地址空间,可以通过ioremap 来使用,比如PCI/PCIe
  2. B:设备使用的总线地址,可以通过IOMMU访问到CPU的物理地址空间,由IOMMU来负责映射;
  3. C:设备使用的总线地址与CPU的物理地址相同,不需要使用IOMMU进行地址转换;

2.1 cache coherence

DMA的操作,通常与cache相关,先了解一下cache coherence:

imageafe62a49388e09dc.png

  • cache coherence设备:设备之间的读写不需要关心cache的一致性问题,硬件将确保数据一致,比如连接在ARM CCI端口上的设备就是cache coherence设备;
  • non-coherence设备:需要额外的软件操作(flush/invalidate)等操作来确保数据一致;

3. DMA mappings

Linux内核中提供了两种dma mapping的接口:Consistent mapping和Stream mapping。

3.1 Consistent DMA mappings

  • consistent mapping:对应于cache-coherence设备,硬件确保device和CPU都能并行访问数据,并能看到彼此的更新,而不需要软件的flush操作;
  • 通常在驱动init时进行map操作,而在deinit时进行unmap操作;

通常在使用consistent dma mapping时,首先需要通过dma_alloc_coherent接口来分配一段区域:

imagec8607192cd8ac47b.png

  • dma_alloc_coherent用于分配coherent内存,并返回对应的虚拟地址;
  • 进行内存分配时,存在三种方式:1)优先从设备专用的dma池开始分配;2)无专用dma池,如果是dma-direct访问,通过dma_direct_alloc分配,而底层是依赖于CMA来分配;3)使用IOMMU的设备,则通过iommu的操作函数集来分配;

3.1.1 device reserved

通常,可以为设备指定专用的dma coherent的区域,有以下的方式:

image0e1228871dec76b8.png

  1. 通过在设备树中添加对应的属性值,驱动中可以调用of_reserved_mem_device_init最终完成dma区域的注册;
  2. 直接通过接口dma_decleare_coherent_memory调用来进行注册;

3.1.2 dma pool

驱动中经常面临buffer的管理,可以使用dma pool机制来处理,大概的原理如下:

image508922a05253647a.png

  • dma-pool以页为单位来进行管理分配,可以通过添加多个dma池来使用;
  • dma-pool子系统的细节描述,需要另起一篇文章了;

3.2 Streaming DMA mappings

  • streaming mapping:对应于non-coherence设备;
  • 通常在单次DMA传输时进行map,在传输完成后进行unmap(除非调用了dma_sync_XXX()函数);
  • non-coherence设备,由于buffer不与其他数据共享cache line,通常会work better;

先看一下数据一致性问题:

image678eea58acc6e642.png

  • dma to device时,需要将cache中的数据flush到memory中;
  • dma from device时,需要先将cache中的数据invalidate掉,避免CPU读取的是原来的数据;

dma_map_single函数如下:

image36c73952933da01a.png

  • map操作时存在两种方式,直接映射或使用iommu来完成映射;

dma_unmap_single是逆操作:

image1b28a6d85713a517.png

从上述函数中可以看到,最终都会调用到arch相关的cache操作,这个与体系结构是强相关的,以arm64为例:

image86cd8f1f371f921d.png

最终的代码将调用到汇编中,操作也较简单,不再赘述了;

独特见解