自旋锁

from:自旋锁


在同步技术中,最为广泛应用的就是加锁(locking)机制了。当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获取一把『锁』。由锁机制保护的资源非常类似于限制于房间内的资源,如果某人进入房间,就把门锁上,等这个人使用完成后,后面的人才可以使用。

当内核控制路径希望访问某个同步的资源,就需要试图获取钥匙『打开门』,当且仅当资源空闲时,它才能成功,然后,只要它还想继续使用这个资源,那么锁就会一直关闭着。当内核控制路径不再使用资源,就释放了锁,门就打开了,另一个内核控制路径就可以访问。

自旋锁(spin lock)是用来在多处理环境中工作的一种特殊的锁,如果内核控制路径发现自旋锁开着,就获取锁并继续自己的执行,相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径『锁着』,就在周围『旋转』,反复执行一条紧密的循环指令1,直到锁被释放。

自旋锁的循环指令表示『busy』,即使等待的内核控制路径无事可做,它也在CPU上保持运行。但是自旋锁非常方便,因为很多内核资源只锁1毫秒的时间片段,所以说,释放CPU和随后又获得CPU都不会消耗多少时间。

一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的,在单处理器系统上,这种锁并不起锁的作用,自旋锁紧紧是禁止或启用内核抢占。在自旋锁等待的时候,内核抢占依旧有效,因此,等待自旋锁释放的进程有可能被更高的优先级的进程替代。

在Linux中,每个自旋锁都用spinlock_t结构表示:

<include/linux/spinlock_types.h>

typedef struct {
    raw_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK    unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK    unsigned int magic, owner_cpu;
    void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC    struct lockdep_map dep_map;
#endif} spinlock_t;


其中raw_spinlock_t代码为:

<include/linux/spinlock_types_up.h>

typedef struct {
    volatile unsigned int slock;
}


其中主要的两个参数为slockbreak_lock,其中slock表示自旋锁的状态,值为1则表示『未枷锁』状态,而任何负数和0都表示已加锁的状态。而break_lock表示进程正在等待自旋锁2

与自旋锁有关的宏如下。

说明
spin_lock_init()把自旋锁设置为1,未锁的状态
spin_lock()循环,知道自旋锁变为1,然后把自旋锁设置为0
spin_unlock()把自旋锁设置为1
spin_unlock_wait()等待,直到自旋锁变为1
spin_is_locked()如果自旋锁设置为1,返回0,否则返回1
spin_trylock()把自旋锁设置为0,若原来锁是1,则返回1,否则为0

具有内核抢占的spin_lock宏


自旋锁的实现和体系结构相关,也和编译时是否允许内核抢占有关,所以这个笔记就仅仅列举了逻辑,并不会找代码,代码实际上可以在<include/linux/spinlick.h>中找,内核提供了两种api接口,一个是smp api,另一个是up api。

在支持内核抢占的情况下,这个宏会获取自旋锁的地址slp作为它的参数,并执行下面的操作。

  1. 调用preemet_disbale()禁用内核抢占。

  2. 调用函数__raw_spin_trylock(),它对自旋锁的slock字段执行原子性的测试和设置操作。

  3. 如果自旋锁中是开着的,则宏结束,内核控制路径获得自旋锁。

  4. 否则内核控制路径无法获得自旋锁,因此宏必须执行循环一直到其他CPU上运行的内核控制路路径释放自旋锁。

  5. 如果break_lock字段等于0,则设置为1,通过监测该字段,拥有锁并在其他CPU上运行的进程可以知道是否有其他进程在等待这个锁。

  6. 执行紧密的循环。

  7. 跳转到第一步再次试图获取自旋锁。

非抢占式的spin_lock宏


如果在没有选择内核抢占的情况下,spin_lock所做的操作就非常不同,宏生成一个汇编语言片段,用于下面紧凑的循环3

1:  lock; decb slp->lock
    jns  sf
2:  pause
    cmpb $0, slp->slock
    jle 2b
    mp 1b
3:


汇编语言指令decb递减自旋锁的值,该指令是原子的,因为带有lock字节前缀,随后检测符号标志,如果它被清0,说明自旋锁被设置为1,因此从标记3处继续执行,否则在标签2处执行紧凑的循环直到自旋锁出现正直,然后从标签1处开始执行。


  1. 可以想象成一个简单的while(is_lock),其中is_lock在有锁的时候为True,但逻辑不止如此简单。

  2. 正在进行紧凑的循环来等待锁的释放。当然这个仅在CMP和内核抢占的情况下使用。

  3. 实际上代码更加复杂。