spin自旋锁
自旋锁不管是内核编程,还是应用层编程都会用到;自旋锁和互斥量类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(也就叫自旋)状态。互斥锁和自旋锁,他们俩是最底层的锁,很多高级的锁都是基于他们去实现的。
spin
是互斥锁,即只有上锁和解锁两个状态值。它一般被描述为一个数的单个位。如果自旋锁可用,那么该单个位将会被被置位并且代码进入临界区,相反,如果这个锁已经被他人获得,代码便进入一个紧凑的循环中反复检查这个锁,直到锁可用。此时这个循环就是自旋锁的“自旋”部分。
在上所说的“置位”必须是原子操作,就算如果有多个进程在任何给定时间自旋,那也只有一个线程能够获得锁。必须小心以避免在超线程处理器上死锁–实现多个虚拟 CPU 以共享一个单个处理器核心和缓存的芯片. 因此实际的自旋锁实现在每个 Linux 支持的体系上都不同. 核心的概念在所有系统上相同, 然而, 当有对自旋锁的竞争, 等待的处理器在一个紧凑循环中执行并且不作有用的工作.
它们的特性上, 自旋锁是打算用在多处理器系统上, 尽管一个运行一个抢占式内核的单处理器工作站的行为如同 SMP
(也就是所谓的多处理器), 如果只考虑到并发. 如果一个非抢占的单处理器系统进入一个锁上的自旋, 它将永远自旋; 没有其他的线程再能够获得 CPU 来释放这个锁. 因此,自旋锁在没有打开抢占的单处理器系统上的操作被优化为什么不作, 除了改变IRQ
屏蔽状态的那些. 由于抢占, 甚至如果你从不希望你的代码在一个 SMP 系统上运行, 你仍然需要实现正确的加锁.
所以总结的来说
- 自旋锁与互斥锁的使用框架以及场景都是相似的
- 互斥锁得不到锁会休眠,而自旋锁是进入到一个紧凑的循环里忙等并检测锁的状态
- 使用场景自旋锁比较适合保护变量赋值、函数调用等
- 自旋锁的机制是用来在多核环境中,由于共享内存,存在对同一资源访问的情况,需要互斥访问机制只有一个核进行操作。
- 当自旋锁变为可用时,CPU不能做其他任何事情,这是自旋锁只能够只能持有一小段时间的原因之一。
- 自旋锁必须初始化:
#include <linux/spinlock.h>
spinlock_t lock = SPIN_LOCK_UNLOCKED;
//或直接调用函数:
void spin_lock_init(spinlock_t *lock);
加锁和解锁
void spin_lock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
在使用自旋锁时要注意几个点:
- 在持有一个锁时避免睡眠
- 自旋锁必须一直是尽可能短的持有时间,过长的时间会对整个系统整体有明显影响
- Need to have interrupts disabled when locked by ordinary threads, if shared by an interrupt handler。
禁止中断的自旋锁spin_lock_irqsave
函数声明:
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
/*
lock:被定义且初始化过的锁;
flags:保存本地中断状态;
*/
函数功能:
-
保存本地中断状态
-
关闭本地中断
-
获取自旋锁
spin_loc_irqsave
禁止中断(只在本地处理器)在获得自旋锁之前,之前的中断状态保存在flags里。且在调用spin_unlock_irqsave()
时,需注意flag
的变量必须是和上锁时是同一个变量。
其中对第二个参数flag
是否需要初始化,网上是这么说的:
在spin_lock_irqsave
里会对其进行初始化。如果不对flag
的值初始化,编译器可能会报警告,为了避免这些警告,最好进行初始化。
spin_lock_irq
void spin_lock_irq(spinlock_t *lock)
与spin_lock_irqsave
的区别:
如果确保在处理器中没有禁止中断的,即确定在释放自旋锁时有打开中断,则可以用spin_lock_irq
代替,并且不必保持跟踪flags。
void spin_lock_bh(spinlock_t *lock)
最后,spin_lock_bh
在获取锁之前禁止软件中断, 但是硬件中断留作打开的。
网上对于一系列锁的总结
面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景
问题:
不论是哪一篇文章对锁的论述时,都提到了会不会放弃CPU这个概念,所以放弃CPU这个是什么样的一个操作?