lockdep原理分析
Linux 的 lockdep(lock dependency validator) 是一个强大的内核调试工具,用于检测 死锁风险、锁顺序错误 和 潜在的锁依赖循环。它主要服务于 内核开发者 和 驱动开发者,在调试并发相关问题时非常有用。
下面从 原理、核心数据结构、工作机制、关键调用栈、典型使用场景 等方面详细分析 lockdep 的实现原理。
lockdep 的核心目标
- 检测是否存在 潜在死锁(即锁循环依赖)
- 检查锁的使用顺序是否一致(避免 锁顺序反转)
- 捕捉 中断上下文、softirq 上下文 中不当的锁操作
- 提示 递归加锁、非法释放锁 等错误
工作机制概述
lockdep 基于以下几个核心理念:
- 每一个锁的加锁顺序都被记录下来
- 每一次加锁会记录当前持有锁和新加锁的关系(边)
- 构建有向图(锁依赖图)
- 每次新建依赖关系后,检测是否形成环(即潜在死锁)
关键数据结构
struct lock_class_key
struct lock_class_key {};
- 是唯一标识某个锁类型的“key”,静态锁必须配有一个静态 key。
- 可通过 DEFINE_LOCKDEP_CLASS() 声明。
struct lock_class
描述某一类锁(非具体实例):
struct lock_class {
struct list_head list;
struct lock_class_key *key;
const char *name;
int subclass;
unsigned int dep_gen_id;
...
};
- 每个类型的锁在系统中只存在一个 lock_class
- 包含锁之间的依赖图等
struct held_lock
struct held_lock {
struct lock_class *class;
u64 acquire_ip;
...
};
- 表示当前进程正在持有的一个锁。
- 会存入当前 task 的
task_struct->held_locks[]
数组中。
struct lockdep_map
所有锁必须通过 lockdep_map 来接入 lockdep 系统:
struct lockdep_map {
struct lock_class_key *key;
const char *name;
int cpu;
unsigned long ip;
};
例如,mutex、spinlock 都内嵌 lockdep_map
加锁路径与依赖图构建
以 mutex_lock()
为例,lockdep 的调用链如下:
mutex_lock()
├── __mutex_lock()
└── mutex_acquire() // lockdep entry point
├── check_deadlock()
├── mark_lock()
└── add_lock_to_held()
mutex_acquire()
核心工作:
- 检查当前 task 是否已经持有新锁
- 如果不是,遍历当前所有持有锁,建立 from -> to 依赖关系
- 检查是否形成环(依赖图中检测环)
- 更新依赖图拓扑
解锁时的函数是:
mutex_unlock()
└── mutex_release()
└── lock_release() // entry
└── remove_lock_from_held()
依赖图结构
Lockdep 实质上构建的是一个 锁依赖的有向图,每个节点是 lock_class,每条边表示“锁 A 被持有时获取锁 B”的顺序。
lock A -----> lock B -----> lock C
↑ |
└------------------------┘ ← 若出现这个环,说明存在潜在死锁
当新建边时(调用 check_deadlock()
),会进行深度优先搜索(DFS)检测是否形成 环。
典型错误检测类型
类型 | 检测原理 |
---|---|
潜在死锁 | 发现依赖图中形成环 |
锁顺序反转 | 前后两次加锁顺序相反 |
中断上下文加锁 | 在不可睡眠上下文使用可能睡眠的锁 |
递归加锁 | 同一 task 多次获取相同锁 |
不匹配 unlock | 解锁未持有的锁 |
持锁时间过长 | 检测锁的持有时长是否超限 |
使用方式
内核配置选项开启
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_LOCKDEP=y
用户空间触发死锁查看
查看 lockdep 报告:
dmesg | grep -i "possible circular locking dependency"
或者:
cat /proc/lockdep
典型错误输出示例:
[ 39.101441] ======================================================
[ 39.101707] WARNING: possible circular locking dependency detected
[ 39.101707] 5.15.187+ #6 Tainted: G W O
[ 39.101707] ------------------------------------------------------
[ 39.101707] thread2/93 is trying to acquire lock:
[ 39.101707] ffffffffc000b458 (&lock_A){+.+.}-{2:2}, at: thread_func2+0x24/0x60 [deadlock_demo]
[ 39.101707]
[ 39.101707] but task is already holding lock:
[ 39.101707] ffffffffc000b418 (&lock_B){+.+.}-{2:2}, at: thread_func2+0xe/0x60 [deadlock_demo]
[ 39.101707]
[ 39.101707] which lock already depends on the new lock.
[ 39.101707]
[ 39.101707]
[ 39.101707] the existing dependency chain (in reverse order) is:
[ 39.101707]
[ 39.101707] -> #1 (&lock_B){+.+.}-{2:2}:
[ 39.101707] _raw_spin_lock+0x27/0x40
[ 39.101707] thread_func1+0x24/0x60 [deadlock_demo]
[ 39.101707] kthread+0x133/0x150
[ 39.101707] ret_from_fork+0x22/0x30
[ 39.101707]
[ 39.101707] -> #0 (&lock_A){+.+.}-{2:2}:
[ 39.101707] __lock_acquire+0x143c/0x2570
[ 39.101707] lock_acquire+0xc6/0x2a0
[ 39.101707] _raw_spin_lock+0x27/0x40
[ 39.101707] thread_func2+0x24/0x60 [deadlock_demo]
[ 39.101707] kthread+0x133/0x150
[ 39.101707] ret_from_fork+0x22/0x30
[ 39.101707]
[ 39.101707] other info that might help us debug this:
[ 39.101707]
[ 39.101707] Possible unsafe locking scenario:
[ 39.101707]
[ 39.101707] CPU0 CPU1
[ 39.101707] ---- ----
[ 39.101707] lock(&lock_B);
[ 39.101707] lock(&lock_A);
[ 39.101707] lock(&lock_B);
[ 39.101707] lock(&lock_A);
[ 39.101707]
[ 39.101707] *** DEADLOCK ***
提供一份人为引发deadlock的demo
死锁风险 Demo(顺序反转)
这个内核模块会创建两个线程,分别以不同顺序加锁两个 spinlock:
// deadlock_demo.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static spinlock_t lock_A;
static spinlock_t lock_B;
static struct task_struct *task1;
static struct task_struct *task2;
static int thread_func1(void *data)
{
while (!kthread_should_stop()) {
spin_lock(&lock_A);
msleep(100); // 模拟处理逻辑
spin_lock(&lock_B);
// 模拟工作
msleep(100);
spin_unlock(&lock_B);
spin_unlock(&lock_A);
msleep(500);
}
return 0;
}
static int thread_func2(void *data)
{
while (!kthread_should_stop()) {
spin_lock(&lock_B);
msleep(100); // 模拟处理逻辑
spin_lock(&lock_A);
// 模拟工作
msleep(100);
spin_unlock(&lock_A);
spin_unlock(&lock_B);
msleep(500);
}
return 0;
}
static int __init deadlock_demo_init(void)
{
pr_info("lockdep deadlock demo init\n");
spin_lock_init(&lock_A);
spin_lock_init(&lock_B);
task1 = kthread_run(thread_func1, NULL, "thread1");
task2 = kthread_run(thread_func2, NULL, "thread2");
return 0;
}
static void __exit deadlock_demo_exit(void)
{
pr_info("lockdep deadlock demo exit\n");
if (task1)
kthread_stop(task1);
if (task2)
kthread_stop(task2);
}
module_init(deadlock_demo_init);
module_exit(deadlock_demo_exit);
MODULE_LICENSE("GPL");
检测:
- thread_func1 获取 A → B 锁顺序
- thread_func2 获取 B → A 锁顺序
- lockdep 检测到锁顺序反转,打印 possible circular locking dependency 警告
注意
- lockdep 只追踪 静态锁(如 mutex、spinlock),不会跟踪动态分配的锁除非正确声明 lockdep_map
- 对性能有一定影响(调试环境使用)
- 支持嵌套锁、子类锁(subclass)
总结
项目 | 说明 |
---|---|
本质 | 检测内核中锁的使用顺序是否一致、是否会形成环 |
原理 | 构建锁依赖图,DFS 检测环 |
数据结构 | lock_class , held_lock , lockdep_map |
使用方式 | 开启 CONFIG_LOCKDEP + 编译带符号内核 |
调用入口 | mutex_acquire() / mutex_release() |