自旋锁是相对于SMP系统而言的。
在单处理器中只要保证在临界区时不发生进程调度(包括进程抢占), 禁止中断,或者即使开启中断,
只要中断例程与临界区无冲突,在或者临界区只有一条指令(中断只发生在指令之间,并不会打断一条执行中的指令),
就会保持操作的同步。
但是在SMP中,情况就会变的复杂的多, 考虑下面一种情况:
1. CPU0中的某一个进程从内存A处读取了一个值, 然后对它进行了修改。
2. CPU1中的某一个进程也同时从内存A处读取了这个值,并且也对它进行了修改。
3. CPU0中的进程把修改后的值写回到了内存A处。
4. CPU1中的进程也把修改后的值写回到了内存A处。
这个操作过程中造成了内存A处的值也预期的结果可能不太一样。 在一个如果CPU0和CPU1的这个操作正好是测试自旋锁值的过程,
那么将导致CPU0和CPU1都以为获得了临界区的操作权限, 那么执行结果将变的更加不可预知了。
所以在SMP系统中常用的一个方法就是采用叫做自旋锁的方法来达到CPU之间的同步操作。
在spin_lock在spinlock.h中定义:
#define spin_lock(lock) _spin_lock(lock)
_spin_lock(lock)在/kernel/spinlock.c中实现:
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
if (unlikely(!_raw_spin_trylock(lock)))
__preempt_spin_lock(lock);
}
1. 函数首先调用preempt_disable();来禁止内核抢占, 如果在CPU在执行自旋锁代码的时候开启了内核抢占,
在这个过程中它有可能被其他CPU抢占, 而其他CPU此时也需要获得这个自旋锁的话, 将会发生自旋,在最坏的情况下它将一直
自旋下去。
2. 然后调用_raw_spin_trylock(lock)来检查下是否能获得这个锁。
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->lock)
:"0" (0) : "memory");
return oldval > 0;
}
这段代码让lock->lock与0值进行交换,存储到oldval中,如果oldval为正值,就返回1,
否则返回0.
当_raw_spin_trylock(lock)返回0值时,表明临界区被加了锁,代码开始执行__preempt_spin_lock()
不断自旋来检查lock是否被释放。
static inline void __preempt_spin_lock(spinlock_t *lock)
{
if (preempt_count() > 1) {
_raw_spin_lock(lock);
return;
}
do {
preempt_enable();
while (spin_is_locked(lock))
cpu_relax();
preempt_disable();
} while (!_raw_spin_trylock(lock));
}
在这个过程中如果禁止了内核抢占的话, 执行_raw_spin_lock()进行自旋。
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->lock) : : "memory");
}
具体代码为spin_lock_string
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
这段代码就是不断的测试lock->lock的值,如果大于0就跳出循环返回。此处的lock指令为会锁定地址总线,
防止其他CPU并发访问,起到了内存屏障的作用。
如果在这个过程中如果开启了内核抢占的话,首先通过preempt_enable()开启内核抢占。
然后一个循环检查lock->lock的值。
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->lock) <= 0)
如果lock->lock的值小于0, 表示临界区不可用, 执行cpu_relax(), 其实就是执行nop指令
来获得短暂的延迟。 当临界区可用的时候执行preempt_disable()来禁止内核抢占。
如果此时还开启内核抢占的话, 那么在执行_raw_spin_trylock(lock)前,它可能在次失去
对临界区的访问权限。 最后调用_raw_spin_trylock(lock)来确保自旋锁确实所得了对临界区的
访问权限。
阅读(518) | 评论(0) | 转发(0) |