您的位置:首页 > 运维架构 > Linux

Linux内核同步机制——自旋锁

2009-05-12 21:42 495 查看
之前已经给出了原子操作部分内容,现将自旋锁部分内容整理如下,其中代码引用自os网站上源码阅读连接中的2.6.22.6版本,体系结构
选用了i386。 信号量部分将在后续给出。 有疑义,欢迎共同探讨!

2.自旋锁
自旋锁是Linux内核常用的锁机制,它只能被一个执行进程持有。当一个进
程去获取已被其他进程占有的自旋锁时,该进程会一直等待,直到锁可用为止。由于自旋锁的这种忙等机制,使得自旋锁不适合长期加锁的情况。另外,自旋锁是不可以递归获取的,也就是说,当一个进程试图去获取自己占有的自旋锁时会导致自旋而死锁。下面分析中引用的源码来自Linux内核2.6.22.6版本。
2.1 自旋锁定义与API
自旋锁是通过spinlock_t结构体定义的,存在于/include/linux/spinlock_types.h文件中,下面与体系结构相关的代码均引自i386体系结构实现。
typedef struct {
raw_spinlock_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
} spinlock_t;
在spinlock_t结构体中,主要成员是raw_spinlock_t类型的raw_lock,自旋锁的操作主要作用在raw_lock的slock整型变量上。下面给出了raw_spinlock_t结构体定义,其存在于/include/asm-i386/spinlock_types.h文件中。
typedef struct
{
unsigned int slock;
} raw_spinlock_t;
下面给出了自旋锁的主要方法,这些方法定义在/include/linux/spinlock.h。
方法名 描述
spin_lock(lock)
获取指定的自旋锁
spin_lock_irq(lock)
禁止本地中断并获取指定的自旋锁
spin_lock_irqsave(lock, flags)
保存本地中断信息,禁止本地中断,并获取指定的锁
spin_unlock(lock)
释放指定的锁
spin_unlock_irq(lock)
释放指定的锁,并激活本地中断
spin_unlock_irqrestore(lock,flags) 释放指定的锁,并恢复本地中断到之前的状态
spin_lock_init(lock)
动态初始化指定的锁
spin_trylock(lock)
试图获取指定的锁,如果未获取,则返回非0
2.2 自旋锁实现
下面简要分析一下spin_lock(lock)和spin_unlock(lock)的具体实现。
(1)spin_lock操作实现
下面列出了spin_lock具体实现的代码,其中__raw_spin_lock实现与体系结构相关,引自i386。
#define spin_lock(lock) _spin_lock(lock)

void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable(); /* 禁止内核抢占 */
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); /* 获取依赖信息 */
_raw_spin_lock(lock); /* 执行获取锁操作 */
}

#define _raw_spin_lock(lock) __raw_spin_lock(&(lock)->raw_lock)

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
asm volatile("/n1:/t"
LOCK_PREFIX " ; decb %0/n/t" /* --lock->slock */
"jns 3f/n"
"2:/t"
"rep;nop/n/t" /* 执行空操作 */
"cmpb $0,%0/n/t" /* 将lock->slock与0进行比较 */
"jle 2b/n/t" /* 锁不可用,跳到2继续忙等 */
"jmp 1b/n" /* 锁可用,跳到1尝试获取锁 */
"3:/n/t"
: "+m" (lock->slock) : : "memory");
}
从上述代码可以看出,spin_lock被宏定义为_spin_lock,而spin_lock通过调用preempt_disable禁止内核抢占,spin_acquire获取与锁相关的依赖信息,然后调用_raw_spin_lock。_raw_spin_lock又被宏定义为__raw_spin_lock,具体的获取锁和忙等都是在__raw_spin_lock中实现的。
__raw_spin_lock的代码是与体系结构相关的,通过扩展内联汇编实现。
具体步骤描述如下,其中LOCK_PREFIX保证其后一条指令原子地执行。
(a)decb %0将lock的slock成员的值减1,其中%0对应lock->slock的存储地址;
(b)检查结果是否大于等于0。若是,则说明锁可以获取,然后跳到标号3处退出。否则,继续往下执行;
(c)不断执行nop和cmpb操作直至lock->slock的值大于等0(锁再次可获得),跳到标号1处再次尝试获取锁。
(2)spin_unlock操作实现
下面列出了spin_unlock具体实现的代码
#define spin_unlock(lock) _spin_unlock(lock)

void __lockfunc _spin_unlock(spinlock_t *lock)
{
spin_release(&lock->dep_map, 1, _RET_IP_); /* 释放依赖信息 */
_raw_spin_unlock(lock); /* 释放自旋锁 */
preempt_enable(); /* 允许内核抢占 */
}

#define _raw_spin_unlock(lock)
__raw_spin_unlock(&(lock)->raw_lock)

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
/* lock->slock = 1 */
asm volatile("movb $1,%0" : "+m" (lock->slock) :: "memory");
}
相比spin_lock操作,spin_unlock实现相对简单,从_raw_spin_unlock代码可以看出,它仅仅是把lock->slock变量的值设置为1。
2.3 自旋锁使用注意事项
1.自旋锁不可以递归地被获取,递归获取会导致自旋,也就是使进程自身死锁。
2.自旋锁不应长期持有。因为长期占用会导致等待锁的其他处理器处于忙等,而不能去处理其他任务,造成处理器资源浪费。
3.自旋锁可以用于中断处理程序中,但在使用锁时,要先禁止本地中断。否则,中断处理程序会打断正持有锁的内核代码,有可能去争用这个已经被持有的自旋锁。此时,中断处理程序会自旋,等待该锁重新可用,而在中断处理程序执行完毕之前,锁的持有者不可能运行,从而导致死锁。
4.自旋锁禁止了内核抢占。这直接可以从spin_lock代码中看出来,它在获取锁之前,先调用preempt_disable函数禁止了内核抢占。
5.持有自旋锁的内核代码不可以睡眠。如果持有自旋锁的内核代码睡眠,另一个内核进程可能去争用已睡眠进程持有的锁。这时争用锁的进程将自旋,而内核又是不可抢占的(因为锁被获取时,内核抢占被禁止),因此当前争用锁的内核进程不会被抢占,处于睡眠状态的持有锁的进程没有机会运行,也就不可能释放锁,从而造成死锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: