[读书笔记]Linux Inside: 同步原语01-Spinlocks
本系列博客为Linux Inside文章中Linux同步原语
的阅读笔记. 主要包含了Spinlocks
、Semaphores
、Mutex
、Reader/Writer semaphores
、SeqLock
、RCU
和Lockdep
.
Linux内核提供了多种同步原语为防止多进程或多线程情况下的Race Condition
. 这些同步原语的使用在内核代码中随处可见. 他们主要有:
- spinlocks;
- mutex;
- semaphores;
- seqlocks;
- atomic operations;
Spinlocks
Spinlocks
是一种低阶的同步机制, 其用一个变量表示该锁的状态: acquired、released. 任何一个想要获得该锁的进程都必须向该变量写入值表示该锁被某进程acquired, 或想要释放锁的进程都必须向该变量写入值表示该锁被某进程released, 即其用变量表示锁的状态. 因此所有相关(写入变量)的操作必须是atomic的以防止出现数据竞争问题.
结构体定义
Linux内核中SpinLocks
由spinlock_t
结构体来表示. 该结构体的定义为:
1 | typedef struct spinlock { |
该结构体的定义位于 include/linux/spinlock_types.h 头文件中. 如果内核中CONFIG_DEBUG_LOCK_ALLOC
设置被禁用, 那么spintlock_t
包含一个联合体, 该Union只有一个字段: raw_spinlock.
raw_spinlock的表示spinlock
的具体实现结构体. 该结构体的定义如下:
1 | typedef struct raw_spinlock { |
其中arch_spinlock_t
代表架构相关的Spinlocks
的具体实现. x86_64
架构中的具体实现为:
1 | typedef struct qspinlock { |
相关处理函数
内核提供的自旋锁相关的函数有:
spin_lock_init
: 对给定的spinlock
初始化.spin_lock
: 锁定给定的spinlock
.spin_unlock
: 释放给定的spinlock
.spin_is_locked
: 检查给定的spinlock
的状态.spin_lock_bh
: 禁止软件中断并锁定给定的spinlock
.spin_unlock_bh
: 释放给定的spinlock
并允许软件中断.
初始化Spinlocks
其中spin_lock_init
宏在include/linux/spinlock.h头文件中声明, 它的具体实现为:
1 |
该宏定义执行了两个操作: 检查给定的spinlock
并且执行__SPIN_LOCK_UNLOCKED
. 检查spinlock
的具体实现很简单, 该函数仅仅返回raw_spinlock_t
以保证我们得到normal raw spinlock
.
1 | static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock) |
正如__SPIN_LOCK_UNLOCKED
的名字所示, 该宏初始化给定的spinlock
并且将其中变量设置为released
状态. 该宏定义在include/linux/spinlock_types.h头文件中, 具体实现为:
1 |
我们无需关系DEBUG
相关的初始化(SPIN_DEBUG_INIT(lockname)
和SPIN_DEP_MAP_INIT(lockname)
). 因此__SPIN_LOCK_UNLOCKED
宏定义将会被展开成:
*(_lock) = (spinlock_t) { .rlock = {.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED} }
.
其中x86_64
架构的__ARCH_SPIN_LOCK_UNLOCKED
的宏定义为:
#define __ARCH_SPIN_LOCK_UNLOCKED { { .val = ATOMIC_INIT(0) } }
.
因此经过一系列的宏定义展开后, spin_lock_init
宏将给定的spin_lock
的atomic
状态变量设置为0, 表示unlocked
状态.
对Spinlocks上锁
spin_lock
函数定义在include/linux/spinlock.h文件中.
1 | static __always_inline void spin_lock(spinlock_t *lock) |
其中raw_spin_lock
的宏定义在相同的文件中. 其宏定义为:
#define raw_spin_lock(lock) _raw_spin_lock(lock)
如果允许SMP并且设置了CONFIG_INLINE_SPIN_LOCK
. 那么_raw_spin_lock
的定义为:
#define _raw_spin_lock(lock) __raw_spin_lock(lock)
该宏对应的函数定义为:
1 | static inline void __raw_spin_lock(raw_spinlock_t *lock) |
该函数首先调用preempt_disable
宏来禁用抢占. 当释放该自旋锁的时候, 抢占将会被允许:
1 | static inline void __raw_spin_unlock(raw_spinlock_t *lock) |
需要通过禁用抢占来防止其他进程抢占该锁. spin_acquire
通过一系列的宏展开为:
#define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)
#define lock_acquire_exclusive(l, s, t, n, i) lock_acquire(l, s, t, 0, 1, n, i)
其中lock_acquire
函数的实现为:
1 | void lock_acquire(struct lockdep_map *lock, unsigned int subclass, |
lock_acquire
通过raw_local_irq_save
宏来关闭硬件中断. 因为给定的自旋锁可能通过开启硬件中断来获取. 这样该进程就不会被抢占, 在该函数的最后通过raw_local_irq_restore
来重新开启中断. 主要的功能在__lock_acquire
函数中完成(作者说到会在之后的章节解读该函数).
在__raw_spin_lock
函数的最后, 会执行LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
LOCK_CONTENDED
该宏定义在include/linux/lockdep.h头文件中, 并只是调用给定的函数.
1 |
此时lock
是do_raw_spin_lock
函数, _lock
是给定的raw_spinlock_t
:
1 | static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock) |
其中arch_spin_lock
宏定义为:
#define arch_spin_lock(l) queued_spin_lock(l)
下一节中将深入了解queued spinlocks
的工作原理和相关概念.
[读书笔记]Linux Inside: 同步原语01-Spinlocks
https://csjsss.github.io/2022/04/20/读书笔记/读书笔记-Linux-Inside-同步原语/