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"