Linux中自旋锁spinlock的实现

文章目录

一、自旋锁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 表示操作数应使用 eaxebxecxedx 寄存器之一

  • "=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 - 标签2
  • rep;nop - 空操作,相当于pause指令:
    • 在循环等待时降低CPU功耗
    • 避免内存顺序冲突
  • cmpb $0,%0 - 比较锁的值与0
  • jle 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
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_irqspin_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. 使用规则总结

  • 短期持有:自旋锁保护区域应尽可能短

  • 不能睡眠:锁区内不能调用可能睡眠的函数

  • 中断安全:根据共享情况选择正确的锁变体

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

---学无止境---

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值