文章目录
一、自旋锁spinlock的实现
自旋锁是一种忙等待的同步机制,当锁被占用时,请求线程会循环检查锁状态而不是立即睡眠
1.前要知识
1.1.锁段宏定义
/* 源码位置:include/linux/spinlock.h */
#define LOCK_SECTION_NAME ".text.lock." __stringify(KBUILD_BASENAME)
#define LOCK_SECTION_START(extra) \
".subsection 1\n\t" \
extra \
".ifndef " LOCK_SECTION_NAME "\n\t" \
LOCK_SECTION_NAME ":\n\t" \
".endif\n"
#define LOCK_SECTION_END ".previous\n\t"
- 目的:将锁相关的代码放到独立的代码段,优化指令缓存,锁操作代码不常执行但很重要,单独分段避免污染常用代码的缓存
1.1.1.LOCK_SECTION_START和LOCK_SECTION_END宏
LOCK_SECTION_NAME
#define LOCK_SECTION_NAME \
".text.lock." __stringify(KBUILD_BASENAME)
-
作用:生成一个 唯一的段名,用于存放锁相关的代码
-
KBUILD_BASENAME- 由内核构建系统自动定义,表示当前编译的源文件名(如
sched如果来自sched.c)
- 由内核构建系统自动定义,表示当前编译的源文件名(如
-
__stringify()- 将宏参数转换为字符串(如
__stringify(foo)→"foo")
- 将宏参数转换为字符串(如
-
示例,如果
KBUILD_BASENAME = "mutex",LOCK_SECTION_NAME展开为:".text.lock.mutex"
LOCK_SECTION_START(extra)
#define LOCK_SECTION_START(extra) \
".subsection 1\n\t" \
extra \
".ifndef " LOCK_SECTION_NAME "\n\t" \
LOCK_SECTION_NAME ":\n\t" \
".endif\n"
-
作用:开始一个 新的子段(subsection),并定义一个 局部标签(label) 作为该段的入口
-
.subsection 1- 切换到 子段 1(GNU 汇编器的特殊段,用于临时代码)
-
extra- 可选的额外汇编指令(如
.align或.type),通常由调用者提供
- 可选的额外汇编指令(如
-
.ifndef ... .endif- 检查
LOCK_SECTION_NAME是否已定义,如果未定义则定义它 - 避免重复定义段名
- 检查
-
示例展开
.subsection 1 .align 4 .ifndef .text.lock.mutex .text.lock.mutex: .endif
LOCK_SECTION_END
#define LOCK_SECTION_END \
".previous\n\t"
- 作用:结束当前子段,切换回 主代码段
.previous- 返回之前的主段(即
.text),恢复正常的代码流
- 返回之前的主段(即
1.2.preempt_disable()和preempt_enable()
可参考博客 https://blog.csdn.net/weixin_51019352/article/details/152009510 二、preempt_disable和preempt_enable的实现 一节
2. 多处理器情况下的自旋锁
2.1.spin_trylock函数
/* 源码文件:include/linux/compiler.h */
#ifdef __CHECKER__ // 在使用 Sparse 静态分析工具检查内核代码时__CHECKER__会被定义
# define __cond_lock(x) ((x) ? ({ __context__(1); 1; }) : 0) // __context__是静态代码分析的特殊标记
#else
# define __cond_lock(x) (x)
/* 源码文件:include/linux/spinlock.h */
#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))
/* 源码文件:include/asm/spinlock.h */
typedef struct {
volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK // 启用了自旋锁(spinlock)的调试功能时,该宏会被定义
unsigned magic;
#endif
} spinlock_t;
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->lock)
:"0" (0) : "memory");
return oldval > 0;
}
/* 源码文件:kernel/spinlock.c */
int __lockfunc _spin_trylock(spinlock_t *lock)
{
preempt_disable();
if (_raw_spin_trylock(lock))
return 1;
preempt_enable();
return 0;
}
2.1.1.宏定义部分
#define spin_trylock(lock) __cond_lock(_spin_trylock(lock))
作用:定义spin_trylock宏,它是尝试获取自旋锁的接口
spin_trylock(lock):用户使用的API宏__cond_lock:编译器相关的宏,用于静态分析,帮助编译器理解锁的获取和释放关系_spin_trylock(lock):实际执行尝试加锁操作的函数
2.1.2.自旋锁数据结构spinlock_t
typedef struct {
volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned magic;
#endif
} spinlock_t;
volatile unsigned int lock:核心锁变量volatile:防止编译器优化,确保每次访问都从内存读取- 值为0表示锁空闲,非0表示锁被占用
unsigned magic(调试模式下):用于调试的魔数,检测锁是否被正确初始化
2.1.3.底层尝试加锁函数_raw_spin_trylock
尝试获取自旋锁,如果锁未被占用(lock->lock == 0),则获取锁并返回 1(成功);否则直接返回 0(失败),不会阻塞
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->lock)
:"0" (0) : "memory");
return oldval > 0; // 返回是否成功(oldval > 0 表示锁空闲)
}
xchgb %b0,%1
xchgb:字节交换指令(exchange byte)%b0:约束条件q对应的字节寄存器(al, bl, cl, dl等)%1:第二个操作数
输出约束
:"=q" (oldval), "=m" (lock->lock)
-
约束条件
q表示操作数应使用eax、ebx、ecx或edx寄存器之一 -
"=q" (oldval):oldval变量使用字节寄存器,=表示输出 -
"=m" (lock->lock):lock->lock是内存操作数
输入约束
:"0" (0)
"0" (0):使用与第0个操作数相同的寄存器,初始值为0
破坏描述
:"memory"
- 告诉编译器内存内容可能被修改,防止优化
执行过程
-
将0存入寄存器
-
原子性地交换寄存器和
lock->lock的值 -
oldval值即交换前的lock->lock值
返回值逻辑
return oldval > 0;
- 如果
oldval > 0(原锁值非0),说明锁空闲,加锁成功返回1 - 如果
oldval == 0(原锁值为0),说明锁已被占用,加锁失败返回0
2.1.4.上层尝试加锁函数_spin_trylock
int __lockfunc _spin_trylock(spinlock_t *lock)
{
preempt_disable();
if (_raw_spin_trylock(lock))
return 1;
preempt_enable();
return 0;
}
函数流程
-
preempt_disable():禁用内核抢占- 防止在尝试加锁过程中被其他高优先级任务抢占
- 保证加锁操作的原子性
-
if (_raw_spin_trylock(lock)):调用底层函数尝试加锁- 如果加锁成功(返回1),直接返回1,保持抢占禁用状态
- 因为加锁成功后,当前任务持有锁,需要保持抢占禁用
-
preempt_enable():仅当加锁失败时启用内核抢占- 加锁失败时,没有持有锁,可以安全地被抢占
-
return 0:加锁失败返回0
2.2.spin_lock函数
/* 源码文件:include/asm/spinlock.h */
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory");
}
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->lock) <= 0)
/* 源码文件:include/asm/processor.h */
static inline void rep_nop(void)
{
__asm__ __volatile__("rep;nop": : :"memory");
}
#define cpu_relax() rep_nop()
/* 源码文件:kernel/spinlock.c */
static inline void __preempt_spin_lock(spinlock_t *lock)
{
if (preempt_count() > 1) {
_raw_spin_lock(lock);
return;
}
do {
preempt_enable();
while (spin_is_locked(lock))
cpu_relax();
preempt_disable();
} while (!_raw_spin_trylock(lock));
}
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
if (unlikely(!_raw_spin_trylock(lock)))
__preempt_spin_lock(lock);
}
/* 源码文件:include/linux/spinlock.h */
#define spin_lock(lock) _spin_lock(lock)
2.2.1.自旋锁的汇编核心实现spin_lock_string
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
1:\t- 标签1,表示循环的起点lock ; decb %0- 关键指令:lock- 总线锁前缀,确保原子操作decb %0- 将lock变量的字节值减1(%0是第一个操作数)- 组合效果:原子地将锁计数器减1
jns 3f- 条件跳转:- 检查上一条指令(
decb)的结果符号位 - 如果结果非负(
jns = jump if not signed),说明锁原本是1(空闲),现在变为0(获取成功),跳转到标签3 - 如果结果为负,说明锁原本是<=0(已被占用),继续执行
- 检查上一条指令(
2:\t- 标签2rep;nop- 空操作,相当于pause指令:- 在循环等待时降低CPU功耗
- 避免内存顺序冲突
cmpb $0,%0- 比较锁的值与0jle 2b- 如果锁值<=0,跳回标签2继续等待jmp 1b- 跳回标签1,重新尝试获取锁3:\n\t- 标签3,成功获取锁后的出口
2.2.2.原始自旋锁函数_raw_spin_lock
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory");
}
__asm__ __volatile__- 内联汇编,volatile防止编译器优化"=m" (lock->lock)- 约束条件:=m表示内存操作数,既可读又可写lock->lock是实际的锁变量
"memory"- 内存屏障,确保编译器不重排内存访问顺序
2.2.3.锁状态检查spin_is_locked
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->lock) <= 0)
- 将锁指针转换为
volatile signed char *:volatile确保每次都是从内存读取,不被缓存signed char因为锁值可能是负数(多个等待者)
- 判断锁值是否<=0:0表示被占用但无等待者,负数表示有等待者
2.2.4.CPU空转指令
static inline void rep_nop(void)
{
__asm__ __volatile__("rep;nop": : :"memory");
}
#define cpu_relax() rep_nop()
rep;nop- 现代x86架构中的pause指令等价物- 作用:
- 在自旋等待时降低CPU功耗
- 避免内存顺序违规
2.2.5.支持抢占的自旋锁实现__preempt_spin_lock
static inline void __preempt_spin_lock(spinlock_t *lock)
{
if (preempt_count() > 1) {
_raw_spin_lock(lock);
return;
}
do {
preempt_enable();
while (spin_is_locked(lock))
cpu_relax();
preempt_disable();
} while (!_raw_spin_trylock(lock));
}
- 检查抢占计数:
preempt_count() > 1表示已经在临界区或中断处理中- 这种情况下直接使用原始自旋锁,避免死锁
- 循环尝试获取锁:
preempt_enable()- 暂时允许抢占,避免长时间自旋时阻塞其他进程while (spin_is_locked(lock)) cpu_relax()- 非竞争性地等待锁释放preempt_disable()- 准备尝试获取锁,禁止抢占_raw_spin_trylock(lock)- 尝试原子获取锁,成功返回1,失败返回0
2.2.6.最终的自旋锁接口_spin_lock
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
if (unlikely(!_raw_spin_trylock(lock)))
__preempt_spin_lock(lock);
}
#define spin_lock(lock) _spin_lock(lock)
-
preempt_disable()- 立即禁止抢占 -
_raw_spin_trylock(lock)- 快速尝试获取锁 -
快速路径:如果获取成功(常见情况),直接返回
-
慢速路径:如果获取失败,调用
__preempt_spin_lock
2.3.spin_unlock函数
/* 源码文件:include/asm/spinlock.h */
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
/* 源码文件:include/linux/spinlock.h */
#define spin_unlock(lock) _spin_unlock(lock)
2.3.1.解锁的汇编核心实现spin_unlock_string
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
xchgb %b0, %1
xchgb- 字节交换指令(exchange byte)%b0- 约束条件中的第一个操作数的字节形式%1- 第二个操作数(lock->lock)- 作用:原子地交换
oldval(值为1)和lock->lock的内存值
约束条件部分
:"=q" (oldval), "=m" (lock->lock) // 输出操作数
:"0" (oldval) // 输入操作数
:"memory" // 破坏描述符
- 输出操作数:
"=q" (oldval)-=q表示使用a、b、c、d寄存器之一,oldval接收交换后的原锁值"=m" (lock->lock)-=m表示内存操作数,lock->lock将被设置为1
- 输入操作数:
"0" (oldval)-0表示使用与第0个操作数相同的寄存器,值为1
- 破坏描述符:
"memory"- 内存屏障,防止编译器重排内存访问顺序
2.3.2. 原始解锁函数_raw_spin_unlock
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
-
char oldval = 1;- 准备要设置的新锁值(1表示空闲) -
xchgb原子交换:- 将lock->lock的值(可能是0或负数)交换到
oldval中 - 将1(空闲状态)写入lock->lock
- 将lock->lock的值(可能是0或负数)交换到
2.3.3.完整的解锁流程
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
#define spin_unlock(lock) _spin_unlock(lock)
第一步:_raw_spin_unlock(lock)
- 原子地将锁值设置为1(空闲)
- 让等待的CPU可以检测到锁已释放
第二步:preempt_enable()
- 重新启用内核抢占
- 与
spin_lock()中的preempt_disable()配对使用 - 允许调度器在当前CPU上调度其他任务
2.3.4.解锁操作的详细场景分析
场景1:无竞争情况
// 加锁后锁值变为0
spin_lock(lock); // lock->lock = 0
// 临界区操作...
spin_unlock(lock); // lock->lock = 1
xchgb将0换出,1换入- 没有等待者,直接完成
场景2:有等待者的情况
// 假设有2个等待者,锁值为-2
// CPU1释放锁时:
xchgb %b0, %1 // oldval = -2, lock->lock = 1
- 等待的CPU检测到锁值从<=0变为1,开始竞争获取
2.3.5.为什么使用xchgb而不是movb?
安全性考虑:
// 不安全的做法:
movb $1, lock->lock // 非原子操作,可能被中断
// 安全的原子操作:
xchgb $1, lock->lock // 原子交换,确保完整性
原子性保证:
xchgb是原子指令,不会被中断打断- 确保锁状态的转变是原子的
- 其他CPU看到的锁状态要么是旧值,要么是新值,不会看到中间状态
2.4.spin_lock_irqsave函数
/* 源码文件:include/asm/spinlock.h */
#define spin_lock_string_flags \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 4f\n\t" \
"2:\t" \
"testl $0x200, %1\n\t" \
"jz 3f\n\t" \
"sti\n\t" \
"3:\t" \
"rep;nop\n\t" \
"cmpb $0, %0\n\t" \
"jle 3b\n\t" \
"cli\n\t" \
"jmp 1b\n" \
"4:\n\t"
static inline void _raw_spin_lock_flags (spinlock_t *lock, unsigned long flags)
{
__asm__ __volatile__(
spin_lock_string_flags
:"=m" (lock->lock) : "r" (flags) : "memory");
}
/* 源码文件:include/asm/system.h */
local_irq_save(x) __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory")
/* 源码文件:kernel/spinlock.c */
unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags);
preempt_disable();
_raw_spin_lock_flags(lock, flags);
return flags;
}
/* 源码文件:include/linux/spinlock.h */
#define spin_lock_irqsave(lock, flags) flags = _spin_lock_irqsave(lock)
2.4.1.带中断标志的自旋锁汇编实现spin_lock_string_flags
#define spin_lock_string_flags \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 4f\n\t" \
"2:\t" \
"testl $0x200, %1\n\t" \
"jz 3f\n\t" \
"sti\n\t" \
"3:\t" \
"rep;nop\n\t" \
"cmpb $0, %0\n\t" \
"jle 3b\n\t" \
"cli\n\t" \
"jmp 1b\n" \
"4:\n\t"
尝试获取锁
1:\t
lock ; decb %0\n\t ; 原子减锁值,%0 = lock->lock
jns 4f\n\t ; 如果非负(获取成功),跳转到标签4
检查中断标志
2:\t
testl $0x200, %1\n\t ; 测试flags的第9位(IF中断标志位)
jz 3f\n\t ; 如果IF=0(中断原被禁用),跳转到标签3
sti\n\t ; 否则启用中断(允许中断处理)
等待循环
3:\t
rep;nop\n\t ; CPU暂停指令,降低功耗
cmpb $0, %0\n\t ; 检查锁值
jle 3b\n\t ; 如果锁仍被占用,继续等待
重新尝试
cli\n\t ; 禁用中断,准备重新竞争锁
jmp 1b\n ; 跳回开头重新尝试获取
4:\n\t ; 成功获取锁的出口
中断标志测试的详细解释
testl $0x200, %1 ; 测试EFLAGS寄存器的IF位(第9位)
EFLAGS寄存器布局:
位 含义
9 IF - Interrupt Enable Flag (中断使能标志)
IF=1: 允许可屏蔽中断
IF=0: 禁止可屏蔽中断
- 如果
flags的IF=1,说明加锁前中断是开启的 - 如果
flags的IF=0,说明加锁前中断已经是关闭的
2.4.2.中断保存和恢复local_irq_save
/* 保存中断状态并禁用中断 */
local_irq_save(x) __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory")
-
pushfl- 将EFLAGS寄存器压栈 -
popl %0- 弹出到变量x中(保存原始中断状态) -
cli- 清除中断标志,禁用中断
2.4.3.完整的加锁流程_spin_lock_irqsave
unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)
{
unsigned long flags;
local_irq_save(flags); // 保存中断状态并禁用中断
preempt_disable(); // 禁用内核抢占
_raw_spin_lock_flags(lock, flags); // 获取自旋锁(带中断控制)
return flags; // 返回原始中断状态
}
典型使用模式:
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
// 临界区代码 - 中断被禁用,防止中断处理程序竞争
spin_unlock_irqrestore(&my_lock, flags); // 恢复中断状态
2.4.4.为什么需要中断保护的自旋锁?
场景:中断处理程序与普通代码的竞争
// 普通进程上下文
void process_context() {
spin_lock(&lock);
// 临界区
spin_unlock(&lock);
}
// 中断处理程序
void interrupt_handler() {
spin_lock(&lock); // 可能死锁!
// 中断临界区
spin_unlock(&lock);
}
问题:如果中断在进程持有锁时发生,中断处理程序尝试获取同一个锁,会导致死锁!
解决方案:spin_lock_irqsave()
void process_context() {
unsigned long flags;
spin_lock_irqsave(&lock, flags); // 禁用中断
// 临界区 - 不会被中断打断
spin_unlock_irqrestore(&lock, flags); // 恢复中断状态
}
2.4.5.关键设计亮点
智能中断管理
testl $0x200, %1\n\t ; 检查原始中断状态
jz 3f\n\t ; 如果原本中断就是关闭的,保持关闭
sti\n\t ; 如果原本中断是开启的,在等待时临时开启
为什么等待时要开启中断?
- 避免长时间自旋等待时阻塞整个系统
- 允许其他中断处理,提高系统响应性
性能优化
- 快速路径:锁空闲时直接获取,不操作中断状态
- 慢速路径:锁被占用时,根据原始中断状态智能管理中断
2.5.spin_unlock_irqrestore函数
/* 源码文件:include/asm/spinlock.h */
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
/* 源码文件:include/asm/system.h */
#define local_irq_restore(x) do { typecheck(unsigned long,x); __asm__ __volatile__("pushl %0 ; popfl": /* no output */ :"g" (x):"memory", "cc"); } while (0)
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
_raw_spin_unlock(lock);
local_irq_restore(flags);
preempt_enable();
}
/* 源码文件:include/linux/spinlock.h */
#define spin_unlock_irqrestore(lock, flags) _spin_unlock_irqrestore(lock, flags)
2.5.1.原始解锁实现_raw_spin_unlock
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
参考 2.3.2. 原始解锁函数_raw_spin_unlock
2.5.2.中断状态恢复的关键实现local_irq_restore
#define local_irq_restore(x) do { \
typecheck(unsigned long,x); \
__asm__ __volatile__("pushl %0 ; popfl": /* no output */ :"g" (x):"memory", "cc"); \
} while (0)
类型检查
typecheck(unsigned long,x); // 编译时检查x确实是unsigned long类型
内联汇编
"pushl %0 ; popfl" // 将flags值压栈,然后弹出到EFLAGS寄存器
-
pushl %0- 将flags变量(保存的原始EFLAGS)压入栈 -
popfl- 从栈中弹出到EFLAGS寄存器,恢复原始中断状态
约束条件
:"g" (x) // 输入操作数,x可以是寄存器或内存
:"memory", "cc" // 破坏描述符:
// "memory" - 内存屏障
// "cc" - 条件代码寄存器(EFLAGS)被修改
2.5.3.完整的解锁流程_spin_unlock_irqrestore
void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
_raw_spin_unlock(lock); // 第一步:释放自旋锁
local_irq_restore(flags); // 第二步:恢复中断状态
preempt_enable(); // 第三步:启用内核抢占
}
2.5.4.EFLAGS寄存器恢复的细节
保存和恢复的完整过程
// 加锁时保存:
pushfl ; 将EFLAGS压栈
popl %flags ; 保存到flags变量
cli ; 禁用中断
// 解锁时恢复:
pushl %flags ; 将保存的flags压栈
popfl ; 恢复到EFLAGS寄存器
EFLAGS的重要位:
位 标志 含义
9 IF 中断使能标志 (1=启用, 0=禁用)
8 TF 陷阱标志 (用于调试)
7 SF 符号标志
6 ZF 零标志
... 其他条件码和系统标志
2.6.spin_lock_irq函数
/* 源码文件:include/asm/spinlock.h */
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory");
}
/* 源码文件:include/asm/system.h */
#define local_irq_disable() __asm__ __volatile__("cli": : :"memory")
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_lock_irq(spinlock_t *lock)
{
local_irq_disable();
preempt_disable();
_raw_spin_lock(lock);
}
/* 源码文件:include/linux/spinlock.h */
#define spin_lock_irq(lock) _spin_lock_irq(lock)
2.6.1.基本自旋锁汇编实现_raw_spin_lock
参考 2.2.1.自旋锁的汇编核心实现spin_lock_string
2.6.2.中断禁用宏local_irq_disable
#define local_irq_disable() __asm__ __volatile__("cli": : :"memory")
cli- Clear Interrupt Flag指令,禁用可屏蔽中断- 没有输入输出操作数
"memory"- 内存屏障,防止编译器重排内存访问
作用:执行后,CPU不再响应可屏蔽中断,但不可屏蔽中断(NMI)仍然可以处理
2.6.3.完整的加锁流程_spin_lock_irq
void __lockfunc _spin_lock_irq(spinlock_t *lock)
{
local_irq_disable(); // 第一步:无条件禁用中断
preempt_disable(); // 第二步:禁用内核抢占
_raw_spin_lock(lock); // 第三步:获取自旋锁
}
2.6.4.与spin_lock_irqsave的关键区别
spin_lock_irq vs spin_lock_irqsave
| 特性 | spin_lock_irq | spin_lock_irqsave |
|---|---|---|
| 中断状态管理 | 无条件禁用中断 | 保存当前状态后禁用中断 |
| 恢复方式 | 必须配对spin_unlock_irq | 使用保存的flags恢复 |
2.6.5.总结
spin_lock_irq() 的设计特点:
-
简单直接:无条件禁用中断,不保存状态
-
性能较好:比
irqsave版本少一次状态保存操作 -
使用要求:开发者必须清楚当前中断状态
-
风险较高:错误使用可能导致中断状态不一致
-
适用场景:已知中断状态的代码路径
使用建议:
- 当不确定中断状态时,使用
spin_lock_irqsave() - 当明确知道需要禁用中断且了解当前状态时,使用
spin_lock_irq()
2.7.spin_unlock_irq函数
/* 源码文件:include/asm/spinlock.h */
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
/* 源码文件:include/asm/system.h */
#define local_irq_enable() __asm__ __volatile__("sti": : :"memory")
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_unlock_irq(spinlock_t *lock)
{
_raw_spin_unlock(lock);
local_irq_enable();
preempt_enable();
}
/* 源码文件:include/linux/spinlock.h */
#define spin_unlock_irq(lock) _spin_unlock_irq(lock)
2.7.1.基本解锁汇编实现_raw_spin_unlock
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
参考 2.3.2. 原始解锁函数_raw_spin_unlock
2.7.2.中断启用宏
#define local_irq_enable() __asm__ __volatile__("sti": : :"memory")
sti- Set Interrupt Flag指令,启用可屏蔽中断- 没有输入输出操作数
"memory"- 内存屏障,防止编译器重排内存访问
作用:执行后,CPU开始响应可屏蔽中断。
2.7.3.完整的解锁流程
void __lockfunc _spin_unlock_irq(spinlock_t *lock)
{
_raw_spin_unlock(lock); // 第一步:释放自旋锁
local_irq_enable(); // 第二步:启用中断
preempt_enable(); // 第三步:启用内核抢占
}
2.8.spin_lock_bh函数
/* 源码文件:include/asm/spinlock.h */
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory");
}
/* 源码文件:iinclude/linux/interrupt.h */
#define local_bh_disable() \
do { preempt_count() += SOFTIRQ_OFFSET; barrier(); } while (0)
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_lock_bh(spinlock_t *lock)
{
local_bh_disable();
preempt_disable();
_raw_spin_lock(lock);
}
/* 源码文件:include/linux/spinlock.h */
#define spin_lock_bh(lock) _spin_lock_bh(lock)
2.8.1.基本自旋锁汇编实现_raw_spin_lock
参考 2.2.1.自旋锁的汇编核心实现spin_lock_string
2.8.2.下半部禁用宏local_bh_disable
#define local_bh_disable() \
do { preempt_count() += SOFTIRQ_OFFSET; barrier(); } while (0)
preempt_count() 结构
Linux内核的preempt_count是一个32位整数,按位域划分:
位域划分:
0-7: 抢占计数器(preempt count)
8-15: 软中断计数器(softirq count)
16-27: 硬中断计数器(irq count)
28-31: 其他标志
SOFTIRQ_OFFSET 定义
#define SOFTIRQ_OFFSET (1UL << 8) // 256,影响软中断计数器位域
执行效果
preempt_count() += SOFTIRQ_OFFSET; // 软中断计数器加1
barrier(); // 编译器屏障,防止重排
作用:增加软中断禁用计数,防止软中断在当前CPU上执行。
2.8.3.完整的加锁流程_spin_lock_bh
void __lockfunc _spin_lock_bh(spinlock_t *lock)
{
local_bh_disable(); // 第一步:禁用下半部(软中断)
preempt_disable(); // 第二步:禁用内核抢占
_raw_spin_lock(lock); // 第三步:获取自旋锁
}
2.8.4.为什么需要下半部保护?
Linux中断处理分为两部分:
-
上半部(Top Half):硬中断处理,快速执行
-
下半部(Bottom Half):软中断,延迟执行更耗时的操作
竞争场景:
// 进程上下文
void process_context() {
spin_lock(&lock);
// 临界区
spin_unlock(&lock); // 可能被软中断打断!
}
// 软中断处理程序
void softirq_handler() {
spin_lock(&lock); // 可能死锁!
// 软中断临界区
spin_unlock(&lock);
}
2.8.5.与其它自旋锁变体的比较
保护范围对比
| 锁类型 | 保护 against | 开销 |
|---|---|---|
spin_lock() | 其他CPU | 最低 |
spin_lock_bh() | 其他CPU + 软中断 | 中等 |
spin_lock_irq() | 其他CPU + 硬中断 | 较高 |
spin_lock_irqsave() | 其他CPU + 硬中断 + 状态保存 | 最高 |
2.9.spin_unlock_bh函数
/* 源码文件:include/asm/spinlock.h */
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->lock) \
:"0" (oldval) : "memory"
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
/* 源码文件:kernel/softirq.c */
void local_bh_enable(void)
{
WARN_ON(irqs_disabled());
/*
* Keep preemption disabled until we are done with
* softirq processing:
*/
preempt_count() -= SOFTIRQ_OFFSET - 1;
if (unlikely(!in_interrupt() && local_softirq_pending()))
do_softirq();
dec_preempt_count();
preempt_check_resched();
}
/* 源码文件:kernel/spinlock.c */
void __lockfunc _spin_unlock_bh(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
local_bh_enable();
}
/* 源码文件:include/linux/spinlock.h */
#define spin_unlock_bh(lock) _spin_unlock_bh(lock)
2.9.1.基本解锁汇编实现_raw_spin_unlock
参考 2.3.2. 原始解锁函数_raw_spin_unlock
2.9.2.下半部启用函数local_bh_enable
void local_bh_enable(void)
{
WARN_ON(irqs_disabled());
/*
* Keep preemption disabled until we are done with
* softirq processing:
*/
preempt_count() -= SOFTIRQ_OFFSET - 1;
if (unlikely(!in_interrupt() && local_softirq_pending()))
do_softirq();
dec_preempt_count();
preempt_check_resched();
}
安全检查
WARN_ON(irqs_disabled());
- 警告,因为在中断禁用状态下启用软中断可能有问题
预emption计数调整
preempt_count() -= SOFTIRQ_OFFSET - 1; // 减去255(SOFTIRQ_OFFSET=256)
local_bh_disable()时:preempt_count += 256(软中断计数+1)local_bh_enable()时:preempt_count -= 255(软中断计数-1,但保持抢占禁用)
效果:软中断计数器减1,但抢占计数器保持+1状态。
软中断处理检查
if (unlikely(!in_interrupt() && local_softirq_pending()))
do_softirq();
条件判断:
!in_interrupt():不在中断上下文中(不是硬中断或软中断)local_softirq_pending():当前CPU有挂起的软中断unlikely():提示分支预测这种情况不常见
作用:如果有挂起的软中断且不在中断上下文,立即执行软中断
最终清理
dec_preempt_count(); // 抢占计数器减1
preempt_check_resched(); // 检查是否需要重新调度
2.9.3.完整的解锁流程_spin_unlock_bh
void __lockfunc _spin_unlock_bh(spinlock_t *lock)
{
_raw_spin_unlock(lock); // 第一步:释放自旋锁
preempt_enable(); // 第二步:启用内核抢占
local_bh_enable(); // 第三步:启用下半部(软中断)
}
2.9.4.local_bh_enable()的巧妙设计
软中断的延迟执行机制
preempt_count() -= SOFTIRQ_OFFSET - 1; // 软中断计数-1,但抢占保持
if (条件满足) {
do_softirq(); // 执行挂起的软中断
}
dec_preempt_count(); // 真正启用抢占
设计意图:在软中断执行期间保持抢占禁用,防止重入问题
执行场景分析
场景1:无挂起软中断
preempt_count() -= 255; // 软中断计数减1
// 检查条件不满足,跳过do_softirq()
dec_preempt_count(); // 抢占计数减1
场景2:有挂起软中断
preempt_count() -= 255; // 软中断计数减1,抢占保持+1
do_softirq(); // 执行所有挂起的软中断
dec_preempt_count(); // 软中断执行完后,抢占计数减1
2.10.spin_trylock_bh函数
/* 源码文件:include/asm/spinlock.h */
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->lock)
:"0" (0) : "memory");
return oldval > 0;
}
/* 源码文件:kernel/spinlock.c */
int __lockfunc _spin_trylock_bh(spinlock_t *lock)
{
local_bh_disable();
preempt_disable();
if (_raw_spin_trylock(lock))
return 1;
preempt_enable();
local_bh_enable();
return 0;
}
/* 源码文件:include/linux/spinlock.h */
#define spin_trylock_bh(lock) __cond_lock(_spin_trylock_bh(lock))
参考 前文
3.spinlock 实现总结
3.1. 设计哲学
- 忙等待:获取不到锁时循环等待,不睡眠
- 短期保护:适用于临界区执行时间短的场景
- 不可睡眠:不能在自旋锁保护区内睡眠
3.2. 多层次实现架构
用户接口层 (spin_lock/spin_unlock)
↓
中间封装层 (_spin_lock/_spin_unlock) + 抢占控制
↓
底层实现层 (架构相关: _raw_spin_lock)
↓
硬件原语 (原子指令: test-and-set, compare-and-swap等)
3.3. 变体锁类型
| 锁类型 | 中断处理 | 使用场景 |
|---|---|---|
spin_lock | 不改变 | 普通情况 |
spin_lock_irq | 禁用中断 | 可能被中断处理程序共享的数据 |
spin_lock_irqsave | 保存/恢复中断状态 | 不确定中断状态的场景 |
spin_lock_bh | 禁用软中断 | 与软中断处理程序共享的数据 |
3.4. 关键优化技术
代码布局优化
// 快速路径(常见情况)
LOCK "decl %0\n\t" // 原子减1
"js 2f\n" // 失败跳转
// 慢速路径(不常见情况)
LOCK_SECTION_START("")
"2:\tcall __down_failed\n\t" // 放在单独代码段
LOCK_SECTION_END
3.5. 使用规则总结
-
短期持有:自旋锁保护区域应尽可能短
-
不能睡眠:锁区内不能调用可能睡眠的函数
-
中断安全:根据共享情况选择正确的锁变体

1599

被折叠的 条评论
为什么被折叠?



