4. QTI HAL典型死锁场景1:Camera HAL3中Stream与Request Queue的锁竞争

在Qualcomm平台的Camera HAL3实现中,Stream锁与Request Queue锁的竞争是最常见、最难复现的死锁模式之一。该场景通常发生在多线程并发处理Buffer分配、Stream配置更新以及Request入队/出队操作时,由于锁获取顺序不一致导致循环等待。

4.1 场景概述与锁模型

Camera HAL3的核心数据通路涉及两个关键临界区:

  • Stream Lock (mStreamLock):保护Stream的配置、Buffer队列和内部状态,通常在configureStreams()、processCaptureRequest()以及Buffer回调中获取。
  • Request Queue Lock (mRequestLock):保护待处理的Capture Request队列,在processCaptureRequest()入队和后台Dequeue线程出队时获取。

死锁的典型触发路径如下表所示:

线程 持有锁 等待锁 操作
App Thread (Binder) mStreamLock mRequestLock 在processCaptureRequest()中先更新Stream状态,再向队列添加Request
Dequeue Thread mRequestLock mStreamLock 从队列取出Request后,需要获取Stream锁来填充Buffer
关键判断依据:当两个线程分别持有对方需要的锁,且都不释放已持有的锁时,即形成循环等待。在Perfetto trace中,如果看到App线程阻塞在pthread_mutex_lock(mRequestLock),而Dequeue线程阻塞在pthread_mutex_lock(mStreamLock),即可确认该死锁模式。

4.2 死锁触发流程详解

以下步骤描述了从正常操作到死锁冻结的完整过程:

  1. App线程发起Capture Request:HAL Service调用processCaptureRequest(),App线程获取mStreamLock以检查Stream状态是否就绪。
  2. Stream状态检查:App线程在持有mStreamLock的情况下,遍历Stream列表,确认Buffer可用。
  3. Request入队:App线程尝试获取mRequestLock,将Request插入待处理队列。此时若Dequeue线程已持有mRequestLock,App线程阻塞。
  4. Dequeue线程处理:后台Dequeue线程持有mRequestLock从队列取出Request,随后尝试获取mStreamLock以将Buffer填充到Stream中。
  5. 循环等待形成:App线程持有mStreamLock等待mRequestLock;Dequeue线程持有mRequestLock等待mStreamLock。两个线程均无法继续执行,系统进入死锁状态。
实战提示:在QTI平台上,该死锁的复现概率与Request吞吐量正相关。当Camera帧率超过30fps或同时开启多路Stream(如Preview+Video+Snapshot)时,锁竞争加剧,死锁更容易触发。

4.3 锁竞争关系SVG图

下图展示了Camera HAL3中Stream Lock与Request Queue Lock的竞争关系及死锁形成路径:

Camera HAL3 Stream与Request Queue锁竞争死锁示意图 Camera HAL3 Stream Lock vs Request Queue Lock 死锁 App Thread (Binder) 持有 mStreamLock Dequeue Thread 持有 mRequestLock mStreamLock mRequestLock 已持有 已持有 等待 等待 ⚠ 循环等待:两个线程互相等待对方释放锁 死锁 → Camera HAL 无响应 操作路径:App Thread: processCaptureRequest() → 获取 mStreamLock → 等待 mRequestLock Dequeue Thread: 取出 Request → 获取 mRequestLock → 等待 mStreamLock

4.4 Perfetto Trace中的关键信号

在Perfetto分析中,识别该死锁需要关注以下Trace信号:

Trace信号 出现位置 死锁指示
futex(0x... FUTEX_WAIT_PRIVATE) App线程的Binder调用栈 App线程在等待mRequestLock对应的futex
futex(0x... FUTEX_WAIT_PRIVATE) Dequeue线程的调用栈 Dequeue线程在等待mStreamLock对应的futex
pthread_mutex_lock + 函数名 两个线程的堆栈顶部 确认锁等待发生在processCaptureRequest和dequeueRequest路径
长时间无processCaptureResult回调 Camera HAL输出事件流 死锁导致HAL无法完成Request处理,无结果返回
Perfetto分析技巧:在Trace中搜索futex系统调用,按等待时间降序排列。如果发现两个线程的futex等待地址不同但互相指向对方持有的锁,且等待时间超过500ms,基本可以判定为死锁。使用sched/sched_switch事件可以精确看到哪个线程在哪个CPU上阻塞。

4.5 锁获取顺序对比

死锁的根源在于锁获取顺序不一致。以下表格对比了正常情况与死锁情况下的锁获取顺序:

场景 App Thread 锁顺序 Dequeue Thread 锁顺序 结果
正常 先 mRequestLock → 后 mStreamLock 先 mRequestLock → 后 mStreamLock 顺序一致,无死锁
死锁 先 mStreamLock → 后 mRequestLock 先 mRequestLock → 后 mStreamLock 顺序相反,循环等待

4.6 代码级复现示例

以下伪代码展示了死锁的触发路径。注意processCaptureRequest中锁获取顺序的反转:

// App Thread: processCaptureRequest()
void processCaptureRequest(CaptureRequest& req) {
    // 错误顺序:先获取 Stream Lock
    mStreamLock.lock();                     // 持有 mStreamLock
    
    // 检查 Stream 状态
    for (auto& stream : mStreams) {
        if (!stream.isBufferAvailable()) {
            // 等待 Buffer
        }
    }
    
    // 然后尝试获取 Request Lock
    mRequestLock.lock();                    // 等待 mRequestLock(可能死锁)
    mRequestQueue.push(req);
    mRequestLock.unlock();
    
    mStreamLock.unlock();
}

// Dequeue Thread: dequeueAndProcess()
void dequeueAndProcess() {
    // 先获取 Request Lock
    mRequestLock.lock();                    // 持有 mRequestLock
    
    CaptureRequest req = mRequestQueue.pop();
    
    // 然后尝试获取 Stream Lock
    mStreamLock.lock();                     // 等待 mStreamLock(可能死锁)
    
    // 填充 Buffer 到 Stream
    fillBufferToStream(req);
    
    mStreamLock.unlock();
    mRequestLock.unlock();
}
修复方案:统一锁获取顺序,确保所有线程都按照先mRequestLock后mStreamLock的顺序获取锁。在processCaptureRequest中,先释放mStreamLock再获取mRequestLock,或者使用try_lock机制避免长时间阻塞。

4.7 死锁检测与预防策略

在QTI平台上,针对该死锁场景的检测与预防措施包括:

  • 静态分析:使用Clang Thread Safety Analysis注解锁获取顺序,在编译期检测不一致。
  • 运行时检测:在关键锁操作前后添加ATRACE_BEGIN/END,通过Perfetto trace分析锁持有时间。
  • 锁排序规范:在代码注释中明确锁层级(Lock Order),所有线程必须遵守全局锁顺序表。
  • 超时机制:对pthread_mutex_timedlock设置超时(如100ms),超时后打印堆栈并恢复。
  • 死锁检测器:在调试版本中启用pthread_mutexattr_settype的死锁检测属性(仅Linux支持)。
QTI平台特有注意:Qualcomm的Camera HAL实现中,mStreamLock可能被多个内部模块(如ISP、JPEG、Video)共享。在修改锁顺序时,需要确认所有模块的锁获取路径,避免引入新的死锁变体。