10 内核开发-避免冲突和死锁-读写锁
课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。
课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。
2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。
3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。
4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。
无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。
这一讲,主要分享如何在内核开模块开发中如何使用 读写锁避免冲突和死锁。
1.定义
在 Linux 内核中,读写锁是一种同步原语,用于协调对共享资源的并发访问。它允许多个线程或进程同时获取读锁以读取共享资源,或者允许单个线程或进程获取写锁以独占访问并修改共享资源。
读写锁通常用于保护内核中的数据结构,这些数据结构可能同时被多个线程或进程访问和修改。例如,文件系统代码使用读写锁来保护文件系统中的数据结构,如 inode 和 dentry。
2.内涵
读写锁含义
读锁:允许多个线程或进程同时获取,以读取共享资源。当持有读锁时,其他线程或进程也可以获取读锁,但不能获取写锁。
写锁:允许单个线程或进程独占获取,以修改共享资源。当持有写锁时,其他线程或进程都不能获取读锁或写锁。
读写锁的优点
- 提高并发性:读写锁允许多个线程或进程同时读取共享资源,从而提高并发性。
- 防止数据竞争:读写锁通过防止多个线程或进程同时写入共享资源来防止数据竞争。
- 提高性能:与互斥锁相比,读写锁在读操作比写操作更频繁的情况下可以提高性能,因为多个线程或进程可以同时获取读锁。
读写锁的缺点
- 优先级反转:读写锁可能会导致优先级反转,其中低优先级线程或进程持有写锁,而高优先级线程或进程等待读锁。
- 死锁:如果线程或进程不正确地使用读写锁,可能会导致死锁。
IRQ 安全锁是一种可以在中断上下文中安全获取和释放的锁。内核读写锁API write_lock_irqsave()、write_unlock_irqrestore()、read_lock_irqsave() 和 read_unlock_irqrestore 函数是 Linux 内核提供的 IRQ 安全锁。
3.使用示例
(a)write_lock_irqsave
void write_lock_irqsave(struct rw_semaphore *lock, unsigned long *flags);
描述:
获取给定读写信号量的写锁,并保存当前的中断标志。
参数:
lock: 要获取写锁的读写信号量。
flags: 保存当前中断标志的地址。
返回值:
无。
行为:
这个函数原子地获取给定读写信号量的写锁。它首先保存当前的中断标志到 flags 参数中,然后禁用中断,最后获取写锁。
注意:
必须使用 write_unlock_irqrestore() 函数来释放写锁。
(b)write_unlock_irqrestore
void write_unlock_irqrestore(struct rw_semaphore *lock, unsigned long *flags);
描述:
释放给定读写信号量的写锁,并恢复之前保存的中断标志。
参数:
lock: 要释放写锁的读写信号量。
flags: 之前保存的中断标志。
返回值:
无。
行为:
这个函数原子地释放给定读写信号量的写锁。它首先恢复之前保存的中断标志,然后释放写锁。
注意:
这个函数必须与 write_lock_irqsave() 函数配对使用。
(c)read_lock_irqsave
void read_lock_irqsave(struct rw_semaphore *lock, unsigned long *flags);
描述:
获取给定读写信号量的读锁,并保存当前的中断标志。
参数:
lock: 要获取读锁的读写信号量。
flags: 保存当前中断标志的地址。
返回值:
无。
行为:
这个函数原子地获取给定读写信号量的读锁。它首先保存当前的中断标志到 flags 参数中,然后禁用中断,最后获取读锁。
注意:
必须使用 read_unlock_irqrestore() 函数来释放读锁。
(d)read_unlock_irqrestore
void read_unlock_irqrestore(struct rw_semaphore *lock, unsigned long *flags);
描述:
释放给定读写信号量的读锁,并恢复之前保存的中断标志。
参数:
lock: 要释放读锁的读写信号量。
flags: 之前保存的中断标志。
返回值:
无。
行为:
这个函数原子地释放给定读写信号量的读锁。它首先恢复之前保存的中断标志,然后释放读锁。
注意:
这个函数必须与 read_lock_irqsave() 函数配对使用。
IRQ 安全性
这四个函数都是 IRQ 安全的,这意味着它们可以在中断上下文中安全地调用。它们禁用中断以确保原子操作,然后在释放锁时恢复中断。
用法示例
以下示例展示了如何使用这些函数来保护一个共享数据结构:
struct rw_semaphore lock;
void write_to_data(struct data *data)
{
unsigned long flags;write_lock_irqsave(&lock, &flags);
// 写入数据
write_unlock_irqrestore(&lock, &flags);
}int read_from_data(struct data *data)
{
unsigned long flags;
int value;read_lock_irqsave(&lock, &flags);
// 读取数据
value = data->value;
read_unlock_irqrestore(&lock, &flags);
return value;
}
在这个示例中,write_to_data() 函数使用写锁保护对 data 结构的写入操作,而 read_from_data() 函数使用读锁保护对 data 结构的读取操作。
4.具体代码使用实践
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/rwlock.h>
static DEFINE_RWLOCK(myrwlock);
static void example_read_lock(void)
{
unsigned long flags;
read_lock_irqsave(&myrwlock, flags);
pr_info("Read Locked\n");
/* Read from something */
read_unlock_irqrestore(&myrwlock, flags);
pr_info("Read Unlocked\n");
}
static void example_write_lock(void)
{
unsigned long flags;
write_lock_irqsave(&myrwlock, flags);
pr_info("Write Locked\n");
/* Write to something */
write_unlock_irqrestore(&myrwlock, flags);
pr_info("Write Unlocked\n");
}
static int __init example_rwlock_init(void)
{
pr_info("example_rwlock started\n");
example_read_lock();
example_write_lock();
return 0;
}
static void __exit example_rwlock_exit(void)
{
pr_info("example_rwlock exit\n");
}
module_init(example_rwlock_init);
module_exit(example_rwlock_exit);
MODULE_DESCRIPTION("Read/Write locks example");
MODULE_LICENSE("GPL");
5.注意事项
- 不要在中断处理程序中休眠:在持有 IRQ 安全锁时不得休眠。这可能会导致死锁或系统不稳定。
- 避免长时间持有锁:长时间持有锁会降低并发性并导致性能问题。
- 注意死锁:不正确地使用锁可能会导致死锁。始终确保以正确的顺序获取和释放锁。
- 小心优先级反转:优先级反转可能是一个问题,尤其是在嵌套锁的情况下。
其他注意事项:
- 这四个函数都禁用中断。在某些情况下,这可能会导致性能下降。
- 如果可能,应使用非 IRQ 安全的锁,因为它们通常具有更好的性能。
Linux 内核还提供了其他类型的锁,例如自旋锁和读者-写者自旋锁。这些锁在某些情况下可能更合适。
遵循这些注意事项可以帮助您更有效和安全地使用 write_lock_irqsave()、write_unlock_irqrestore()、read_lock_irqsave() 和 read_unlock_irqrestore() 函数。
6.最佳实践
- 仅在需要时使用 IRQ 安全锁:只有在需要在中断上下文中保护共享数据时才使用 write_lock_irqsave()、write_unlock_irqrestore()、read_lock_irqsave() 和 read_unlock_irqrestore() 函数。在非中断上下文中,应使用非 IRQ 安全的锁(如 write_lock() 和 read_lock())。
- 尽可能缩小临界区:在持有锁时,应将代码保持在最小限度。这将最大限度地减少持有锁的时间,从而提高并发性。
- 避免嵌套锁:嵌套锁可能会导致死锁。如果需要在同一代码路径中获取多个锁,请尝试使用分层锁定策略。
- 正确地配对锁调用:始终确保使用 write_unlock_irqrestore() 函数释放 write_lock_irqsave() 获取的写锁,并使用 read_unlock_irqrestore() 函数释放 read_lock_irqsave() 获取的读锁。
- 考虑优先级反转:如果高优先级线程持有读锁,而低优先级线程持有写锁,可能会发生优先级反转。为了缓解这种情况,可以考虑使用自旋锁或读者-写者自旋锁。
7.总结
正确使用 write_lock_irqsave(), write_unlock_irqrestore(), read_lock_irqsave() 和 read_unlock_irqrestore()的最佳实践及注意事项。
最佳实践 | 注意事项 | |
读写锁 | 仅在需要在中断上下文中保护共享数据时使用这些函数。
|
不要在中断处理程序中休眠。
|