自旋锁
自旋锁用于处理器之间的互斥,适合保护很短的临界区,并且不允许在临界区睡眠。申请自旋锁的时候,如果自旋锁被其他处理器占有,本处理器自旋等待(也称为忙等待)。
进程、软中断和硬中断都可以使用自旋锁。
目前内核的自旋锁是排队自旋锁(queued spinlock,也称为“FIFO ticket spinlock”),算法类似于银行柜台的排队叫号。
自旋锁的定义:
include/linux/spinlock_types.h
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
...
} raw_spinlock_t;
typedef struct spinlock {
union {
struct raw_spinlock rlock;
...
};
} spinlock_t;
可以看到,数据类型spinlock对数据类型raw_spinlock做了封装,spinlock和raw_spinlock(原始自旋锁)有什么关系呢?
Linux内核有一个实时内核分支(开启配置宏CONFIG_PREEMPT_RT)来支持硬实时特性,内核主线只支持软实时。
对于没有打上实时内核补丁的内核,spinlock只是封装raw_spinlock,它们完全一样。如果打上实时内核补丁,那么spinlock使用实时互斥锁保护临界区,在临界区内可以被抢占和睡眠,但raw_spinlock还是自旋锁。
目前主线版本还没有合并实时内核补丁,说不定哪天就会合并进来,为了使代码可以兼容实时内核,最好坚持3个原则:
(1)尽可能使用spinlock;
(2)绝对不允许被抢占和睡眠的地方,使用raw_spinlock,否则使用spinlock;
(3)如果临界区足够小,使用raw_spinlock。
各种处理器架构需要自定义数据类型arch_spinlock_t,ARM64架构的定义:
arch/arm64/include/asm/spinlock_types.h
#include <asm-generic/qspinlock_types.h>
include/asm-generic/qspinlock_types.h
typedef struct qspinlock {
union {
atomic_t val;
/*
* By using the whole 2nd least significant byte for the
* pending bit, we can allow better optimization of the lock
* acquisition for the pending bit holder.
*/
#ifdef __LITTLE_ENDIAN /* 小端格式 */
struct {
u8 locked;
u8 pending;
};
struct {
u16 locked_pending;
u16 tail;
};
#else /* 大端格式 */
struct {
u16 tail;
u16 locked_pending;
};
struct {
u8 reserved[2];
u8 pending;
u8 locked;
};
#endif
};
} arch_spinlock_t;
定义并且初始化静态自旋锁的方法:
DEFINE_SPINLOCK(x)
在运行时动态初始化自旋锁的方法:
spin_lock_init(_lock)
申请自旋锁的函数:
(1)申请自旋锁,如果锁被其他处理器占有,当前处理器自旋等待
void spin_lock(spinlock_t *lock);
(2)申请自旋锁,并且禁止当前处理器的软中断
void spin_lock_bh(spinlock_t *lock);
(3)申请自旋锁,并且禁止当前处理器的硬中断
void spin_lock_irq(spinlock_t *lock);
(4)申请自旋锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断
spin_lock_irqsave(lock, flags)
(4)申请自旋锁,如果申请成功,返回1;如果锁被其他处理器占有,当前处理器不等待,立即返回0
int spin_trylock(spinlock_t *lock);
释放自旋锁的函数:
(1)释放自旋锁
void spin_unlock(spinlock_t *lock);
(2)释放自旋锁,并且开启当前处理器的软中断
void spin_unlock_bh(spinlock_t *lock);
(3)释放自旋锁,并且开启当前处理器的硬中断
void spin_unlock_irq(spinlock_t *lock);
(4)释放自旋锁,并且恢复当前处理器的硬中断状态
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
定义并且初始化静态原始自旋锁的方法:
DEFINE_RAW_SPINLOCK(x)
在运行时动态初始化原始自旋锁的方法:
raw_spin_lock_init(lock)
申请原始自旋锁的函数:
(1)申请原始自旋锁,如果锁被其他处理器占有,当前处理器自旋等待
raw_spin_lock(lock)
(2)申请原始自旋锁,并且禁止当前处理器的软中断
raw_spin_lock_bh(lock)
(3)申请原始自旋锁,并且禁止当前处理器的硬中断
raw_spin_lock_irq(lock)
(4)申请原始自旋锁,保存当前处理器的硬中断状态,并且禁止当前处理器的硬中断
raw_spin_lock_irqsave(lock, flags)
(5)申请原始自旋锁,如果申请成功,返回1;如果锁被其他处理器占有,当前处理器不等待,立即返回0
raw_spin_trylock(lock)
释放原始自旋锁的函数:
(1)释放原始自旋锁
raw_spin_unlock(lock)
(2)释放原始自旋锁,并且开启当前处理器的软中断
raw_spin_unlock_bh(lock)
(3)释放原始自旋锁,并且开启当前处理器的硬中断
raw_spin_unlock_irq(lock)
(4)释放原始自旋锁,并且恢复当前处理器的硬中断状态
raw_spin_unlock_irqrestore(lock, flags)