分类: LINUX
2011-03-17 09:06:50
在这篇文章中,我将会介绍 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) |
在
#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) |
我们再来看看 read_lock() 和 read_unlock() 这两个函式,在 UP 环境底下是这个样子的:
#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) |
混合使用
针对 rwlock_t 这组函式除了上面提到的用法外,事实上,还是能混合 spin_lock() 及 spin_unlock() 来使用的。由于 rwlock_t 能允许多个 reader,所以如果在 interrupt handler 中只会读取受保护的资料结构,而不会去修改他的话,那我们能使用 spin_lock() 这组函式,不过当行程要修改资料结构时,还是得呼叫 write_spin_lock() 及 write_spin_unlock() 这组的函式。这样既能增加执行的效率,又能确保重要资料结构的稳定性了。
结论
虽然在 UP 的环境中,保护重要的资料结构只要呼叫 cli() 和 sti() 就好,不过随着 SMP 技术的成熟,相信 SMP 系统会逐渐的增加,为了让自己写的程式具有可移植性,善用 spinlock 这项机制相信会为未来省下相当修改程式码的功夫。