本附录按调用环节汇总专栏中讨论过的 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_MAP |
iommufd 中在 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 术语表》