文章目录
-
-
- [🚨 深入解析自旋锁死锁案例:从代码到原理 🚨](#🚨 深入解析自旋锁死锁案例:从代码到原理 🚨)
- [一、驱动程序代码详解 (`spinlock_deadlock.c`) 🖥️](#一、驱动程序代码详解 (
spinlock_deadlock.c
) 🖥️) - [二、用户态测试程序 (`test_deadlock.c`) 🔍](#二、用户态测试程序 (
test_deadlock.c
) 🔍) - [三、编译脚本 (`Makefile`) 🔧](#三、编译脚本 (
Makefile
) 🔧) - [四、场景深度解析 🧩](#四、场景深度解析 🧩)
-
- [场景1:递归加锁 🔄](#场景1:递归加锁 🔄)
- [场景2:无限循环 ♾️](#场景2:无限循环 ♾️)
- [场景3:长时间操作 ⏰](#场景3:长时间操作 ⏰)
- [场景4:主动调度 🔄](#场景4:主动调度 🔄)
- [六、最佳实践总结 🏆](#六、最佳实践总结 🏆)
-

🚨 深入解析自旋锁死锁案例:从代码到原理 🚨
本文通过 4 个典型死锁场景 的完整代码实现,结合逐行注释和原理分析,帮助开发者深入理解自旋锁死锁的触发条件与规避方法。所有代码均经过实际内核环境验证(Linux 5.15+)。
关键词 :自旋锁
🔒 死锁
☠️ 内核调试
🐞 并发编程
⚡
一、驱动程序代码详解 (spinlock_deadlock.c
) 🖥️
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/version.h>
#define DEVICE_NAME "deadlock_dev"
static int major; // 设备主编号
static struct class *cls; // 设备类指针
static spinlock_t my_spinlock; // 核心自旋锁变量 🔄
/********************** 场景选择宏 **********************/
// 取消注释其中一个进行测试 🧪
// #define SCENARIO_1 // 递归加锁
// #define SCENARIO_2 // 无限循环
// #define SCENARIO_3 // 长时间操作
#define SCENARIO_4 // 主动调度 ⚠️
/* 设备打开回调函数 */
static int device_open(struct inode *inode, struct file *filp) {
#ifdef SCENARIO_1
// 🚫 场景1:递归加锁(禁止行为)
spin_lock(&my_spinlock); // 第一次获取锁
spin_lock(&my_spinlock); // ❗ 第二次获取同一锁导致死锁 ☠️
printk(KERN_INFO "Double lock acquired\n");
spin_unlock(&my_spinlock); // 释放锁(但永远不会执行到这里)
spin_unlock(&my_spinlock);
#elif defined(SCENARIO_2)
// 🚫 场景2:无限循环占用锁
spin_lock(&my_spinlock); // 获取锁
while(1) { // ⚠️ 故意制造无限循环
printk("Holding lock forever\n");
mdelay(1000); // 每1秒打印一次(保持锁不释放)
}
spin_unlock(&my_spinlock); // 永远不会执行
#elif defined(SCENARIO_3)
// 🚫 场景3:长时间临界区操作
spin_lock(&my_spinlock); // 获取锁
for (int i=0; i<1000000; i++) {
printk("Long operation %d\n", i); // ⏳ 百万次打印操作
} // 模拟长时间占用锁
spin_unlock(&my_spinlock);
#elif defined(SCENARIO_4)
// 🚫 场景4:持有锁时主动调度
spin_lock(&my_spinlock);
printk("Calling schedule()\n");
schedule(); // ⚠️ 主动让出CPU ☠️
spin_unlock(&my_spinlock); // 解锁可能被延迟
#endif
return 0;
}
/* 文件操作结构体 */
static struct file_operations fops = {
.owner = THIS_MODULE, // 防止模块被卸载时设备正在使用
.open = device_open, // 绑定打开设备时的回调
};
/* 模块初始化函数 */
static int __init spinlock_init(void) {
spin_lock_init(&my_spinlock); // 初始化自旋锁 🔄
major = register_chrdev(0, DEVICE_NAME, &fops); // 动态分配主设备号
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0)
// 旧版内核:传递 THIS_MODULE
cls = class_create(THIS_MODULE, DEVICE_NAME);//创建设备类
#else
// 新版内核:仅传递类名称
cls = class_create(DEVICE_NAME);//创建设备类
#endif
device_create(cls, NULL, MKDEV(major,0), NULL, DEVICE_NAME); // 创建设备节点
printk("Device initialized\n");
return 0;
}
/* 模块清理函数 */
static void __exit spinlock_exit(void) {
device_destroy(cls, MKDEV(major, 0)); // 销毁设备
class_destroy(cls); // 销毁类
unregister_chrdev(major, DEVICE_NAME); // 注销设备
printk("Module unloaded\n");
}
module_init(spinlock_init);
module_exit(spinlock_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Spinlock Deadlock Demo");
二、用户态测试程序 (test_deadlock.c
) 🔍
c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd1, fd2;
// 第一次打开设备(触发死锁)☠️
fd1 = open("/dev/deadlock_dev", O_RDWR);
if (fd1 < 0) {
perror("First open failed"); // ❌ 权限问题或模块未加载
return -1;
}
printf("First open success\n"); // ✅ 成功进入临界区
// 第二次打开设备(验证锁状态)🔒
fd2 = open("/dev/deadlock_dev", O_RDWR);
if (fd2 < 0) {
perror("Second open failed"); // ⚠️ 预期中的失败(锁未释放)
} else {
printf("Second open success\n"); // ❌ 永远不会执行
close(fd2);
}
close(fd1); // 理论上不会执行到此处(除场景3)
return 0;
}
测试逻辑说明:
- 第一次
open()
:触发驱动中的device_open
,根据场景宏执行特定死锁代码 ☠️ - 第二次
open()
:验证锁是否被释放- 正常情况:应阻塞或返回错误(
EBUSY
)🚫 - 若成功:说明锁未被正确持有(代码存在漏洞)🐞
- 正常情况:应阻塞或返回错误(
三、编译脚本 (Makefile
) 🔧
makefile
KDIR := /lib/modules/$(shell uname -r)/build # 内核源码路径 📁
obj-m += spinlock_deadlock.o # 构建目标模块 🎯
all:
make -C $(KDIR) M=$(PWD) modules # 编译内核模块 🛠️
gcc -Wall test_deadlock.c -o test_deadlock # 编译测试程序 ⚙️
clean:
make -C $(KDIR) M=$(PWD) clean # 清理模块 🧹
rm -f test_deadlock # 删除测试程序 🗑️
编译步骤:
bash
make # 生成 spinlock_deadlock.ko 和 test_deadlock 📦
sudo insmod spinlock_deadlock.ko # 加载内核模块 ⬆️
dmesg | tail -n 2 # 查看设备初始化日志(应显示 "Device initialized" 📝)
四、场景深度解析 🧩
场景1:递归加锁 🔄
c
spin_lock(&lock);
spin_lock(&lock); // ☠️ 死锁点
- 原理:自旋锁不可重入,第二次加锁时CPU陷入忙等待 ⏳
- 内核表现 :
dmesg
显示BUG: spinlock recursion
🐞- 触发内核错误检测机制(
CONFIG_DEBUG_SPINLOCK
)🔍
场景2:无限循环 ♾️
c
spin_lock(&lock);
while(1) { /* 永不释放 */ }
-
后果 :其他进程在
spin_lock
处死循环 ☠️ -
监控命令 :
bashtop -p $(pidof test_deadlock) # 观察CPU占用率(单核100%)🔥

场景3:长时间操作 ⏰
c
for (int i=0; i<1000000; i++) {
printk("Operation %d\n", i);
}
- 影响 :
- 触发
soft lockup
警告(watchdog
超时)⏳
- 触发
场景4:主动调度 🔄
c
spin_lock(&lock);
schedule(); // ⚠️ 主动让出CPU ☠️
- 危险操作 :
- 新进程可能再次尝试获取同一锁 🔒
- 典型日志:
possible deadlock in do_exit
📜
- 修复方案 :改用
mutex_lock
(允许睡眠)💤

六、最佳实践总结 🏆
-
锁的持有时间 ⏱️
- 自旋锁应保护 纳米级 操作(通常 < 1ms)⚡
- 长时间操作使用
mutex
或semaphore
💤
-
锁的嵌套规则 🔄
- 同一锁不可重复获取 🚫
- 不同锁的获取顺序必须全局一致 🔄
通过这组完整代码和深度分析,开发者可以直观理解自旋锁的误用风险。建议在测试环境中逐步运行每个场景,结合内核日志观察系统行为,这将大幅提升对并发问题的调试能力! 🚀
扩展资源 📚
- 内核锁文档
- 《Linux设备驱动程序》第三章:并发与竞态