layout | title | category | description | tags |
---|---|---|---|---|
post |
自旋锁 |
内核同步 |
自旋锁... |
自旋锁 spin_lock |
在同步技术中,最为广泛应用的就是加锁(locking)机制了。当内核控制路径必须访问共享数据结构或进入临界区时,就需要为自己获取一把『锁』。由锁机制保护的资源非常类似于限制于房间内的资源,如果某人进入房间,就把门锁上,等这个人使用完成后,后面的人才可以使用。
当内核控制路径希望访问某个同步的资源,就需要试图获取钥匙『打开门』,当且仅当资源空闲时,它才能成功,然后,只要它还想继续使用这个资源,那么锁就会一直关闭着。当内核控制路径不再使用资源,就释放了锁,门就打开了,另一个内核控制路径就可以访问。
自旋锁(spin lock)是用来在多处理环境中工作的一种特殊的锁,如果内核控制路径发现自旋锁开着,就获取锁并继续自己的执行,相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径『锁着』,就在周围『旋转』,反复执行一条紧密的循环指令1,直到锁被释放。
自旋锁的循环指令表示『busy』,即使等待的内核控制路径无事可做,它也在CPU上保持运行。但是自旋锁非常方便,因为很多内核资源只锁1毫秒的时间片段,所以说,释放CPU和随后又获得CPU都不会消耗多少时间。
一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的,在单处理器系统上,这种锁并不起锁的作用,自旋锁紧紧是禁止或启用内核抢占。在自旋锁等待的时候,内核抢占依旧有效,因此,等待自旋锁释放的进程有可能被更高的优先级的进程替代。
在Linux中,每个自旋锁都用spinlock_t结构表示:
{% highlight c++ %} 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; {% endhighlight %}
其中raw_spinlock_t代码为:
{% highlight c++ %} typedef struct { volatile unsigned int slock; } {% endhighlight %}
其中主要的两个参数为slock和break_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 |
自旋锁的实现和体系结构相关,也和编译时是否允许内核抢占有关,所以这个笔记就仅仅列举了逻辑,并不会找代码,代码实际上可以在*<include/linux/spinlick.h>*中找,内核提供了两种api接口,一个是smp api,另一个是up api。
在支持内核抢占的情况下,这个宏会获取自旋锁的地址slp作为它的参数,并执行下面的操作。
- 调用*preemet_disbale()*禁用内核抢占。
- 调用函数*__raw_spin_trylock(),它对自旋锁的slock*字段执行原子性的测试和设置操作。
- 如果自旋锁中是开着的,则宏结束,内核控制路径获得自旋锁。
- 否则内核控制路径无法获得自旋锁,因此宏必须执行循环一直到其他CPU上运行的内核控制路路径释放自旋锁。
- 如果break_lock字段等于0,则设置为1,通过监测该字段,拥有锁并在其他CPU上运行的进程可以知道是否有其他进程在等待这个锁。
- 执行紧密的循环。
- 跳转到第一步再次试图获取自旋锁。
如果在没有选择内核抢占的情况下,spin_lock所做的操作就非常不同,宏生成一个汇编语言片段,用于下面紧凑的循环3:
{% highlight asm %} 1: lock; decb slp->lock jns sf 2: pause cmpb $0, slp->slock jle 2b mp 1b 3: {% endhighlight %}
汇编语言指令decb递减自旋锁的值,该指令是原子的,因为带有lock字节前缀,随后检测符号标志,如果它被清0,说明自旋锁被设置为1,因此从标记3处继续执行,否则在标签2处执行紧凑的循环直到自旋锁出现正直,然后从标签1处开始执行。