公司动态

第五章:BO的共享:5.2.5 dynamic dma-buf:共享下的迁移

📅 2026/8/27 10:37:49
第五章:BO的共享:5.2.5 dynamic dma-buf:共享下的迁移
5.2.4 结尾留下了一个悬念导入方通过dma_buf_attach → dma_buf_map_attachment拿到sg_table之后这张表里的 DMA 地址就固定指向了 buffer 当前所在的物理位置。可如果导出方是 GPU它的显存随时可能因为 TTM eviction、显存吃紧而把这块 BO 从 VRAM 搬到 GTT、甚至换出。一旦 buffer 搬了家导入方手里那张旧sg_table就变成了指向错误位置的「野地址」。「静态导入方」的解法简单粗暴map 时 pin 住后端存储映射存在期间谁也别想动它。代价是显存被长期pin住——一块被 RDMA 网卡映射走的 GPU BO哪怕 GPU 自己显存告急也无法迁移或换出。对渲染、AI 这类要反复共享大量 buffer 的场景这种「钉死」是不可接受的。Dynamic dma-buf动态 dma-buf就是为化解这对矛盾而生导入方不再要求 pin而是向导出方注册一个move_notify回调导出方每次要搬动 buffer 前先逐个通知所有动态导入方「我要动了请把你的旧映射作废、稍后重建」。于是显存重新变得可迁移共享也不再以pin为代价。1. 静态 vs 动态回顾 5.2.4导入方是否「动态」完全取决于dma_buf_dynamic_attach()时传入的importer_ops是否为NULL。structdma_buf_attachment*dma_buf_attach(structdma_buf*dmabuf,structdevice*dev){returndma_buf_dynamic_attach(dmabuf,dev,NULL,NULL);/* 静态importer_ops NULL */}传入非NULL的importer_ops其中带move_notify就成了动态导入方。二者的行为差异可以浓缩成一张表静态导入方importer_ops NULL动态导入方importer_ops ! NULL判定函数dma_buf_attachment_is_dynamic() false truemap 时是否 pin 后端是pin_on_map成立否buffer 可否被迁移映射期间不可可——迁移前经move_notify通知迁移后旧sg_table不存在此问题立即失效须重新 map同步责任core 帮忙等 KERNEL fence导入方自己遵守 write fence典型使用者简单 scanout、不支持 ODP 的 RDMAGPU↔GPU 互导、支持 move 的现代驱动一句话静态用「pin 住不许动」换简单动态用「随时准备重映射」换显存不被 pin。2. move_notify动态机制的心脏动态机制的全部精髓就在导出方搬移 buffer 前调用的这一个函数voiddma_buf_move_notify(structdma_buf*dmabuf){structdma_buf_attachment*attach;dma_resv_assert_held(dmabuf-resv);list_for_each_entry(attach,dmabuf-attachments,node)if(attach-importer_ops)attach-importer_ops-move_notify(attach);}遍历dmabuf-attachments链表导出方无需知道对端是谁照着链表逐个通知即可。只通知动态导入方if (attach-importer_ops)静态导入方没有move_notify也不需要——它们的 buffer 本就被 pin 住不会移动。必须在持resv锁时调用dma_resv_assert_held这保证了「通知导入方」与「实际搬移」之间不会有并发的新映射插进来。回调本身也在持锁状态下执行。struct dma_buf_attach_ops的定义印证了这套契约structdma_buf_attach_ops{bool allow_peer2peer;/* 导入方能否处理无 struct page 的对端资源 */void(*move_notify)(structdma_buf_attachment*attach);/* [可选] buffer 正在移动 */};内核头文件对move_notify的语义有极精确的描述值得逐条拆解“Mappings stay valid and are not directly affected by this callback”——回调触发的瞬间旧映射在物理上仍然有效move_notify只是一个「预警」不是「已经搬完」。“But the DMA-buf can now be in a different physical location, so all mappings should be destroyed and re-created as soon as possible”——导入方收到通知后应尽快作废旧sg_table并在下次使用前重建。“New mappings can be created after this callback returns, and will point to the new location”——回调返回后新建的映射指向的就是搬移后的新位置。3. pin / unpin动态导入方的「临时钉住」动态机制并非完全否定 pin而是把 pin 从「map 时强制」降级为「按需临时」。dma_buf_pin()/dma_buf_unpin()专供动态导入方在确实需要一段稳定物理地址如把 buffer 拿去做 scanout 上屏时使用intdma_buf_pin(structdma_buf_attachment*attach){structdma_buf*dmabufattach-dmabuf;intret0;WARN_ON(!attach-importer_ops);/* 只有动态导入方能调用 */dma_resv_assert_held(dmabuf-resv);if(dmabuf-ops-pin)retdmabuf-ops-pin(attach);returnret;}voiddma_buf_unpin(structdma_buf_attachment*attach){structdma_buf*dmabufattach-dmabuf;WARN_ON(!attach-importer_ops);dma_resv_assert_held(dmabuf-resv);if(dmabuf-ops-unpin)dmabuf-ops-unpin(attach);}三个约束值得留意WARN_ON(!attach-importer_ops)这两个 API只允许动态导入方调用。静态导入方的 pin 是在 map 内部自动完成的5.2.4 的dma_buf_pin_on_map不走这条路。内核注释明确限制用途dma_buf_pin的注释写道「only for limited use cases like scanout and not for temporary pin operations」「不得让用户态通过此接口 pin 住任意数量的 buffer」——防止动态机制被滥用成变相的「无限钉死」。unpin 之后 buffer 重新可迁移注释点明 unpin「allows the exporter to move any mapping of attach again and inform the importer through move_notify」——即 unpin 让 buffer 回到「可搬移、搬移时发move_notify」的常态。于是三者构成一个清晰的层次move_notify是常态可迁移pin/unpin是临时的例外短暂钉住做 scanout静态导入方的 map 时 pin 才是永久钉死。4. amdgpu 实战move_notify 里到底做了什么抽象讲完落到 amdgpu 看 GPU 驱动如何实现这套契约。amdgpu 作为动态导入方时注册的回调表是staticconststructdma_buf_attach_opsamdgpu_dma_buf_attach_ops{.allow_peer2peertrue,.move_notifyamdgpu_dma_buf_move_notify};allow_peer2peer true表示 amdgpu 能处理「对端 VRAM 直连P2P、没有 struct page」的资源——这也是它在 attach 时协商 P2P 能力的前提。核心是move_notify的实现staticvoidamdgpu_dma_buf_move_notify(structdma_buf_attachment*attach){structdrm_gem_object*objattach-importer_priv;structamdgpu_bo*bogem_to_amdgpu_bo(obj);structttm_operation_ctxctx{false,false};structttm_placementplacement{};structamdgpu_vm_bo_base*bo_base;intr;/* 1. 让本地这块「导入 BO」失效 */amdgpu_vm_bo_invalidate(bo,false);if(!bo-tbo.resource||bo-tbo.resource-mem_typeTTM_PL_SYSTEM)return;/* 2. 用空 placement 校验把它踢回 SYSTEM即作废旧的物理映射 */rttm_bo_validate(bo-tbo,placement,ctx);.../* 3. 遍历所有把该 BO 映射进 GPU 虚拟地址空间的 VM更新页表 */for(bo_basebo-vm_bo;bo_base;bo_basebo_base-next){structamdgpu_vm*vmbo_base-vm;structdma_resv*resvvm-root.bo-tbo.base.resv;...ramdgpu_vm_handle_moved(adev,vm,NULL);/* 重建 GPU 页表映射 */...}}对照第 2 节的头文件语义这段代码把「作废 重建」落到了实处amdgpu_vm_bo_invalidate 空placement的ttm_bo_validate把这块导入 BO 的旧映射作废推回 SYSTEM 域。这正是响应「buffer 换了物理位置旧映射要销毁」。遍历bo-vm_bo更新 GPU 页表amdgpu 不只是作废 CPU/DMA 侧的sg_table它把「BO 已移动」的事实一路传导到自己的 GPUVM 页表——所有引用这块 BO 的 GPU 虚拟地址映射都要跟着更新。这一步呼应了第九章 GPUVM 的amdgpu_vm_handle_moved。全程在持锁下操作 VM 的resv与dma_buf_move_notify持dmabuf-resv锁的约定一脉相承避免与命令提交并发。而 amdgpu 作为导出方时的 pin/unpinamdgpu_dma_buf_pin/amdgpu_dma_buf_unpin则展示了move_notify能力如何反向影响 pin 策略staticintamdgpu_dma_buf_pin(structdma_buf_attachment*attach){structamdgpu_bo*bogem_to_amdgpu_bo(attach-dmabuf-priv);u32 domainsbo-allowed_domains;/* 只有开启 move_notify 时才允许 pin 进 VRAM 做 P2P * 否则退回 GTT避免 GPU 与 RDMA 在无 move 通知时的钉死冲突 */if(!IS_ENABLED(CONFIG_DMABUF_MOVE_NOTIFY)){domains~AMDGPU_GEM_DOMAIN_VRAM;}else{list_for_each_entry(attach,dmabuf-attachments,node)if(!attach-peer2peer)domains~AMDGPU_GEM_DOMAIN_VRAM;}...returnamdgpu_bo_pin(bo,domains);}这段逻辑的判断极具代表性只有在编译开启CONFIG_DMABUF_MOVE_NOTIFY即动态机制可用时amdgpu 才敢把 buffer pin 进 VRAM 供对端 P2P 直连否则一律退回 GTT系统内存中转。原因正是注释所说——没有 move 通知时一旦 pin 进 VRAM 就可能与 RDMA 产生「谁也不能动」的钉死冲突。5. 一次完整的动态搬移时序把上述环节串成一条时间线动态 dma-buf 的价值就一目了然动态导入方另一 GPU / 网卡dma-buf core导出方 GPUTTM动态导入方另一 GPU / 网卡dma-buf core导出方 GPUTTM导入阶段不 pin用 sg_table 驱动 DMAbuffer 仍可迁移显存告急TTM 决定把这块 BO 从 VRAM 搬到 GTT下次要用时重新映射dma_buf_dynamic_attach(dmabuf, dev, importer_ops)1dma_buf_map_attachment() → sg_table指向 VRAM2dma_resv_lock(dmabuf-resv)3dma_buf_move_notify(dmabuf)4importer_ops-move_notify(attach)5作废旧 sg_table / 更新自身页表6真正搬移 bufferVRAM → GTT7dma_resv_unlock()8dma_buf_map_attachment() → 新 sg_table指向 GTT9对比静态路径——静态导入方在第一次 map 时就把 buffer pin 死在 VRAM导出方的 TTM 根本无法执行上图中间那次搬移。动态机制通过一次move_notify回调把「先通知、后搬移、再重映射」三拍拆开既让共享得以继续又把迁移的自由还给了导出方。6. 小结与延伸动态 dma-buf 的全部要义可以收束为三句话move_notify是核心导出方搬移前遍历attachments逐个通知动态导入方作废旧映射它把「共享」与「可迁移」这对矛盾拆解为「先预警、再搬移、后重建」的时序协作。pin/unpin是临时例外仅动态导入方可调用用于 scanout 等确需稳定地址的短暂场景用完即 unpin 让 buffer 回归可迁移常态——绝不可退化为变相的永久钉死。静态与动态的分水岭在 attach是否传importer_ops含move_notify一次决定人格静态以 pin 换简单动态以重映射换灵活。至此5.2 节从 anon_inode、dma-buf 机制、dma-buf exporter 实现、attach/map/sg_table的importer 实现 到本节的动态机制已经把「内核内部两个驱动如何共享一块 buffer」讲清楚了。但又自然而然引出另一个方向用户态能不能直接分配一块可共享、零拷贝的 dma-buf而不必经过某个具体 GPU 驱动这个问题可以上升到一个更大的主题如何把内核态一些机制让用户态也可用这个主题同样令人兴奋。那就让我们继续兴奋地前行吧这正是5.2.6 dma-buf heaps 与 udmabuf要回答的——它把 dma-buf 的分配权从内核驱动下放到用户态。我们这里的分析只是共享下的迁移涉及的冰山一角背后涉及的逻辑很多下面是相关的主题相关阅读move_notify里对 GPU 页表的更新amdgpu_vm_handle_moved属于第九章 GPUVM 的范畴dma_resv锁与 fence 的配合详见 6.3 隐式同步 dma_resv。