《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 24 章 Linux 设备驱动的调试进阶

《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》

第 24 章 Linux 设备驱动的调试进阶

参考:宋宝华 著,机械工业出版社,2015年版


24.1 动态调试(Dynamic Debug)

24.1.1 动态调试的概念

动态调试(Dynamic Debug)是 Linux 内核提供的一种运行时按需开启调试输出 的机制,无需重新编译内核或驱动即可控制 pr_debug()dev_dbg() 等调试消息的输出:

复制代码
动态调试 vs 传统调试方式:

传统方式(#define DEBUG):
  需要在源码中添加 #define DEBUG
  需要重新编译驱动
  所有调试信息同时输出,难以过滤
  生产环境不能使用

动态调试:
  无需修改源码,无需重新编译
  运行时精确控制哪些文件/函数/行的调试信息输出
  可以按文件、函数、行号、模块过滤
  生产环境可以按需开启,不影响性能
  支持显示文件名、行号、函数名、线程 ID 等附加信息

24.1.2 动态调试的配置

内核配置

bash 复制代码
# 内核配置(make menuconfig)
# → Kernel hacking
#   → [*] Dynamic printk() support (CONFIG_DYNAMIC_DEBUG=y)

# 验证配置
grep CONFIG_DYNAMIC_DEBUG /boot/config-$(uname -r)
# CONFIG_DYNAMIC_DEBUG=y

挂载 debugfs

bash 复制代码
# 挂载 debugfs(通常已自动挂载)
mount -t debugfs none /sys/kernel/debug

# 验证动态调试控制文件存在
ls /sys/kernel/debug/dynamic_debug/
# control

24.1.3 动态调试控制语法

bash 复制代码
# 控制文件格式:
# echo "查询条件 操作" > /sys/kernel/debug/dynamic_debug/control

# 操作符:
# +p:开启打印(print)
# -p:关闭打印
# +f:显示函数名
# +l:显示行号
# +m:显示模块名
# +t:显示线程 ID
# =p:只开启打印(清除其他标志)

# 查询条件关键字:
# file <文件名>:按文件名过滤
# func <函数名>:按函数名过滤
# line <行号>:按行号过滤
# module <模块名>:按模块名过滤
# format <格式字符串>:按消息内容过滤
# all:匹配所有

24.1.4 动态调试的使用案例

bash 复制代码
# ── 查看当前动态调试状态 ──────────────────────────────────────

# 查看所有可调试的打印点
cat /sys/kernel/debug/dynamic_debug/control | head -20
# drivers/i2c/i2c-core.c:1234 [i2c_core]i2c_transfer =_ "i2c transfer\n"
# drivers/gpio/gpio-imx.c:56 [gpio_imx]imx_gpio_get =_ "gpio get\n"
# ...
# 格式:文件:行号 [模块]函数名 标志 "消息格式"
# 标志:_ 表示未开启,p 表示已开启打印

# 统计可调试点数量
wc -l /sys/kernel/debug/dynamic_debug/control
# 12345 /sys/kernel/debug/dynamic_debug/control

# ── 按文件开启调试 ────────────────────────────────────────────

# 开启特定文件的所有调试输出
echo "file drivers/i2c/i2c-core.c +p" > \
    /sys/kernel/debug/dynamic_debug/control

# 开启多个文件
echo "file drivers/gpio/gpio-imx.c +p" >> \
    /sys/kernel/debug/dynamic_debug/control

# 关闭特定文件的调试输出
echo "file drivers/i2c/i2c-core.c -p" > \
    /sys/kernel/debug/dynamic_debug/control

# ── 按模块开启调试 ────────────────────────────────────────────

# 开启整个模块的调试输出
echo "module my_driver +p" > /sys/kernel/debug/dynamic_debug/control

# 开启模块调试并显示文件名、行号、函数名
echo "module my_driver +pflm" > /sys/kernel/debug/dynamic_debug/control
# p:打印
# f:函数名
# l:行号
# m:模块名

# 关闭模块调试
echo "module my_driver -p" > /sys/kernel/debug/dynamic_debug/control

# ── 按函数开启调试 ────────────────────────────────────────────

# 开启特定函数的调试输出
echo "func my_driver_probe +p" > /sys/kernel/debug/dynamic_debug/control
echo "func my_driver_read +p" >> /sys/kernel/debug/dynamic_debug/control

# ── 按行号开启调试 ────────────────────────────────────────────

# 开启特定行的调试输出
echo "file my_driver.c line 45 +p" > \
    /sys/kernel/debug/dynamic_debug/control

# 开启行范围的调试输出
echo "file my_driver.c line 40-60 +p" > \
    /sys/kernel/debug/dynamic_debug/control

# ── 按消息内容过滤 ────────────────────────────────────────────

# 只开启包含特定字符串的调试消息
echo "format \"i2c transfer\" +p" > \
    /sys/kernel/debug/dynamic_debug/control

# ── 开启所有调试输出 ──────────────────────────────────────────

# 开启所有模块的调试输出(谨慎使用,输出量极大)
echo "all +p" > /sys/kernel/debug/dynamic_debug/control

# 关闭所有调试输出
echo "all -p" > /sys/kernel/debug/dynamic_debug/control

# ── 通过内核启动参数开启 ──────────────────────────────────────

# 在 U-Boot 中设置内核启动参数
# bootargs = "... dyndbg=\"module my_driver +p\""

# 或在 /etc/modprobe.d/ 中配置
echo "options my_driver dyndbg=+p" > /etc/modprobe.d/my_driver.conf

24.1.5 在驱动中使用动态调试

c 复制代码
/*
 * 驱动中使用动态调试的正确方式
 */
#include <linux/kernel.h>
#include <linux/device.h>

/* pr_debug:支持动态调试(需要 CONFIG_DYNAMIC_DEBUG=y)*/
pr_debug("驱动初始化,参数 val=%d\n", val);

/* dev_dbg:带设备信息的调试输出(推荐在驱动中使用)*/
dev_dbg(&pdev->dev, "设备 %s 初始化完成\n", dev_name(&pdev->dev));
dev_dbg(&client->dev, "I2C 读取寄存器 0x%02x = 0x%02x\n", reg, val);

/*
 * 注意:
 * 1. pr_debug 和 dev_dbg 在 CONFIG_DYNAMIC_DEBUG=y 时支持动态控制
 * 2. 在 CONFIG_DYNAMIC_DEBUG=n 时,如果没有定义 DEBUG 宏,这些调用被编译掉
 * 3. 不要使用 printk(KERN_DEBUG ...) 代替,它不支持动态调试
 */

/* 验证动态调试效果 */
/*
 * 加载驱动后:
 * echo "module my_driver +pflm" > /sys/kernel/debug/dynamic_debug/control
 * dmesg | grep my_driver
 * 输出示例:
 * [  5.123] my_driver my_driver_probe:45 my_driver: 设备 my_device 初始化完成
 * 格式:[时间] 模块名 函数名:行号 消息
 */

24.2 ftrace

24.2.1 ftrace 简介

ftrace(Function Tracer)是 Linux 内核内置的函数级跟踪框架,可以跟踪内核函数调用、中断、调度等事件,是分析内核行为和性能问题的强大工具:

复制代码
ftrace 的主要功能:

1. 函数跟踪(Function Tracer)
   跟踪内核函数的调用和返回
   可以过滤特定函数

2. 函数图跟踪(Function Graph Tracer)
   显示函数调用关系(树形结构)
   显示每个函数的执行时间

3. 事件跟踪(Event Tracer)
   跟踪内核预定义的事件(中断、调度、系统调用等)
   支持自定义事件

4. 延迟跟踪(Latency Tracer)
   跟踪中断延迟、调度延迟
   用于实时性分析

5. 唤醒延迟跟踪(Wakeup Tracer)
   跟踪进程从唤醒到运行的延迟

24.2.2 ftrace 的基本使用

bash 复制代码
# ftrace 通过 debugfs 控制
cd /sys/kernel/debug/tracing

# 查看可用的跟踪器
cat available_tracers
# blk function_graph wakeup_dl wakeup_rt wakeup function nop

# 查看当前跟踪器
cat current_tracer
# nop   ← 默认不跟踪

# 查看可用的事件
ls events/
# block  ext4  gpio  i2c  irq  kmem  net  sched  signal  skb  ...

# ── 函数跟踪器 ────────────────────────────────────────────────

# 设置函数跟踪器
echo function > current_tracer

# 设置要跟踪的函数(支持通配符)
echo "my_driver_*" > set_ftrace_filter
echo "i2c_transfer" >> set_ftrace_filter

# 开始跟踪
echo 1 > tracing_on

# 执行要调试的操作
cat /dev/my_device

# 停止跟踪
echo 0 > tracing_on

# 查看跟踪结果
cat trace
# # tracer: function
# #
# #           TASK-PID   CPU#  ||||    TIMESTAMP  FUNCTION
# #              | |       |   ||||       |         |
#          cat-1234  [000] ....  1234.567890: my_driver_open <-do_dentry_open
#          cat-1234  [000] ....  1234.567891: my_driver_read <-vfs_read
#          cat-1234  [000] ....  1234.567892: my_driver_release <-__fput

# 清空跟踪缓冲区
echo > trace

# ── 函数图跟踪器(显示调用关系和耗时)────────────────────────

# 设置函数图跟踪器
echo function_graph > current_tracer

# 设置要跟踪的函数
echo "my_driver_read" > set_graph_function

# 开始跟踪
echo 1 > tracing_on
cat /dev/my_device
echo 0 > tracing_on

# 查看结果
cat trace
# # tracer: function_graph
# #
# # CPU  DURATION                  FUNCTION CALLS
# # |     |   |                     |   |   |   |
#  0)               |  my_driver_read() {
#  0)   0.123 us    |    mutex_lock();
#  0)               |    copy_to_user() {
#  0)   0.456 us    |      __copy_to_user();
#  0)   0.789 us    |    }
#  0)   0.234 us    |    mutex_unlock();
#  0)   1.602 us    |  }

# ── 事件跟踪 ──────────────────────────────────────────────────

# 查看 I2C 相关事件
ls events/i2c/
# i2c_read  i2c_result  i2c_write  i2c_reply

# 使能 I2C 事件跟踪
echo 1 > events/i2c/enable

# 使能特定事件
echo 1 > events/i2c/i2c_write/enable
echo 1 > events/i2c/i2c_read/enable

# 开始跟踪
echo 1 > tracing_on
# 执行 I2C 操作...
echo 0 > tracing_on

# 查看结果
cat trace
# cat-1234  [000] ....  1234.567: i2c_write: i2c-1 #0 a=048 f=0000 l=1 [00]
# cat-1234  [000] ....  1234.568: i2c_read:  i2c-1 #1 a=048 f=0001 l=2

# ── 中断延迟跟踪 ──────────────────────────────────────────────

# 跟踪中断禁止的最大延迟
echo irqsoff > current_tracer
echo 1 > tracing_on
# 运行一段时间...
echo 0 > tracing_on
cat trace
# 显示中断被禁止的最长时间段的调用栈

# ── 使用 trace-cmd 工具(更方便)────────────────────────────

# 安装 trace-cmd
sudo apt-get install trace-cmd

# 记录函数调用
sudo trace-cmd record -p function -l "my_driver_*" cat /dev/my_device

# 分析记录
trace-cmd report

# 记录事件
sudo trace-cmd record -e i2c cat /dev/my_device
trace-cmd report

24.2.3 ftrace 在驱动调试中的应用

bash 复制代码
# 案例一:分析驱动函数调用链
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo "my_driver_*" > /sys/kernel/debug/tracing/set_graph_function
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 触发驱动操作
echo "test" > /dev/my_device

echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace

# 案例二:分析中断处理时间
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo "my_irq_handler" > /sys/kernel/debug/tracing/set_graph_function
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 等待中断触发...
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
# 查看中断处理函数的执行时间

# 案例三:跟踪内存分配
echo 1 > /sys/kernel/debug/tracing/events/kmem/kmalloc/enable
echo 1 > /sys/kernel/debug/tracing/events/kmem/kfree/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 加载驱动...
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace | grep "my_driver"

# 案例四:分析调度延迟
echo wakeup > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
# 运行实时任务...
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
# 显示最大唤醒延迟

# 清理
echo nop > /sys/kernel/debug/tracing/current_tracer
echo > /sys/kernel/debug/tracing/set_ftrace_filter
echo > /sys/kernel/debug/tracing/trace

24.3 perf 性能分析

24.3.1 perf 简介

perf 是 Linux 内核自带的性能分析工具,基于硬件性能计数器(PMU)和内核跟踪点,可以分析 CPU 使用率、缓存命中率、函数调用热点等:

复制代码
perf 的主要功能:

perf stat:统计程序运行期间的性能事件
perf record:采样记录性能数据
perf report:分析 perf record 的结果
perf top:实时显示 CPU 热点函数
perf trace:跟踪系统调用(类似 strace)
perf annotate:显示带注释的汇编代码
perf script:将 perf.data 转换为文本格式
perf diff:比较两次 perf record 的结果

24.3.2 perf 的安装和基本使用

bash 复制代码
# 安装 perf
sudo apt-get install linux-tools-$(uname -r) linux-tools-generic

# 验证安装
perf --version
# perf version 4.0.0

# ── perf stat:性能统计 ────────────────────────────────────────

# 统计程序运行期间的性能事件
perf stat ./my_app
# Performance counter stats for './my_app':
#
#          1,234.56 msec task-clock                #    0.987 CPUs utilized
#                 5      context-switches          #    0.004 K/sec
#                 0      cpu-migrations            #    0.000 K/sec
#               123      page-faults               #    0.100 K/sec
#     3,456,789,012      cycles                    #    2.800 GHz
#     2,345,678,901      instructions              #    0.68  insn per cycle
#       456,789,012      branches                  #  369.999 M/sec
#         1,234,567      branch-misses             #    0.27% of all branches

# 统计特定事件
perf stat -e cache-misses,cache-references,instructions,cycles ./my_app

# 统计内核模块的性能(需要 root)
sudo perf stat -e cycles,instructions insmod my_driver.ko

# ── perf top:实时热点分析 ────────────────────────────────────

# 实时显示 CPU 热点函数
sudo perf top

# 只分析特定进程
sudo perf top -p 1234

# 只分析内核函数
sudo perf top -K

# 显示特定事件的热点
sudo perf top -e cache-misses

# ── perf record + report:详细分析 ───────────────────────────

# 记录性能数据(采样)
sudo perf record -g ./my_app    # -g:记录调用栈
sudo perf record -g -a sleep 10 # -a:记录所有 CPU,持续 10 秒

# 分析记录结果
sudo perf report
# 交互式界面,显示热点函数和调用链

# 以文本格式输出
sudo perf report --stdio

# 生成火焰图
sudo perf record -g -F 99 ./my_app  # -F 99:99Hz 采样频率
sudo perf script | ./FlameGraph/stackcollapse-perf.pl | \
    ./FlameGraph/flamegraph.pl > flamegraph.svg

# ── perf trace:系统调用跟踪 ─────────────────────────────────

# 跟踪系统调用(类似 strace,但开销更小)
sudo perf trace ./my_app

# 只跟踪特定系统调用
sudo perf trace -e open,read,write,ioctl ./my_app

# ── perf annotate:汇编级分析 ────────────────────────────────

# 显示热点函数的汇编代码(带性能注释)
sudo perf annotate my_driver_read

24.3.3 perf 在驱动调试中的应用

bash 复制代码
# 案例一:分析驱动的 CPU 使用率
sudo perf record -g -p $(pgrep my_app) sleep 10
sudo perf report --stdio | head -50

# 案例二:分析 Cache 命中率
sudo perf stat -e cache-misses,cache-references \
    -e L1-dcache-loads,L1-dcache-load-misses \
    ./my_app
# 输出:
# 1,234,567  cache-misses    #   12.34% of all cache refs
# 9,876,543  cache-references

# 案例三:分析中断频率
sudo perf stat -e irq:irq_handler_entry -a sleep 5
# 统计 5 秒内的中断次数

# 案例四:分析 DMA 传输性能
sudo perf record -e dma:dma_map_sg,dma:dma_unmap_sg -a sleep 5
sudo perf report

# 案例五:比较优化前后的性能
sudo perf record -o before.data ./my_app_v1
sudo perf record -o after.data ./my_app_v2
sudo perf diff before.data after.data

# 案例六:分析内核驱动函数热点
sudo perf record -g -e cycles:k -a sleep 10  # 只记录内核态
sudo perf report --stdio | grep "my_driver"

24.4 内存检测工具(KASAN、Valgrind)

24.4.1 KASAN(Kernel Address Sanitizer)

KASAN 是 Linux 内核内置的内存错误检测工具,可以检测以下问题:

复制代码
KASAN 能检测的内存错误:

1. 越界访问(Out-of-bounds access)
   访问了分配内存之外的区域
   例:char buf[10]; buf[10] = 'x';  ← 越界

2. 释放后使用(Use-after-free)
   访问了已经释放的内存
   例:kfree(ptr); *ptr = 1;  ← 危险!

3. 释放后再次释放(Double-free)
   对同一块内存调用两次 kfree
   例:kfree(ptr); kfree(ptr);  ← 危险!

4. 栈越界(Stack out-of-bounds)
   访问了栈变量之外的区域

5. 全局变量越界(Global out-of-bounds)
   访问了全局数组之外的区域

KASAN 的配置和使用

bash 复制代码
# 内核配置(make menuconfig)
# → Kernel hacking
#   → Memory Debugging
#     → [*] KASAN: runtime memory debugger (CONFIG_KASAN=y)
#     → KASAN mode (Inline instrumentation)
#     → [*] KASan stack instrumentation

# 编译内核(KASAN 会显著增加内存使用和降低性能,仅用于调试)
make -j$(nproc)

# 加载有内存错误的驱动
sudo insmod buggy_driver.ko

# KASAN 检测到错误时的输出示例:
# ==================================================================
# BUG: KASAN: slab-out-of-bounds in my_driver_write+0x24/0x80 [my_driver]
# Write of size 1 at addr ffff880007a80010 by task cat/1234
#
# CPU: 0 PID: 1234 Comm: cat Tainted: G    B   O    4.0.0 #1
# Hardware name: QEMU Standard PC
# Call Trace:
#  dump_stack+0x4d/0x65
#  print_address_description+0x1f4/0x290
#  kasan_report+0x138/0x180
#  my_driver_write+0x24/0x80 [my_driver]
#  vfs_write+0x8c/0x17c
#  sys_write+0x44/0x74
#
# Allocated by task 1234:
#  kmalloc+0x1b/0x30
#  my_driver_open+0x34/0x80 [my_driver]
#
# The buggy address belongs to the object at ffff880007a80000
#  which belongs to the cache kmalloc-16 of size 16
# ==================================================================

# 分析 KASAN 报告:
# 1. 错误类型:slab-out-of-bounds(堆越界)
# 2. 出错位置:my_driver_write+0x24
# 3. 内存分配位置:my_driver_open+0x34
# 4. 使用 addr2line 定位具体代码行
arm-linux-gnueabihf-addr2line -e my_driver.ko 0x24

制造和检测内存错误的示例

c 复制代码
/*
 * 演示 KASAN 检测各种内存错误
 */

/* 错误1:堆越界写 */
static int test_heap_oob_write(void)
{
    char *buf = kmalloc(10, GFP_KERNEL);
    if (!buf) return -ENOMEM;

    buf[10] = 'x';  /* ← KASAN 会在此处报告 slab-out-of-bounds */

    kfree(buf);
    return 0;
}

/* 错误2:释放后使用 */
static int test_use_after_free(void)
{
    char *buf = kmalloc(10, GFP_KERNEL);
    if (!buf) return -ENOMEM;

    kfree(buf);
    buf[0] = 'x';  /* ← KASAN 会在此处报告 use-after-free */

    return 0;
}

/* 错误3:栈越界 */
static int test_stack_oob(void)
{
    char buf[10];
    buf[10] = 'x';  /* ← KASAN 会在此处报告 stack-out-of-bounds */
    return 0;
}

/* 正确的内存使用 */
static int test_correct(void)
{
    char *buf = kmalloc(10, GFP_KERNEL);
    if (!buf) return -ENOMEM;

    memset(buf, 0, 10);  /* 正确:在分配范围内操作 */
    buf[9] = 'x';        /* 正确:最后一个有效字节 */

    kfree(buf);
    return 0;
}

24.4.2 Valgrind(用户空间内存检测)

Valgrind 是用户空间程序的内存检测工具,不能直接用于内核驱动,但可以用于测试驱动的用户空间测试程序

bash 复制代码
# 安装 Valgrind
sudo apt-get install valgrind

# ── Memcheck:内存错误检测(最常用)────────────────────────────

# 检测内存错误
valgrind --leak-check=full --show-leak-kinds=all \
         --track-origins=yes --verbose \
         ./test_my_driver

# 输出示例:
# ==1234== Memcheck, a memory error detector
# ==1234== Invalid write of size 1
# ==1234==    at 0x10001234: test_heap_oob (test_driver.c:45)
# ==1234==    by 0x10001567: main (test_driver.c:100)
# ==1234==  Address 0x5204e8a is 0 bytes after a block of size 10 alloc'd
# ==1234==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck.so)
# ==1234==    by 0x10001200: test_heap_oob (test_driver.c:43)
#
# ==1234== LEAK SUMMARY:
# ==1234==    definitely lost: 1,024 bytes in 1 blocks
# ==1234==    indirectly lost: 0 bytes in 0 blocks
# ==1234==      possibly lost: 0 bytes in 0 blocks
# ==1234==    still reachable: 0 bytes in 0 blocks

# ── Callgrind:函数调用分析 ────────────────────────────────────

# 记录函数调用数据
valgrind --tool=callgrind ./test_my_driver

# 分析结果
callgrind_annotate callgrind.out.1234

# 使用 KCachegrind 可视化
kcachegrind callgrind.out.1234

# ── Helgrind:线程错误检测 ────────────────────────────────────

# 检测数据竞争和死锁
valgrind --tool=helgrind ./test_my_driver_threaded

# ── Massif:内存使用分析 ──────────────────────────────────────

# 分析内存使用情况
valgrind --tool=massif ./test_my_driver

# 可视化内存使用
ms_print massif.out.1234

24.4.3 kmemleak ------ 内核内存泄漏检测

bash 复制代码
# 内核配置
# CONFIG_DEBUG_KMEMLEAK=y

# 使用 kmemleak
mount -t debugfs none /sys/kernel/debug

# 触发扫描
echo scan > /sys/kernel/debug/kmemleak

# 查看泄漏报告
cat /sys/kernel/debug/kmemleak
# unreferenced object 0xffff880007a80000 (size 1024):
#   comm "insmod", pid 1234, jiffies 4294967295
#   hex dump (first 32 bytes):
#     00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
#   backtrace:
#     [<ffffffff811234ab>] kmalloc+0x1b/0x30
#     [<ffffffffc0001234>] my_driver_init+0x34/0x80 [my_driver]
#     [<ffffffff810abcde>] do_one_initcall+0x3e/0x170

# 清除已知泄漏(重新开始检测)
echo clear > /sys/kernel/debug/kmemleak

# 禁用 kmemleak(减少性能影响)
echo off > /sys/kernel/debug/kmemleak

24.5 lockdep 死锁检测

24.5.1 lockdep 的概念

lockdep(Lock Dependency Validator)是 Linux 内核内置的锁依赖关系验证器,可以在运行时检测潜在的死锁和锁使用错误:

复制代码
lockdep 能检测的问题:

1. 死锁(Deadlock)
   线程A持有锁1,等待锁2
   线程B持有锁2,等待锁1
   → 循环等待,永远无法解锁

2. 锁顺序违反(Lock Order Violation)
   代码路径1:先获取锁A,再获取锁B
   代码路径2:先获取锁B,再获取锁A
   → 可能导致死锁(即使当前没有死锁)

3. 中断上下文中使用睡眠锁
   在中断处理函数中调用 mutex_lock()
   → 可能导致死锁(中断上下文不能睡眠)

4. 递归锁(Recursive Lock)
   同一线程对同一个非递归锁加锁两次
   → 死锁

5. 锁的不正确使用
   在持有自旋锁时调用可能睡眠的函数

24.5.2 lockdep 的配置

bash 复制代码
# 内核配置(make menuconfig)
# → Kernel hacking
#   → Lock Debugging (spinlocks, mutexes, etc...)
#     → [*] RT Mutex debugging, deadlock detection (CONFIG_DEBUG_RT_MUTEXES=y)
#     → [*] Spinlock and rw-lock debugging: basic checks (CONFIG_DEBUG_SPINLOCK=y)
#     → [*] Mutex debugging: basic checks (CONFIG_DEBUG_MUTEXES=y)
#     → [*] Lock debugging: detect incorrect freeing of live locks (CONFIG_DEBUG_LOCK_ALLOC=y)
#     → [*] Lock debugging: prove locking correctness (CONFIG_PROVE_LOCKING=y)
#     → [*] Lock usage statistics (CONFIG_LOCK_STAT=y)

24.5.3 lockdep 的使用和报告分析

bash 复制代码
# lockdep 自动运行,检测到问题时打印警告
# 示例:循环锁依赖警告

# ======================================================
# WARNING: possible circular locking dependency detected
# 4.0.0 #1 Not tainted
# ------------------------------------------------------
# my_app/1234 is trying to acquire lock:
#  (&dev->mutex_b){+.+.+.}, at: [<ffffffffc0001234>] my_func_b+0x24/0x80 [my_driver]
#
# but task is already holding lock:
#  (&dev->mutex_a){+.+.+.}, at: [<ffffffffc0001567>] my_func_a+0x17/0x40 [my_driver]
#
# which lock already depends on the new lock.
#
# the existing dependency chain (in reverse order) is:
#
# -> #1 (&dev->mutex_b){+.+.+.}:
#        lock_acquire+0x9e/0x1c0
#        mutex_lock_nested+0x5b/0x3b0
#        my_func_b+0x24/0x80 [my_driver]
#        my_func_a+0x17/0x40 [my_driver]
#
# -> #0 (&dev->mutex_a){+.+.+.}:
#        lock_acquire+0x9e/0x1c0
#        mutex_lock_nested+0x5b/0x3b0
#        my_func_a+0x17/0x40 [my_driver]
#
# other info that might help us debug this:
#  Possible unsafe locking scenario:
#
#        CPU0                    CPU1
#        ----                    ----
#   lock(&dev->mutex_a);
#                                lock(&dev->mutex_b);
#                                lock(&dev->mutex_a);
#   lock(&dev->mutex_b);
#
#  *** DEADLOCK ***
# ======================================================

24.5.4 常见死锁场景及解决方法

c 复制代码
/*
 * 场景一:循环锁依赖(最常见的死锁原因)
 */

/* ❌ 错误:两个函数以不同顺序获取锁 */
static void func_a(struct my_dev *dev)
{
    mutex_lock(&dev->mutex_a);   /* 先获取 mutex_a */
    mutex_lock(&dev->mutex_b);   /* 再获取 mutex_b */
    /* ... */
    mutex_unlock(&dev->mutex_b);
    mutex_unlock(&dev->mutex_a);
}

static void func_b(struct my_dev *dev)
{
    mutex_lock(&dev->mutex_b);   /* 先获取 mutex_b */
    mutex_lock(&dev->mutex_a);   /* 再获取 mutex_a ← 与 func_a 顺序相反!*/
    /* ... */
    mutex_unlock(&dev->mutex_a);
    mutex_unlock(&dev->mutex_b);
}

/* ✅ 正确:所有代码路径以相同顺序获取锁 */
static void func_a_fixed(struct my_dev *dev)
{
    mutex_lock(&dev->mutex_a);   /* 始终先获取 mutex_a */
    mutex_lock(&dev->mutex_b);   /* 再获取 mutex_b */
    /* ... */
    mutex_unlock(&dev->mutex_b);
    mutex_unlock(&dev->mutex_a);
}

static void func_b_fixed(struct my_dev *dev)
{
    mutex_lock(&dev->mutex_a);   /* 始终先获取 mutex_a */
    mutex_lock(&dev->mutex_b);   /* 再获取 mutex_b */
    /* ... */
    mutex_unlock(&dev->mutex_b);
    mutex_unlock(&dev->mutex_a);
}

/*
 * 场景二:中断上下文中使用睡眠锁
 */

/* ❌ 错误:在中断处理函数中使用 mutex */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;

    mutex_lock(&dev->mutex);  /* ← 危险!中断上下文不能睡眠 */
    /* ... */
    mutex_unlock(&dev->mutex);

    return IRQ_HANDLED;
}

/* ✅ 正确:中断上下文使用自旋锁 */
static irqreturn_t my_irq_handler_fixed(int irq, void *dev_id)
{
    struct my_dev *dev = dev_id;
    unsigned long flags;

    spin_lock_irqsave(&dev->spinlock, flags);  /* 自旋锁,不会睡眠 */
    /* ... */
    spin_unlock_irqrestore(&dev->spinlock, flags);

    return IRQ_HANDLED;
}

/*
 * 场景三:持有自旋锁时调用可能睡眠的函数
 */

/* ❌ 错误:持有自旋锁时调用 kmalloc(GFP_KERNEL) */
static void my_func(struct my_dev *dev)
{
    spin_lock(&dev->lock);

    void *buf = kmalloc(1024, GFP_KERNEL);  /* ← 危险!GFP_KERNEL 可能睡眠 */

    spin_unlock(&dev->lock);
    kfree(buf);
}

/* ✅ 正确:持有自旋锁时使用 GFP_ATOMIC */
static void my_func_fixed(struct my_dev *dev)
{
    spin_lock(&dev->lock);

    void *buf = kmalloc(1024, GFP_ATOMIC);  /* GFP_ATOMIC 不会睡眠 */

    spin_unlock(&dev->lock);
    kfree(buf);
}

/*
 * 场景四:递归锁
 */

/* ❌ 错误:同一线程对同一 mutex 加锁两次 */
static void my_func(struct my_dev *dev)
{
    mutex_lock(&dev->mutex);
    /* ... */
    my_sub_func(dev);  /* 如果 my_sub_func 也调用 mutex_lock,则死锁 */
    mutex_unlock(&dev->mutex);
}

static void my_sub_func(struct my_dev *dev)
{
    mutex_lock(&dev->mutex);  /* ← 死锁!mutex 已被同一线程持有 */
    /* ... */
    mutex_unlock(&dev->mutex);
}

/* ✅ 正确方法一:重构代码,避免递归加锁 */
static void my_sub_func_nolock(struct my_dev *dev)
{
    /* 假设调用者已持有锁 */
    /* ... */
}

static void my_func_fixed(struct my_dev *dev)
{
    mutex_lock(&dev->mutex);
    /* ... */
    my_sub_func_nolock(dev);  /* 调用不加锁的版本 */
    mutex_unlock(&dev->mutex);
}

24.5.5 lockdep 的调试命令

bash 复制代码
# 查看锁统计信息
cat /proc/lock_stat
# lock_stat version 0.4
# ---------------------------------------------------------------
# class name    con-bounces    contentions   waittime-min   waittime-max   waittime-total   acq-bounces   acquisitions   holdtime-min   holdtime-max   holdtime-total
# ---------------------------------------------------------------
# &dev->mutex:          0              0              0              0              0              0              5              0              0              0

# 重置锁统计
echo 0 > /proc/lock_stat

# 查看 lockdep 状态
cat /proc/lockdep_stats
# lock-classes:                          123
# direct dependencies:                   456
# indirect dependencies:                 789
# all direct dependencies:              1234
# dependency chains:                     567
# in-hardirq chains:                      12
# in-softirq chains:                      34
# in-process chains:                     521
# stack-trace entries:                  5678
# combined max dependencies:           12345
# hardirq-safe locks:                     10
# hardirq-unsafe locks:                   20
# softirq-safe locks:                     15
# softirq-unsafe locks:                   25
# irq-safe locks:                         30
# irq-unsafe locks:                       40

# 查看所有锁类
cat /proc/lockdep | head -50

# 查看锁依赖关系
cat /proc/lockdep_chains | head -20

本章小结

章节 核心知识点 关键工具/命令
24.1 动态调试 动态调试概念(运行时按需开启);控制语法(file/func/line/module/format);+p/+f/+l/+m/+t标志;驱动中使用pr_debug/dev_dbg;内核启动参数配置 /sys/kernel/debug/dynamic_debug/controlecho "module my_driver +pflm"
24.2 ftrace ftrace功能(函数/函数图/事件/延迟跟踪);函数跟踪器(set_ftrace_filter);函数图跟踪器(显示调用关系和耗时);事件跟踪(I2C/中断/调度);trace-cmd工具 /sys/kernel/debug/tracing/trace-cmd recordtrace-cmd report
24.3 perf性能分析 perf stat(性能统计);perf top(实时热点);perf record+report(详细分析);perf trace(系统调用);火焰图生成;Cache命中率分析 perf statperf topperf record -gperf report
24.4 内存检测工具 KASAN(越界/UAF/double-free/栈越界);KASAN报告解读;Valgrind(Memcheck/Callgrind/Helgrind/Massif);kmemleak内核内存泄漏检测 CONFIG_KASAN=yvalgrind --leak-check=full/sys/kernel/debug/kmemleak
24.5 lockdep死锁检测 lockdep检测的5类问题;循环锁依赖报告解读;4种常见死锁场景及修复(循环依赖/中断上下文/自旋锁+GFP_KERNEL/递归锁);锁统计命令 CONFIG_PROVE_LOCKING=y/proc/lock_stat/proc/lockdep_stats

调试工具选择指南

复制代码
问题类型 → 推荐工具:

调试信息输出控制 → 动态调试(Dynamic Debug)
  无需重编译,运行时按需开启 pr_debug/dev_dbg

函数调用链分析 → ftrace(function_graph)
  查看函数调用关系和执行时间

性能热点分析 → perf(perf top / perf record)
  找出 CPU 使用最多的函数

内存越界/UAF → KASAN(内核)/ Valgrind(用户空间)
  检测内存访问错误

内存泄漏 → kmemleak(内核)/ Valgrind --leak-check(用户空间)
  检测未释放的内存

死锁/锁顺序 → lockdep
  检测潜在的死锁和锁使用错误

系统调用跟踪 → strace / perf trace
  分析用户空间与驱动的交互

综合性能分析 → PowerTOP / perf
  分析系统整体性能和功耗

参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年