Linux dma_resv机制原理、实现与应用详解
1. 原理
1.1 背景与需求
在现代Linux内核的显卡、视频、存储等子系统中,设备间共享内存缓冲区已成为常态。多个驱动、多个硬件单元可能并发访问同一缓冲区,且每个访问可能是异步的。如何在保证高性能的前提下,实现跨设备、跨驱动的同步与访问控制,是内核设计的难点。
传统的同步原语(如mutex、semaphore、completion等)主要用于进程/线程间同步,难以满足以下需求:
-
跨设备/驱动同步:如GPU渲染、显示、视频编解码等场景,多个驱动需安全共享同一缓冲区。
-
异步操作管理:如DMA传输、硬件加速等,操作完成时机不可预测。
-
多种访问类型:如读、写、内核管理等,需区分不同类型的同步需求。
-
高性能:避免不必要的阻塞和轮询,支持批量操作和回调机制。
为此,Linux内核引入了dma_resv(reservation object)机制,作为DMA-BUF等缓冲区的同步管理核心。dma_resv通过集成dma_fence机制,实现了高效、灵活的异步同步和访问控制。
1.2 dma_resv的基本概念
dma_resv的核心思想是:为每个共享缓冲区维护一个“预留对象”,统一管理所有相关的同步栅栏(dma_fence),并区分不同的访问类型(如读、写、内核管理等)。
主要目标:
-
支持多种访问类型的同步(读、写、内核、bookkeep)
-
支持批量管理多个dma_fence对象
-
支持高效的迭代、查询和等待操作
-
支持RCU安全访问和并发读写
-
支持死锁保护和优先级继承(通过ww_mutex)
1.3 dma_resv与dma_fence的关系
dma_resv本质上是一个dma_fence的容器和管理者。每个dma_resv对象可包含多个dma_fence,分别表示不同类型的同步依赖。dma_fence负责异步操作的完成通知和回调,dma_resv负责整体的同步管理和访问控制。
dma_resv的典型应用场景:
-
DMA-BUF缓冲区的跨设备同步
-
TTM/DRM内存管理的访问控制
-
显卡驱动的命令提交与依赖管理
-
多媒体管线的异步协作
实现
2.1 核心数据结构
2.1.1 struct dma_resv
struct dma_resv {struct ww_mutex lock; // 写侧锁,支持死锁保护struct dma_resv_list __rcu *fences; // fence数组,RCU保护
};
-
lock:用于保护写操作,采用ww_mutex(wound-wait mutex),支持死锁检测和优先级继承。
-
fences:RCU保护的dma_fence数组,支持并发读写和安全迭代。
2.1.2 enum dma_resv_usage
enum dma_resv_usage {DMA_RESV_USAGE_KERNEL, // 内核管理DMA_RESV_USAGE_WRITE, // 隐式写同步DMA_RESV_USAGE_READ, // 隐式读同步DMA_RESV_USAGE_BOOKKEEP // 不参与隐式同步
};
-
区分不同类型的同步需求,支持访问类型的优先级和升级。
2.1.3 struct dma_resv_iter
struct dma_resv_iter {struct dma_resv *obj; // 关联的dma_resv对象enum dma_resv_usage usage; // 当前迭代的usage类型struct dma_fence *fence; // 当前处理的fenceenum dma_resv_usage fence_usage;// 当前fence的usageunsigned int index; // 当前索引struct dma_resv_list *fences; // fence数组unsigned int num_fences; // fence数量bool is_restarted; // 是否重启迭代
};
- 支持高效的fence迭代和查询,兼容RCU和锁保护。
2.2 访问类型与优先级
dma_resv通过usage类型区分不同的同步需求,并定义了严格的优先级关系:
-
KERNEL < WRITE < READ < BOOKKEEP
-
查询某一usage类型时,返回该类型及更低优先级的所有fence
-
fence可升级usage,但不可降级
这保证了写操作需等待所有读/写/内核操作完成,读操作只需等待写操作完成,bookkeep类型不参与隐式同步。
2.3 锁定与并发控制
2.3.1 写侧锁定
dma_resv采用ww_mutex作为写侧锁,支持死锁检测和优先级继承。典型用法:
dma_resv_lock(&obj, ctx); // ctx为死锁保护上下文
// ... 修改fences ...
dma_resv_unlock(&obj);
-
支持trylock、interruptible lock、slowpath lock等多种模式
-
支持多线程/多驱动并发写操作
2.3.2 读侧RCU保护
读操作采用RCU机制保护,支持并发读写和安全迭代。典型用法:
struct dma_resv_iter cursor;
dma_resv_iter_begin(&cursor, &obj, usage);
for (fence = dma_resv_iter_first_unlocked(&cursor); fence; fence = dma_resv_iter_next_unlocked(&cursor)) {// 处理fence
}
dma_resv_iter_end(&cursor);
-
fence数组采用RCU指针,支持安全替换和延迟释放
-
fence对象本身采用引用计数,避免UAF
2.4 fence的管理与操作
2.4.1 fence的添加与替换
dma_resv支持批量添加和替换fence:
dma_resv_reserve_fences(&obj, num_fences); // 预留空间
dma_resv_add_fence(&obj, fence, usage); // 添加fence
dma_resv_replace_fences(&obj, context, fence, usage); // 替换指定context的fence
- fence添加不可失败,保证命令提交的原子性
- 支持按context和usage类型替换fence,灵活管理依赖关系
2.4.2 fence的查询与迭代
支持多种查询方式:
- 获取所有fence:
dma_resv_get_fences()
- 获取单个fence:
dma_resv_get_singleton()
- 迭代所有fence:
dma_resv_for_each_fence()
、dma_resv_for_each_fence_unlocked()
2.4.3 fence的等待与同步
支持批量等待和超时:
long dma_resv_wait_timeout(&obj, usage, intr, timeout);
-
等待指定usage类型及更低优先级的所有fence完成
-
支持中断和超时
-
支持deadline hint优化
2.4.4 fence的升级与降级
fence可通过重复添加升级usage类型,但不可降级。这保证了同步语义的正确性。
2.5 RCU安全与内存管理
dma_resv的fence数组采用RCU指针,支持安全替换和延迟释放。fence对象本身采用引用计数,自动管理生命周期。
-
fence数组替换时,旧数组延迟释放,避免并发访问UAF
-
fence对象销毁时,自动释放所有引用
2.6 死锁保护与优先级继承
写侧锁采用ww_mutex,支持wound-wait死锁检测和优先级继承。多驱动/多线程并发写时,自动检测死锁并优雅恢复。
-
ctx为死锁保护上下文,支持多级锁定
-
支持slowpath锁定,保证系统健壮性
2.7 典型API接口
dma_resv_init() / dma_resv_fini()://初始化/销毁对象dma_resv_lock() / dma_resv_unlock()://写侧锁定/解锁dma_resv_reserve_fences() / dma_resv_add_fence()://预留/添加fencedma_resv_replace_fences()://替换fencedma_resv_get_fences() / dma_resv_get_singleton(): //查询fencedma_resv_wait_timeout()://等待fence完成dma_resv_set_deadline()://设置deadline hintdma_resv_test_signaled()://测试fence是否完成dma_resv_describe()://调试信息输出
3. 应用
3.1 DMA-BUF跨设备同步
3.1.1 隐式同步
DMA-BUF机制通过dma_resv实现跨设备的隐式同步。每个DMA-BUF对象关联一个dma_resv对象,所有生产者/消费者设备通过添加/等待fence实现同步。
-
生产者设备提交写fence
-
消费者设备等待写fence完成后再读
-
支持多种usage类型,灵活管理依赖关系
示例:
struct dma_buf *dmabuf = ...;
struct dma_resv *resv = dmabuf->resv;
dma_resv_lock(resv, NULL);
dma_resv_add_fence(resv, fence, DMA_RESV_USAGE_WRITE);
dma_resv_unlock(resv);
3.1.2 显式同步
用户空间可通过sync_file机制将fence导出为文件描述符,实现显式同步。内核通过dma_resv管理所有fence,实现高效的同步和错误传播。
3.2 GPU/DRM内存管理
3.2.1 TTM/DRM对象的访问控制
TTM/DRM内存管理采用dma_resv对象管理每个buffer的访问依赖。命令提交时,驱动通过添加fence实现依赖管理,后续操作通过等待fence保证数据一致性。
3.2.2 多设备协作
在多GPU、多显示、多视频协作场景,dma_resv实现了跨设备的同步和访问控制。每个buffer的所有依赖通过dma_resv统一管理,避免死锁和性能瓶颈。
3.3 多媒体管线异步协作
在Camera-ISP-GPU等多设备协作场景,dma_resv结合dma_fence实现了异步操作的串联和同步。每个环节的完成通过fence通知下游设备,保证数据流正确同步。
3.4 错误处理与超时恢复
dma_resv支持错误码传播和超时处理。驱动可在硬件异常、超时等场景下强制信号所有fence,并设置错误码,通知所有依赖者。
long ret = dma_resv_wait_timeout(resv, DMA_RESV_USAGE_WRITE, true, timeout);
if (ret == 0) {// 超时处理dma_fence_set_error(fence, -ETIMEDOUT);dma_fence_signal(fence);
}
3.5 性能优化与最佳实践
-
优先使用批量操作和回调机制,避免轮询等待
-
合理设置usage类型,提升并发性能
-
正确管理引用计数和RCU,避免内存泄漏和UAF
-
避免在持锁/中断上下文中等待fence,防止死锁
-
利用deadline hint优化功耗和调度
3.6 与其他同步原语的对比
特性 | dma_resv | mutex | spinlock | completion | semaphore |
---|---|---|---|---|---|
基本用途 | 异步跨设备同步 | 互斥 | 临界区保护 | 一次性事件 | 计数资源 |
支持异步 | ✓ | ✗ | ✗ | 部分 | 部分 |
支持批量 | ✓ | ✗ | ✗ | ✗ | ✗ |
支持回调 | ✓ | ✗ | ✗ | ✗ | ✗ |
支持RCU | ✓ | ✗ | ✗ | ✗ | ✗ |
支持优先级继承 | ✓ | ✓(RT) | ✗ | ✗ | ✗ |
典型场景 | DMA-BUF, GPU, 多媒体 | 进程同步 | 内核临界区 | 事件通知 | 资源管理 |
4. 总结
dma_resv作为Linux内核专门设计的异步同步管理机制,具备以下优势:
-
高效的跨设备、跨驱动同步管理
-
灵活的访问类型和优先级控制
-
深度集成dma_fence机制,实现异步完成通知和回调
-
支持批量操作、RCU安全、死锁保护和优先级继承
-
丰富的调试和监控接口,便于系统调优和问题定位
在GPU、显示、视频、DMA-BUF等现代异构计算场景中,dma_resv已成为不可或缺的核心组件。
Synchronization of access is necessary precisely because the object is shared. 因为共享,所以同步。
推荐的学习路线为:
1. 先理解共享机制。参见博文:dma-buf机制。留意同步机制的使用即可;
2. 理解单个同步对象的机制和实现。参见博文:dma-fence机制;
3. 单个同步对象并不足以控制对共享对象多需求的异步访问,因此需要多个同步对象来控制,这就是本文的dma_resv。
如有帮助,请三连:点赞、收藏、加关注。