Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock
Linux内核源码分析 -- 同步原语 -- 自旋锁 spinlock_t
typedef struct spinlock { union { struct raw_spinlock rlock; #ifdef CONFIG_DEBUG_LOCK_ALLOC # define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map)) struct { u8 __padding[LOCK_PADSIZE]; struct lockdep_map dep_map; }; #endif }; } spinlock_t;
如果一个处理程序尝试执行受自旋锁
保护的代码,那么代码将会被锁住,直到占有锁的处理程序释放掉。
自旋锁 一共有两种状态
- acquired
- released
自旋锁获取(spinlock acquire
)
自旋锁释放(spinlock released
)
raw_spinlock
结构
typedef struct raw_spinlock { arch_spinlock_t raw_lock; #ifdef CONFIG_GENERIC_LOCKBREAK unsigned int break_lock; #endif } raw_spinlock_t;
x86
的 arch_spinlock
结构
typedef struct arch_spinlock { union { __ticketpair_t head_tail; struct __raw_tickets { __ticket_t head, tail; } tickets; }; } arch_spinlock_t;
Linux内核在自旋锁
上提供了一下主要的操作:
spin_lock_init
——给定的自旋锁
进行初始化;spin_lock
——获取给定的自旋锁
;spin_lock_bh
——禁止软件中断并且获取给定的自旋锁
。spin_lock_irqsave
和spin_lock_irq
——禁止本地处理器上的中断,并且保存/不保存之前的中断状态的标识 (flag)
;spin_unlock
——释放给定的自旋锁
;spin_unlock_bh
——释放给定的自旋锁
并且启动软件中断;spin_is_locked
- 返回给定的自旋锁
的状态;- 等等
spin_lock_init —— 对给定的自旋锁进行初始化
#define spin_lock_init(_lock) do { spinlock_check(_lock); raw_spin_lock_init(&(_lock)->rlock); } while (0)
spinlock_check
检查 _lock
返回已知的自旋锁
的 raw_spinlock_t
,来确保我们精确获得正常 (normal)
原生自旋锁
static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock) { return &lock->rlock; }
raw_spin_lock_init
宏
这个宏为给定的自旋锁
执行初始化操作,并且将锁设置为释放 (released)
状态
# define raw_spin_lock_init(lock) do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)
__RAW_SPIN_LOCK_UNLOCKED
宏
#define __RAW_SPIN_LOCK_UNLOCKED(lockname) (raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname) #define __RAW_SPIN_LOCK_INITIALIZER(lockname) { .raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, SPIN_DEBUG_INIT(lockname) SPIN_DEP_MAP_INIT(lockname) } #define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } }
展开 __RAW_SPIN_LOCK_UNLOCKED
宏就是
*(lock) = __ARCH_SPIN_LOCK_UNLOCKED;
展开 raw_spin_lock_init
就是
*(&(_lock)->rlock) = __ARCH_SPIN_LOCK_UNLOCKED;
在 spin_lock_init
宏的扩展之后,给定的自旋锁
将会初始化并且状态变为——解锁 (unlocked)
。
初始化操作其实就是把 给定的自旋锁 的 rlock
设置成 0
,表示锁是 released
状态
spin_lock —— 获取给定的自旋锁
static __always_inline void spin_lock(spinlock_t *lock) { raw_spin_lock(&lock->rlock); }
raw_spin_lock
宏
#define _raw_spin_lock(lock) __raw_spin_lock(lock)
__raw_spin_lock
函数
static inline void __raw_spin_lock(raw_spinlock_t *lock) { preempt_disable(); // 禁用抢占(当程序正在自旋锁时,这个已经获取锁的程序必须阻止其他程序方法的抢占) spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); }
跳过 spin_acquire
分析 LOCK_CONTENDED
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
LOCK_CONTENDED
#define LOCK_CONTENDED(_lock, try, lock) lock(_lock)
其实 lock
就是 do_raw_spin_lock
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock) { __acquire(lock); // [稀疏(sparse)]相关宏 arch_spin_lock(&lock->raw_lock); } #define arch_spin_lock(l) queued_spin_lock(l)
arch_spinlock
typedef struct arch_spinlock { union { __ticketpair_t head_tail; struct __raw_tickets { __ticket_t head, tail; } tickets; }; } arch_spinlock_t;
这个自旋锁
的变体被称为——标签自旋锁 (ticket spinlock)
当锁被获取,如果有程序想要获取自旋锁,它就会将 tail
的值加 1
,如果 tail != head
,那么程序就会被锁住,直到 tail == head
arch_spin_lock
#define __TICKET_LOCK_INC 1 #define cpu_relax() asm volatile("rep; nop") static __always_inline void arch_spin_lock(arch_spinlock_t *lock) { register struct __raw_tickets inc = { .tail = TICKET_LOCK_INC }; // tail = 1 inc = xadd(&lock->tickets, inc); // 这个操作过后 &lock->tickets = inc (这是个原子操作,详细请自行搜索 xadd) // 锁的关键就在这里了,只要 inc.head == inc.tail 成立,就说明这个锁没有被其他进程获取 if (likely(inc.head == inc.tail)) goto out; // 这个锁没有被获取,直接跳到 out 去执行 // inc.head != inc.tail 说明有线程获取了这个锁,进入这个循环,等待这个锁被释放 for (;;) { unsigned count = SPIN_THRESHOLD; // 这是类似于信号量的 timeout 的东西,这个变量定义了进程 "等多久" (while执行多少次) do { // 把 head 读出来 inc.head = READ_ONCE(lock->tickets.head); // 对比 head 和 tail,相等就说明这个锁被释放了 if (__tickets_equal(inc.head, inc.tail)) goto clear_slowpath; cpu_relax(); // #define cpu_relax() asm volatile("rep; nop"),就是一个 nop 指令,啥都不做 } while (--count); __ticket_lock_spinning(lock, inc.tail); } clear_slowpath: __ticket_check_and_clear_slowpath(lock, inc.head); out: barrier(); // 屏障指令(防止 CPU 乱序) }
spin_unlock -- 释放给定的自旋锁
其实这个锁的释放就是让 head
加 1
核心操作
__add(&lock->tickets.head, TICKET_LOCK_INC, UNLOCK_LOCK_PREFIX);
这样的话所有的等待进程就形成一个队列
head
是当前获得锁的进程的编号
tail
就是正在等待的进程的编号
在锁没有被释放的时候, 一直有进程请求这个锁,请求一次 tail
就加 1
释放锁的时候是 head
加 1
, 这样对应的 tail
(head == tail
)的进程就能获得锁
就像是这样的
+-------+ head | 3 | +-------+ +-------+-------+-------+-------+ tail | 4 | 5 | 6 | 7 | +-------+-------+-------+-------+
现在 tail
等于 3
, head
等于 3
释放锁后, head
等于 4
+-------+ head | 4 | +-------+ +-------+-------+-------+-------+ tail | 4 | 5 | 6 | 7 | +-------+-------+-------+-------+
这样 tail == 4
的进程就能获得锁
本文参考
《Linux Inside》:https://github.com/0xAX/linux-insides
《内核揭秘(中文版)》:https://github.com/MintCN/linux-insides-zh