ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

附录 B DMA 接口汇总表

附录 B DMA 接口汇总表

本附录按调用环节汇总专栏中讨论过的 DMA 相关接口,范围涵盖内核 dma-mapping 接口、散列表、dma-buf 与 P2PDMA、用户页面与 HMM、IOMMU 内核接口,以及 VFIO、iommufd、io_uring 与 RDMA verbs 等用户态接口。接口名称以本专栏所依据的内核源码树为准;成对出现的接口(映射与解除映射、分配与释放)合并为一行。接口在不同内核版本中的签名可能变化,使用时应以目标内核的头文件为准。

一、设备寻址能力声明

驱动在探测阶段向 dma-mapping 层声明设备的寻址能力与分段约束,后续的分配与映射均以此为依据。

接口作用与要点
dma_set_mask()/dma_set_coherent_mask()分别设置流式映射与一致性分配所用的 DMA 地址掩码;平台无法满足时返回错误,驱动应回退到更窄的掩码
dma_set_mask_and_coherent()同时设置上述两种掩码,是多数驱动的常用写法
dma_get_required_mask()返回覆盖系统全部内存所需的掩码,供驱动判断是否有必要启用更宽的寻址模式
dma_addressing_limited()判断设备掩码能否覆盖全部内存;返回真表示部分映射可能经由 swiotlb 反弹
dma_set_max_seg_size()声明设备单个 DMA 段的最大长度,约束散列表合并的结果
dma_set_seg_boundary()声明 DMA 段不得跨越的地址边界
dma_set_min_align_mask()要求 swiotlb 反弹时保留原地址在该掩码范围内的低位偏移
dma_max_mapping_size()返回单次映射允许的最大长度;启用 swiotlb 时受反弹缓冲区段长限制
dma_opt_mapping_size()返回不致明显降低映射效率的建议长度,供驱动确定传输粒度
dma_get_merge_boundary()返回 IOMMU 能够合并相邻段的边界粒度;返回 0 表示不支持合并

二、一致性分配

一致性缓冲区在其生命周期内由 CPU 与设备同时访问,无需显式同步,适用于描述符环、完成队列等小数据量、且生命周期较长的控制结构。

接口作用与要点
dma_alloc_coherent()/dma_free_coherent()分配一致性缓冲区,同时返回 CPU 虚拟地址与 DMA 地址;在非一致平台上通常以不可缓存映射实现
dma_alloc_attrs()/dma_free_attrs()带属性的通用分配接口,dma_alloc_coherent()是其不带属性的形式
dmam_alloc_coherent()托管版本,驱动解除绑定时自动释放
dma_mmap_attrs()把一致性缓冲区映射到用户态地址空间
dma_pool_create()/dma_pool_alloc()/dma_pool_free()/dma_pool_destroy()在一致性内存上切分固定大小的小块,可指定对齐与不得跨越的边界,适用于大量小型描述符

三、非一致与非连续分配

此类接口分配的内存可被 CPU 缓存访问,所有权交接须由驱动显式同步。

接口作用与要点
dma_alloc_noncoherent()/dma_free_noncoherent()分配可缓存的 DMA 缓冲区,返回 CPU 虚拟地址;访问前后须调用dma_sync_single_for_*()
dma_alloc_pages()/dma_free_pages()与上一行语义相同,但返回struct page,便于纳入页面管理
dma_alloc_noncontiguous()/dma_free_noncontiguous()分配物理上可不连续的内存,以sg_table返回;IOMMU 处于翻译模式时可获得连续的 IOVA

四、流式映射

流式映射把已有的内核内存临时交给设备访问,映射期间缓冲区归设备所有,结束后必须解除映射。

接口作用与要点
dma_map_single()/dma_unmap_single()映射一段线性映射区内的内核虚拟地址;不适用于 vmalloc 地址
dma_map_page()/dma_unmap_page()以页面与页内偏移指定缓冲区,可映射高端内存页
dma_map_phys()/dma_unmap_phys()以物理地址指定缓冲区,是流式映射的统一入口,其余单段映射接口均可归结到此;配合DMA_ATTR_MMIO可映射没有struct page的 MMIO 区域
dma_map_resource()/dma_unmap_resource()映射设备 MMIO 物理地址,用于设备访问另一设备的 BAR,不执行 cache 维护
dma_mapping_error()检查映射返回的 DMA 地址是否有效;每次单段映射后都必须调用

五、散列表

散列表描述由多段内存组成的缓冲区。映射后的 DMA 段数可能少于原始段数,遍历时须使用映射后的段数。

接口作用与要点
sg_alloc_table()分配并初始化sg_table
dma_map_sg()/dma_unmap_sg()映射散列表,返回映射后的 DMA 段数,失败时返回 0;解除映射时须传入映射前的原始段数
dma_map_sgtable()/dma_unmap_sgtable()以sg_table为参数的版本,成功返回 0、失败返回负的错误码,并把映射后的段数记录在表中,可避免两种段数混用
for_each_sg()/for_each_sgtable_dma_sg()遍历散列表;后者按映射后的 DMA 段遍历
sg_dma_address()/sg_dma_len()读取段的 DMA 地址与长度,二者只在映射后有效,不可与 CPU 侧的页面与长度混用

六、同步与所有权交接

流式映射与非一致分配的缓冲区在 CPU 与设备之间交接所有权时,须调用同步接口完成必要的 cache 维护或 swiotlb 拷贝。

接口作用与要点
dma_sync_single_for_cpu()/dma_sync_single_for_device()把单段缓冲区的所有权交还 CPU 或交给设备
dma_sync_single_range_for_cpu()/dma_sync_single_range_for_device()只同步映射中的一个子区间,用于接收缓冲区按实际长度同步
dma_sync_sg_for_cpu()/dma_sync_sg_for_device()散列表版本
dma_sync_sgtable_for_cpu()/dma_sync_sgtable_for_device()以sg_table为参数的散列表版本
dma_need_sync()判断某一映射是否需要同步;返回假时驱动可在热路径上省略同步调用
page_pool的PP_FLAG_DMA_MAP/PP_FLAG_DMA_SYNC_DEV由 page_pool 代为完成页面的 DMA 映射,以及页面回收时交给设备的同步,使映射在页面循环使用期间保持有效

七、两步 IOVA 映射

两步接口把 IOVA 分配与页表填充分开,使多段物理内存映射到一段连续 IOVA,并只做一次 IOTLB 同步。

接口作用与要点
dma_iova_try_alloc()预分配一段 IOVA;设备不经 IOMMU 翻译时返回假,调用者应回退到逐段流式映射
dma_iova_link()/dma_iova_unlink()把一段物理内存映射到已分配 IOVA 的指定偏移,或解除该映射
dma_iova_sync()对已建立的全部链接一次完成 IOMMU 映射同步
dma_use_iova()判断当前状态是否使用了两步 IOVA 映射,供解除映射路径选择对应的处理

八、方向与属性

接口作用与要点
DMA_TO_DEVICE/DMA_FROM_DEVICE/DMA_BIDIRECTIONAL数据流向,决定映射与同步时执行写回还是失效;映射与同步、解除映射须使用相同方向
DMA_ATTR_SKIP_CPU_SYNC映射或解除映射时跳过 cache 维护,由驱动在合适时机自行同步
DMA_ATTR_WEAK_ORDERING允许平台对该映射的访问放宽排序,在 PCIe 上对应宽松排序
DMA_ATTR_WRITE_COMBINE一致性分配时以写合并属性建立 CPU 映射
DMA_ATTR_NO_KERNEL_MAPPING一致性分配时不建立内核虚拟映射,适用于 CPU 不访问的大缓冲区
DMA_ATTR_FORCE_CONTIGUOUS要求分配物理连续的内存
DMA_ATTR_NO_WARN分配或映射失败时不输出告警,适用于设有回退路径的调用
DMA_ATTR_MMIO表明被映射的物理地址不是普通系统内存(通常为 MMIO),dma-mapping 层不对其执行 cache 维护;用于点对点映射,PCI 设备须先经 P2PDMA 接口判定是否适用

九、内存屏障

接口作用与要点
dma_wmb()保证 CPU 对一致性内存的写入先于后续写入对设备可见,典型用法是在置位描述符所有权位之前
dma_rmb()保证读到设备写回的状态位之后,再读取同一描述符的其余字段

十、跨设备共享与点对点

接口作用与要点
dma_buf_export()/dma_buf_fd()导出方把缓冲区封装为 dma-buf,并取得可交给用户态的文件描述符
dma_buf_get()/dma_buf_put()导入方由文件描述符取得 dma-buf 引用,或释放该引用
dma_buf_attach()/dma_buf_detach()静态导入方挂接到 dma-buf;映射期间缓冲区保持固定
dma_buf_dynamic_attach()动态导入方挂接,提供dma_buf_attach_ops,在缓冲区即将移动时经invalidate_mappings()接收通知
dma_buf_map_attachment()/dma_buf_unmap_attachment()取得导入设备视角下的sg_table,其中的 DMA 地址由导出方按导入设备生成
dma_buf_pin()/dma_buf_unpin()动态导入方固定缓冲区,使导出方在解除固定之前不再移动它;仅限扫描输出等有限场景
dma_buf_begin_cpu_access()/dma_buf_end_cpu_access()界定 CPU 访问 dma-buf 的区间,由导出方完成相应的 cache 维护
dma_buf_vmap()/dma_buf_vunmap()取得 dma-buf 的内核虚拟映射
pci_p2pdma_distance()计算 P2P 内存提供方与各访问方之间的拓扑距离;返回负值表示不支持点对点传输

十一、用户页面与设备页表镜像

接口作用与要点
pin_user_pages()/pin_user_pages_fast()/pin_user_pages_remote()固定用户态页面供 DMA 访问;长期固定须带FOLL_LONGTERM
hmm_range_fault()读取进程页表在指定区间的快照,必要时触发缺页,供设备页表镜像使用
mmu_interval_notifier_insert()/mmu_interval_read_begin()/mmu_interval_read_retry()注册区间失效通知,并以序列号检测快照期间是否发生了页表变化
migrate_vma_setup()/migrate_vma_pages()/migrate_vma_finalize()在系统内存与设备私有内存之间分三步迁移页面

十二、IOMMU 内核接口

接口作用与要点
iommu_map()/iommu_unmap()在指定 IOMMU 域中建立或解除 IOVA 到物理地址的映射,由 dma-iommu 层或 VFIO 等直接管理域的使用者调用
iommu_sva_bind_device()/iommu_sva_unbind_device()把设备绑定到进程地址空间,使设备以进程虚拟地址发起 DMA
iommu_sva_get_pasid()取得绑定对应的 PASID,由驱动写入设备的工作描述符

十三、用户态接口

接口作用与要点
VFIO_GROUP_SET_CONTAINER传统 VFIO 接口中把 IOMMU group 加入容器
VFIO_IOMMU_MAP_DMA/VFIO_IOMMU_UNMAP_DMA在容器的 IOMMU 域中建立或解除用户态虚拟地址到 IOVA 的映射,内核同时固定对应页面
VFIO_DEVICE_BIND_IOMMUFD把 VFIO 设备文件绑定到 iommufd
IOMMU_IOAS_MAPiommufd 中在 I/O 地址空间对象内建立映射
IORING_REGISTER_BUFFERS向 io_uring 预先注册缓冲区,页面固定一次,避免每次 I/O 重复固定
IORING_OP_SEND_ZC零拷贝发送;第一个完成事件只表示数据已交给协议栈,缓冲区须在收到带IORING_CQE_F_NOTIF标志的通知之后才能复用
IORING_REGISTER_ZCRX_IFQ注册零拷贝接收所用的网络接口队列与内存区域
ibv_reg_mr()注册 RDMA 内存区域,固定页面并取得本地与远端访问密钥
ibv_reg_dmabuf_mr()以 dma-buf 文件描述符注册内存区域,用于网卡直接访问 GPU 显存
ibv_post_send()/ibv_post_recv()向队列对提交发送或接收工作请求

十四、典型调用顺序

  • 一致性缓冲区:dma_set_mask_and_coherent()→dma_alloc_coherent()→ CPU 与设备共同访问,必要处以dma_wmb()/dma_rmb()排序 →dma_free_coherent()。
  • 发送方向的流式映射:CPU 填充数据 →dma_map_single()或dma_map_page()→dma_mapping_error()→ 设备读取 → 完成中断 →dma_unmap_single()或dma_unmap_page()。
  • 接收方向的循环缓冲区:dma_map_page()(DMA_FROM_DEVICE)→ 设备写入 →dma_sync_single_for_cpu()→ CPU 处理 →dma_sync_single_for_device()→ 缓冲区重新交给设备,直至最终dma_unmap_page()。
  • 散列表:sg_alloc_table()并填充 →dma_map_sgtable()→for_each_sgtable_dma_sg()以sg_dma_address()/sg_dma_len()填写设备描述符 → 设备访问 →dma_unmap_sgtable()。

系列导航:

  • 上一篇《附录 A 术语表》
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进