技术开发 频道

Linux驱动开发必看:详解神秘内核

  2.5.2 原子操作

  原子操作用于执行轻量级的、仅执行一次的操作,例如修改计数器、有条件的增加值、设置位等。原子操作可以确保操作的串行化,不再需要锁进行并发访问保护。原子操作的具体实现取决于体系架构。

  为了在释放内核网络缓冲区(称为skbuff)之前检查是否还有余留的数据引用,定义于net/core/skbuff.c文件中的skb_release_data()函数将进行如下操作:

1 if (!skb->cloned ||
2   /* Atomically decrement and check if the returned value is zero */
3     !atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 :
4                        1,&skb_shinfo(skb)->dataref)) {
5   /* ... */
6   kfree(skb->head);
7 }

  当skb_release_data()执行的时候,另一个调用skbuff_clone()(也在net/core/skbuff.c文件中定义)的执行单元也许在同步地增加数据引用计数值:

/* ... */
/* Atomically bump up the data reference count */
atomic_inc(
&(skb_shinfo(skb)->dataref));
/* ... */

  原子操作的使用将确保数据引用计数不会被这两个执行单元“蹂躏”。它也消除了使用锁去保护单一整型变量的争论。

  内核也支持set_bit()、clear_bit()和test_and_set_bit()操作,它们可用于原子地位修改。查看include/asm-your-arch/atomic.h文件可以看出你所在体系架构所支持的原子操作。

  2.5.3 读—写锁

  另一个特定的并发保护机制是自旋锁的读—写锁变体。如果每个执行单元在访问临界区的时候要么是读要么是写共享的数据结构,但是它们都不会同时进行读和写操作,那么这种锁是最好的选择。允许多个读线程同时进入临界区。读自旋锁可以这样定义:

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 */

  net/ipx/ipx_route.c中的IPX路由代码是使用读—写锁的真实示例。一个称作ipx_routes_lock的读—写锁将保护IPX路由表的并发访问。要通过查找路由表实现包转发的执行单元需要请求读锁。需要添加和删除路由表中入口的执行单元必须获取写锁。由于通过读路由表的情况比更新路由表的情况多得多,使用读—写锁提高了性能。

  和传统的自旋锁一样,读—写锁也有相应的irq变体:read_lock_irqsave()、read_unlock_ irqrestore()、write_lock_irqsave()和write_unlock_irqrestore()。这些函数的含义与传统自旋锁相应的变体相似。

  2.6内核引入的顺序锁(seqlock)是一种支持写多于读的读—写锁。在一个变量的写操作比读操作多得多的情况下,这种锁非常有用。前文讨论的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的情况很复杂,因此,只有在确保你确实需要使用它而不是前文的其他原语的时候,才适宜选择它。include/linux/ rcupdate.h文件中定义了RCU的数据结构和接口函数,Documentation/RCU/*提供了丰富的文档。

  fs/dcache.c文件中包含一个RCU的使用示例。在Linux中,每个文件都与一个目录入口信息(dentry结构体)、元数据信息(存放在inode中)和实际的数据(存放在数据块中)关联。每次操作一个文件的时候,文件路径中的组件会被解析,相应的dentry会被获取。为了加速未来的操作,dentry结构体被缓存在称为dcache的数据结构中。任何时候,对dcache进行查找的数量都远多于dcache的更新操作,因此,对dcache的访问适宜用RCU原语进行保护。

0
相关文章