lockdep分析与样例

lockdep原理分析

Linux 的 lockdep(lock dependency validator) 是一个强大的内核调试工具,用于检测 死锁风险、锁顺序错误 和 潜在的锁依赖循环。它主要服务于 内核开发者 和 驱动开发者,在调试并发相关问题时非常有用。

下面从 原理、核心数据结构、工作机制、关键调用栈、典型使用场景 等方面详细分析 lockdep 的实现原理。

lockdep 的核心目标

  • 检测是否存在 潜在死锁(即锁循环依赖)
  • 检查锁的使用顺序是否一致(避免 锁顺序反转)
  • 捕捉 中断上下文、softirq 上下文 中不当的锁操作
  • 提示 递归加锁、非法释放锁 等错误

工作机制概述

lockdep 基于以下几个核心理念:

  • 每一个锁的加锁顺序都被记录下来
  • 每一次加锁会记录当前持有锁和新加锁的关系(边)
  • 构建有向图(锁依赖图)
  • 每次新建依赖关系后,检测是否形成环(即潜在死锁)

关键数据结构

  1. struct lock_class_key
struct lock_class_key {};
  • 是唯一标识某个锁类型的“key”,静态锁必须配有一个静态 key。
  • 可通过 DEFINE_LOCKDEP_CLASS() 声明。
  1. 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
  • 包含锁之间的依赖图等
  1. struct held_lock
struct held_lock {
    struct lock_class *class;
    u64 acquire_ip;
    ...
};
  • 表示当前进程正在持有的一个锁。
  • 会存入当前 task 的 task_struct->held_locks[] 数组中。
  1. 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() 核心工作:

  1. 检查当前 task 是否已经持有新锁
  2. 如果不是,遍历当前所有持有锁,建立 from -> to 依赖关系
  3. 检查是否形成环(依赖图中检测环)
  4. 更新依赖图拓扑

解锁时的函数是:

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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值