Skip to content

Latest commit

 

History

History
97 lines (70 loc) · 4.96 KB

2014-05-03-read-and-write-spin-lock.md

File metadata and controls

97 lines (70 loc) · 4.96 KB
layout title category description tags
post
读/写自旋锁
内核同步
读/写自旋锁...
自旋锁

读/写自旋锁的引入是为了增加内核的并发能力,只要没有内核控制路径对数据结构进行修改,读/写自旋锁就允许多个内核控制路径同时读一个数据结构,如果一个内核控制路径想对这个数据结构进行操作,那么它必须首先获取读/写自旋锁的写锁,写锁的授权独占访问这个资源。当然,允许对数据结构的并发可以提高系统性能。

每个读/写自旋锁都是一个rwlock_t结构,代码如下:

<include/linux/spinlock_types.h>

{% highlight c++ %} typedef struct { raw_rwlock_t raw_lock; #ifdef CONFIG_GENERIC_LOCKBREAK unsigned int break_lock; #endif #ifdef CONFIG_DEBUG_SPINLOCK unsigned int magic, owner_cpu; void *owner; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif } rwlock_t; {% endhighlight %}

自旋锁的实现是体系结构相关的,所以我们可以看x86里面自旋锁的结构,从上面的结构体中我们可以看到有一个raw_rwlock_t的结构体对象raw_lock,我们可以看看在arch/x86中的raw_rwlock_t对象。

<arch/x86/include/asm/spinlock_types.h>

{% highlight c++ %} typedef struct { unsigned int lock; } raw_rwlock_t; {% endhighlight %}

我们可以看到,这个结构体里就就只有一个lock字段。

lock字段是一个32位的字段,分为两个不同的部分:

  1. 24位计数器,表示对受保护的数据结构并发地进行读操作和内核控制路径的数目,这个计数器的二进制补码存放在这个字段的0~23位。
  2. 『未锁』标志字段,当没有内核控制路径在读或者写时设置这个位,否则就清0。这个『未锁』标志存放在lock字段的第24位。

如果自旋锁为空,那么lock字段的值位0x010000000,如果一个两个或者多个进程因为读获取了自旋锁,那么lock字段的值位0x00ffffff,0x00fffffe等。与spinlock_t结构一样,rwlock_t结构也包含break_lock字段。

rwlock_init宏把读/写自旋锁的lock字段初始化位未锁的状态,把break_lock初始化为0。

为读获取和释放一个锁

read_lock宏作用于读/写自旋锁的地址rwlp,与spin_lock宏非常相似,如果编译内核时选择了内核抢占选项,read_lock宏执行与*spin_lock()非常相似的操作,只是有一点不同,该宏执行了__raw_read_trylock()*函数获得读/写自旋锁。

<arch/x86/include/asm/spinlock.h>

{% highlight c++ %} static inline int __raw_read_trylock(raw_rwlock_t *lock) { atomic_t *count = (atomic_t *)lock;

if (atomic_dec_return(count) >= 0)
    return 1;
atomic_inc(count);
return 0;

} {% endhighlight %}

读/写锁计数器lock字段时通过原子操作来访问的,尽管如此,但整个函数对计数器的操作并不是原子性的。例如,在用if语句完成对计数器的值的测试之后并返回1之前,计数器的值可能发生变化。不过函数能够正常工作。

实际上,只有在递减之前计数器的值不为0或者负数的情况下,函数才返回1,因为计数器等于0x01000000表示没有任何进程占用锁,等于0x00ffffff表示有一个读者,而等于0x00000000表示有一个写者。

read_lock宏原子地把自旋锁的值减去1,以此增加读者的个数,如果函数递减操作产生一个非复制,就获得自旋锁,否则就调用*__read_lock_failed()函数,这个函数原子地增加lock字段以取消由readl_lock宏执行的递减操作,然后循环,直到lock字段变为正数。接下来,__read_lock_failed()*又试图获得自旋锁。

解锁的过程相当简单,使用read_unlock宏只是简单的增加了lock字段的计数器以减少读者的计数,然后调用*preempt_enable()*重新启用内核抢占。

为写获取和释放一个锁

write_lock宏的实现方式与*spin_lock()read_lock()相似,例如,如果支持内核抢占,则该函数禁用内核抢占并通过__raw_write_trylock()*立即获得锁,如果该函数返回0,则说明锁已经被占用,然后该宏就重新启用内核抢占并开始等待循环。

<arch/x86/include/asm/spinlock.h>

{% highlight c++ %} static inline int __raw_write_trylock(raw_rwlock_t *lock) { atomic_t *count = (atomic_t *)lock;

if (atomic_sub_and_test(RW_LOCK_BIAS, count))
    return 1;
atomic_add(RW_LOCK_BIAS, count);
return 0;

} {% endhighlight %}

函数*__raw_write_trylock()*从读/写自旋锁中减去0x01000000,从而清除未上锁标志,如果减操作产生0,说明没有读者,则获取锁并返回1,否则,函数原子地在自旋锁上加0x01000000以取消减操作。

释放锁同样就暗淡,使用write_unlock宏即可,将相应地位标记为未锁状态,然后再调用*preempt_enable()*重新启用内核抢占。