分类: LINUX
2014-02-22 23:49:13
在这篇文章中, 我将会介绍 Kernel 提供用来使用 spinlock 的
function。除此之外,我还会告诉各位,为何在 SMP 的环境里,使用 spinlock 会比将所有 CPU 的中断 disable
这个方法来的有效率,我也会告诉各位如何针对不同的使用需求,使 spinlock 的 cost 再降低,进而使系统的效能更好...
前言
在 Linux Kernel
里有着许多重要的资料结构,这些资料在作业系统的运作中扮演着举足轻重的角色。然而,Linux
是个多任务的作业系统,也就是在同一时间里可以同时有许多的行程在执行,所以,很有可能某个行程在依序读取 inode list,同时却又有另一个在 inode
list 里加入新的 inode,这会造成什幺情形呢?这会造成 inode list 的不稳定。所以,在 Kernel
里,我们需要一个机制,可以使得当我们在修改某个重要的资料结构时,不能被中断,即使被中断了,这个资料结构由于还没修改完,别的行程也都不能去读取和修
改它。Linux Kernel提供了 spinlock 这个机制可以使我们做到这样的功能。
有的人会想到当我们在修改某个重要的资料结构时,将 中断都 disable
掉就好了,等修改完了再将中断 enable 不就得了,何必还要再提供一个 spinlock 来做同样的事。在 uni-processor
的环境底下,的确是如此。所谓 uni-processor 就是指只有一个 CPU 的电脑,但是在SMP的环境下就不是这幺一回事了。
我们知道现在 Linux 已经有支持 SMP,也就是可以使用多颗 CPU
来加快系统的速度,如果当我们在修改重要的资料结构时,将执行修改工作的 CPU 中断 disable 掉的话,只有目前的这个 CPU 的执行不会被中断,在 SMP
环境下,还有别的 CPU 正同时运作,如果别的 CPU 也去修改这个资料结构的话,就会造成同时有两个 CPU 在修改它,不稳定性就会产生。解决方法是将全部的
CPU 中断都 disable 掉,等修改完之后,再全部都 enable 起来。但是这样的做法其 cost 会很大,整个系统的效能会 down
下来。因此,Linux Kernel 才会提供 spinlock 这样的机制,它不会将全部 CPU 的中断 disable
掉,所以效率比上述的方法好,但同时却又能确保资料的稳定性,不会有某个行程在修改它,另外又有一个行程在读取或修改它的情形发生。
在这篇文章中,我将会介绍 Kernel 提供用来使用 spinlock 的
function。除此之外,我还会告诉各位,为何在 SMP 的环境里,使用 spinlock 会比将所有 CPU 的中断 disable
这个方法来的有效率,我也会告诉各位如何针对不同的使用需求,使 spinlock 的 cost 再降低,进而使系统的效能更好。
spinlock的资料结构
spinlock 的资料结构在 Linux底下是以
spinlock_t 来表示的,在 SMP 和 UP 环境底下两者的栏位有一些差异,其实在 UP 底下 spinlock_t
可以说是一个空的结构,空就是空的,为何要说"可以说是空的"呢?这是因为 gcc 版本的问题,gcc 在 2.8 版以前结构的内容必须不能是空的,而在 2.8
版之后就可以,所以在 UP 环境底下,会根据 gcc 的版本而设定不同的 spinlock_t 结构栏位,但基本上,在 UP 环境底下,是根本不会用到
spinlock_t 结构里的栏位的,详情请见以下诸节即可了解。
由于 spinlock
主要是用在SMP的环境底下,所以,以下我们就只针对在SMP环境底下的 spinlock_t 结构来讨论,它的结构内容是这样子的:
typedef struct { volatile unsigned int lock; } spinlock t; |
使用
spinlock
spinlock t xxx lock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin lock irqsave (&xxx lock, flags) ...critical section... spin unlock irqrestore (&xxx lock, flags) |
在
这个档案里定义了 spin_lock_irqsave() 及 spin_lock_irqrestore() 这两个
function。
#define spin_lock_irqsave(lock,flags) do while (0) #define spin_unlock_irqrestore(lock,flags) do while (0) |
#define local_irq_save(x) __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory") #define local_irq_restore(x) __asm__ __volatile__("pushl %0 ; popfl" /* no output */ :"g" (x):"memory") |
#ifdef __SMP__ #include #else /* !SMP */ ....... #endif |
UP 环境下的
Implementation
我们先来看看在 UP 的环境下, spin_lock(lock) 会变成什幺样子。
#define spin_lock(x) (void)lock #define spin_unlock(x) do {} while(0) |
spinlock_t xxx_lock = SPIN_LOCK_UNLOCKED; unsigned long flags; local_save_flags(flags); cli(); ... critical section ... local_restore_flags(flags); |
SMP 环境下的
Implementation
在 SMP 的环境底下, spin_lock() 和 spin_unlock() 这两个函式的原始码是放在
中。
extern inline void spin_lock(spinlock_t *plock) { __asm__ __volatile__( spin_lock_string :"=m" (__dummy_lock(plock))); } |
extern inline void spin_lock(spinlock_t *plock) { 1: lock ; btsl ,plock; jc 2f; .section .text.lock,"ax" 2: testb ,plock; rep;nop; jne 2b; jmp 1b; .previous } |
#define SPIN_LOCK_UNLOCKED (spinlock_t) |
看完了 spin_lock(),再来看 spin_unlock() 就会发觉简单多了。
#define spin_unlock(lock) __asm__ __volatile__( spin_unlock_string :"=m" (__dummy_lock(lock))) |
spin_unlock(plock) { lock; btrl , plock; } |
看到这里,各位应该可以了解 spinlock 的运作方式及其基本的使用方法了,接下来,我要跟各位介绍 spinlock
的另一种小小的变型,叫 read-write spinlock。
第二种的使用方式
有
些资料结构是这样子的,我们希望有人在修改它的内容时,别人都不能读取或修改它,但是当没有人在修改它时,可以同时有很多人去读取它的内容。我们称这样的
spinlock 为 read-write spinlock。 Kernel 为它定义了 rwlock_t,放在
里。使用方式是这样子的。
rwlock_t xxx_lock = RW_LOCK_UNLOCKED; unsigned long flags; read_lock_irqsave(&xxx_lock, flags); ... critical section that only reads the info ... read_unlock_irqrestore(&xxx_lock, flags); write_lock_irqsave(&xxx_lock, flags); ... read and write exclusive access to the info ... write_unlock_irqrestore(&xxx_lock, flags); |
我们来看看read这组函式的原始码是怎幺样子的:
#define read_lock_irqsave(lock, flags) do { local_irq_save(flags); read_lock(lock); } while (0) #define read_unlock_irqrestore(lock, flags) do { read_unlock(lock); local_irq_restore(flags); } while (0) |
#define read_lock(lock) (void)(lock) /* Not "unused variable". */ #define read_unlock(lock) do while(0) |
至 于 SMP 底下 rwlock
的实作方式我就不再赘述,基本上它们的实作方式都是差不多的,只有一点要特别说的是,由于 rwlock 可以容许多个 reader,但却只能有一个
writer,所以,它不会只用到 rwlock_t.lock 的第 0 个 bit 而已。事实上,rwlock_t.lock 是个 32bit 的
unsigned int 型别的变数,因此,它用第 0 到 30 个 bit 当作 reader 的 counter,而第 31 个 bit 则是用来给
writer 使用的。当第 31 个 bit 为 1 时,表示目前 rwlock 被 writer 锁住,此时前 30 个 bit 都应该是
0,表示此时没有任何的 reader。因此,可以推断 rwlock 同一时间最多可以有 2 的 30 次方个 reader。
第三种使用
spinlock
的方式
我 们可以看到以上两种的使用机制都是以 disable 中断的方式来做的,虽然 disable
中断很简单,只要一个指令就行了,但事实上,这个指令的 cost 对 CPU 来讲是蛮大的。所以, Kernel 还提供另一组的函式,它不 disable
中断,所以,它的执行速度会比上面两种来得有效率一些。 但是,上帝是公平的,它让你速度快,相对的它也提供的某些限制。这个限制就是就如果你确定 interrupt
handler 不会用到这个受保护的资料结构时,那你就可以考虑用这一组的函式, 以加快程序的执行。其实,这一组函式我们已经在上面见过了。
spin_lock(&lock); ... spin_unlock(&lock); |
spin_lock(&lock); .... <------ interrupt spin_lock(&lock); ... spin_unlock(&lock) |