分类: LINUX
2011-04-16 22:33:40
自旋锁:单处理器非抢占式内核和对称多处理器或抢占式内核 Linux 2.4.x及以前的版本都是非抢占式内核方式,如果编译成 单处理器系统,在同一时间只有一个进程在执行,除非它自己放 弃,不然只有通过"中断"才能中断其执行。因此,在单处理器非 抢占式内核中,如果需要修改某个重要的数据结构,或者执行某 些关键代码,只需要禁止中断。但是在对称多处理器,仅仅禁止 某个CPU的中断是不够的,当然我们也可以将所有CPU的中断都禁 止,但这样做开销很大,整个系统的性能会明显下降。 此外,即使在单处理器上,如果内核是抢占式的,也可能出现 不同进程上下文同时进入临界区的情况。为此,Linux内核中提供 了"自旋锁(spinlock)"的同步机制。 自旋锁与互斥锁有点类似 ,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单 元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释 放了锁,"自旋"因此而得名。 因此,中断(或软中断)禁止用于防止同一CPU上中断(或软中断) 对共享资源的非同步访问。而自旋锁则防止在不同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") 打开CPU的本地中断使用sti汇编指令。 spin_lock_bh/spin_unlock_bh/spin_trylock_bh spin_lock_bh函数在得到自旋锁的同时禁止本地软中断, spin_unlock_bh函数释放自旋锁的同时,也打开本地的软中断。 #define spin_lock_bh(lock) do { local_bh_disable(); spin_lock(lock); } while (0) #define local_bh_disable() cpu_bh_disable(smp_processor_id()) #define cpu_bh_disable(cpu) \ do { local_bh_count(cpu)++; barrier(); } while (0) #define spin_unlock_bh(lock) do { spin_unlock(lock); local_bh_enable(); } while (0) #define spin_trylock_bh(lock) ({ int __r; local_bh_disable();\ __r = spin_trylock(lock); \ if (!__r) local_bh_enable(); \ __r; }) spin_lock/spin_unlock/spin_trylock 上面各组函数最终都需要调用自旋锁操作函数。spin_lock函数用于获得 自旋锁,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里, 直到该自旋锁的保持者释放。spin_unlock函数则释放自旋锁。此外,还 有一个spin_trylock函数。尽力获得自旋锁lock,如果能立即获得锁, 它获得锁并返回真,否则不能立即获得锁,立即返回假。它不会自旋等待 lock被释放。 spin_lock/spin_unlock/spin_trylock在UP环境下由于Linux 2.4.x 为不可抢占的内核,在单处理器环境下,自旋锁什么都不需要做。自旋锁 类型spinlock_t设置为空,除了在GCC的早期版本中不支持内容为空的数 据结构。typedef struct {} spinlock_t;相应地,自旋锁的操作函数不 进行任何实质性处理。 #define spin_lock_init(lock) do { } while(0) #define spin_lock(lock) (void)(lock) /* Not "unused variable". */ #define spin_is_locked(lock) (0)#define spin_trylock(lock) ({1; }) #define spin_unlock_wait(lock) do { } while(0) #define spin_unlock(lock) do { } while(0) spin_lock/spin_unlock/spin_trylock在SMP环境下,自旋锁类型spinlock_t 含有一个unsigned int域lock。未加锁时值为1,加锁后值为0或负值。声明时 也使用了volatile描述符,要求编译器不要对其作优化处理,对它的读写都需 要从内存中访问。 typedef struct { volatile unsigned int lock;} spinlock_t; static inline void spin_lock(spinlock_t *lock) { __asm__ __volatile__( spin_lock_string :"=m" (lock->lock) : : "memory");} |