1. 前言
在博文:linux drm_syncobj 机制原理与应用中介绍了内核drm_syncobj机制的相关原理,本篇是用户态libdrm中的syncobj的api实现分析。
syncobj 的设计初衷是为用户空间提供一种高效、灵活的 gpu 任务同步原语。
它具备如下特点:
- 对象化管理:同步状态以对象(syncobj)形式存在,用户空间可通过句柄引用和操作。
- 多次信号/等待:支持多次信号和等待,适合异步和多阶段任务。
- 跨进程同步:支持同步对象的导入/导出,实现进程间同步。
- timeline 扩展:部分驱动支持 timeline syncobj,可表示多个时间点的同步状态。
传统 fence 仅能表示单次同步事件,且生命周期受限。而 syncobj 支持多次信号/等待,且可导入/导出,适合复杂的同步场景。timeline syncobj 更是将同步扩展到多时间点,极大提升了灵活性。
2. syncobj 相关 ioctls概览与典型流程
syncobj 的所有操作都通过 ioctl 与内核 drm 驱动交互。
和内核实现一文中的维度划分保持一致,把ioctl划分如下。
维度 | ioctl 名称 | 功能描述 |
---|---|---|
创建与销毁 | drm_ioctl_syncobj_create | 创建同步对象,分配资源并返回句柄 |
drm_ioctl_syncobj_destroy | 销毁同步对象,释放资源 | |
导入与导出 | drm_ioctl_syncobj_handle_to_fd | 将同步对象句柄导出为文件描述符,实现跨进程同步 |
drm_ioctl_syncobj_fd_to_handle | 从文件描述符导入同步对象句柄,实现跨进程同步 | |
信号与重置 | drm_ioctl_syncobj_signal | 将同步对象设为已完成(signaled)状态 |
drm_ioctl_syncobj_reset | 将同步对象设为未完成(unsignaled)状态 | |
时间线操作 | drm_ioctl_syncobj_timeline_signal | 对 timeline syncobj 的指定时间点进行信号 |
drm_ioctl_syncobj_timeline_wait | 等待 timeline syncobj 的指定时间点完成 | |
drm_ioctl_syncobj_timeline_query | 查询 timeline syncobj 当前时间点状态 | |
等待与事件通知 | drm_ioctl_syncobj_wait | 等待同步对象变为已完成状态,支持阻塞/非阻塞及超时 |
drm_ioctl_syncobj_query | 查询同步对象的当前状态 |
典型流程:
- 创建 syncobj:通过
drmsyncobjcreate
创建同步对象,获得句柄。将该句柄随任务传递给内核驱动。 - 导入/导出:如需跨进程同步,通过
drmsyncobjhandletofd
和drmsyncobjfdtohandle
实现句柄与 fd 的转换。 - 信号/重置:根据任务完成情况,调用
drmsyncobjsignal
或drmsyncobjreset
设置同步对象状态。 - timeline 操作:如需多时间点同步,使用 timeline 相关 api 进行信号、等待和查询。
- 等待/通知:通过
drmsyncobjwait
或drmsyncobjtimelinewait
等待任务完成(任务完成后,会signal该任务关联的步骤1的同步对象),驱动后续处理。 - 销毁:任务完成后,调用
drmsyncobjdestroy
释放资源。
3. 创建与销毁
3.1 创建同步对象
3.1.1 drm_ioctl_syncobj_create
该 ioctl 用于在内核中分配一个同步对象,并返回其句柄。
用户空间通过 libdrm 的 drmsyncobjcreate
api 调用。
- 支持普通 syncobj 和 timeline syncobj 创建
- 句柄在用户空间唯一标识同步对象
- 可指定初始状态(已信号/未信号)
drm_public int drmsyncobjcreate(int fd, uint32_t flags, uint32_t *handle) { struct drm_syncobj_create args; int ret; memclear(args); args.flags = flags; args.handle = 0; ret = drmioctl(fd, drm_ioctl_syncobj_create, &args); if (ret) return ret; *handle = args.handle; return 0; }
fd
:打开的drm 设备文件描述符flags
:可选标志,如 drm_syncobj_create_signaled(初始为已信号状态)、drm_syncobj_create_timeline(创建 timeline syncobj)handle
:返回的同步对象句柄
3.2 销毁同步对象
3.2.1 drm_ioctl_syncobj_destroy
该 ioctl 用于释放同步对象资源。libdrm 封装为 drmsyncobjdestroy
。
drm_public int drmsyncobjdestroy(int fd, uint32_t handle) { struct drm_syncobj_destroy args; memclear(args); args.handle = handle; return drmioctl(fd, drm_ioctl_syncobj_destroy, &args); }
fd
:drm 设备文件描述符handle
:要销毁的同步对象句柄
后续的api我不再给出具体实现,感兴趣的朋友请查看源码。
4. 导入与导出
4.1 导出同步对象到文件描述符
4.1.1 drm_ioctl_syncobj_handle_to_fd
该 ioctl 用于将 syncobj 句柄导出为文件描述符,实现跨进程同步。
libdrm 封装为 drmsyncobjhandletofd
。
int drmsyncobjhandletofd(int fd, uint32_t handle, int *out_fd);
4.2 从文件描述符导入同步对象
4.2.1 drm_ioctl_syncobj_fd_to_handle
该 ioctl 用于将文件描述符导入为 syncobj 句柄。
libdrm 封装为 drmsyncobjfdtohandle
。
drmsyncobjfdtohandle(fd, syncobj_fd, &imported_handle);
5. 信号与重置
5.1 信号同步对象
5.1.1 drm_ioctl_syncobj_signal
该 ioctl 用于将同步对象设为已完成(signaled)状态。
libdrm 封装为 drmsyncobjsignal
。
int drmsyncobjsignal(int fd, const uint32_t *handles, uint32_t count);
5.2 重置同步对象
5.2.1 drm_ioctl_syncobj_reset
该 ioctl 用于将同步对象设为未完成(unsignaled)状态。
libdrm 封装为 drmsyncobjreset
。
int drmsyncobjreset(int fd, const uint32_t *handles, uint32_t count);
6. 时间线操作
这个高级用法,我还没有在项目中用到过,有用例的朋友欢迎分享。
6.1 timeline syncobj 简介
timeline syncobj 是对传统同步对象的扩展,允许一个 syncobj 维护多个时间点(value),每个 value 都可单独信号和等待。
适用于多帧渲染、批量任务调度等复杂场景。
6.2 timeline 信号
6.2.1 drm_ioctl_syncobj_timeline_signal
该 ioctl 用于对 timeline syncobj 的指定时间点进行信号。
libdrm 封装为 drmsyncobjtimelinesignal
。
int drmsyncobjtimelinesignal(int fd, const uint32_t *handles, const uint64_t *points, uint32_t count);
fd
:drm 设备文件描述符handles
:syncobj 句柄数组points
:对应时间点数组count
:数量
6.3 timeline 等待
6.3.1 drm_ioctl_syncobj_timeline_wait
该 ioctl 用于等待 timeline syncobj 的指定时间点完成。
libdrm 封装为 drmsyncobjtimelinewait
。
int drmsyncobjtimelinewait(int fd, const uint32_t *handles, const uint64_t *points, uint32_t count, uint64_t timeout_nsec, uint32_t flags, uint32_t *first_signaled);
fd
:drm 设备文件描述符handles
:syncobj 句柄数组points
:对应时间点数组count
:数量timeout_nsec
:超时时间(纳秒)flags
:等待标志first_signaled
:返回第一个完成的 syncobj 索引
等待与事件通知
7.1 普通等待
7.1.1 drm_ioctl_syncobj_wait
该 ioctl 用于等待普通 syncobj 变为已完成状态。
libdrm 封装为 drmsyncobjwait
。
- 支持阻塞和非阻塞等待
- 可批量等待多个同步对象
int drmsyncobjwait(int fd, const uint32_t *handles, uint32_t count, uint64_t timeout_nsec, uint32_t flags, uint32_t *first_signaled);
fd
:drm 设备文件描述符handles
:syncobj 句柄数组count
:数量timeout_nsec
:超时时间flags
:等待标志(如阻塞/非阻塞)first_signaled
:返回第一个完成的 syncobj 索引
8. 应用示例
应用场景:要使用dma引擎给一个大buffer填充数据,即buffer填充任务;然后把该buffer交给gpu的渲染任务,渲染任务要等待填充任务完成后在进行。
填充任务的执行是一个dma引擎设备;渲染任务的执行是一个gpu设备。cpu负责给这两个设备发送任务和数据。涉及到跨设备、跨驱动的异步操作的同步问题。
该场景的实现代码如下。
- 用户态:
uint32_t dma_syncobj; //创建drm_syncobj对象,获取句柄 drmsyncobjcreate(fd, flags, &dma_syncobj); //将句柄随transfer任务传递给内核 dma_transfer(bo, size, data, dma_syncobj); //其他准备工作 ..... //等待transfer的任务完成,即dma_syncobj同步对象被signal drmsyncobjwait(fd, &dma_syncobj, 1, ...); submit_render_jobs(fd, jobs);
- 内核驱动:
do_dma_transfer(bo, size, data, dma_syncobj) { //使用dma引擎传输数据到bo dma_transfer(bo, size, data); //signal dma_syncobj同步对象 drm_syncobj_replace_fence(syn_obj, signaled_fence); }
9. 总结
drm_syncobj 作为 libdrm 用户空间的同步对象抽象,通过一系列 drm_ioctl_syncobj_* ioctl,为开发者提供了高效、灵活的 gpu 任务同步机制。其支持创建与销毁、导入与导出、信号与重置、时间线操作、等待与事件通知等多种操作,适用于多队列渲染、跨进程同步、异步任务调度等多种应用场景。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论