这是来自Essential Linux Device Drivers的直接引用,这可能是您正在寻找的。看起来最后处理 RCU 的部分可能是你感兴趣的。
读写锁
另一种专门的并发调节机制是自旋锁的读写器变体。如果临界区的使用使得单独的线程可以读取或写入共享数据结构,但不要同时执行这两种操作,那么这些锁很自然。允许同时在一个关键区域内有多个读取器线程。阅读器自旋锁定义如下:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
read_lock(&myrwlock); /* Acquire reader lock */
/* ... Critical Region ... */
read_unlock(&myrwlock); /* Release lock */
但是,如果一个写线程进入临界区,其他读线程或写线程就不能进入。要使用写入器自旋锁,您可以这样写:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
write_lock(&myrwlock); /* Acquire writer lock */
/* ... Critical Region ... */
write_unlock(&myrwlock); /* Release lock */
查看中的 IPX 路由代码,net/ipx/ipx_route.c
了解读写器自旋锁的真实示例。称为读写锁的读写器ipx_routes_lock
保护 IPX 路由表不被同时访问。需要查找路由表以转发数据包的线程请求读取器锁。需要从路由表中添加或删除条目的线程获取写入器锁。这提高了性能,因为路由表查找的实例通常比路由表更新的实例多得多。
像普通的自旋锁一样,读写锁也有相应的 irq 变体——即 , read_lock_irqsave()
,
read_lock_irqrestore()
,write_lock_irqsave()
和write_lock_irqrestore()
. 这些函数的语义类似于常规自旋锁的语义。
在 2.6 内核中引入的序列锁或 seqlocks 是读写器锁,其中写入器优先于读取器。如果对变量的写操作远多于读访问,这很有用。jiffies_64
本章前面讨论的变量就是一个例子
。编写器线程不会等待可能在临界区中的读者。因此,阅读器线程可能会发现它们在临界区中的条目已失败并且可能需要重试:
u64 get_jiffies_64(void) /* Defined in kernel/time.c */
{
unsigned long seq;
u64 ret;
do {
seq = read_seqbegin(&xtime_lock);
ret = jiffies_64;
} while (read_seqretry(&xtime_lock, seq));
return ret;
}
write_seqlock()
编写器使用和保护关键区域write_sequnlock()
。
2.6 内核引入了另一种称为读取复制更新 (RCU) 的机制,当读取器的数量远远超过写入器时,它会提高性能。基本思想是阅读器线程可以在没有锁定的情况下执行。编写器线程更复杂。它们对数据结构的副本执行更新操作并替换读者看到的指针。原始副本会一直保留到所有 CPU 上的下一次上下文切换,以确保完成所有正在进行的读取操作。请注意,使用 RCU 比使用迄今为止讨论的原语更复杂,并且只有在您确定它是适合该工作的工具时才应该使用它。RCU 数据结构和接口函数在include/linux/rcupdate.h
. 中有充足的文档
Documentation/RCU/*
。
有关 RCU 使用示例,请查看fs/dcache.c
. 在 Linux 上,每个文件都与目录条目信息(存储在名为 dentry 的结构中)、元数据信息(存储在 inode 中)和实际数据(存储在数据块中)相关联。每次对文件进行操作时,都会解析文件路径中的组件,获取对应的dentries。dentry 被缓存在称为 dcache 的数据结构中,以加快未来的操作。在任何时候,dcache 查找的数量都远远多于 dcache 更新,因此对 dcache 的引用使用 RCU 原语进行保护。