4. drm_atomic_state 结构体:state 的创建、引用计数、生命周期管理
在 DRM atomic 提交流程中,drm_atomic_state 是整个事务的核心载体。它封装了一次原子操作中所有 CRTC、Plane、Connector 的状态快照,以及本次提交所需的全部元数据。理解 state 的创建、引用计数与生命周期管理,是掌握 atomic 提交机制的关键前提。
核心要点:drm_atomic_state 是一个“一次性”事务对象,创建后经过“获取锁 → 检查 → 提交 → 清理”四个阶段,引用计数确保在异步提交场景下对象不会被提前释放。
4.1 drm_atomic_state 结构体定义
该结构体定义在 include/drm/drm_atomic.h 中,是 atomic 框架的“总控结构”。
struct drm_atomic_state {
struct kref ref; // 引用计数
struct drm_device *dev; // 所属 DRM 设备
/* 允许的最大对象数量 */
uint32_t num_connector;
uint32_t num_crtc;
uint32_t num_plane;
/* 指向各个对象状态数组的指针 */
struct drm_connector_state __rcu **connectors;
struct drm_crtc_state __rcu **crtcs;
struct drm_plane_state __rcu **planes;
/* 对象索引映射 */
int *connector_ids;
int *crtc_ids;
int *plane_ids;
/* 提交时是否允许模式设置 */
bool allow_modeset;
/* 异步提交标志 */
bool async_update;
/* 用于跟踪已获取锁的对象位图 */
unsigned long *acquire_ctx;
/* 工作队列项(用于异步提交) */
struct work_struct commit_work;
};
设计意图:state 使用指针数组而非链表来存储各对象的状态,目的是支持 O(1) 随机访问。每个对象在 state 中的索引由
drm_atomic_get_*_state() 系列函数动态分配。
4.2 state 的创建:drm_atomic_state_alloc()
创建 state 的入口函数是 drm_atomic_state_alloc(),它完成以下步骤:
- 分配内存:通过
kzalloc()分配drm_atomic_state结构体。 - 初始化引用计数:将
ref初始化为 1(kref_init)。 - 关联设备:设置
state->dev = dev。 - 预分配状态数组:根据设备支持的 CRTC/Plane/Connector 数量,分配指针数组和 ID 数组。
- 初始化 acquire_ctx:为锁上下文分配位图。
struct drm_atomic_state *drm_atomic_state_alloc(struct drm_device *dev)
{
struct drm_atomic_state *state;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return NULL;
kref_init(&state->ref);
state->dev = dev;
/* 分配状态数组 */
state->crtcs = kcalloc(dev->mode_config.num_crtc,
sizeof(*state->crtcs), GFP_KERNEL);
state->planes = kcalloc(dev->mode_config.num_plane,
sizeof(*state->planes), GFP_KERNEL);
state->connectors = kcalloc(dev->mode_config.num_connector,
sizeof(*state->connectors), GFP_KERNEL);
/* 分配 ID 映射数组 */
state->crtc_ids = kcalloc(dev->mode_config.num_crtc,
sizeof(*state->crtc_ids), GFP_KERNEL);
// ... 类似处理 plane_ids, connector_ids
return state;
}
注意:state 创建后并未立即与任何具体对象绑定。真正的状态获取发生在
drm_atomic_get_crtc_state()、drm_atomic_get_plane_state() 等函数调用时,这些函数会检查 state 中是否已有该对象的状态,若无则分配并复制当前状态。
4.3 引用计数机制
atomic state 使用 struct kref 进行引用计数管理,主要涉及以下操作:
| 操作 | 函数 | 说明 |
|---|---|---|
| 获取引用 | drm_atomic_state_get() |
调用 kref_get(),增加引用计数 |
| 释放引用 | drm_atomic_state_put() |
调用 kref_put(),计数归零时触发清理 |
| 清理回调 | __drm_atomic_state_free() |
释放所有子状态、数组内存、锁上下文 |
引用计数的典型使用场景:
- 同步提交:在
drm_atomic_commit()返回前,state 的引用计数为 1;提交完成后立即调用drm_atomic_state_put()释放。 - 异步提交:当
drm_atomic_commit()返回时,state 可能仍在硬件处理中。此时框架会额外获取一次引用,待硬件完成中断处理后再释放。 - 错误路径:如果在检查阶段失败,调用者需手动调用
drm_atomic_state_put()释放 state。
// 典型的使用模式
struct drm_atomic_state *state = drm_atomic_state_alloc(dev);
// ... 填充状态 ...
ret = drm_atomic_check_only(state);
if (ret) {
drm_atomic_state_put(state); // 检查失败,释放 state
return ret;
}
ret = drm_atomic_commit(state);
// 提交后,state 的引用由框架管理
drm_atomic_state_put(state); // 释放调用者的引用
4.4 生命周期管理:四个阶段
一个 drm_atomic_state 的完整生命周期分为四个阶段:
阶段总览:创建 → 填充 → 提交 → 清理
4.5 异步提交中的生命周期管理
在异步提交场景下,state 的生命周期管理更为复杂:
- 提交返回时:
drm_atomic_commit()返回后,state 可能仍在硬件处理队列中。 - 引用计数增加:框架在提交函数内部调用
drm_atomic_state_get(),将引用计数从 1 增加到 2。 - 调用者释放:调用者调用
drm_atomic_state_put(),引用计数降为 1。 - 硬件完成:当硬件完成刷新(VBlank 中断或完成回调)时,框架调用
drm_atomic_state_put(),引用计数归零。 - 自动清理:
kref_put()触发__drm_atomic_state_free(),释放所有资源。
// 异步提交内部简化流程
int drm_atomic_commit(struct drm_atomic_state *state)
{
// ... 检查、获取锁 ...
if (state->async_update) {
// 异步路径:增加引用,防止 state 被提前释放
drm_atomic_state_get(state);
// 提交到硬件后立即返回
queue_work(system_unbound_wq, &state->commit_work);
return 0;
}
// 同步路径:等待硬件完成
// ...
return 0;
}
// 工作队列处理函数
static void commit_work(struct work_struct *work)
{
struct drm_atomic_state *state =
container_of(work, struct drm_atomic_state, commit_work);
// 等待硬件完成
// ...
// 释放框架持有的引用
drm_atomic_state_put(state);
}
最佳实践:驱动开发者应始终遵循“谁获取,谁释放”的原则。在异步提交中,框架会额外获取一次引用,驱动无需关心硬件完成后的清理细节。但驱动必须确保在
commit_tail() 等回调中不会访问已释放的 state。
4.6 常见陷阱与调试建议
| 陷阱 | 后果 | 解决方案 |
|---|---|---|
忘记调用 drm_atomic_state_put() |
内存泄漏,state 及其子状态永远无法释放 | 在错误路径和正常路径都确保调用 drm_atomic_state_put() |
| 在异步提交后继续访问 state | UAF(Use-After-Free),内核崩溃 | 提交后立即将 state 指针置 NULL,不再访问 |
多次调用 drm_atomic_state_put() |
引用计数提前归零,UAF | 使用 drm_atomic_state_put() 的配对调用,避免重复释放 |
| 在检查失败后未释放 state | 内存泄漏 | 检查失败后立即调用 drm_atomic_state_put() |
调试技巧:开启内核配置
CONFIG_DRM_DEBUG_MM 和 CONFIG_DEBUG_KOBJECT_RELEASE,可以在 state 释放时打印调试信息。使用 ftrace 跟踪 drm_atomic_state_alloc 和 __drm_atomic_state_free 的调用栈,帮助定位引用计数问题。