Chinaunix首页 | 论坛 | 博客
  • 博客访问: 93379
  • 博文数量: 38
  • 博客积分: 950
  • 博客等级: 准尉
  • 技术积分: 235
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-01 10:17
文章分类
文章存档

2011年(38)

我的朋友

分类: LINUX

2011-05-16 17:19:22


自旋锁是相对于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)来确保自旋锁确实所得了对临界区的
访问权限。
阅读(486) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~