Linux Kernel/lockdep 锁依赖检查

用 BFS 找出循环锁依赖的短路径

Linux lockdep 在加入新的锁依赖前,用 BFS 查找反向可达路径;若形成环,报告一条较短的依赖链,帮助定位潜在死锁。

v6.12并发与一致性核验 2026-10-12
相关概念

工程问题#

某条执行路径持有 A 再获取 B,另一条路径可能以相反次序获取锁;依赖关系也可能经过多个锁类间接成环。调试器需要从已经观察到的依赖中找到冲突,并给出便于阅读的路径。

实际实现#

lockdep 把锁类之间的获取顺序组织为依赖图。check_noncircular 从待获取锁出发,调用 check_path 和 __bfs_forwards 搜索已持有锁;搜索根据读写锁依赖类型过滤无效路径,并利用访问标记、父指针与环形队列逐层扩展。找到匹配后,由父链生成循环依赖报告。

处理流程#

  1. 获取新锁时,初始化反向可达性检查所需的搜索根
  2. 用环形队列按层展开锁类的依赖列表,跳过已处理状态
  3. 按强依赖规则过滤不构成死锁路径的读写锁组合
  4. 若搜索到已持有锁,则回溯父链并输出循环锁依赖诊断

源码片段#

固定版本 · 原文摘录
kernel/locking/lockdep.cv6.12 · L2180–L2193
查看原文件
2180check_noncircular(struct held_lock *src, struct held_lock *target,2181		  struct lock_trace **const trace)2182{2183	enum bfs_result ret;2184	struct lock_list *target_entry;2185	struct lock_list src_entry;21862187	bfs_init_root(&src_entry, src);21882189	debug_atomic_inc(nr_cyclic_checks);21902191	ret = check_path(target, &src_entry, hlock_conflict, NULL, &target_entry);21922193	if (unlikely(ret == BFS_RMATCH)) {
check_noncircular · 循环依赖检查入口

建立搜索根并调用 check_path,目标是确认新增依赖是否会与已记录路径成环;匹配后的报告逻辑位于函数后半段。

kernel/locking/lockdep.cv6.12 · L2154–L2168
查看原文件
2154static noinline enum bfs_result2155check_path(struct held_lock *target, struct lock_list *src_entry,2156	   bool (*match)(struct lock_list *entry, void *data),2157	   bool (*skip)(struct lock_list *entry, void *data),2158	   struct lock_list **target_entry)2159{2160	enum bfs_result ret;21612162	ret = __bfs_forwards(src_entry, target, match, skip, target_entry);21632164	if (unlikely(bfs_error(ret)))2165		print_bfs_bug(ret);21662167	return ret;2168}
check_path · 调用 BFS

把冲突匹配函数与搜索根交给 __bfs_forwards,出现搜索错误时单独报告,不把错误当成“未发现路径”。

kernel/locking/lockdep.cv6.12 · L1743–L1764
查看原文件
1743	lockdep_assert_locked();17441745	__cq_init(cq);1746	__cq_enqueue(cq, source_entry);17471748	while ((lock = __bfs_next(lock, offset)) || (lock = __cq_dequeue(cq))) {1749		if (!lock->class)1750			return BFS_EINVALIDNODE;17511752		/*1753		 * Step 1: check whether we already finish on this one.1754		 *1755		 * If we have visited all the dependencies from this @lock to1756		 * others (iow, if we have visited all lock_list entries in1757		 * @lock->class->locks_{after,before}) we skip, otherwise go1758		 * and visit all the dependencies in the list and mark this1759		 * list accessed.1760		 */1761		if (lock_accessed(lock))1762			continue;1763		else1764			mark_lock_accessed(lock);
__bfs · 队列初始化与访问标记

初始化环形队列,按当前兄弟项或出队顺序推进,跳过已处理的依赖列表。

kernel/locking/lockdep.cv6.12 · L1809–L1830
查看原文件
1809		first = true;1810		head = get_dep_list(lock, offset);1811		list_for_each_entry_rcu(entry, head, entry) {1812			visit_lock_entry(entry, lock);18131814			/*1815			 * Note we only enqueue the first of the list into the1816			 * queue, because we can always find a sibling1817			 * dependency from one (see __bfs_next()), as a result1818			 * the space of queue is saved.1819			 */1820			if (!first)1821				continue;18221823			first = false;18241825			if (__cq_enqueue(cq, entry))1826				return BFS_EQUEUEFULL;18271828			cq_depth = __cq_get_elem_count(cq);1829			if (max_bfs_queue_depth < cq_depth)1830				max_bfs_queue_depth = cq_depth;
__bfs · 扩展依赖列表

为依赖设置访问关系,只将列表首项入队,其余兄弟项由 __bfs_next 访问;显式检查队列容量并记录最大深度。

四段原文从循环检查入口追到 BFS 调用与队列扩展。强依赖过滤、命中判定和诊断回溯在同一完整文件的相邻代码中,未把整个锁验证器等同于一个通用 BFS 函数。

收益与取舍#

工程分析

带来的收益

  • 在实际线程卡死前,根据已观察到的锁获取顺序发现潜在循环依赖
  • BFS 优先得到边数较少的依赖路径,便于定位导致环路的锁获取位置

付出的代价

  • 普通邻接表 BFS 的遍历成本为 O(V+E);此实现还包含强依赖状态过滤、诊断与锁链缓存,不能把该式当成一次获取锁的完整开销
  • 环形队列容量由配置限制,队列满会报搜索错误,不能当成不存在依赖路径
  • 结论依赖已经观察到的锁类和获取顺序,以及正确的锁注解;未执行路径不自动获得验证

可比较的选择

深度优先搜索也能判断可达性,但首次找到的路径不保证边数最少,可能使诊断链更长。

只检查直接的 A→B、B→A逻辑简单,但会漏掉经由多个锁类构成的循环依赖。

版本与适用范围#

对应 Linux v6.12、启用相应 lockdep 调试配置的锁类依赖检查;报告潜在锁序冲突,不代表当前线程已处于实际死锁。源码中 __bfs_next 先遍历同一依赖列表的兄弟项,队列仅保存列表首项以节省空间;不能把它简化成不加过滤地遍历任意原始有向图。

参考来源#

源码、官方文档与原始论文
01
linux · BFS 的工程用途说明

kernel/locking/lockdep.c · BFS 的工程用途说明

源码v6.12
02
linux · check_noncircular / check_path

kernel/locking/lockdep.c · check_noncircular / check_path

源码v6.12
03
linux · __bfs · 强依赖过滤与队列扩展

kernel/locking/lockdep.c · __bfs · 强依赖过滤与队列扩展

源码v6.12

相关实现与比较#

1 个相关案例
12 个匹配案例