linux内核中最常见的锁是自旋锁(spin lock)。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被争用的自旋锁,那么该线程就会一直进行忙循环等待锁重新可用。要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行。在任意时间,自旋锁都可以防止多于一个的执行线程同时进入临界区。
一个被正用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这种行为是自旋锁的特点。自旋锁不应该被长时间持有。持有自旋锁的时间最好小于完成两次上下文切换的的时间。
自旋锁的实现和体系结构密切相关,代码往往使用汇编实现。
因为自旋锁在同一时刻至多被一个执行线程持有,所以一个时刻只能有一个线程位于临界区内,这就为多处理器提供了防止并发访问所需要的包含机制。在单处理器机器上,编译的时候并不会加入自旋锁。
自旋锁不能递归!
自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它会导致睡眠)。在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。注意,需要关闭的只是当前处理器上的中断,因为如果中断发生在不同的处理器上,即使中断处理程序在同一锁上的自旋,也不会妨碍锁的持有者最终释放锁。
下面是相关文件如下:
内核提供的禁止中断同时请求锁的接口:
- 在<Spinlock.h(include/linux)>中
- #if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
- #define spin_lock_irqsave(lock, flags) flags = _spin_lock_irqsave(lock)
- #define read_lock_irqsave(lock, flags) flags = _read_lock_irqsave(lock)
- #define write_lock_irqsave(lock, flags) flags = _write_lock_irqsave(lock)
- #ifdef CONFIG_DEBUG_LOCK_ALLOC
- #define spin_lock_irqsave_nested(lock, flags, subclass) /
- flags = _spin_lock_irqsave_nested(lock, subclass)
- #else
- #define spin_lock_irqsave_nested(lock, flags, subclass) /
- flags = _spin_lock_irqsave(lock)
- #endif
- #else
- #define spin_lock_irqsave(lock, flags) _spin_lock_irqsave(spinlock_t * lock)(lock, flags)
- #define read_lock_irqsave(lock, flags) _read_lock_irqsave(lock, flags)
- #define write_lock_irqsave(lock, flags) _write_lock_irqsave(lock, flags)
- #define spin_lock_irqsave_nested(lock, flags, subclass) /
- spin_lock_irqsave(lock, flags)
- #endif
- #define spin_unlock_irqrestore(lock, flags) /
- _spin_unlock_irqrestore(lock, flags)
函数spin_lock_irqsave()保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁。spin_lock_irqsave()对指定的锁解锁,然后让中断恢复到加锁前的状态。所以即使中断最初是被禁止的,你的代码也不会错误地激活它们,相反,会继续让它们禁止。flag变量应该由数值传递,因为锁函数有些是通过宏的方式实现的。
加锁和解锁分别可以禁止和允许内核抢占。
如果能确定中断在加锁前是激活的,就不需要在解锁后恢复中断之前的状态了。这时可以使用spin_lock_irq()和spin_unlock_irq()。
- #define spin_lock_irq(lock) _spin_lock_irq(lock)
- #if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /
- !defined(CONFIG_SMP)
- # define read_unlock_irq(lock) _read_unlock_irq(lock)
- #else
- # define spin_unlock_irq(lock) /
- do { /
- __raw_spin_unlock(&(lock)->raw_lock); /
- __release(lock); /
- local_irq_enable(); /
- } while (0)
由于内核庞大而复杂,所以在内核的执行路线上,很难搞清楚中断在当前调用点上是否处于激活状态,故一般不提倡使用spin_lock_irq()方法。
针对自旋锁的操作:
spin_lock_init()用来初始化动态创建的自旋锁。
- #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)
- #define spin_trylock(lock) __cond_lock(lock, _spin_trylock(lock))
spin_trylock()试图获得某个特定的自旋锁,如果该锁已经被争用,那么立刻返回非0值,而不会自旋等待锁被释放;如果获得这个自旋锁,返回0。
spin_is_locked()用于检查特定的锁当前是否已被占用,如被占用返回非0,否则返回0。
- #define spin_lock(lock) _spin_lock(lock)
- #if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || /
- !defined(CONFIG_SMP)
- # define spin_unlock(lock) _spin_unlock(lock)
- #else
- # define spin_unlock(lock) /
- do {__raw_spin_unlock(&(lock)->raw_lock); __release(lock); } while (0)
自旋锁和下半部
函数spin_lock_bh()用于获取指定锁,同时它会禁止所有下半部的执行。spin_unlock_bh()函数执行相反的操作。
- #define spin_lock_bh(lock) _spin_lock_bh(lock)
- #define spin_unlock_bh(lock) _spin_unlock_bh(lock)
- void __lockfunc _spin_lock_bh(spinlock_t *lock)
- {
- local_bh_disable();
- preempt_disable();
- spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
- _raw_spin_lock(lock);
- }
- void __lockfunc _spin_unlock_bh(spinlock_t *lock)
- {
- spin_release(&lock->dep_map, 1, _RET_IP_);
- _raw_spin_unlock(lock);
- preempt_enable_no_resched();
- local_bh_enable_ip((unsigned long)__builtin_return_address(0));
- }
由于下半部可以抢占进程上下文中的代码,所以当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行包含,所以需要加锁的同时还要禁止下半部执行。同样,由于中断处理程序可以抢占下半部,所以如果中断处理程序和下半部共享数据,那么就必须在获取恰当的锁的同时还有禁止中断。