08 内核开发-避免冲突和死锁-spinlock
课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。
课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。
2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。
3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。
4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。
无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。
这一讲,主要分享如何在内核开模块开发中如何使用 spinlock 避免冲突和死锁。
1.定义
自旋锁是一种轻量级锁,用于保护临界区,即必须互斥访问的代码段。它们通过忙循环的方式工作,这意味着当锁被获取时,它会一直旋转,直到锁被释放。
2.内涵
自旋锁通过原子指令来操作一个标志位来实现。当一个线程需要获取锁时,它会不断检查标志位,直到它变为可用状态。一旦标志位变为可用,线程会立即设置标志位以指示它已获取锁。
与互斥锁的区别,与互斥锁相比,自旋锁具有以下特点:
- 更轻量级:自旋锁不会引起线程阻塞,因为它只进行忙循环而不是进入睡眠状态。
- 更快的争用:在低争用的情况下,自旋锁比互斥锁更有效率。
然而,自旋锁也有以下缺点:
- CPU 消耗:在高争用的情况下,自旋锁会导致 CPU 消耗过大,因为线程会不断进行忙循环。
- 不公平:自旋锁可能会导致优先级较低的线程无限期地等待,因为优先级较高的线程会在忙循环中反复获取锁。
3.使用示例
static spinlock_t sl_dynamic; #定义
spin_lock_init()
spin_lock_irqsave():获取自旋锁。
spin_unlock_irqrestore():释放自旋锁。
spin_lock_init()
功能:初始化一个自旋锁。
参数:
lock:要初始化的自旋锁。
描述:
spin_lock_init() 初始化自旋锁 lock 为未锁定的状态。它必须在使用自旋锁之前调用。
spin_lock_irqsave()
功能:获取自旋锁并禁用中断。
参数:
lock:要获取的自旋锁。
flags:一个指针,用于存储被禁用的中断标志。
描述:
spin_lock_irqsave() 获取自旋锁 lock 并禁用中断。它在获取锁之前将当前的中断标志存储在 flags 中。这确保了在临界区内不会发生中断。
spin_unlock_irqrestore()
功能:释放自旋锁并恢复中断。
参数:
lock:要释放的自旋锁。
flags:先前由 spin_lock_irqsave() 存储的中断标志。
描述:
spin_unlock_irqrestore() 释放自旋锁 lock 并恢复先前由 spin_lock_irqsave() 禁用的中断。它使用 flags 参数来恢复中断标志。
某个使用使用场景
spin_lock_irqsave() 和 spin_unlock_irqrestore() 通常一起使用来保护临界区,其中需要禁用中断以确保独占访问。示例以下是一个使用 spin_lock_irqsave() 和 spin_unlock_irqrestore() 来保护共享计数器的示例:
static DEFINE_SPINLOCK(my_spinlock);
static int shared_counter;void increment_counter(void)
{
unsigned long flags;// 获取自旋锁并禁用中断
spin_lock_irqsave(&my_spinlock, flags);// 在临界区内执行操作
shared_counter++;// 释放自旋锁并恢复中断
spin_unlock_irqrestore(&my_spinlock, flags);
}
此示例使用 spin_lock_irqsave() 获取自旋锁并禁用中断,然后在临界区内递增 shared_counter。
然后它使用 spin_unlock_irqrestore() 释放自旋锁并恢复中断。
4.具体代码实践
/****** hello.c ******/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(sl_static);
static spinlock_t sl_dynamic;
static void example_spinlock_static(void)
{
unsigned long flags;
spin_lock_irqsave(&sl_static, flags);
pr_info("Locked static spinlock\n");
/* Do something or other safely. Because this uses 100% CPU time, this
* code should take no more than a few milliseconds to run.
* 适合时间不太长的任务
*/
spin_unlock_irqrestore(&sl_static, flags);
pr_info("Unlocked static spinlock\n");
}
static void example_spinlock_dynamic(void)
{
unsigned long flags;
spin_lock_init(&sl_dynamic);
spin_lock_irqsave(&sl_dynamic, flags);
pr_info("Locked dynamic spinlock\n");
/* Do something or other safely. Because this uses 100% CPU time, this
* code should take no more than a few milliseconds to run.
* 适合时间不太长的任务
*/
spin_unlock_irqrestore(&sl_dynamic, flags);
pr_info("Unlocked dynamic spinlock\n");
}
static int __init example_spinlock_init(void)
{
pr_info("demo spinlock started\n");
example_spinlock_static();
example_spinlock_dynamic();
return 0;
}
static void __exit example_spinlock_exit(void)
{
pr_info("demo spinlock exit\n");
}
module_init(example_spinlock_init);
module_exit(example_spinlock_exit);
MODULE_DESCRIPTION("Spinlock demo");
MODULE_LICENSE("GPL");
使用 make编译,sudo insmod hello.ko 安装模块后, 通过dmesg 查看日这日志信息
5.注意事项
Linux 内核中自旋锁 spinlock 的注意事项:
- 避免长时间持有自旋锁:自旋锁在持有时会消耗大量 CPU,因此应避免在临界区内进行长时间操作。
- 避免嵌套自旋锁:在持有自旋锁时获取另一个自旋锁可能会导致死锁。
- 优先使用自旋锁来保护小而经常被访问的临界区:自旋锁在争用率低时非常有效,但争用率高时效率会下降。
- 考虑使用其他同步原语,例如互斥锁或读写锁,来保护大或不频繁访问的临界区:这些同步原语在高争用情况下比自旋锁更有效率。
其他注意事项:
- 自旋锁在多处理器系统上比在单处理器系统上更有效率,因为线程可以在不同的处理器上同时旋转。
- 使用自旋锁时,应禁用中断,以避免中断处理程序获取锁并导致死锁。
- 自旋锁可以与原子操作结合使用,以实现更精细的同步。
6.最佳实践
- 仅在绝对必要时使用自旋锁:考虑使用其他同步原语,例如互斥锁或读写锁,这些原语在高争用情况下更有效率。
- 保持临界区尽可能小:这将减少自旋锁被持有的时间,从而提高整体性能。
- 避免在自旋锁的临界区内进行阻塞操作:这将导致 CPU 浪费,因为线程会一直旋转而不会释放锁。
- 使用自旋锁来保护共享数据结构时,要小心死锁:确保自旋锁的获取顺序不会导致循环等待。
- 使用自旋锁来保护中断处理程序时,要小心优先级反转:确保自旋锁不会被优先级较低的线程长时间持有,从而导致优先级较高的线程饥饿。
7.总结
自旋锁只在争用率低的情况下比互斥锁更有效率。如果临界区经常被争用,那么互斥锁通常是更好的选择。
优点 | 自缺点 | |
自旋锁 | 低开销:自旋锁比互斥锁和其他同步原语具有更低的开销,因为它们不会导致线程被阻塞。 快速响应:自旋锁具有更快的响应时间,因为它们不会导致线程被阻塞。 | CPU 消耗:如果自旋锁被长时间持有,则会消耗大量 CPU,因为线程会一直旋转而不会释放锁。 优先级反转:如果自旋锁被优先级较低的线程长时间持有,则会导致优先级较高的线程饥饿。 |