当对某个数据结构的操作可以像这样被划分为读/写两种类别时,类似读/写锁这样的机制就很有用了。Linux的读写自旋锁为读和写分别提供了不同的锁,一个或多个读任务可以并发的持有读者锁;用于写的锁最多只能被一个写任务持有,而且此时不能有并发的读操作。有时候读写锁叫做共享排斥锁,或者并发排斥锁,因为这种锁以共享(对读者而言)和排斥(对写着而言)的形式获得使用。
- /*
- * Read-write spinlocks, allowing multiple readers
- * but only one writer.
- */
- 在<Spinlock_types.h(include/linux)>中
- typedef struct {
- raw_rwlock_t raw_lock;
- #if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
- 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;
- 在<Spinlock.h(include/linux)>
- #define write_lock(lock) _write_lock(lock)
- #define read_lock(lock) _read_lock(lock)
- 在<Spinlock_api_up.>
- #define _read_lock(lock) __LOCK(lock)
- #define _write_lock(lock) __LOCK(lock)
- 在<hSpinlock.c(kernel)>
- void __lockfunc _write_lock(rwlock_t *lock)
- {
- preempt_disable();
- rwlock_acquire(lock->dep_map, 0, 0, _RET_IP_);
- _raw_write_lock(lock);
- }
通常情况下,读锁和写锁会位于完全分割开的代码分支中。注意,不能把一个读锁“升级”为写锁。如下代码:
- rwlock_t mr_rwlock = RW_LOCK_UNLOCK;
- read_lock(&mr_rwlock);
- write_lock(&mr_rwlock);
将会带来死锁。因为写锁会不断的自旋,等待所有的读者释放锁,其中也包括它自己。所以当确实需要写操作时,要一开始就请求写锁。如果写和读不能清晰地分开的话,那么使用一般的自旋锁就行了,不要使用读写自旋锁。
多个读者可以安全地获得同一个读锁,即使一个线程递归的获得同一个读锁也是安全的,这个特性是读写自旋锁真正成为一种有用并且常用的优化手段。如果中断处理程序中只有读操作而没有写操作,那么就可以混合使用“中断禁止”锁,使用read_lock()而不是read_lock_irqsave()对读进行保护。不过,还是需要write_lock_irqsave()禁止所有写操作的中断,否则,中断里的读操作就有可能锁死在写锁上。
读写自旋锁的方法:
- #ifdef CONFIG_DEBUG_SPINLOCK
- extern void __spin_lock_init(spinlock_t *lock, const char *name,
- struct lock_class_key *key);
- # define spin_lock_init(lock) /
- do { /
- static struct lock_class_key __key; /
- /
- __spin_lock_init((lock), #lock, &__key); /
- } while (0)
- #else
- # define spin_lock_init(lock) /
- do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)
- #endif
- #ifdef CONFIG_DEBUG_SPINLOCK
- extern void __rwlock_init(rwlock_t *lock, const char *name,
- struct lock_class_key *key);
- # define rwlock_init(lock) /
- do { /
- static struct lock_class_key __key; /
- /
- __rwlock_init((lock), #lock, &__key); /
- } while (0)
- #else
- # define rwlock_init(lock) /
- do { *(lock) = RW_LOCK_UNLOCKED; } while (0)
- #endif
- #define spin_is_locked(lock) __raw_spin_is_locked(&(lock)->raw_lock)
- /**
- * spin_unlock_wait - wait until the spinlock gets unlocked
- * @lock: the spinlock in question.
- */
- #define spin_unlock_wait(lock) __raw_spin_unlock_wait(&(lock)->raw_lock)
- #define read_trylock(lock) __cond_lock(lock, _read_trylock(lock))
- #define write_trylock(lock) __cond_lock(lock, _write_trylock(lock))
- #if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
- #define read_lock_irqsave(lock, flags) flags = _read_lock_irqsave(lock)
- #define write_lock_irqsave(lock, flags) flags = _write_lock_irqsave(lock)
- #else
- #define read_lock_irqsave(lock, flags) _read_lock_irqsave(lock, flags)
- #define write_lock_irqsave(lock, flags) _write_lock_irqsave(lock, flags)
- #endif
- #define read_lock_irq(lock) _read_lock_irq(lock)
- #define read_lock_bh(lock) _read_lock_bh(lock)
- #define write_lock_irq(lock) _write_lock_irq(lock)
- #define write_lock_bh(lock) _write_lock_bh(lock)
- #define read_lock_irq(lock) _read_lock_irq(lock)
- #define read_lock_bh(lock) _read_lock_bh(lock)
- #define write_lock_irq(lock) _write_lock_irq(lock)
- #define write_lock_bh(lock) _write_lock_bh(lock)
- /*
- * We inline the unlock functions in the nondebug case:
- */
- #if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /
- !defined(CONFIG_SMP)
- # define read_unlock(lock) _read_unlock(lock)
- # define write_unlock(lock) _write_unlock(lock)
- # define spin_unlock_irq(lock) _spin_unlock_irq(lock)
- # define read_unlock_irq(lock) _read_unlock_irq(lock)
- # define write_unlock_irq(lock) _write_unlock_irq(lock)
- #else
- # define read_unlock(lock) /
- do {__raw_read_unlock(&(lock)->raw_lock); __release(lock); } while (0)
- # define write_unlock(lock) /
- do {__raw_write_unlock(&(lock)->raw_lock); __release(lock); } while (0)
- # define spin_unlock_irq(lock) /
- do { /
- __raw_spin_unlock(&(lock)->raw_lock); /
- __release(lock); /
- local_irq_enable(); /
- } while (0)
- # define read_unlock_irq(lock) /
- do { /
- __raw_read_unlock(&(lock)->raw_lock); /
- __release(lock); /
- local_irq_enable(); /
- } while (0)
- # define write_unlock_irq(lock) /
- do { /
- __raw_write_unlock(&(lock)->raw_lock); /
- __release(lock); /
- local_irq_enable(); /
- } while (0)
- #endif
- #define spin_unlock_irqrestore(lock, flags) /
- _spin_unlock_irqrestore(lock, flags)
- #define spin_unlock_bh(lock) _spin_unlock_bh(lock)
- #define read_unlock_irqrestore(lock, flags) /
- _read_unlock_irqrestore(lock, flags)
- #define read_unlock_bh(lock) _read_unlock_bh(lock)
- #define write_unlock_irqrestore(lock, flags) /
- _write_unlock_irqrestore(lock, flags)
- #define write_unlock_bh(lock) _write_unlock_bh(lock)
在使用Linux读写自旋锁时,最后要考虑的一点是这种机制照顾读比照顾写多。当读锁被持有时,写操作为了互斥访问而只能等待;但是,读者却可以继续成功地占用锁。自旋等待的写者在所有读者释放锁之前是无法获得锁的。
如果加锁时间不长且代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择。