有了原子操作,就可以了制作控制临界区的锁机制了。自旋锁就是其中的一个代表。
自旋锁机制可以用门和锁的例子来比喻。进程执行到某个临界区,相当于要进入一栋房子,这是进程会检查屋内是否有人(进程),如果屋内没有人,则直接拿起钥匙进入并把门锁上(进入临界区);如果屋内有人(进程),则在门口等待(忙等待)屋内的进程出来再进去。可以看出,自旋锁最多只能被一个进程持有,如果有新的进程希望获取自旋锁,它将会一直忙等待直到前一个持有自旋锁的进程释放锁。
linux下的实现
自旋锁的结构定义在<linux/Spinlock_types.h>内:
typedefstructraw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsignedint break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsignedint magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
typedefstructspinlock {
union {
structraw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
spinlock的调用接口定义在<linux/spinlock.h>中,加锁自旋锁有四个函数(或宏)
自旋锁接口 | 说明 |
void spin_lock(spinlock_t *lock) | 获取指定的锁 |
void spin_lock_bh(spinlock_t *lock) | 获取指定锁并禁止下半部分(bottom half)中断 |
void spin_lock_irq(spinlock_t *lock) | 获取锁并禁止本地中断 |
spin_lock_irqsave(lock, flags) | 宏,获取锁并保存本地中断状态(flags) |
对应的有四个返回获取状态的加锁函数(或宏):
自旋锁接口 | 说明 |
int spin_trylock(spinlock_t *lock) | 同spin_lock,在获取失败时返回非0 |
int spin_trylock_bh(spinlock_t *lock) | 同spin_lock_bh,在获取失败时返回非0 |
int spin_trylock_irq(spinlock_t *lock) | 同spin_lock_irq,在获取失败时返回非0 |
spin_trylock_irqsave(lock, flags) | 同spin_lock_irqsave,在获取失败时返回非0 |
以及四个释放自旋锁函数:
自旋锁接口 | 说明 |
void spin_unlock(spinlock_t *lock) | 释放锁 |
void spin_unlock_bh(spinlock_t *lock) | 释放锁并开启下半部分中断 |
void spin_unlock_irq(spinlock_t *lock) | 释放锁并打开本地中断 |
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) | 释放锁并恢复本地中断状态(flags) |
除此之外,还有检测、等待锁函数:
自旋锁接口 | 说明 |
void spin_unlock_wait(spinlock_t *lock) | 等待,直到释放锁 |
int spin_is_locked(spinlock_t *lock) | 已上锁则返回非0,没有上锁则返回0 |
int spin_can_lock(spinlock_t *lock) | 没有上锁则返回非0,上锁则返回0 |
这些函数最底层的调用是这样的:
#define__LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
#define___LOCK(lock) \
do { __acquire(lock); (void)(lock); } while (0)
以及:
#define __UNLOCK(lock) \
do { preempt_enable(); ___UNLOCK(lock); } while (0)
spin_lock_irq函数在上锁时会先禁用抢占,然后获取锁;释放锁时先启用抢占,再释放锁。而spin_lock_irqsave更进一步,它会将本地的抢占状态保存起来,释放锁之后恢复原来的抢占状态,这样就避免了原来抢占被禁止而释放锁后抢占被打开的情况。
arch_spinlock的具体实现与体系结构有关,定义在<asm/Spinlock_types.h>下:
typedefstructarch_spinlock {
union {
__ticketpair_t head_tail;
struct__raw_tickets {
__ticket_t head, tail;
} tickets;
};
} arch_spinlock_t;
可以看出,自旋锁在硬件支持的基础上进一步封装,已经打包成了完整可用的锁结构。在内核代码的很多地方,特别是涉及到device操作的代码都会用到自旋锁。
自旋锁的调用
自旋锁的调用方法如下:
DEFINE_SPINLOCK(lock);
spin_lock(&lock);
/* 临界区 */
spin_unlock(&lock);
例如,<sound/ppc/Beep.c>文件实现了蜂鸣器的控制接口,蜂鸣器事件函数为
staticint snd_pmac_beep_event(struct input_dev *dev, unsignedint type, unsignedint code, int hz)
该函数在snd_pmac_attach_beep函数内进行了注册:
input_dev->event = snd_pmac_beep_event;
函数内对蜂鸣器的控制语句:
structsnd_pmac *chip;
structpmac_beep *beep;
unsignedlong flags;
//…
if (!hz) {
spin_lock_irqsave(&chip->reg_lock, flags);
if (beep->running)
snd_pmac_beep_stop(chip);
spin_unlock_irqrestore(&chip->reg_lock, flags);
return 0;
}
在这段代码中,先是获取chip的锁,将本地中断状态保存起来(flags),然后进入临界区,操作蜂鸣器beep,如果beep在running,即蜂鸣器在响,则停掉beep。然后退出临界区,从flags恢复本地中断状态。
除了device操作,下面将要介绍的信号量的实现中也用到了自旋锁,具体内容将在后面详细说明。
需要留意的是,自旋锁的临界区要尽可能简短,避免其他进程等待(占用CPU原地等待)太久。而且不能递归地调用自旋锁。递归调用自旋锁会导致死锁的发生。