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(),它完成以下步骤:

  1. 分配内存:通过 kzalloc() 分配 drm_atomic_state 结构体。
  2. 初始化引用计数:将 ref 初始化为 1(kref_init)。
  3. 关联设备:设置 state->dev = dev。
  4. 预分配状态数组:根据设备支持的 CRTC/Plane/Connector 数量,分配指针数组和 ID 数组。
  5. 初始化 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 的完整生命周期分为四个阶段:

阶段总览:创建 → 填充 → 提交 → 清理
drm_atomic_state 生命周期四阶段 阶段1:创建 drm_atomic_state_alloc() 阶段2:填充 drm_atomic_get_*_state() 阶段3:提交 drm_atomic_commit() 阶段4:清理 __drm_atomic_state_free() 各阶段关键操作: • 创建:分配内存、初始化 kref、预分配状态数组、初始化 acquire_ctx • 填充:通过 drm_atomic_get_*_state() 获取并复制对象当前状态,设置属性变更 • 提交:检查 → 获取锁 → 硬件提交(同步/异步),引用计数管理 • 清理:释放子状态内存、释放数组、释放锁上下文、释放 state 本身

4.5 异步提交中的生命周期管理

在异步提交场景下,state 的生命周期管理更为复杂:

  1. 提交返回时:drm_atomic_commit() 返回后,state 可能仍在硬件处理队列中。
  2. 引用计数增加:框架在提交函数内部调用 drm_atomic_state_get(),将引用计数从 1 增加到 2。
  3. 调用者释放:调用者调用 drm_atomic_state_put(),引用计数降为 1。
  4. 硬件完成:当硬件完成刷新(VBlank 中断或完成回调)时,框架调用 drm_atomic_state_put(),引用计数归零。
  5. 自动清理: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 的调用栈,帮助定位引用计数问题。

4.7 总结:state 生命周期速查表

drm_atomic_state 生命周期速查 drm_atomic_state_alloc ref=1, 分配数组 drm_atomic_get_*_state 复制当前状态 drm_atomic_check_only 验证状态合法性 drm_atomic_commit 同步/异步提交 引用计数变化路径: 同步提交:ref=1(创建)→ ref=1(提交返回)→ drm_atomic_state_put() → ref=0 → 释放 异步提交:ref=1(创建)→ ref=2(提交时 get)→ ref=1(调用者 put)→ ref=0(硬件完成时 put)→ 释放