自旋锁:单处理器非抢占式内核和对称多处理器或抢占式内核(转)

上一篇 / 下一篇  2012-07-23 22:40:20 / 个人分类:linux内核

自旋锁:单处理器非抢占式内核和对称多处理器或抢占式内核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")

打开CPU的本地中断使用sti汇编指令。spin_lock_bh/spin_unlock_bh/spin_trylock_bhspin_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_trylockUP环境下由于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_trylockSMP环境下在SMP环境下,自旋锁类型spinlock_t含有一个unsigned intlock。未加锁时值为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");}


TAG:

 

评分:0

我来说两句

日历

« 2024-05-08  
   1234
567891011
12131415161718
19202122232425
262728293031 

数据统计

  • 访问量: 54920
  • 日志数: 40
  • 建立时间: 2012-07-17
  • 更新时间: 2012-10-01

RSS订阅

Open Toolbar