转自http://www.51testing.com/html/63/524463-818251.html
自旋锁:单处理器非抢占式内核和对称多处理器或抢占式内核。
Linux 2.4.x及以前的版本都是非抢占式内核方式,如果编译成单处理器系统,在同一时间只有一个进程在执行,除非它自己放弃,不然只有通过"中断"才能中断其执行。因此,在单处理器非抢占式内核中,如果需要修改某个重要的数据结构,或者执行某些关键代码,只需要禁止中断。但是在对称多处理器,仅仅禁止某个CPU的中断是不够的,当然我们也可以将所有CPU的中断都禁止,但这样做开销很大,整个系统的性能会明显下降。
此外,即使在单处理器上,如果内核是抢占式的,也可能出现不同进程上下文同时进入临界区的情况。为此,Linux内核中提供了"自旋锁(spinlock)"的同步机制。 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"因此而得名。因此,中断(或软中断)禁止用于防止同一CPU上中断(或软中断)对共享资源的非同步访问。而自旋锁则防止在不同CPU上的执行单元对共享资源的同时访问,以及不同进程上下文互相抢占导致的对共享资源的非同步访问。
我们Linux 2.4.21为基础,分析x86平台下自旋锁的类型及应用方式,相关代码在源代码树的include/linux/spinlock.h以及include/asm-i386/spinlock.h中。
Linux内核中的自旋锁在Linux内核中,自旋锁的基本使用方式如下:先声明一个spinlock_t类型的自旋锁变量,并初始化为"未加锁"状态。在进入临界区之前,调用加锁函数获得锁,在退出临界区之前,调用解锁函数释放锁。例如:
spinlock_t lock = SPIN_LOCK_UNLOCKED;
spin_lock(&lock);/* 临界区 */
spin_unlock(&lock);获得自旋锁和释放自旋锁的函数有多种变体。
spin_lock_irqsave/spin_unlock_irqrestore相对于自旋锁的其它函数组,这一组函数是最"安全"的,使用频率也最多。在调用spin_lock_irqsave之前,我们还需要声明一个unsign long类型的变量(例如flag),该函数可以顺序完成下列操作:
1. 将CPU的标志寄存器的内容保存在变量flag中;
2. 禁止CPU的本地中断;
3. 调用spin_lock获得自旋锁。而spin_unlock_irqrestore函数则在调用spin_unlock释放自旋锁之后,将变量flag保存的值恢复到CPU的标志寄存器中。
#define spin_lock_irqsave(lock, flags) do { local_irq_save(flags); spin_lock(lock); } while (0)
#define local_irq_save(x) __save_and_cli(x)
#define __save_and_cli(x) do { __save_flags(x); __cli(); } while(0);
#define __save_flags(x) __asm__ __volatile__("pushfl ; popl %0":"=g" (x): /* no input */)
保存CPU的标志寄存器方法是:首先调用pushfl将标志寄存器压栈,再调用popl从栈中弹出保存在变量参数中。
#define __cli() __asm__ __volatile__("cli": : :"memory")禁止CPU的本地中断使用cli汇编指令。#define spin_unlock_irqrestore(lock, flags) do { spin_unlock(lock); local_irq_restore(flags); } while (0)
#define local_irq_restore(x) __restore_flags(x)
#define __restore_flags(x) __asm__ __volatile__("pushl %0 ; popfl": /* no output */ :"g" (x):"memory", "cc")
恢复CPU的标志寄存器方法是:首先调用pushl将变量参数压到栈中,再调用popfl从栈中弹出保存到标志寄存器。需要注意的是,这里没有显式执行开中断的动作。实际上,在标志寄存器中保持了原来的中断状态,在恢复寄存器的同时将中断也恢复到以前的状态。
spin_lock_irq/spin_unlock_irq和上面一组函数的不同在于,这一组函数并不涉及标志寄存器。spin_lock_irq函数首先禁止CPU的本地中断,再调用spin_lock获得自旋锁。而spin_unlock_irq函数则首先调用spin_unlock释放自旋锁,再打开CPU的本地中断。
#define spin_lock_irq(lock) do { local_irq_disable(); spin_lock(lock); } while (0)
#define local_irq_disable() __cli()#define spin_unlock_irq(lock) do { spin_unlock(lock); local_irq_enable(); } while (0)
#define local_irq_enable() __sti()
#define __sti() __asm__ __volatile__("sti": : :"memory")