3. drm_atomic_state 结构体详解:结构体成员、引用计数、生命周期管理
drm_atomic_state 是 DRM 原子提交框架的核心数据结构,它承载了一次原子操作中所有 CRTC、Plane、Connector 的完整状态快照。理解其内部成员、引用计数机制以及生命周期管理,是正确使用原子 API 和避免内存泄漏、UAF(Use-After-Free)等问题的前提。
3.1 核心结构体成员
drm_atomic_state 定义在 include/drm/drm_atomic.h 中,其关键成员可分为三类:状态对象指针数组、提交控制标志、以及内部管理字段。
| 成员名称 | 类型 | 说明 |
|---|---|---|
crtcs |
struct drm_crtc_state *[] |
指向每个 CRTC 的新状态指针数组,索引为 CRTC 的 index。 |
planes |
struct drm_plane_state *[] |
指向每个 Plane 的新状态指针数组,索引为 Plane 的 index。 |
connectors |
struct drm_connector_state *[] |
指向每个 Connector 的新状态指针数组,索引为 Connector 的 index。 |
num_connector |
int |
本次提交中实际涉及的 Connector 数量。 |
allow_modeset |
bool |
是否允许本次提交触发 modeset(模式设置)。 |
legacy_cursor_update |
bool |
是否为 legacy 光标更新(用于兼容旧接口)。 |
async_update |
bool |
是否为异步更新(不等待 vblank)。 |
commit_work |
struct work_struct |
用于异步提交的工作队列项。 |
ref |
struct kref |
引用计数,用于管理对象的生命周期。 |
关键理解:
crtcs、planes、connectors 三个数组是状态存储的核心。每个数组元素要么是 NULL(表示该对象不参与本次提交),要么指向一个通过 drm_atomic_get_crtc_state() 等函数获取的、引用计数已增加的新状态对象。
3.2 引用计数机制
drm_atomic_state 使用内核标准的 struct kref 进行引用计数管理。引用计数的变化遵循以下规则:
- 初始计数为 1:当通过
drm_atomic_state_alloc()分配时,引用计数被设置为 1。 - 获取状态时增加:调用
drm_atomic_get_crtc_state()、drm_atomic_get_plane_state()、drm_atomic_get_connector_state()时,内部会调用drm_atomic_state_get()增加引用计数。 - 提交时转移所有权:当调用
drm_atomic_commit()时,框架会接管 state 对象的所有权,并在提交完成后(同步或异步)调用drm_atomic_state_put()释放引用。 - 错误路径需手动释放:如果在构建状态过程中发生错误(如内存不足、校验失败),驱动必须调用
drm_atomic_state_put()来释放已分配的状态。
常见陷阱: 驱动在
atomic_check 回调中如果提前返回错误,必须确保之前通过 drm_atomic_get_*_state() 获取的状态对象引用已被正确释放。否则会导致引用泄漏,最终引发内核内存泄漏。
3.3 生命周期管理
一个典型的 drm_atomic_state 生命周期包含以下阶段:
- 分配阶段:驱动或用户态通过 IOCTL 触发,调用
drm_atomic_state_alloc()创建空状态对象。 - 构建阶段:通过
drm_atomic_add_affected_*()和drm_atomic_get_*_state()填充状态对象数组。 - 校验阶段:调用
drm_atomic_check_only()或drm_atomic_commit()触发校验,驱动atomic_check回调被调用。 - 提交阶段:校验通过后,状态被提交到硬件。同步提交直接应用;异步提交通过
commit_work延迟执行。 - 清理阶段:提交完成后,框架调用
drm_atomic_state_put()减少引用计数。当计数归零时,drm_atomic_state_free()被调用,释放所有子状态对象和自身内存。
生命周期要点: 在异步提交场景中,
drm_atomic_state 的生命周期会跨越多个内核调度周期。驱动在 atomic_flush 或 atomic_commit_tail 回调中必须持有对 state 的引用,直到硬件完成状态切换。通常框架会在 drm_atomic_helper_commit_hw_done() 之后才释放引用。
3.4 状态对象关系图
下图展示了 drm_atomic_state 与 CRTC、Plane、Connector 状态对象之间的引用关系:
3.5 引用计数操作 API
内核提供了以下 API 用于管理 drm_atomic_state 的引用计数:
| API 函数 | 作用 | 调用时机 |
|---|---|---|
drm_atomic_state_alloc() |
分配并初始化 state,ref 设为 1 | IOCTL 入口或驱动内部创建提交时 |
drm_atomic_state_get() |
增加引用计数(kref_get) | 获取子状态、异步提交持有引用时 |
drm_atomic_state_put() |
减少引用计数(kref_put),归零时释放 | 提交完成、错误路径、驱动不再需要时 |
drm_atomic_state_put_unlocked() |
不带锁的版本,用于已持有锁的上下文 | 在 atomic_check 等已加锁回调中 |
最佳实践: 在驱动自定义的
atomic_commit_tail 实现中,如果需要将 state 传递给异步工作队列,务必在提交工作前调用 drm_atomic_state_get(),并在工作完成后调用 drm_atomic_state_put()。这样可以防止 state 在异步处理过程中被提前释放。
3.6 生命周期状态机
下图展示了 drm_atomic_state 从创建到销毁的完整状态转换:
3.7 驱动中的典型使用模式
以下代码片段展示了驱动在 atomic_commit 回调中如何正确管理 state 的生命周期:
static int my_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool nonblock)
{
int ret;
// 1. 校验阶段:框架已调用 atomic_check
ret = drm_atomic_helper_check(dev, state);
if (ret) {
// 校验失败,框架会自动释放 state
return ret;
}
// 2. 非阻塞提交:增加引用,交给工作队列
if (nonblock) {
drm_atomic_state_get(state);
if (!queue_work(system_unbound_wq, &state->commit_work)) {
drm_atomic_state_put(state);
return -EBUSY;
}
return 0;
}
// 3. 阻塞提交:直接执行
drm_atomic_helper_commit_tail(state);
return 0;
}
注意: 在非阻塞提交中,驱动必须确保
commit_work 回调函数在完成硬件操作后调用 drm_atomic_state_put()。如果工作队列被取消或延迟,可能导致 state 泄漏或 UAF。建议使用 drm_atomic_helper_commit() 辅助函数,它已经正确处理了这些细节。
3.8 总结
- 结构体核心:
drm_atomic_state通过三个指针数组(crtcs、planes、connectors)管理所有子状态对象。 - 引用计数:使用
kref机制,通过drm_atomic_state_get()/put()管理生命周期,计数归零时自动释放。 - 生命周期阶段:分配 → 构建 → 校验 → 提交 → 清理,每个阶段都有明确的 API 调用和引用计数变化规则。
- 驱动责任:在错误路径和异步提交中必须正确管理引用,避免泄漏或 UAF。