3. 读写锁API详解:rwlock_t、read_lock()、write_lock()、read_unlock()、write_unlock()
读写锁(rwlock)是Linux内核中一种重要的同步机制,它允许多个读者同时访问共享资源,但写者必须独占访问。本章节将深入剖析读写锁的核心API,包括数据结构定义、锁操作函数以及使用注意事项,帮助你在内核编程中正确、高效地使用读写锁。
3.1 核心数据结构:rwlock_t
rwlock_t 是读写锁的类型定义,在 <linux/rwlock.h> 中声明。它是一个结构体,内部维护了锁的状态信息,用于区分当前是读锁定还是写锁定状态。
// 定义并初始化一个读写锁
rwlock_t my_rwlock = __RW_LOCK_UNLOCKED(my_rwlock);
// 或者使用宏在运行时初始化
rwlock_t my_rwlock;
rwlock_init(&my_rwlock);
关键点:
rwlock_t 是一个轻量级的锁结构,不包含等待队列信息。当写者无法获取锁时,它会自旋等待,因此读写锁适用于临界区执行时间较短的场景。
3.2 读锁操作:read_lock() / read_unlock()
读锁用于保护共享资源的读取操作。多个读者可以同时持有读锁,不会相互阻塞。读锁的获取和释放必须成对出现。
read_lock(&my_rwlock);
// 临界区:读取共享数据
// 例如:int val = shared_data;
read_unlock(&my_rwlock);
读锁行为特征:
- 并发性: 任意数量的读者可以同时持有读锁。
- 写者阻塞: 只要有读者持有锁,写者就无法获取写锁,必须自旋等待。
- 不可递归: 同一个执行上下文不能递归获取读锁,否则可能导致死锁。
3.3 写锁操作:write_lock() / write_unlock()
写锁用于保护共享资源的修改操作。写者必须独占锁,即写锁与任何其他读锁或写锁都互斥。
write_lock(&my_rwlock);
// 临界区:修改共享数据
// 例如:shared_data = new_value;
write_unlock(&my_rwlock);
写锁注意事项:
- 独占性: 写锁获取时,必须等待所有读者释放读锁,且后续读者会被阻塞。
- 优先级反转: 如果读者持续持有锁,写者可能长时间无法获取锁,导致写者饥饿。
- 不可在中断上下文使用: 标准
write_lock()会调用spin_lock(),可能引起睡眠,不适合中断上下文。
3.4 读写锁操作流程与状态转换
下面的SVG图展示了读写锁的核心状态转换与操作流程,帮助理解读者和写者之间的互斥关系。
3.5 读写锁API对比总结
下表汇总了读写锁核心API的功能、使用场景和注意事项,方便快速查阅。
| API函数 | 功能描述 | 使用场景 | 注意事项 |
|---|---|---|---|
read_lock() |
获取读锁,允许多个读者并发 | 读取共享数据,不修改 | 不可递归;不可在中断上下文使用 |
read_unlock() |
释放读锁 | 与 read_lock() 配对 |
必须在同一执行上下文中调用 |
write_lock() |
获取写锁,独占访问 | 修改共享数据 | 可能引起写者饥饿;不可在中断上下文使用 |
write_unlock() |
释放写锁 | 与 write_lock() 配对 |
必须在同一执行上下文中调用 |
rwlock_init() |
动态初始化读写锁 | 运行时初始化锁变量 | 确保在使用前调用 |
3.6 读写锁使用示例:保护全局计数器
以下代码展示了如何使用读写锁保护一个全局计数器,实现多读者并发读取、写者独占更新的模式。
#include <linux/rwlock.h>
static rwlock_t counter_lock = __RW_LOCK_UNLOCKED(counter_lock);
static int global_counter = 0;
// 读者函数:读取计数器值
int read_counter(void)
{
int val;
read_lock(&counter_lock);
val = global_counter;
read_unlock(&counter_lock);
return val;
}
// 写者函数:更新计数器值
void write_counter(int new_val)
{
write_lock(&counter_lock);
global_counter = new_val;
write_unlock(&counter_lock);
}
性能提示: 读写锁在读多写少的场景下性能优异,因为读者之间不会相互阻塞。但如果写操作频繁,写者可能长时间无法获取锁,导致性能下降。此时可以考虑使用RCU(Read-Copy Update)机制作为替代方案。
3.7 常见错误与避坑指南
- 忘记释放锁: 在错误路径或提前返回时未调用
read_unlock()或write_unlock(),导致死锁。 - 递归获取读锁: 同一个执行上下文再次调用
read_lock(),可能引起自旋死锁。 - 在中断上下文使用标准读写锁: 标准读写锁会调用
spin_lock(),可能引起睡眠。应使用read_lock_irqsave()等变体。 - 读者持有锁时间过长: 导致写者饥饿,影响系统整体性能。
中断上下文专用API: 如果需要在中断处理程序中使用读写锁,请使用
read_lock_irqsave() / read_unlock_irqrestore() 和 write_lock_irqsave() / write_unlock_irqrestore() 变体,它们会保存和恢复中断状态,避免死锁。
通过本章节的学习,你应该已经掌握了读写锁的核心API及其使用方法。在实际内核编程中,请根据共享资源的访问模式(读多写少 vs 写多读少)和上下文环境(进程上下文 vs 中断上下文)选择合适的锁机制。