自旋锁
自旋锁
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: 任务管理、互斥锁、中断管理、SMP 基础
自旋锁(Spinlock)是一种忙等待的同步机制。与互斥锁在无法获取时会阻塞任务不同,自旋锁在无法获取时会让 CPU 不停地循环检查("自旋"),直到锁可用。它主要用于 SMP 多核系统中保护极短的临界区,以及在中断上下文中使用。
🎯 学习目标
- 理解自旋锁的忙等待机制及其适用场景
- 掌握自旋锁的基本 lock/unlock 操作
- 理解 IRQ 安全版本的使用场景
- 了解自旋锁调试检查机制
- 能正确区分自旋锁与互斥锁的使用场景
一、自旋锁概念
1.1 什么是自旋锁
自旋锁是一种不会导致任务阻塞的锁。当锁被占用时,请求锁的 CPU 在循环中不断检查锁状态,直到获得锁。
┌─────────────────────────────────────────────────────────────────┐
│ 自旋锁模型(SMP 系统) │
│ │
│ CPU 0 CPU 1 │
│ ┌──────────┐ ┌──────────┐ │
│ │ 任务 A │ │ 任务 B │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ lock(spin) │ lock(spin) │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 获得锁! │ │ 锁被占用 │ │
│ │ 进入临界区│ │ 自旋等待..│←── 循环检查 │
│ └────┬─────┘ └────┬─────┘ │
│ │ 临界区执行 │ 仍在自旋.. │
│ │ │ │
│ │ unlock(spin) │ 获得锁! │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 释放锁 │ │ 进入临界区│ │
│ └──────────┘ └──────────┘ │
│ │
│ ⚠️ 关键约束:持有自旋锁期间不能睡眠、不能阻塞 │
└─────────────────────────────────────────────────────────────────┘1.2 自旋锁 vs 互斥锁
| 特性 | 自旋锁 | 互斥锁 |
|---|---|---|
| 等待方式 | 忙等待(CPU 空转) | 阻塞等待(CPU 让出) |
| 使用场景 | 极短临界区(微秒级) | 较长的临界区 |
| 中断上下文 | 支持 | 不支持 |
| SMP 保护 | 核心场景 | 通过内部自旋锁保护 |
| 任务睡眠 | 持有期间禁止睡眠 | 持有期间可以睡眠 |
| 优先级继承 | 不支持 | 支持 |
| 递归持有 | 不支持 | 支持 |
| 开销 | 无上下文切换开销 | 有上下文切换开销 |
1.3 SMP 与非 SMP 行为
| 配置 | 自旋锁行为 |
|---|---|
OS_USING_SMP 启用 | 锁包含架构相关的原子操作(arch_spinlock_t arch_lock),真正保护多核临界区 |
OS_USING_SMP 未启用 | 自旋锁退化为几乎空操作(只有调试检查),结构体只剩 padding 字段 |
1.4 自旋锁使用场景
一句话核心:自旋锁适用于保护极短的临界区(微秒级),尤其是多核(SMP)任务之间、任务与中断之间共享数据的场景。它用"忙等待"换取了"无上下文切换开销",适合临界区极短、不希望任务被阻塞的场合。
与互斥锁的本质区别决定适用场景(见 1.2):互斥锁阻塞让出 CPU(适合长临界区、支持优先级继承),自旋锁忙等待占住 CPU(适合极短临界区、可在中断用)。
典型场景一:SMP 多核任务间保护共享变量(最典型)
两个 CPU 上的任务并发修改同一个全局变量,用自旋锁保证原子性:
CPU0 任务A ──os_spin_lock──► [临界区:改 g_shared_data] ──os_spin_unlock──►
CPU1 任务B ──os_spin_lock──► [自旋等待,直到锁释放] ──进入临界区────►为什么用自旋锁:临界区只有几行代码、执行时间微秒级。如果用互斥锁,获取/释放要经历"任务阻塞→调度→唤醒",开销反而大于临界区本身;自旋锁只是循环检查锁状态,开销极小。
典型场景二:任务与中断共享数据(必须用 IRQ 安全版本)
任务和中断服务程序(ISR)都访问同一块数据,用 os_spin_lock_irqsave / os_spin_unlock_irqrestore:
任务 ──os_spin_lock_irqsave(&lock, &irq_save)──► 读/写共享缓冲 ──os_spin_unlock_irqrestore──►
ISR ──os_spin_lock_irqsave(&lock, &irq_save)──► 写共享缓冲 ──os_spin_unlock_irqrestore──►为什么必须用 irqsave 版本:中断上下文不能"睡眠",只能用忙等锁;同时必须关中断,防止当前 CPU 上正在执行的临界区被本 CPU 的中断打断,造成重入。irqsave 会保存并禁用中断,解锁时恢复原状态。
典型场景三:多核之间需要 CPU 间同步(arch 原子锁)
SMP 下 os_spin_lock 会调用 arch_spin_lock(架构原子指令,如 ARM 的 ldrex/strex、RISC-V 的 amoswap),保证多核间的互斥访问。
什么时候不该用自旋锁(选型边界)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 临界区较长(可能阻塞/睡眠) | 互斥锁 | 自旋锁忙等待会占住 CPU,阻塞会死锁 |
| 单核、任务间互斥 | 互斥锁 | 单核下无多核竞争,用互斥锁即可 |
| 需要优先级继承 | 互斥锁 | 自旋锁不支持优先级继承 |
| 持有期间需要睡眠/等信号量 | 互斥锁 | 持有自旋锁期间绝对禁止睡眠/阻塞,否则死锁 |
| 需要递归持有 | 互斥锁 | 自旋锁不支持递归 |
结合 OneOS 源码的使用注意点
- 持有时间必须极短:
os_spin_lock持锁期间会os_schedule_lock()锁调度,若临界区过长会影响实时性。 - 不能阻塞/睡眠:持锁期间调用
os_sem_pend等阻塞函数会导致死锁。 - 中断共享数据必须用
irqsave版本:普通os_spin_lock不关中断,无法防 ISR 重入。 os_spin_lock_irq_disable无条件启用中断:不恢复原状态,慎用。- 必须由获取锁的 CPU 释放:SMP 下跨 CPU 释放会触发调试断言(见 2.6)。
1.5 自旋锁控制块(os_spinlock_t)字段详解
自旋锁的控制块 os_spinlock_t(即 struct os_spinlock)定义在 kernel/include/os_spinlock.h 中,其字段随配置宏动态变化:
来源:kernel/include/os_spinlock.h
#define OS_SPINLOCK_INIT_FLAG (0x5A5AA5A5)
struct os_spinlock
{
#ifdef OS_USING_SMP
arch_spinlock_t arch_lock;
#endif
#ifdef OS_USING_SPINLOCK_CHECK
int32_t owner_cpu;
uint32_t object_inited;
void *owner;
#endif
#if !defined(OS_USING_SMP) && !defined(OS_USING_SPINLOCK_CHECK)
uint8_t padding;
#endif
};
typedef struct os_spinlock os_spinlock_t;各字段含义如下:
| 字段 | 所属配置 | 类型 | 说明 |
|---|---|---|---|
arch_lock | OS_USING_SMP | arch_spinlock_t | 架构原子锁,SMP 下多核互斥的实际载体(ARM ldrex/strex、RISC-V amoswap) |
owner_cpu | OS_USING_SPINLOCK_CHECK | int32_t | 持有锁的 CPU 编号,用于检测跨 CPU 释放锁 |
object_inited | OS_USING_SPINLOCK_CHECK | uint32_t | 初始化标志,恒为 OS_SPINLOCK_INIT_FLAG (0x5A5AA5A5),检测锁是否已初始化 |
owner | OS_USING_SPINLOCK_CHECK | void * | 持有锁的任务指针,用于检测重入和释放者是否当前任务 |
padding | 非 SMP 且非 CHECK | uint8_t | 占位字节,保证结构体非空 |
字段与概念对应表:
| 自旋锁概念 | 对应字段 | 依赖配置 |
|---|---|---|
| 多核互斥原子操作 | arch_lock | OS_USING_SMP |
| 锁是否已初始化 | object_inited | OS_USING_SPINLOCK_CHECK |
| 当前持有锁的 CPU | owner_cpu | OS_USING_SPINLOCK_CHECK |
| 当前持有锁的任务 | owner | OS_USING_SPINLOCK_CHECK |
关键点:
- 自旋锁没有"等待队列"(与信号量/互斥锁/邮箱不同)——它不记录等待者,而是靠"忙等待"让 CPU 原地打转。因此它没有
resource_node、没有全局资源链表,不需要挂到系统资源表。 - 自旋锁字段全部由配置宏决定:SMP 开→有
arch_lock;调试开→有 owner 相关字段;两者都不开→仅 1 字节padding。 object_inited用固定魔数0x5A5AA5A5标识已初始化,便于检测"未初始化就加锁"的错误。
二、API 详解
以下 API 定义在 kernel/include/os_spinlock.h 中:
#include <os_spinlock.h>2.1 静态定义
用宏在编译期定义并初始化一个自旋锁变量。完整宏定义(kernel/include/os_spinlock.h,与项目一致,含原始英文注释):
#define OS_SPINLOCK_INIT_FLAG (0x5A5AA5A5)
#ifdef OS_USING_SMP
#define SPIN_LOCK_INIT .arch_lock = ARCH_SPINLOCK_INIT_VALUE(SPINLOCK_ARCH_UNLOCKED),
#else
#define SPIN_LOCK_INIT
#endif
#ifdef OS_USING_SMP
#define SPIN_LOCK_INIT_VALUE(val) .arch_lock = ARCH_SPINLOCK_INIT_VALUE(val),
#else
#define SPIN_LOCK_INIT_VALUE(val)
#endif
#ifdef OS_USING_SPINLOCK_CHECK
#define SPINLOCK_DEBUG_INIT .owner_cpu = -1, .object_inited = OS_SPINLOCK_INIT_FLAG, .owner = OS_NULL,
#else
#define SPINLOCK_DEBUG_INIT
#endif
#if !defined(OS_USING_SMP) && !defined(OS_USING_SPINLOCK_CHECK)
#define SPINLOCK_PADDING_INIT .padding = 0,
#else
#define SPINLOCK_PADDING_INIT
#endif
#define OS_DEFINE_SPINLOCK(var) os_spinlock_t var = {SPIN_LOCK_INIT SPINLOCK_DEBUG_INIT SPINLOCK_PADDING_INIT}
#define OS_SPINLOCK_INITIAL_VALUE(val) \
{ \
SPIN_LOCK_INIT_VALUE(val) SPINLOCK_DEBUG_INIT SPINLOCK_PADDING_INIT \
}各宏详细解释:
OS_DEFINE_SPINLOCK(var) —— 定义并初始化自旋锁变量
#define OS_DEFINE_SPINLOCK(var) os_spinlock_t var = {SPIN_LOCK_INIT SPINLOCK_DEBUG_INIT SPINLOCK_PADDING_INIT}- 展开为:
os_spinlock_t var = { ... };(一个带初始化列表的自旋锁变量声明)。 - 初始化列表由三部分组成,随配置宏展开/留空:
| 初始化片段 | 何时展开 | 作用 |
|---|---|---|
SPIN_LOCK_INIT | SMP 开启时 | 初始化 arch_lock 为"未锁定"(SPINLOCK_ARCH_UNLOCKED) |
SPINLOCK_DEBUG_INIT | OS_USING_SPINLOCK_CHECK 开启时 | 初始化 owner_cpu = -1、object_inited = 0x5A5AA5A5、owner = OS_NULL |
SPINLOCK_PADDING_INIT | 非 SMP 且非 CHECK 时 | 初始化 padding = 0 |
- 用法示例:
OS_DEFINE_SPINLOCK(g_data_lock);
/* SMP+CHECK 下展开为:
* os_spinlock_t g_data_lock = {
* .arch_lock = ARCH_SPINLOCK_INIT_VALUE(SPINLOCK_ARCH_UNLOCKED),
* .owner_cpu = -1, .object_inited = 0x5A5AA5A5, .owner = OS_NULL,
* };
*/关键点:OS_DEFINE_SPINLOCK 是静态定义方式(编译期变量,无需 init)。若要动态分配(malloc),则必须调用 os_spin_lock_init(见 2.2)初始化。SPIN_LOCK_INIT 展开的 SPINLOCK_ARCH_UNLOCKED 确保锁初始为"未锁定"状态。
OS_SPINLOCK_INITIAL_VALUE(val) —— 生成自旋锁初始化列表(用于结构体成员)
#define OS_SPINLOCK_INITIAL_VALUE(val) \
{ \
SPIN_LOCK_INIT_VALUE(val) SPINLOCK_DEBUG_INIT SPINLOCK_PADDING_INIT \
}- 生成一个初始化列表(
{...}),供自旋锁作为其他结构体的成员时使用。 val指定arch_lock的初始锁定值(SPINLOCK_ARCH_LOCKED/SPINLOCK_ARCH_UNLOCKED)。- 用法:在结构体中
os_spinlock_t lock = OS_SPINLOCK_INITIAL_VALUE(SPINLOCK_ARCH_UNLOCKED);。
2.2 初始化自旋锁
void os_spin_lock_init(os_spinlock_t *lock);| 参数 | 说明 |
|---|---|
lock | 要初始化的自旋锁指针 |
| 返回值 | 无 |
用于动态分配或需要重新初始化的自旋锁;静态
OS_DEFINE_SPINLOCK定义的锁已初始化,无需再调。
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock_init(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
#ifdef OS_USING_SMP
arch_spin_lock_init(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
lock->owner_cpu = -1;
lock->object_inited = OS_SPINLOCK_INIT_FLAG;
lock->owner = OS_NULL;
#endif
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_lock_init() 时无条件执行(入口硬性校验)。lock 非空是后续所有字段赋值的前提。
第二步:初始化架构原子锁(条件:OS_USING_SMP 开启)—— 复位 arch_lock
#ifdef OS_USING_SMP
arch_spin_lock_init(&lock->arch_lock);
#endif进入本步的条件:OS_USING_SMP 已启用(否则整段不编译,arch_lock 字段都不存在)。
arch_spin_lock_init(&lock->arch_lock):把架构原子锁复位为未锁定状态,为后续多核互斥做好准备。
第三步:初始化调试字段(条件:OS_USING_SPINLOCK_CHECK 开启)—— 复位 owner 相关字段
#ifdef OS_USING_SPINLOCK_CHECK
lock->owner_cpu = -1;
lock->object_inited = OS_SPINLOCK_INIT_FLAG;
lock->owner = OS_NULL;
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用(否则整段不编译)。
owner_cpu = -1:表示暂无 CPU 持有(初始无效 CPU 编号)。object_inited = OS_SPINLOCK_INIT_FLAG:写入初始化魔数0x5A5AA5A5——后续加锁时若该值不对,说明锁未初始化,触发断言。owner = OS_NULL:暂无持有任务。
关键点:os_spin_lock_init 与 OS_DEFINE_SPINLOCK 的初始化效果相同,只是时机不同——前者运行时初始化(适合动态分配),后者编译期初始化。初始化后锁处于"未锁定"状态。
使用示例:
os_spinlock_t *lock = (os_spinlock_t *)malloc(sizeof(os_spinlock_t));
if (lock != OS_NULL)
{
os_spin_lock_init(lock); /* 动态分配后必须初始化 */
os_spin_lock(lock); /* 使用锁:加锁 */
do_critical_work(); /* 临界区 */
os_spin_unlock(lock); /* 解锁 */
free(lock);
}2.3 基本锁操作
void os_spin_lock(os_spinlock_t *lock); /* 获取自旋锁(忙等) */
os_bool_t os_spin_trylock(os_spinlock_t *lock); /* 尝试获取自旋锁(非阻塞) */
void os_spin_unlock(os_spinlock_t *lock); /* 释放自旋锁 */| 函数 | 说明 |
|---|---|
os_spin_lock | 获取自旋锁,如果锁被占用则自旋等待 |
os_spin_trylock | 尝试获取自旋锁,成功返回 OS_TRUE,失败返回 OS_FALSE(不等待) |
os_spin_unlock | 释放自旋锁 |
os_spin_lock() —— 获取自旋锁(忙等)
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
os_schedule_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_lock() 时无条件执行(入口硬性校验)。
第二步:锁定调度(无条件执行)—— 防止持锁期间任务被抢占
os_schedule_lock();进入本步的条件:第一步断言通过。os_schedule_lock() 锁住调度器——持有自旋锁期间当前任务不会被调度器切换出去(保证临界区原子性)。
第三步:加锁前调试检查(条件:OS_USING_SPINLOCK_CHECK 开启)—— 检测未初始化/重入
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。
_k_spin_lock_dbg(lock)内部检查(见 2.6):object_inited == 0x5A5AA5A5:锁已初始化;- 当前任务不是
lock->owner(防同一任务重入自旋锁); - SMP 下当前 CPU 不是
owner_cpu(防同一 CPU 重入)。
第四步:获取架构原子锁(条件:OS_USING_SMP 开启)—— 多核互斥忙等
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif进入本步的条件:OS_USING_SMP 已启用。
arch_spin_lock(&lock->arch_lock):架构原子锁操作。若锁被其他 CPU 持有,当前 CPU 在此自旋忙等(循环ldrex/strex或amoswap),直到获得锁。- 非 SMP 下无此步骤(单核无跨核竞争,仅靠
os_schedule_lock保证互斥)。
第五步:记录锁持有者(条件:OS_USING_SPINLOCK_CHECK 开启)—— 供解锁时校验
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。
_k_spin_lock_set_owner(lock)把owner设为当前任务、owner_cpu设为当前 CPU,供os_spin_unlock校验"是否由正确持有者释放"。
os_spin_trylock() —— 尝试获取自旋锁(非阻塞)
完整源码(kernel/source/os_spinlock.c,与项目一致):
os_bool_t os_spin_trylock(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
os_schedule_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
if (arch_spin_trylock(&lock->arch_lock) == OS_TRUE)
{
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return OS_TRUE;
}
else
{
os_schedule_unlock();
return OS_FALSE;
}
#else
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return OS_TRUE;
#endif
}分步详解:
第一步:参数校验 + 锁调度(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));
os_schedule_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif进入本步的条件:调用 os_spin_trylock() 时无条件执行。先断言 lock 非空,再 os_schedule_lock() 锁调度,调试模式下加锁前检查(同 os_spin_lock 第三步)。
第二步:尝试获取原子锁(条件:OS_USING_SMP 开启)—— 成功返回 OS_TRUE,失败返回 OS_FALSE
#ifdef OS_USING_SMP
if (arch_spin_trylock(&lock->arch_lock) == OS_TRUE)
{
_k_spin_lock_set_owner(lock);
return OS_TRUE;
}
else
{
os_schedule_unlock();
return OS_FALSE;
}
#else
_k_spin_lock_set_owner(lock);
return OS_TRUE;
#endif进入本步的条件:第一步断言通过。
- 子分支 A(SMP 下,条件
arch_spin_trylock(...) == OS_TRUE):锁空闲,arch_spin_trylock一次性成功获取(非忙等)→ 调试下记录 owner → 返回OS_TRUE。 - 子分支 B(SMP 下,条件
arch_spin_trylock(...) == OS_FALSE):锁被占用→os_schedule_unlock()撤销锁调度 → 返回OS_FALSE(不等待,立即返回失败)。 - 子分支 C(非 SMP,无条件):单核下无跨核竞争,
arch_spin_trylock必然成功 → 记录 owner → 返回OS_TRUE。
关键点:os_spin_trylock 与 os_spin_lock 的区别是不忙等——锁被占用时立即返回 OS_FALSE,调用方需自行处理"获取失败"(如执行其他工作或稍后再试)。
os_spin_unlock() —— 释放自旋锁
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_unlock(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_schedule_unlock();
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_unlock() 时无条件执行(入口硬性校验)。
第二步:解锁前校验 + 清除持有者(条件:OS_USING_SPINLOCK_CHECK 开启)—— 检测错误释放
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。
_k_spin_unlock_dbg(lock):校验owner == 当前任务且owner_cpu == 当前 CPU——若由非持有者或跨 CPU 释放,触发断言。_k_spin_lock_cancel_owner(lock):把owner置OS_NULL、owner_cpu置-1(清除持有者信息)。
第三步:释放架构原子锁(条件:OS_USING_SMP 开启)—— 让其他 CPU 可获取
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif进入本步的条件:OS_USING_SMP 已启用。arch_spin_unlock 释放原子锁,使其他自旋等待的 CPU 能获取。
第四步:解锁调度(无条件执行)—— 恢复调度
os_schedule_unlock();进入本步的条件:第三步完成。os_schedule_unlock() 恢复调度(与 os_spin_lock 的 os_schedule_lock 配对),此后当前任务可被调度器切换。
关键点:os_spin_lock/os_spin_unlock 是严格配对的——加锁时 os_schedule_lock,解锁时 os_schedule_unlock;加锁时 arch_spin_lock,解锁时 arch_spin_unlock。漏掉任一步都会破坏互斥或导致调度错乱。
使用示例:
OS_DEFINE_SPINLOCK(g_data_lock);
static int g_shared = 0;
void task_a_access(void)
{
os_spin_lock(&g_data_lock);
g_shared++; /* 极短临界区 */
os_spin_unlock(&g_data_lock);
}2.4 IRQ 安全版本
void os_spin_lock_irqsave(os_spinlock_t *lock, os_ubase_t *irq_save);
void os_spin_unlock_irqrestore(os_spinlock_t *lock, os_ubase_t irq_save);| 函数 | 说明 |
|---|---|
os_spin_lock_irqsave | 获取自旋锁,同时保存当前中断状态到 irq_save,然后禁用中断 |
os_spin_unlock_irqrestore | 释放自旋锁,同时恢复之前保存的中断状态 |
这是最常用的版本,用于保护任务和中断共享的临界区。配对使用。
os_spin_lock_irqsave() —— 加锁 + 保存并禁用中断
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock_irqsave(os_spinlock_t *lock, os_ubase_t *irq_save)
{
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));
*irq_save = os_irq_lock();
os_schedule_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;
}分步详解:
第一步:参数校验(条件:lock、irq_save 均非空)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));进入本步的条件:调用 os_spin_lock_irqsave() 时无条件执行。irq_save 是传出参数(保存中断状态),必须非空。
第二步:保存并禁用中断(无条件执行)—— *irq_save 记录原中断状态
*irq_save = os_irq_lock();进入本步的条件:第一步断言通过。os_irq_lock() 返回原中断状态并禁用中断,存入 *irq_save。这样解锁时可用 os_irq_unlock(irq_save) 恢复原状态。
为什么必须保存状态? 若在临界区前中断本来已启用,直接禁用即可;但若本来已禁用(如嵌套),解锁时若无条件启用会破坏外层的禁用。irq_save 记录原状态,保证恢复正确。
第三步:锁调度 + 加锁前检查(条件:见前)—— 与基本锁一致
os_schedule_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif进入本步的条件:第二步完成。os_schedule_lock() 锁调度;调试模式下加锁前检查(同 os_spin_lock 第三步)。
第四步:获取架构原子锁 + 记录持有者(条件:OS_USING_SMP / OS_USING_SPINLOCK_CHECK 开启)—— 与基本锁一致
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif进入本步的条件:第三步完成。
- SMP 下
arch_spin_lock多核忙等获取原子锁; - 调试下
_k_spin_lock_set_owner记录持有者。
os_spin_unlock_irqrestore() —— 解锁 + 恢复中断状态
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_unlock_irqrestore(os_spinlock_t *lock, os_ubase_t irq_save)
{
OS_ASSERT((lock != OS_NULL));
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_unlock(irq_save);
os_schedule_unlock();
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_unlock_irqrestore() 时无条件执行。
第二步:解锁前校验 + 清除持有者(条件:OS_USING_SPINLOCK_CHECK 开启)—— 与基本锁一致
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。校验是否由正确持有者释放,并清除 owner。
第三步:释放原子锁 + 恢复中断状态(条件:SMP 开启 / 无条件)—— 与加锁对称
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_unlock(irq_save);
os_schedule_unlock();进入本步的条件:第二步完成。
- SMP 下
arch_spin_unlock释放原子锁; os_irq_unlock(irq_save)恢复中断状态(用irqsave保存的原状态);os_schedule_unlock()恢复调度。
关键点 —— irqsave/irqrestore 配对使用:
os_spin_lock_irqsave(lock, &irq_save)加锁 + 存原中断状态;os_spin_unlock_irqrestore(lock, irq_save)解锁 + 恢复原中断状态(而不是无条件启用)。- 必须配对,且
irq_save变量贯穿两者。若漏配,中断状态会错乱(中断被永久禁用或提前启用)。
其他 IRQ 版本(os_spin_lock_irq / os_spin_lock_irq_rt / os_spin_unlock_irq)
void os_spin_lock_irq(os_spinlock_t *lock, os_ubase_t *irq_save);
void os_spin_lock_irq_rt(os_spinlock_t *lock, os_ubase_t *irq_save);
void os_spin_unlock_irq(os_spinlock_t *lock, os_ubase_t irq_save);| 函数 | 说明 |
|---|---|
os_spin_lock_irq | 获取自旋锁并禁用中断(不锁调度) |
os_spin_lock_irq_rt | 实时自旋锁:关中断尝试获取,失败则先恢复中断再重试(避免长时间关中断) |
os_spin_unlock_irq | 释放自旋锁并恢复中断 |
os_spin_lock_irq() —— 加锁 + 禁用中断(不锁调度)
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock_irq(os_spinlock_t *lock, os_ubase_t *irq_save)
{
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));
*irq_save = os_irq_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;
}分步详解:
第一步:参数校验(条件:lock、irq_save 均非空)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));进入本步的条件:调用 os_spin_lock_irq() 时无条件执行。irq_save 是传出参数,必须非空。
第二步:保存并禁用中断(无条件执行)
*irq_save = os_irq_lock();进入本步的条件:第一步断言通过。os_irq_lock() 保存原中断状态并禁用中断,存入 *irq_save。
第三步:加锁前检查 + 获取原子锁 + 记录持有者(条件:CHECK/SMP 开启)—— 与 os_spin_lock 基本一致
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif进入本步的条件:第二步完成。与 os_spin_lock 不同:这里没有 os_schedule_lock()——os_spin_lock_irq 通过禁用中断来保证临界区原子性(单核下关中断即阻止任务切换),不额外锁调度。
关键点 —— os_spin_lock vs os_spin_lock_irq 的区别:
os_spin_lock:锁调度(os_schedule_lock),不关中断——适合任务上下文,但防不了本 CPU 的 ISR 打断。os_spin_lock_irq:禁用中断(os_irq_lock),不锁调度——适合任务与中断共享的临界区。
os_spin_lock_irq_rt() —— 实时自旋锁
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock_irq_rt(os_spinlock_t *lock, os_ubase_t *irq_save)
{
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));
while (1)
{
*irq_save = os_irq_lock();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
if (arch_spin_trylock(&lock->arch_lock) == OS_TRUE)
{
break;
}
else
{
os_irq_unlock(*irq_save);
}
#else
break;
#endif
}
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;
}分步详解:
第一步:参数校验(条件:lock、irq_save 均非空)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));
OS_ASSERT((irq_save != OS_NULL));进入本步的条件:调用 os_spin_lock_irq_rt() 时无条件执行。
第二步:循环尝试获取锁(条件:OS_USING_SMP 开启)—— 失败则先恢复中断再重试
while (1)
{
*irq_save = os_irq_lock();
if (arch_spin_trylock(&lock->arch_lock) == OS_TRUE)
{
break;
}
else
{
os_irq_unlock(*irq_save);
}
}进入本步的条件:第一步断言通过。
- 子分支 A:条件
arch_spin_trylock(...) == OS_TRUE(锁空闲)→ 获取成功,break跳出循环。 - 子分支 B:条件
arch_spin_trylock(...) == OS_FALSE(锁被占用)→os_irq_unlock(*irq_save)先恢复中断,回到while(1)重试。
关键点 —— 为什么叫"实时"(rt):
- 普通
os_spin_lock在 SMP 下自旋时一直保持中断禁用,会长时间关中断,影响实时性。 os_spin_lock_irq_rt在自旋等待期间先恢复中断(os_irq_unlock),让中断能响应;只有拿到锁的那次才真正关中断。这样既保证互斥,又避免长时间关中断。
第三步:记录持有者(条件:OS_USING_SPINLOCK_CHECK 开启)
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;进入本步的条件:第二步 break(成功获取)。调试下记录持有者。
os_spin_unlock_irq() —— 解锁 + 恢复中断
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_unlock_irq(os_spinlock_t *lock, os_ubase_t irq_save)
{
OS_ASSERT((lock != OS_NULL));
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_unlock(irq_save);
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_unlock_irq() 时无条件执行。
第二步:解锁前校验 + 清除持有者(条件:CHECK 开启)
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。校验释放者身份,清除 owner。
第三步:释放原子锁 + 恢复中断状态
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_unlock(irq_save);进入本步的条件:第二步完成。SMP 下释放原子锁,os_irq_unlock(irq_save) 恢复原中断状态。
使用示例(IRQ 安全版本,任务与中断共享数据):
OS_DEFINE_SPINLOCK(g_uart_lock);
static char g_uart_buffer[256];
static int g_uart_index = 0;
/* 中断服务程序:写入数据 */
void uart_rx_isr(void)
{
os_ubase_t irq_save;
char data = read_uart_data();
os_spin_lock_irqsave(&g_uart_lock, &irq_save); /* 加锁 + 保存并禁用中断 */
g_uart_buffer[g_uart_index++] = data; /* 临界区 */
if (g_uart_index >= 256)
g_uart_index = 0;
os_spin_unlock_irqrestore(&g_uart_lock, irq_save); /* 解锁 + 恢复中断状态 */
}
/* 任务:读取数据 */
void uart_reader_task(void *parameter)
{
os_ubase_t irq_save;
while (1)
{
os_spin_lock_irqsave(&g_uart_lock, &irq_save);
int local_index = g_uart_index;
os_spin_unlock_irqrestore(&g_uart_lock, irq_save);
printf("buffer index: %d\n", local_index);
os_task_sleep(100);
}
}2.5 简化 IRQ 版本
void os_spin_lock_irq_disable(os_spinlock_t *lock); /* 获取自旋锁并禁用中断 */
void os_spin_unlock_irq_enable(os_spinlock_t *lock); /* 释放自旋锁并启用中断 */注意:这个版本无条件启用中断,不会恢复之前的中断状态。如果调用前中断已禁用,
unlock后会被错误地启用。
os_spin_lock_irq_disable() —— 加锁 + 禁用中断(不保存状态)
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_lock_irq_disable(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
os_irq_disable();
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_lock_irq_disable() 时无条件执行。
第二步:禁用中断(无条件执行,不保存状态)
os_irq_disable();进入本步的条件:第一步断言通过。os_irq_disable() 无条件禁用中断——与 os_irq_lock() 不同,它不返回/不保存原状态。
第三步:加锁前检查 + 获取原子锁 + 记录持有者(条件:CHECK/SMP 开启)
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_dbg(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_lock(&lock->arch_lock);
#endif
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_lock_set_owner(lock);
#endif进入本步的条件:第二步完成。与 os_spin_lock_irq 类似(不锁调度),但用 os_irq_disable(不保存状态)代替 os_irq_lock(保存状态)。
os_spin_unlock_irq_enable() —— 解锁 + 启用中断
完整源码(kernel/source/os_spinlock.c,与项目一致):
void os_spin_unlock_irq_enable(os_spinlock_t *lock)
{
OS_ASSERT((lock != OS_NULL));
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_enable();
return;
}分步详解:
第一步:参数校验(条件:lock != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT((lock != OS_NULL));进入本步的条件:调用 os_spin_unlock_irq_enable() 时无条件执行。
第二步:解锁前校验 + 清除持有者(条件:CHECK 开启)
#ifdef OS_USING_SPINLOCK_CHECK
_k_spin_unlock_dbg(lock);
_k_spin_lock_cancel_owner(lock);
#endif进入本步的条件:OS_USING_SPINLOCK_CHECK 已启用。校验释放者身份,清除 owner。
第三步:释放原子锁 + 启用中断
#ifdef OS_USING_SMP
arch_spin_unlock(&lock->arch_lock);
#endif
os_irq_enable();进入本步的条件:第二步完成。SMP 下释放原子锁,os_irq_enable() 无条件启用中断。
关键点 —— 这个版本的缺陷:
os_spin_unlock_irq_enable用os_irq_enable()无条件启用中断,不像irqrestore恢复原状态。- 若调用前中断已禁用(如嵌套在别的关中断区),解锁后会被错误地启用,破坏外层的中断保护。
- 因此它只在明确知道"调用前中断是启用的"时才安全。否则应使用
os_spin_lock_irqsave/os_spin_unlock_irqrestore。
使用示例:
OS_DEFINE_SPINLOCK(g_simple_lock);
void simple_irq_example(void)
{
/* 前提:调用前中断是启用的(否则 unlock 会错误地启用中断) */
os_spin_lock_irq_disable(&g_simple_lock);
/* 临界区(中断已禁用) */
do_critical_work();
os_spin_unlock_irq_enable(&g_simple_lock);
}2.6 调试检查
在 oneos_config.h 中启用 OS_USING_SPINLOCK_CHECK 后,自旋锁结构体会增加调试字段(见 1.5)。加锁/解锁时会调用四个调试辅助函数检测错误。
完整源码(kernel/source/os_spinlock.c,与项目一致):
OS_INLINE void _k_spin_lock_dbg(os_spinlock_t *lock)
{
os_task_t *task;
OS_ASSERT(OS_SPINLOCK_INIT_FLAG == lock->object_inited);
task = _k_task_self();
if (OS_NULL != task)
{
OS_ASSERT_EX(*((volatile void **)(&lock->owner)) != (void *)task,
"Task[%s] spinlock recursion\r\n",
task->name);
#ifdef OS_USING_SMP
OS_ASSERT_EX((*((volatile int32_t *)(&lock->owner_cpu))) != os_cpu_id_get(),
"[%d]Cpu[%d] spinlock recursion task[%s]",
(*((volatile uint32_t *)(&lock->owner_cpu))),
os_cpu_id_get(),
task->name);
#endif
}
return;
}
OS_INLINE void _k_spin_unlock_dbg(os_spinlock_t *lock)
{
os_task_t *task;
task = _k_task_self();
if (OS_NULL != task)
{
OS_ASSERT_EX((*((volatile void **)(&lock->owner))) == ((void *)task),
"Spinlock owner[%s] is not current[%s]\n",
(OS_NULL == lock->owner) ? "none" : ((os_task_t *)lock->owner)->name,
task->name);
#ifdef OS_USING_SMP
OS_ASSERT_EX((*((volatile int32_t *)(&lock->owner_cpu))) == os_cpu_id_get(),
"Spinlock owner cpu[%d] is not current cpu[%d], owner[%s] current[%s]",
lock->owner_cpu,
os_cpu_id_get(),
(OS_NULL == lock->owner) ? "none" : ((os_task_t *)lock->owner)->name,
task->name);
#endif
}
return;
}
OS_INLINE void _k_spin_lock_set_owner(os_spinlock_t *lock)
{
os_task_t *task;
task = _k_task_self();
if (OS_NULL != task)
{
*((volatile void **)(&lock->owner)) = (void *)task;
#ifdef OS_USING_SMP
*((int32_t *)(&lock->owner_cpu)) = os_cpu_id_get();
#else
*((int32_t *)(&lock->owner_cpu)) = 0;
#endif
}
return;
}
OS_INLINE void _k_spin_lock_cancel_owner(os_spinlock_t *lock)
{
*(volatile void **)(&lock->owner) = OS_NULL;
*(volatile int32_t *)(&lock->owner_cpu) = -1;
return;
}四个辅助函数的作用:
| 函数 | 在何时调用 | 检测内容 |
|---|---|---|
_k_spin_lock_dbg | os_spin_lock* 加锁前 | ①锁已初始化(object_inited == 0x5A5AA5A5);②当前任务不是 owner(防同一任务重入);③当前 CPU 不是 owner_cpu(SMP 防同一 CPU 重入) |
_k_spin_unlock_dbg | os_spin_unlock* 解锁前 | ④owner == 当前任务(必须由持有者释放);⑤owner_cpu == 当前 CPU(SMP 不能跨 CPU 释放) |
_k_spin_lock_set_owner | 加锁成功后 | 把 owner 设为当前任务、owner_cpu 设为当前 CPU |
_k_spin_lock_cancel_owner | 解锁时 | 清 owner = OS_NULL、owner_cpu = -1 |
分步详解(以 _k_spin_lock_dbg 为例):
第一步:检测锁是否已初始化(条件:OS_USING_SPINLOCK_CHECK 开启)—— 未初始化则断言崩溃
OS_ASSERT(OS_SPINLOCK_INIT_FLAG == lock->object_inited);进入本步的条件:os_spin_lock* 加锁时启用调试检查(_k_spin_lock_dbg 被调用)。
- 条件
object_inited != 0x5A5AA5A5:锁未初始化(或已损坏)→ 断言崩溃,提示应先os_spin_lock_init或用OS_DEFINE_SPINLOCK定义。
第二步:检测同一任务重入(条件:存在当前任务 task != OS_NULL)
task = _k_task_self();
if (OS_NULL != task)
{
OS_ASSERT_EX(*((volatile void **)(&lock->owner)) != (void *)task,
"Task[%s] spinlock recursion\r\n",
task->name);进入本步的条件:当前存在任务上下文(_k_task_self() != OS_NULL,即不是中断/空闲态)。
- 条件
owner == task(当前任务已持有该锁却再次加锁)→ 触发spinlock recursion断言——自旋锁不支持递归持有。
第三步:检测同一 CPU 重入(条件:OS_USING_SMP 开启且存在当前任务)
#ifdef OS_USING_SMP
OS_ASSERT_EX((*((volatile int32_t *)(&lock->owner_cpu))) != os_cpu_id_get(),
"[%d]Cpu[%d] spinlock recursion task[%s]",
(*((volatile uint32_t *)(&lock->owner_cpu))),
os_cpu_id_get(),
task->name);
#endif进入本步的条件:OS_USING_SMP 已启用且存在当前任务。
- 条件
owner_cpu == 当前 CPU(同一 CPU 上重复获取)→ 触发断言(即使 owner 被清,CPU 信息也能暴露重入)。
调试检查能检测的错误汇总:
| 错误类型 | 触发条件 | 对应断言 |
|---|---|---|
| 未初始化就加锁 | object_inited != 0x5A5AA5A5 | OS_ASSERT |
| 同一任务重入锁 | owner == 当前任务 | spinlock recursion |
| 同一 CPU 重入锁(SMP) | owner_cpu == 当前 CPU | spinlock recursion |
| 非持有者释放 | owner != 当前任务 | Spinlock owner is not current |
| 跨 CPU 释放(SMP) | owner_cpu != 当前 CPU | Spinlock owner cpu is not current cpu |
三、使用原则
3.1 何时使用自旋锁
| 场景 | 推荐 |
|---|---|
| SMP 多核,极短临界区(几行代码) | ✅ 自旋锁 |
| 中断与任务共享数据 | ✅ 自旋锁 + irqsave |
| 单核,任务间互斥 | 使用互斥锁 |
| 临界区较长(可能阻塞) | 使用互斥锁 |
| 需要优先级继承 | 使用互斥锁 |
3.2 自旋锁使用注意事项
- 持有时间要极短:持有自旋锁期间不能做任何可能阻塞的操作(睡眠、等待信号量等)
- 不能递归:自旋锁不支持递归持有
- 中断安全:如果数据可能被中断访问,必须使用
irqsave版本 - 释放顺序:必须由获取锁的 CPU 释放锁
- 避免死锁:如果同时持有多个锁,必须按固定顺序获取
📝 本节小结
核心要点回顾
本节围绕自旋锁机制展开,核心要点如下:
1. 自旋锁的本质
- 自旋锁是忙等待同步机制——获取不到锁时 CPU 原地循环检查,不阻塞任务。
- 用"占住 CPU"换"无上下文切换开销",适合极短临界区(微秒级)。
- 与互斥锁互补:互斥锁阻塞让出 CPU(长临界区、支持优先级继承),自旋锁忙等(极短/中断临界区)。
2. 控制块与配置宏
| 配置 | struct os_spinlock 字段 | 行为 |
|---|---|---|
OS_USING_SMP | arch_lock | 多核原子互斥(ldrex/strex 等) |
OS_USING_SPINLOCK_CHECK | owner_cpu/object_inited/owner | 调试检查(防重入、防跨 CPU 释放) |
| 两者都不开 | padding | 几乎空操作 |
3. 三种加锁方式的区别
| 方式 | 关中断 | 锁调度 | 适用 |
|---|---|---|---|
os_spin_lock | 否 | 是 | 任务上下文(单核互斥) |
os_spin_lock_irqsave | 是(保存原状态) | 是 | 任务与中断共享数据(最常用) |
os_spin_lock_irq | 是(保存原状态) | 否 | 任务与中断共享数据 |
os_spin_lock_irq_rt | 自旋时先恢复中断 | 否 | 高实时性要求 |
os_spin_lock_irq_disable | 是(不保存) | 否 | 明确知道中断状态时 |
4. IRQ 安全版本必须配对使用
os_spin_lock_irqsave(lock, &irq_save)与os_spin_unlock_irqrestore(lock, irq_save)严格配对。irq_save保存原中断状态,解锁时恢复原状态而非无条件启用。
5. 调试检查(OS_USING_SPINLOCK_CHECK)
- 可检测:未初始化就加锁、同一任务/CPU 重入、非持有者释放、跨 CPU 释放。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 持有自旋锁期间睡眠/阻塞 | 绝对禁止——忙等待期间阻塞会死锁,应改用互斥锁 |
| 临界区过长 | 自旋锁只保护极短临界区;长的用互斥锁 |
任务与中断共享数据却用 os_spin_lock | 用 os_spin_lock_irqsave/os_spin_unlock_irqrestore(防 ISR 重入) |
irqsave/irqrestore 不配对 | 必须配对,irq_save 贯穿两者,否则中断状态错乱 |
用 os_spin_unlock_irq_enable 而无条件启用中断 | 若调用前中断已禁用会被错误启用;应改 irqrestore |
| 同一任务递归加锁 | 自旋锁不支持递归,会触发 spinlock recursion 断言 |
| 跨 CPU 释放锁(SMP) | 必须由获取锁的 CPU 释放,否则触发调试断言 |
忽略 os_spin_trylock 返回值 | 必须检查返回值:OS_TRUE 才进入临界区,OS_FALSE 不进入 |
下一步
自旋锁用于极短临界区的忙等保护。接下来请学习下一小节:
- 4.3.7 工作队列:了解如何把耗时的延迟工作交给内核工作队列任务异步执行