Chinaunix首页 | 论坛 | 博客
  • 博客访问: 358430
  • 博文数量: 167
  • 博客积分: 2867
  • 博客等级: 少校
  • 技术积分: 1306
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-12 00:08
文章分类

全部博文(167)

文章存档

2017年(10)

2016年(5)

2015年(9)

2014年(10)

2013年(5)

2012年(17)

2011年(110)

2010年(1)

我的朋友

分类: 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_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环境下,自旋锁类型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");}


阅读(1044) | 评论(0) | 转发(0) |
0

上一篇:do while(0)用处

下一篇:linux kernel 内存分配

给主人留下些什么吧!~~