《奔跑吧Linux内核(第2版)卷2:调试与案例分析》- 自旋锁的使用与死锁检测
[复制链接]
本帖最后由 maskmoo 于 2024-3-25 09:56 编辑
在 Linux 系统中,死锁是一种常见的系统状态,通常由于内核中的锁竞争而引起。其中,自旋锁是 Linux 内核中一种常用的同步机制,用于保护共享资源免受并发访问的干扰。本文将深入探讨软死锁的根源,分析 Linux 内核中锁竞争问题,并结合实验流程展示自旋锁的使用及可能导致软死锁的错误实践。
Linux自旋锁介绍:
自旋锁是一种轻量级的同步原语,它使用忙等待的方式来保护临界区。在 Linux 内核中,自旋锁通过将处理器的执行状态设置为忙等待的方式来实现,当锁被占用时,线程会一直处于循环中直到锁被释放。这种方式在短期内等待临界区资源时效率较高,但长时间的自旋等待可能会导致系统性能下降。
实验流程总结:
-
QEMU加载 Linux 系统及登录: 通过 QEMU 加载 Linux 系统,在登录后进行实验操作。
-
编译加载实验代码: 编译并加载实验所需的代码,其中包括包含自旋锁的内核模块。
-
加载内核模块并触发软死锁: 加载内核模块后,运行系统,观察软死锁的触发情况。
-
软死锁检测: 通过开启 Lockdep 功能,并重新编译 Linux 内核镜像,以检测和定位软死锁的发生点。
1 启动 QEMU+runninglinuxkernel
cd home/rlk/rlk/runninglinuxkernel_5.0
./run_rlk_arm64.sh run
QEMU加载linux系统登录名: benshushu 密码:123
2 编译加载实验代码
编译实验的参考代码;
cd /mnt/rlk_lab/rlk_basic/chapter_10_lock/lab1
make
3 加载内核模块。
su
sudo insmod spinlock-nest.ko
运行一会系统的 watchdog 触发了 soft lockup ,在Linux系统中,软死锁(soft lockup)是一种系统状态,通常由于内核中的某些部分长时间运行而未能释放CPU而引起的。这可能是由于以下原因导致的软死锁:
-
内核中的无限循环:某些内核代码可能存在逻辑错误或编程错误,导致进入无限循环的状态。这会导致内核的某些部分长时间占用CPU而无法释放,从而导致软死锁。
-
中断处理:软死锁也可能是由于中断处理程序长时间运行而未能释放CPU。如果中断处理程序占用了太多的CPU时间,将导致其他进程无法获得执行时间,从而引发软死锁。
-
内核中的锁竞争:在多线程环境下,如果内核中的某些关键区域没有正确地使用锁来保护,则可能会发生锁竞争,导致某些线程长时间占用CPU而不释放,最终导致软死锁。
-
硬件问题:有时软死锁可能是由于硬件故障或不稳定性引起的。例如,硬件中断无法正确触发或处理,可能会导致中断处理程序长时间运行而引发软死锁。
这里软死锁的原因就是锁竞争的问题导致的,后面可以看下具体的代码实现。
其中用spinlock-nect.c的代码实现如下所示,这段代码是一个简单的Linux内核模块实现在内核中使用自旋锁和内存分配功能。主要逻辑包括:
- 定义了一个静态自旋锁和全局变量,分别用于同步和存储分配的页面。
- 实现了一个nest_lock函数,该函数获取自旋锁并尝试分配内存页,然后释放自旋锁并释放内存页,但存在错误的自旋锁操作。
- 实现了一个lockdep_thread函数,该函数作为内核线程运行,循环调用nest_lock函数。
- 在模块初始化函数中启动了lockdep_thread线程,如果线程创建失败则返回错误。
- 在模块退出函数中停止了lock_thread线程。
static DEFINE_SPINLOCK(hack_spinA);
static struct page *page;
static struct task_struct *lock_thread;
static int nest_lock(void)
{
int order = 5;
spin_lock(&hack_spinA);
page = alloc_pages(GFP_KERNEL, order);
if (!page) {
printk("cannot alloc pages\n");
return -ENOMEM;
}
spin_lock(&hack_spinA);
msleep(10);
__free_pages(page, order);
spin_unlock(&hack_spinA);
spin_unlock(&hack_spinA);
return 0;
}
static int lockdep_thread(void *nothing)
{
set_freezable();
set_user_nice(current, 0);
while (!kthread_should_stop()) {
msleep(10);
nest_lock();
}
return 0;
}
static int __init my_init(void)
{
lock_thread = kthread_run(lockdep_thread, NULL, "lockdep_test");
if (IS_ERR(lock_thread)) {
printk("create kthread fail\n");
return PTR_ERR(lock_thread);
}
return 0;
}
static void __exit my_exit(void)
{
kthread_stop(lock_thread);
}
MODULE_LICENSE("GPL");
module_init(my_init);
module_exit(my_exit);
3 死锁检测
死锁的检测依赖 Lockdep 功能,需要打开 CONFIG_DEBUG_LOCKDEP 选项。所以需要修改并重新编译运行在qemu中的linux镜像给。
回到Ubuntu主机修改linux内核配置文件 arch/arm64/configs/debian _defconfig.增加如下配置
cd /home/rlk/rlk/runninglinuxkernel_5.0/arch/arm64/configs/debian_defconfig
CONFIG_PROVE_LOCKING=y
CONFIG_LOCKDEP=y
CONFIG_LOCK_STAT=y
CONFIG_DEBUG_LOCKDEP=y
sudo ./run_debian_arm64.sh build_kernel
sudo ./run_rlk_arm64.sh update_rootfs
完成后重新启动qemu虚拟机并编译加载测试程序(我这边环境更新rootfs没有成功,遇到各种奇怪的问题。后来是从新拉了下库解决的)
git clone https://e.coding.net/benshushu/runninglinuxkernel_5.0/runninglinuxkernel_5.0.git
内核日志可以看到死锁发生的路径和发生时的函数调用的栈信息
总结:
通过本实验了解自旋锁的特性和适用场景对于避免软死锁等系统性能问题至关重要。希望本文能够为读者提供理解和解决软死锁问题的一些思路和方法。
|