一、问题背景
在开发 LT9211C 的 V4L2 子设备驱动时,实现了一个控制接口:
v4l2-ctl -d /dev/v4l-subdevX --set-ctrl=chip_init=1
该命令触发 .s_ctrl()
中的设备初始化逻辑。驱动中使用了 mutex 来保护共享状态:
mutex_lock(<9211c->confctl_mutex);
...
mutex_unlock(<9211c->confctl_mutex);
但执行命令时,.s_ctrl()
永远卡在 mutex_lock()
,导致控制操作无法完成。
二、初步怀疑与验证
1:其他线程(如 workqueue)持有锁
- 存在工作队列周期性使用
mutex_trylock()
,怀疑抢占了锁 - 注释掉所有
schedule_work()
和工作队列逻辑 - 结果:问题仍然存在,
.s_ctrl()
卡住不动
2:驱动中其他路径加了锁
- 全局查找所有
mutex_lock()
、mutex_trylock()
路径 - 结果:确认无其他加锁路径
三、深入排查
尝试用 mutex_is_locked()
检查锁状态
在 .s_ctrl()
内部加打印:
pr_info("locked = %d\n", mutex_is_locked(<->confctl_mutex));
结果显示锁始终处于加锁状态,但代码中没人持有锁。
四、关键线索发现
在控件初始化函数中:
handler->lock = <9211c->confctl_mutex;
lt9211c->chip_init_ctrl = v4l2_ctrl_new_custom(handler,
<9211c_ctrl_chip_init, NULL);
.s_ctrl()
回调正好是 chip_init
控件对应的处理函数。
这意味着:V4L2 框架在调用.s_ctrl()
之前已经自动加了handler->lock
对应的 mutex
五、框架调用链分析
VIDIOC_S_CTRL
└── v4l2_ctrl_handler_setup()
└── mutex_lock(handler->lock); //框架加锁
└── ctrl->ops->s_ctrl() //调用驱动 .s_ctrl()
└── mutex_unlock(handler->lock);
因此 .s_ctrl()
执行时已经处于加锁状态,若再次 mutex_lock()
同一把锁,就会死锁(线程阻塞等待自己)。
六、最终修复方式
删除 .s_ctrl()
中的重复加锁:
- mutex_lock(<->confctl_mutex); // 不需要
...
- mutex_unlock(<->confctl_mutex); // 不需要
改为直接执行逻辑(由框架管理加解锁)。
七、验证结果
- 编译驱动
重新执行控制命令:
v4l2-ctl --set-ctrl=chip_init=1
- 控制操作立即成功返回,卡死问题彻底解决。
八、经验总结
项目 | 说明 |
---|---|
问题类型 | 自陷死锁(Self-deadlock) |
原因 | .s_ctrl() 中手动 mutex_lock() 框架已加锁的 mutex |
最大误导点 | 锁好像“被别人持有”,实际是“自己卡自己” |
排查突破口 | handler->lock 设置 → 框架自动加锁机制分析 |
根本解决方法 | 不在 .s_ctrl() 中重复加锁 handler->lock 指向的 mutex |
凡设置了handler->lock
的 V4L2 控制器,.s_ctrl()
中禁止重复加锁,否则极易造成自陷死锁。
九、其他调试方法
pr_info("s_ctrl: current thread = %s, is_locked = %d\n",
current->comm,
mutex_is_locked(<->confctl_mutex));
可以查看当前的锁被谁持有。
评论 (0)