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