抢占式内核与非抢占式内核中的自旋锁(spinlock)的差别
spin_lock()
在Linux2.6中,spin_lock()宏有两种实现方式,一种是具有内核抢占的spin_lock(),一种是非抢占式内核中的spin_lock(),下面先看下自旋锁的数据结构,在Linux中,每个自旋锁都用spinlock_t结构表示,如下:
typedef struct { /** * 该字段表示自旋锁的状态,值为1表示未加锁,任何负数和0都表示加锁 */ volatile unsigned int slock; #ifdef CONFIG_PREEMPT /** * 表示进程正在忙等待自旋锁。 * 只有内核支持SMP和内核抢占时才使用本标志。 */ unsigned int break_lock; #endif } spinlock_t;
spin_lock()定义如下:
#define spin_lock(lock) _spin_lock(lock)
具有内核抢占的_spin_lock宏
/** * 通过BUILD_LOCK_OPS(spin, spinlock);定义了_spin_lock,进而实现了spin_lock * 这是在具有内核抢占时,spin_lock的实现。 */ #define BUILD_LOCK_OPS(op, locktype) \ void __lockfunc _##op##_lock(locktype##_t *lock) \ { \ /** * preempt_disable禁用内核抢占。 * 必须在测试spinlock的值前,先禁止抢占,原因很简单: * 下面的循环中有可能会成功获得自旋锁,如果获得锁之后被抢占了,将造成死锁 */ preempt_disable(); \ for (;;) { \ /** * 调用_raw_spin_trylock,它对自旋锁的slock字段进行原子性的测试和设置。 * 本质上它执行以下代码: * movb $0,%al * xchgb %al, slp->slock * xchgb原子性的交换al和slp->slock内存单元的内容。如果原值>0,就返回1,否则返回0 * 换句话说,如果原来的锁是开着的,就关掉它,它返回成功标志。如果原来就是锁着的,再次设置锁标志,并返回0。 */ if (likely(_raw_##op##_trylock(lock))) \ /** * 如果旧值是正的,表示锁是打开的,宏结束,已经获得自旋锁了。 * 注意:返回后,本函数的一个负作用就是禁用抢占了。配对使用unlock时再打开抢占。 * 请想一下禁用抢占的必要性。 */ break; \ /** * 否则,无法获得自旋锁,就循环一直到其他CPU释放自旋锁。 * 在循环前,暂时打开preempt_enable。也就是说,在等待自旋锁的中间,进程是可能被抢占的。 */ preempt_enable(); \ /** * break_lock表示有其他进程在等待锁。 * 拥有锁的进程可以判断这个标志,如果进程把持锁的时间太长,可以提前释放锁。 */ if (!(lock)->break_lock) \ (lock)->break_lock = 1; \ /** * 执行等待循环,cpu_relax简化成一条pause指令,对应rep;nop,即空操作 * 为什么要加入cpu_relax,是有原因的,表面上看,可以用一段死循环的汇编来代替这个循环 * 但是实际上是不能那样的的,那样会锁住总线,unlock想设置值都不能了。 * cpu_relax就是要让CPU休息一下,把总线暂时让出来。 */ while (!op##_can_lock(lock) && (lock)->break_lock) \ cpu_relax(); \ /** * 上面的死循环lock的值已经变化了。那么关抢占后,再次调用_raw_spin_trylock * 真正的获得锁还是在_raw_spin_trylock中。 */ preempt_disable(); \ } \ }
_raw_spin_trylock如下:
static inline int _raw_spin_trylock(spinlock_t *lock) { char oldval; __asm__ __volatile__( "xchgb %b0,%1" :"=q" (oldval), "=m" (lock->slock) :"0" (0) : "memory"); return oldval > 0; }
非抢占式内核中的_spin_lock()
void __lockfunc _spin_lock(spinlock_t *lock) { preempt_disable(); //一直关闭可抢占 , _raw_spin_lock(lock); }
_raw_spin_lock(lock)如下:
/** * 对自旋锁的slock字段执行原子性的测试和设置操作。 */ static inline void _raw_spin_lock(spinlock_t *lock) { #ifdef CONFIG_DEBUG_SPINLOCK if (unlikely(lock->magic != SPINLOCK_MAGIC)) { printk("eip: %p\n", __builtin_return_address(0)); BUG(); } #endif __asm__ __volatile__( spin_lock_string :"=m" (lock->slock) : : "memory"); } /* spin_lock_string如下: */ #define spin_lock_string \ "\n1:\t" \ /** * %0对应上面的lock->slock * decb递减自旋锁的值。它有lock前缀,因此是原子的。 */ "lock ; decb %0\n\t" \ /** * 如果结果为0(不是负数),说明锁是打开的,跳到3f处继续执行。 */ "jns 3f\n" \ /** * 否则,结果为负,说明锁是关闭的。就执行死循环,等待它的值变化。 */ "2:\t" \ "rep;nop\n\t" \ /** * 比较lock值,直到它变化,才跳到开头,试图再次获得锁。 * 否则,继续死循环等lock值变化。 ..自选期间不打开可抢占 */ "cmpb $0,%0\n\t" \ "jle 2b\n\t" \ "jmp 1b\n" \ "3:\n\t"
arch_spin_lock
浙公网安备 33010602011771号