互斥锁
互斥锁
📚 本节导读
学习时长: 约 30 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: 任务管理、优先级、信号量基础
互斥锁(Mutex,Mutual Exclusion)是保护共享资源免受并发访问破坏的核心机制。与信号量不同,互斥锁具有所有权概念,只有持有锁的任务才能释放锁,并且 OneOS 实现了优先级继承来防止优先级反转。
🎯 学习目标
- 理解互斥锁与信号量的本质区别
- 掌握互斥锁的 lock/unlock 操作
- 理解优先级反转问题及优先级继承的解决方案
- 掌握递归互斥锁的使用场景
- 能够正确使用互斥锁保护共享资源
一、互斥锁概念
1.1 什么是互斥锁
互斥锁是一种拥有所有权的同步对象。同一时刻只有一个任务可以持有锁,只有持有锁的任务才能释放锁。
┌─────────────────────────────────────────────────────────────────┐
│ 互斥锁模型 │
│ │
│ ┌──────────┐ lock ┌──────────────────┐ │
│ │ 任务 A │ ─────────────────→│ 互斥锁 │ │
│ └──────────┘ 获取锁,进入临界区│ ┌────────────┐ │ │
│ │ │ │ owner: A │ │ │
│ ▼ │ │ recursive: │ │ │
│ ┌──────────┐ 临界区执行 │ │ count: 1 │ │ │
│ │ 共享资源 │ ←─────────────────│ │ wait_list │ │ │
│ └──────────┘ │ └────────────┘ │ │
│ │ └──────────────────┘ │
│ ▼ │
│ ┌──────────┐ unlock │
│ │ 任务 A │ ─────────────────→ 释放锁,唤醒等待者 │
│ └──────────┘ │
│ │
│ ┌──────────┐ lock (失败) │
│ │ 任务 B │ ─────────────────→ 进入等待队列,阻塞 │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘1.2 互斥锁 vs 信号量
| 特性 | 互斥锁 | 信号量 |
|---|---|---|
| 所有权 | 有,只有持有者可以释放 | 无,任何任务/中断都可以释放 |
| 优先级继承 | 支持 | 不支持 |
| 递归持有 | 支持(可选) | 不支持 |
| 中断中使用 | 否 | 仅 post 可以 |
| 典型用途 | 保护共享资源 | 资源计数、任务同步 |
1.3 优先级反转与优先级继承
优先级反转是实时系统中经典的并发问题。考虑以下场景:
优先级: 任务 H (高) > 任务 M (中) > 任务 L (低)
时间 →
───────────────────────────────────────────────────────────────
任务 H: [就绪] [等待锁] [获得锁][临界区]
任务 M: [就绪][执行中...........][完成]
任务 L: [获得锁][临界区......] [释放锁]
───────────────────────────────────────────────────────────────- 任务 L 获得锁,进入临界区
- 任务 H 就绪,抢占任务 L,但需要锁,被阻塞
- 任务 M 就绪,抢占任务 L 执行(任务 M 不需要锁)
- 任务 H 被任务 M 无限期阻塞——优先级反转
OneOS 的解决方案:优先级继承
当任务 H 因等待锁而阻塞时,OneOS 将持有锁的任务 L 的优先级临时提升到任务 H 的优先级。这样任务 M 无法抢占任务 L,任务 L 可以快速完成临界区并释放锁,任务 H 即可获得锁。
启用优先级继承后:
───────────────────────────────────────────────────────────────
任务 H: [就绪] [等待锁] [获得锁][临界区]
任务 M: [就绪] [就绪.........] [执行]
任务 L: [获得锁][临界区(优先级提升到 H)][释放锁]
───────────────────────────────────────────────────────────────1.4 互斥锁控制块(os_mutex_t)字段详解
互斥锁的内部控制块 os_mutex_t(即 struct os_mutex)定义在 kernel/source/os_prototypes.h 中,包含所有权、等待队列、优先级继承等关键字段。完整源码如下:
struct os_mutex
{
os_list_node_t task_list_head; /* Block task list head */
os_list_node_t resource_node; /* Node in resource list */
os_list_node_t hold_node;
os_task_t *owner; /* Mutex owner */
uint32_t lock_count; /* Current lock count */
os_bool_t is_recursive; /* Support recursive call. */
uint8_t object_inited; /* If mutex object is inited, value is OS_KOBJ_INITED */
uint8_t object_alloc_type; /* Indicates whether memory is allocated dynamically or statically,
value is OS_ALLOC_TYPE_STATIC or OS_KOBJ_ALLOC_TYPE_DYNAMIC */
uint8_t wake_type; /* The type to wake up blocking tasks, value is OS_MUTEX_WAKE_TYPE_PRIO
or OS_MUTEX_WAKE_TYPE_FIFO */
uint8_t original_priority;
char name[OS_NAME_MAX + 1]; /* Mutex name */
os_spinlock_t lock; /* Spin lock used under SMP */
};各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
task_list_head | os_list_node_t | 等待队列头:被该锁阻塞的任务挂在这个链表上(PRIO 模式按优先级排序) |
resource_node | os_list_node_t | 资源链表节点:把互斥锁挂到互斥锁资源链表(gs_os_mutex_resource_list_head),供系统统一管理 |
hold_node | os_list_node_t | 持有锁链表节点:把互斥锁挂到持有者任务的 hold_mutex_list_head 链表上 |
owner | os_task_t * | 持有者:当前拥有该锁的任务(锁空闲时为 OS_NULL),是"所有权"概念的核心 |
lock_count | uint32_t | 锁计数:普通锁为 0/1;递归锁可 >1(递归加锁次数) |
is_recursive | os_bool_t | 递归标志:OS_TRUE 为递归锁,OS_FALSE 为普通锁 |
object_inited | uint8_t | 内核对象初始化状态(OS_KOBJ_INITED / OS_KOBJ_DEINITED) |
object_alloc_type | uint8_t | 分配类型:OS_ALLOC_TYPE_STATIC(静态)/ OS_KOBJ_ALLOC_TYPE_DYNAMIC(动态) |
wake_type | uint8_t | 唤醒策略:OS_MUTEX_WAKE_TYPE_PRIO(按优先级)或 OS_MUTEX_WAKE_TYPE_FIFO(按等待时间) |
original_priority | uint8_t | 原始优先级:持有者获取锁时的优先级,优先级继承恢复时以此为基准 |
name | char[OS_NAME_MAX+1] | 互斥锁名称 |
lock | os_spinlock_t | 自旋锁(仅 SMP 编译),保护互斥锁内部操作的并发安全 |
这两个链表头是从哪来的?
上表提到的 gs_os_mutex_resource_list_head 和 hold_mutex_list_head,来源完全不同:
① gs_os_mutex_resource_list_head —— 互斥锁模块的全局变量
它是 os_mutex.c 中定义的一个全局静态链表头,系统只有一个,专门承载所有互斥锁的 resource_node:
/* kernel/source/os_mutex.c */
static os_list_node_t gs_os_mutex_resource_list_head = OS_LIST_INIT(gs_os_mutex_resource_list_head);
static OS_DEFINE_SPINLOCK(gs_os_mutex_resource_list_lock);- 用
OS_LIST_INIT在编译期初始化为空链表。 - 因为是
static全局变量,整个系统只有这一个互斥锁资源链表头。 - 旁边的
gs_os_mutex_resource_list_lock是配套的自旋锁,保护该链表的并发访问。
② hold_mutex_list_head —— 任务控制块(TCB)的字段
它是 os_task_t 结构体中的一个成员字段(见 4.2 任务控制块),每个任务都有自己独立的一个:
/* kernel/source/os_prototypes.h,os_task_t 结构体内部 */
#if defined(OS_USING_MUTEX)
os_list_node_t hold_mutex_list_head; /* 持有互斥锁链表头 */
#endif- 它是任务的一个属性,不是互斥锁模块的全局变量。
- 每个任务在创建时都自带一个空的
hold_mutex_list_head。 - 互斥锁被某任务持有时,其
hold_node就挂到该任务的hold_mutex_list_head上。
对比总结:
| 链表头 | 属于谁 | 数量 | 用途 |
|---|---|---|---|
gs_os_mutex_resource_list_head | 互斥锁模块(全局) | 1 个(全局) | 挂所有互斥锁的 resource_node |
hold_mutex_list_head | 每个任务(TCB 字段) | 每个任务 1 个 | 挂该任务持有的各锁的 hold_node |
resource_node(资源链表节点)详解
作用:把互斥锁挂到互斥锁资源链表 gs_os_mutex_resource_list_head 上,让系统能"一眼看到所有已创建的互斥锁"。
澄清:每种 IPC 对象各有自己独立的资源链表,并不是所有对象共享一个链表。例如互斥锁用
gs_os_mutex_resource_list_head、信号量用gs_os_semaphore_resource_list_head、事件用gs_os_event_resource_list_head(见各对象的resource_node字段)。这里的"互斥锁资源链表"只管理互斥锁这一种对象。
它什么时候被挂上/取下?
| 时机 | 操作 | 源码(os_mutex.c) |
|---|---|---|
| 创建时 | os_list_add_tail(&gs_os_mutex_resource_list_head, &mutex->resource_node) | 把新锁加入互斥锁资源链表尾部 |
| 销毁时 | os_list_del(&mutex->resource_node) | 从互斥锁资源链表移除该锁 |
互斥锁资源链表示意图:
gs_os_mutex_resource_list_head
│
▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 互斥锁 A │───►│ 互斥锁 B │───►│ 互斥锁 C │
└──────────┘ └──────────┘ └──────────┘
resource_node resource_node resource_node为什么需要这个链表? 它把系统中所有互斥锁统一管理起来,内核通过遍历它就能:
| 用途 | 说明 |
|---|---|
| 防重复创建 | 创建时遍历链表,检查某个静态控制块地址是否已被占用(见 2.2 第二步) |
| 检查存在性 | os_mutex_check_exist() 遍历链表判断某互斥锁是否有效 |
| 系统管理 | shell 的 list_mutex 等命令遍历链表列出所有互斥锁 |
一句话:resource_node 让互斥锁"被系统看见"——相当于把锁登记到一本"全局台账"里,方便统一管理、查询、防重复。
hold_node(持有锁链表节点)详解
作用:把互斥锁挂到持有者任务的 hold_mutex_list_head 链表上,记录"某个任务当前持有了哪些互斥锁"。
它什么时候被挂上/取下?
| 时机 | 操作 | 源码(os_mutex.c) |
|---|---|---|
| 加锁成功时 | os_list_add_tail(¤t_task->hold_mutex_list_head, &mutex->hold_node) | 把锁挂到持有者任务的持有锁链表 |
| 解锁时 | os_list_del(&mutex->hold_node) | 从持有者的持有锁链表移除该锁 |
持有锁链表示意图(假设任务 T 同时持有互斥锁 A 和 B):
任务 T 的 hold_mutex_list_head
│
▼
┌──────────┐ ┌──────────┐
│ 互斥锁 A │───►│ 互斥锁 B │
└──────────┘ └──────────┘
hold_node hold_node注意:resource_node 和 hold_node 是互斥锁 TCB 中的两个不同节点,它们各自挂在不同的链表上:
resource_node→ 挂到全局资源链表(所有互斥锁共享一个链表)。hold_node→ 挂到某个任务的持有锁链表(每个任务的链表独立)。
为什么需要这个链表? 它记录"每个任务当前持有哪些锁",内核通过这些信息:
| 用途 | 说明 |
|---|---|
| 销毁持锁任务检查 | os_task_destroy() 时检查 task->hold_mutex_list_head 是否为空;非空则返回 OS_BUSY,禁止销毁持锁任务(否则锁会悬空、引发死锁) |
| 优先级继承恢复 | 解锁时 _k_mutex_restore_priority() 遍历持有者所有持有的锁,计算正确的恢复优先级(任务可能同时持有多个锁,见 4.2.4 的 4.4 节) |
| 诊断/调试 | 查看某任务当前占用了哪些互斥锁 |
一句话:hold_node 让系统知道"这个任务手里攥着哪些锁"——是任务销毁检查和优先级继承恢复的依据。
resource_node 与 hold_node 对比:
| 对比项 | resource_node | hold_node |
|---|---|---|
| 挂在哪个链表 | 互斥锁资源链表(gs_os_mutex_resource_list_head) | 持有者任务的持有锁链表 |
| 记录的内容 | "系统里有哪些互斥锁" | "这个任务持有哪些互斥锁" |
| 管理视角 | 全局(所有锁) | 任务(某个任务) |
| 主要用途 | 防重复创建、check_exist、shell 列出 | 销毁持锁任务检查、优先级继承恢复 |
关键字段与互斥锁核心概念的对应:
| 互斥锁概念 | 对应字段 | 说明 |
|---|---|---|
| 所有权 | owner、lock_count | owner 记录持有者,只有它能解锁 |
| 互斥性 | lock_count | 普通锁为 1,同一时刻只有一个任务持有 |
| 等待机制 | task_list_head | 拿不到锁的任务挂到等待队列阻塞 |
| 优先级继承 | original_priority | 记录原始优先级,解锁时恢复(见 1.3) |
| 递归持有 | is_recursive、lock_count | 递归锁 lock_count 可 >1,计数归零才释放 |
| 任务销毁检查 | hold_node | 通过它判断任务是否持有锁(销毁持锁任务返回 OS_BUSY) |
说明:用户代码不应直接访问这些内部字段,而是通过
os_mutex.h提供的 API 操作。此处的字段解析用于帮助理解互斥锁的底层工作原理。
1.5 互斥锁与多个链表的整体关系图
互斥锁一共涉及三个链表,分别承载"互斥锁自身的管理"、"锁被哪个任务持有"、"哪些任务在等这把锁"三类信息:
| 链表 | 链表头 | 属于谁 | 存的内容 |
|---|---|---|---|
| 互斥锁资源链表 | gs_os_mutex_resource_list_head | 互斥锁模块(全局 1 个) | 所有已创建的互斥锁(挂 resource_node) |
| 持有锁链表 | hold_mutex_list_head | 每个任务(TCB 字段,各 1 个) | 某任务持有的各锁(挂 hold_node) |
| 锁的等待队列 | task_list_head(互斥锁内字段) | 互斥锁自己 | 等待这把锁的阻塞任务 |
三个链表与互斥锁的整体关系图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ 【链表 1】互斥锁资源链表(全局 1 个) │
│ gs_os_mutex_resource_list_head │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 互斥锁A │──►│ 互斥锁B │──►│ 互斥锁C │ ← 通过各锁的 resource_node 串联 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 【链表 2】持有锁链表(每个任务各 1 个,存于 TCB) │
│ 任务 T 的 hold_mutex_list_head → 互斥锁C.hold_node → 互斥锁X.hold_node │
│ ▲ │
│ │ (锁 C 被任务 T 持有时,其 hold_node 挂到这里) │
│ │
│ 【链表 3】锁的等待队列(每个互斥锁 1 个,存于互斥锁内) │
│ 互斥锁C 的 task_list_head → 任务 T1 → 任务 T2 → 任务 T3 │
│ ▲ │
│ │ (任务 T1/T2/T3 拿不到锁 C 时,被 k_block_task 挂到这里阻塞) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘阶段一:创建后、未被任何任务持有
互斥锁 C(刚创建)
┌──────────────────────────────┐
│ resource_node ═══► 已挂到互斥锁资源链表 (创建时 os_list_add_tail 挂入)
│ hold_node ═══► 未挂任何链表 (还没有任务持有它)
│ task_list_head ═══► 空等待队列 (还没有任务阻塞等它)
└──────────────────────────────┘
互斥锁资源链表:... → C.resource_node → ...
任务 T 的 hold_mutex_list_head:为空
互斥锁 C 的 task_list_head:为空(无任务等待)阶段二:被任务 T 加锁成功后,且任务 T1 在等待
互斥锁 C(被任务 T 持有)
┌──────────────────────────────┐
│ resource_node ═══► 仍在互斥锁资源链表 (不变,仍被系统管理)
│ hold_node ═══► 挂到任务 T 的 (加锁时 os_list_add_tail 挂入)
│ hold_mutex_list_head
│ task_list_head ═══► 有任务 T1 在等待 (T1 拿不到锁,被 k_block_task 阻塞)
└──────────────────────────────┘
互斥锁资源链表:... → C.resource_node → ...(不变)
任务 T 的 hold_mutex_list_head:C.hold_node →(还有其他任务 T 持有的锁)
互斥锁 C 的 task_list_head:T1(阻塞等待中)图中要表达的核心关系:
| 链表 | 属于谁 | 挂哪个节点 | 何时挂入 |
|---|---|---|---|
互斥锁资源链表(gs_os_mutex_resource_list_head) | 互斥锁模块(全局 1 个) | resource_node | 创建时(os_list_add_tail) |
持有锁链表(hold_mutex_list_head) | 持有者任务(TCB 字段,各 1 个) | hold_node | 加锁成功时 |
锁的等待队列(task_list_head) | 互斥锁自己 | 被阻塞的任务 | 加锁失败(锁被占用)时(k_block_task) |
一句话:
resource_node让互斥锁"被系统管理"(全局可见)。hold_node让系统知道"这个锁被哪个任务持有"(任务销毁检查、优先级继承的依据)。task_list_head记录"哪些任务在等这把锁"(阻塞队列,锁释放时从中唤醒等待者)。
二、API 详解
以下 API 定义在 kernel/include/os_mutex.h 中:
#include <os_mutex.h>注意:使用互斥锁需要在
oneos_config.h中启用OS_USING_MUTEX宏。
2.1 静态定义
#define OS_MUTEX_DEFINE(name) os_mutex_dummy_t name2.2 创建互斥锁
os_mutex_id os_mutex_create(os_mutex_dummy_t *mutex_cb,
const char *name,
os_bool_t recursive);| 参数 | 说明 |
|---|---|
mutex_cb | 互斥锁控制块指针。静态创建时传入 OS_MUTEX_DEFINE 定义的变量地址;动态创建时传 OS_NULL |
name | 互斥锁名称 |
recursive | OS_TRUE 表示递归锁,OS_FALSE 表示普通锁 |
| 返回值 | 成功返回互斥锁 ID,失败返回 OS_NULL |
完整源码(kernel/source/os_mutex.c,与项目一致):
os_mutex_id os_mutex_create(os_mutex_dummy_t *mutex_cb, const char *name, os_bool_t recursive)
{
os_bool_t flag;
const os_mutex_t *iter_mutex;
const os_list_node_t *pos;
os_mutex_t *mutex;
uint8_t alloc_type;
os_mutex_id mutex_id;
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != mutex_cb);
#endif
flag = OS_TRUE;
if (OS_NULL != mutex_cb)
{
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_mutex_resource_list_lock);
os_list_for_each(pos, &gs_os_mutex_resource_list_head)
{
iter_mutex = os_list_entry(pos, os_mutex_t, resource_node);
if (iter_mutex == mutex)
{
mutex = OS_NULL;
os_spin_unlock(&gs_os_mutex_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, MUTEX_TAG, "%s", OS_KERNEL_ERR_INFO);
flag = OS_FALSE;
break;
}
}
}
#ifdef OS_USING_HEAP
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
mutex = (os_mutex_t *)OS_KERNEL_MALLOC(sizeof(os_mutex_t));
if (OS_NULL == mutex)
{
OS_KERN_LOG(KERN_ERROR, MUTEX_TAG, "Malloc mutex memory failed");
flag = OS_FALSE;
}
}
#endif
if (OS_TRUE == flag)
{
_k_mutex_init(mutex, name, recursive, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_mutex_resource_list_lock);
}
os_list_add_tail(&gs_os_mutex_resource_list_head, &mutex->resource_node);
os_spin_unlock(&gs_os_mutex_resource_list_lock);
}
mutex_id = OS_TYPE_CONVERT(os_mutex_id, mutex);
return mutex_id;
}分步详解:
第一步:参数校验
OS_ASSERT(OS_FALSE == os_is_irq_active());
#ifndef OS_USING_HEAP
OS_ASSERT(OS_NULL != mutex_cb);
#endif详细解释:
OS_ASSERT(OS_FALSE == os_is_irq_active()):不能在中断上下文中创建互斥锁。- 未启用堆时断言
mutex_cb != OS_NULL:必须提供静态控制块(无法动态分配)。
第二步:静态创建(处理静态控制块)
if (OS_NULL != mutex_cb)
{
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_cb);
alloc_type = OS_ALLOC_TYPE_STATIC;
os_spin_lock(&gs_os_mutex_resource_list_lock);
os_list_for_each(pos, &gs_os_mutex_resource_list_head)
{
iter_mutex = os_list_entry(pos, os_mutex_t, resource_node);
if (iter_mutex == mutex)
{
mutex = OS_NULL;
os_spin_unlock(&gs_os_mutex_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, MUTEX_TAG, "%s", OS_KERNEL_ERR_INFO);
flag = OS_FALSE;
break;
}
}
}详细解释:用户传入非空 mutex_cb 时用静态控制块(标记 OS_ALLOC_TYPE_STATIC),并遍历互斥锁资源链表检查该控制块地址是否已被占用(防重复创建)。
第三步:动态创建(分配内存)
else
{
alloc_type = OS_KOBJ_ALLOC_TYPE_DYNAMIC;
mutex = (os_mutex_t *)OS_KERNEL_MALLOC(sizeof(os_mutex_t));
if (OS_NULL == mutex)
{
OS_KERN_LOG(KERN_ERROR, MUTEX_TAG, "Malloc mutex memory failed");
flag = OS_FALSE;
}
}详细解释:用户传入 OS_NULL 时从堆分配控制块(需 OS_USING_HEAP),失败则置 flag = OS_FALSE。
第四步:初始化并加入资源链表
if (OS_TRUE == flag)
{
_k_mutex_init(mutex, name, recursive, alloc_type);
if (OS_KOBJ_ALLOC_TYPE_DYNAMIC == (alloc_type & OS_KOBJ_ALLOC_TYPE_DYNAMIC))
{
os_spin_lock(&gs_os_mutex_resource_list_lock);
}
os_list_add_tail(&gs_os_mutex_resource_list_head, &mutex->resource_node);
os_spin_unlock(&gs_os_mutex_resource_list_lock);
}详细解释:
_k_mutex_init():初始化互斥锁各字段(名称、recursive递归标志、分配类型等)。- 动态创建时加自旋锁后把互斥锁登记到互斥锁资源链表(静态创建时第二步已加过锁)。
- 返回转换后的互斥锁 ID。
创建互斥锁的流程与链表示意图:
① 创建流程概览(对应 2.2 分步详解):
os_mutex_create(cb, name, recursive)
│
▼
① 参数校验(断言)
- 不能在中断中创建
- 未启用堆时必须传静态 cb
│
▼
② 静态 or 动态? (cb 是否为 NULL)
├─ 静态:用用户提供的控制块,检查是否重复使用
└─ 动态:从堆 malloc 控制块
│
▼
③ _k_mutex_init() 初始化控制块字段
│
▼
④ os_list_add_tail() 把锁挂入互斥锁资源链表 ←── 链表操作(见 ② 图)
│
▼
返回互斥锁 ID② 链表操作详解(环形双向链表,os_list_add_tail 挂入尾部):
资源链表通过每个互斥锁结构体内的 resource_node 节点(含 next/prev 指针)把各锁串起来。链表头 gs_os_mutex_resource_list_head 用 OS_LIST_INIT 初始化为空链表(next=prev=自身)。
创建前(已有 A、B):
next next
head ─────────────────► A.resource_node ───► B.resource_node ──► (回到 head)
gs_os_mutex_resource ◄────────────────────◄
_list_head prev prev
(链表头,next/prev │ │
指向首尾) ┌──┴──┐ ┌──┴──┐
│互斥锁A│ │互斥锁B│
└─────┘ └─────┘
执行 os_list_add_tail(&head, &C.resource_node):
head.prev 原本指向 B(尾)→ 改为指向 C
B.next 原本指向 head → 改为指向 C
C.next ────────► 指向 head(成为新的尾)
C.prev ────────► 指向 B创建后(A、B、C):
next next next
head ─────────────────► A.resource_node ───► B.resource_node ───► C.resource_node ──► (回到 head)
gs_os_mutex_resource ◄────────────────────◄───────────────────◄
_list_head prev prev prev
(链表头) ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
│互斥锁A│ │互斥锁B│ │互斥锁C│(新)
└─────┘ └─────┘ └─────┘关键点:
| 要点 | 说明 |
|---|---|
链表连接的是 resource_node | 链表通过结构体内的 resource_node 节点的 next/prev 指针把各互斥锁串起来,而非直接存结构体地址 |
| 环形双向链表 | 头节点 next 指向第一个锁,最后一个锁的 next 指回头节点,形成环 |
| 新锁挂到尾部 | os_list_add_tail 修改 head.prev 所指的尾节点,把 C 插到尾部 |
hold_node 此时不使用 | 创建时锁未被持有,hold_node 暂不挂链表(加锁成功后才挂到持有者任务,见 1.5 和 2.4) |
创建完成后,这个互斥锁就"被系统看见了"——可通过遍历资源链表找到它,供
os_mutex_check_exist()、shell 列出等使用。
2.3 销毁互斥锁
os_err_t os_mutex_destroy(os_mutex_id mutex_id);| 参数 | 说明 |
|---|---|
mutex_id | 要销毁的互斥锁 ID |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 销毁成功 |
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_destroy(os_mutex_id mutex_id)
{
os_mutex_t *mutex;
os_err_t ret;
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_FALSE == os_is_irq_active());
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
#ifndef OS_USING_HEAP
OS_ASSERT(OS_ALLOC_TYPE_STATIC == mutex->object_alloc_type);
#endif
ret = OS_SUCCESS;
if (OS_ALLOC_TYPE_STATIC == mutex->object_alloc_type)
{
_k_mutex_deinit(mutex);
}
else
{
_k_mutex_deinit(mutex);
OS_KERNEL_FREE(mutex);
}
return ret;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_ASSERT(OS_FALSE == os_is_irq_active()) | 不能在中断上下文中销毁(销毁会释放内存) |
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited) | 互斥锁必须已初始化(防止重复销毁) |
OS_ASSERT(OS_ALLOC_TYPE_STATIC == ...) | 未启用堆时互斥锁必须是静态分配 |
_k_mutex_deinit(mutex) | 反初始化互斥锁(清理各字段、等待队列) |
OS_KERNEL_FREE(mutex) | 仅动态分配的互斥锁释放堆内存 |
return OS_SUCCESS | 返回成功 |
关键点:只释放动态分配的内存——静态互斥锁(用户提供控制块)只 _k_mutex_deinit() 不释放;动态的才 OS_KERNEL_FREE。
2.4 加锁 / 解锁
os_err_t os_mutex_lock(os_mutex_id mutex_id, os_tick_t timeout);
os_err_t os_mutex_unlock(os_mutex_id mutex_id);| 函数 | 说明 |
|---|---|
os_mutex_lock | 获取互斥锁。timeout 为 OS_NO_WAIT 时非阻塞尝试;OS_WAIT_FOREVER 时永久等待 |
os_mutex_unlock | 释放互斥锁。只有持有锁的任务才能调用 |
os_mutex_lock() 完整源码与分步详解
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_lock(os_mutex_id mutex_id, os_tick_t timeout)
{
os_task_t *current_task;
os_bool_t need_sched;
os_err_t ret;
os_mutex_t *mutex;
OS_KERNEL_INIT();
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_FALSE == mutex->is_recursive);
OS_ASSERT(OS_FALSE == os_is_irq_active());
OS_ASSERT((OS_FALSE == os_is_irq_disabled()) || (OS_NO_WAIT == timeout));
OS_ASSERT((OS_FALSE == os_is_schedule_locked()) || (OS_NO_WAIT == timeout));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));
ret = OS_SUCCESS;
need_sched = OS_FALSE;
current_task = k_task_self();
if (current_task == OS_NULL)
{
return OS_SUCCESS;
}
OS_ASSERT_EX(mutex->owner != current_task, "Task(%s) use mutex(%s) recursively!", current_task->name, mutex->name);
OS_KERNEL_ENTER();
if (mutex->lock_count == 0U)
{
mutex->lock_count = 1U;
mutex->owner = current_task;
mutex->original_priority = current_task->current_priority;
os_list_add_tail(¤t_task->hold_mutex_list_head, &mutex->hold_node);
OS_KERNEL_EXIT();
}
else
{
if (timeout == OS_NO_WAIT)
{
OS_KERNEL_EXIT();
ret = OS_BUSY;
}
else
{
/* Priority inherit */
if ((OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type) &&
(current_task->current_priority < mutex->owner->current_priority))
{
(void)_k_mutex_set_owner_priority(mutex, current_task->current_priority);
}
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_FALSE);
}
OS_KERNEL_ENTER();
if ((OS_SUCCESS != ret) && (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type))
{
need_sched = _k_mutex_restore_priority(mutex);
}
if (need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
}
}
return ret;
}分步详解:
第一步:参数校验(含"禁止递归用普通锁"断言)
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_FALSE == mutex->is_recursive);
OS_ASSERT(OS_FALSE == os_is_irq_active());
OS_ASSERT((OS_FALSE == os_is_irq_disabled()) || (OS_NO_WAIT == timeout));
OS_ASSERT((OS_FALSE == os_is_schedule_locked()) || (OS_NO_WAIT == timeout));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));详细解释:
| 断言 | 作用 |
|---|---|
mutex != OS_NULL | 互斥锁指针非空 |
object_inited == OS_KOBJ_INITED | 互斥锁必须已初始化 |
is_recursive == OS_FALSE | 普通锁禁止递归加锁(递归锁用 os_mutex_recursive_lock,见 2.5) |
os_is_irq_active() == OS_FALSE | 不能在中断中加锁(会阻塞) |
后两条 timeout 断言 | 只有需要真正等待时才限制:不能在中断禁用/调度锁定时调用 |
timeout 范围断言 | 超时值合法 |
第二步:互斥锁空闲(lock_count == 0)—— 直接获取
if (mutex->lock_count == 0U)
{
mutex->lock_count = 1U;
mutex->owner = current_task;
mutex->original_priority = current_task->current_priority;
os_list_add_tail(¤t_task->hold_mutex_list_head, &mutex->hold_node);
OS_KERNEL_EXIT();
}详细解释:锁空闲时直接获得所有权:
lock_count = 1:标记锁已被占用。owner = current_task:记录持有者(所有权概念的核心)。original_priority = current_task->current_priority:记录持有者当前优先级,供优先级继承恢复用。os_list_add_tail(...hold_mutex_list_head...):把该锁挂到持有者的持有锁链表(后续销毁任务时检查)。
第三步:互斥锁被占用 —— 等待或返回忙
else
{
if (timeout == OS_NO_WAIT)
{
OS_KERNEL_EXIT();
ret = OS_BUSY;
}
else
{
/* Priority inherit */
if ((OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type) &&
(current_task->current_priority < mutex->owner->current_priority))
{
(void)_k_mutex_set_owner_priority(mutex, current_task->current_priority);
}
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_FALSE);
}
OS_KERNEL_ENTER();
if ((OS_SUCCESS != ret) && (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type))
{
need_sched = _k_mutex_restore_priority(mutex);
}
if (need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
}
}详细解释:
① timeout == OS_NO_WAIT(不等待):
OS_KERNEL_EXIT()退出临界区,ret = OS_BUSY(锁被占用且不等待,立即返回忙)。
② timeout != OS_NO_WAIT(阻塞等待,先执行优先级继承):
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type) && (current_task->current_priority < mutex->owner->current_priority):若等待者优先级高于持有者,调用_k_mutex_set_owner_priority()把持有者优先级临时提升到等待者优先级(详见 4.2.4 优先级继承)。k_block_task():把当前任务挂到锁的等待队列并让出 CPU,直到被唤醒或超时。最后一个参数决定唤醒方式:OS_TRUE= 按优先级(PRIO)、OS_FALSE= 按 FIFO。k_block_task()返回后(被唤醒或超时),重新进入临界区OS_KERNEL_ENTER()。
为什么说"返回后(被唤醒或超时)"?—— k_block_task() 不是普通函数
k_block_task() 和普通函数调用完全不同——它调用后不会立即返回,而是让当前任务"休眠"在那里,直到被唤醒或超时后任务恢复运行时才返回。整个过程如下:
当前任务调用 os_mutex_lock() → 锁被占用 → 调用 k_block_task()
│
▼
① 把当前任务从就绪队列移除,挂到 mutex->task_list_head 等待队列
② 当前任务状态 设为 BLOCK(有超时则加 SLEEP)
③ 触发调度,当前任务让出 CPU ──► 【当前任务在此"暂停"】
│(这段时间当前任务不在运行,CPU 执行别的任务)
▼
④ 其他任务 os_mutex_unlock() 唤醒等待者(或超时 tick 到达)
└─ k_unblock_task() 把当前任务从等待队列移回就绪队列,并设置 switch_retval
│
▼
⑤ 调度器重新选中当前任务,它恢复运行
└─ 从 k_block_task() 的调用处"返回",return task->switch_retval
│
▼
⑥ 回到 os_mutex_lock() 里 k_block_task() 的下一行继续执行关键理解:
k_block_task()的"返回"发生在任务重新获得 CPU 之后(步骤 ⑤),而不是调用当时(步骤 ③)。- 它返回的值是
task->switch_retval(os_block.c中return task->switch_retval):- 被唤醒(
k_unblock_task设置switch_retval = OS_SUCCESS)→ 返回OS_SUCCESS,表示成功拿到锁。 - 超时(延时到期,tick 中断处理)→ 返回超时错误码,表示没能拿到锁。
- 被唤醒(
- 所以
os_mutex_lock()在k_block_task()返回后,通过检查ret就能判断:任务到底是成功获得锁还是超时失败,从而决定下一步(成功则继续,超时则恢复持有者优先级等)。 - 若
ret != OS_SUCCESS(超时/失败)且为 PRIO 策略,调用_k_mutex_restore_priority()恢复持有者优先级(因为优先级继承已结束),返回是否需要调度。 - 按
need_sched决定OS_KERNEL_EXIT_SCHED()(触发调度)或OS_KERNEL_EXIT()(仅退出)。
锁的"等待队列"到底是哪个?
k_block_task() 的第三个参数就是等待队列——在 os_mutex_lock() 中传的是 &mutex->task_list_head,即互斥锁控制块(os_mutex_t)中的 task_list_head 字段(见 1.4 字段详解)。所有因拿不到该锁而被阻塞的任务,都会挂到这个链表上:
互斥锁 mutex 控制块
┌───────────────────────────┐
│ task_list_head ──► 等待队列头 │
│ ┌──────────────────────┐ │
│ │ 任务 T1(被阻塞) │ │
│ │ 任务 T2(被阻塞) │ │
│ │ 任务 T3(被阻塞) │ │
│ └──────────────────────┘ │
│ owner = 持有者任务 │
│ hold_node(挂持有者链表) │
└───────────────────────────┘关键点:
| 队列 | 链表头 | 挂入时机 |
|---|---|---|
| 锁的等待队列 | mutex->task_list_head | 任务拿不到锁被阻塞时,k_block_task() 把它挂到这里 |
| 锁的资源链表 | gs_os_mutex_resource_list_head | 锁创建时挂入(管理所有锁) |
| 锁的持有者链表 | 持有者任务的 hold_mutex_list_head | 锁被某任务持有成功时挂入 |
区别:task_list_head(等待队列)存的是"等待这把锁的阻塞任务";而 resource_node / hold_node 是互斥锁自己挂到管理链表的节点。三者链表完全不同,不要混淆。
os_mutex_unlock() 完整源码与分步详解
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_unlock(os_mutex_id mutex_id)
{
os_task_t *current_task;
os_task_t *block_task;
os_bool_t need_sched;
os_mutex_t *mutex;
OS_KERNEL_INIT();
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_FALSE == mutex->is_recursive);
OS_ASSERT(OS_FALSE == os_is_irq_active());
need_sched = OS_FALSE;
current_task = k_task_self();
if (current_task != OS_NULL)
{
/* Mutex only can be unlocked by owner */
OS_ASSERT_EX(mutex->owner == current_task,
"The mutex(addr: %p, name: %s, owner: %s) can't unlock by task(%s)",
mutex, mutex->name, mutex->owner->name, current_task->name);
OS_KERNEL_ENTER();
os_list_del(&mutex->hold_node);
/* Restore task priority */
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
need_sched = _k_mutex_restore_priority(mutex);
}
/* Get the new owner, if any */
if (!os_list_empty(&mutex->task_list_head))
{
block_task = os_list_first_entry(&mutex->task_list_head, os_task_t, task_node);
need_sched = k_unblock_task(block_task);
mutex->owner = block_task;
mutex->original_priority = block_task->current_priority;
os_list_add_tail(&mutex->owner->hold_mutex_list_head, &mutex->hold_node);
}
else
{
mutex->owner = OS_NULL;
mutex->lock_count = 0;
}
if (os_list_empty(¤t_task->hold_mutex_list_head) &&
(current_task->current_priority != current_task->backup_priority))
{
OS_KERN_LOG(KERN_INFO, MUTEX_TAG,
"New priority(%u) takes effect, old priority(%u)",
current_task->backup_priority, current_task->current_priority);
k_readyq_remove(current_task);
current_task->current_priority = current_task->backup_priority;
k_readyq_put(current_task);
need_sched = OS_TRUE;
}
if (need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
}
return OS_SUCCESS;
}分步详解:
第一步:所有权检查
/* Mutex only can be unlocked by owner */
OS_ASSERT_EX(mutex->owner == current_task,
"The mutex(...) can't unlock by task(%s)", ...);详细解释:只有持有者(owner)才能解锁。若其他任务尝试解锁,触发断言失败——这正是互斥锁"所有权"的强制保证。
第二步:优先级继承恢复
os_list_del(&mutex->hold_node);
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
need_sched = _k_mutex_restore_priority(mutex);
}详细解释:
- 从持有者的持有锁链表移除该锁。
_k_mutex_restore_priority():恢复持有者的优先级(因为优先级继承已不再需要)。若持有多个锁,会遍历计算正确的优先级(详见 4.2.4 的 4.4 节)。
第三步:转移所有权 / 释放锁
if (!os_list_empty(&mutex->task_list_head))
{
block_task = os_list_first_entry(&mutex->task_list_head, os_task_t, task_node);
need_sched = k_unblock_task(block_task);
mutex->owner = block_task; /* 新持有者 */
mutex->original_priority = block_task->current_priority;
os_list_add_tail(&mutex->owner->hold_mutex_list_head, &mutex->hold_node);
}
else
{
mutex->owner = OS_NULL; /* 无等待者,释放锁 */
mutex->lock_count = 0;
}详细解释:
- 有任务在等待:唤醒等待队列头部的任务(PRIO 模式为最高优先级),把它设为新持有者(
owner = block_task),锁直接转交。 - 无任务等待:清空
owner、lock_count = 0,锁完全释放。
关键点 —— 锁的转移与所有权:互斥锁解锁时,若有人等待,锁直接转交给等待者(mutex->owner = block_task,等待者被唤醒后直接成为新持有者,无需重新走一遍 lock)。
注意:"直接唤醒等待者"这个行为信号量也有(
os_semaphore_post()同样用k_unblock_task()唤醒等待者,等待者从os_semaphore_wait()返回后直接拿到信号量)。所以它不是互斥锁与信号量的区别。真正的区别在于所有权(owner):
- 互斥锁:解锁时必须把
owner显式转交给新等待者(mutex->owner = block_task),因为互斥锁有"持有者"概念,只有持有者能再次解锁。- 信号量:post 唤醒等待者时不涉及所有权,信号量没有"谁拥有它"的概念,唤醒只是让等待者继续执行。
2.5 递归锁
os_err_t os_mutex_recursive_lock(os_mutex_id mutex_id, os_tick_t timeout);
os_err_t os_mutex_recursive_unlock(os_mutex_id mutex_id);递归锁允许同一个任务多次获取同一个锁。每次 recursive_lock 增加内部计数(lock_count),每次 recursive_unlock 减少计数,直到计数归零才真正释放锁(把所有权转交他人或完全释放)。
它与普通锁(os_mutex_lock/unlock)的核心区别:
| 对比项 | 普通锁 | 递归锁 |
|---|---|---|
| 创建参数 | os_mutex_create(..., OS_FALSE) | os_mutex_create(..., OS_TRUE) |
| 同一任务重复加锁 | 触发断言(不允许) | 允许,计数累加 |
| 释放条件 | 一次 unlock 即释放 | 计数归零才真正释放 |
| 适用场景 | 简单互斥 | 递归调用/嵌套函数中需重复持锁 |
os_mutex_recursive_lock() 完整源码
来源: kernel/source/os_mutex.c
os_err_t os_mutex_recursive_lock(os_mutex_id mutex_id, os_tick_t timeout)
{
os_task_t *current_task;
os_bool_t need_sched;
os_err_t ret;
os_mutex_t *mutex;
OS_KERNEL_INIT();
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_TRUE == mutex->is_recursive);
OS_ASSERT(OS_FALSE == os_is_irq_active());
OS_ASSERT((OS_FALSE == os_is_irq_disabled()) || (OS_NO_WAIT == timeout));
OS_ASSERT((OS_FALSE == os_is_schedule_locked()) || (OS_NO_WAIT == timeout));
OS_ASSERT((timeout < (OS_TICK_MAX / 2)) || (OS_WAIT_FOREVER == timeout));
ret = OS_SUCCESS;
need_sched = OS_FALSE;
current_task = k_task_self();
if (current_task != OS_NULL)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&mutex->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
if ((0U == mutex->lock_count) || (current_task == mutex->owner))
{
if (0U == mutex->lock_count)
{
mutex->original_priority = current_task->current_priority;
mutex->owner = current_task;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_lock();
#endif
os_list_add_tail(¤t_task->hold_mutex_list_head, &mutex->hold_node);
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
#endif
}
mutex->lock_count++;
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&mutex->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
}
else
{
if (timeout == OS_NO_WAIT)
{
OS_KERNEL_EXIT();
ret = OS_BUSY;
}
else
{
if (timeout == OS_NO_WAIT)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&mutex->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
ret = OS_BUSY;
}
else
{
/* Priority inherit */
if ((OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type) &&
(current_task->current_priority < mutex->owner->current_priority))
{
(void)_k_mutex_set_owner_priority(mutex, current_task->current_priority);
}
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_FALSE);
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&mutex->lock, &irq_save);
k_kernel_spin_lock();
#else
OS_KERNEL_ENTER();
#endif
if ((OS_SUCCESS != ret) && (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type))
{
need_sched = _k_mutex_restore_priority(mutex);
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
os_spin_unlock_irq(&mutex->lock, irq_save);
if (need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
}
}
}
}
return ret;
}分步详解:
第一步:参数校验(含递归锁断言)
OS_ASSERT(OS_TRUE == mutex->is_recursive);详细解释:与普通锁最大的断言差异是 OS_ASSERT(OS_TRUE == mutex->is_recursive)——必须是递归锁才能调用 os_mutex_recursive_lock(),否则断言失败。其余断言(非空、已初始化、非中断上下文等)与普通锁一致。
第二步:核心判断 —— 锁空闲或自己是持有者
if ((0U == mutex->lock_count) || (current_task == mutex->owner))
{
if (0U == mutex->lock_count)
{
mutex->original_priority = current_task->current_priority;
mutex->owner = current_task;
os_list_add_tail(¤t_task->hold_mutex_list_head, &mutex->hold_node);
}
mutex->lock_count++;
OS_KERNEL_EXIT();
}详细解释:这就是递归锁的核心递归逻辑:
(0U == mutex->lock_count):锁空闲,当前任务成为持有者(记录owner、original_priority、挂hold_node)。(current_task == mutex->owner):锁已经被当前任务持有(递归加锁)——不再重新设置 owner,直接lock_count++。- 无论哪种情况,最终都
lock_count++,即递归加锁只增加计数,不重复设置 owner。
示例:同一任务连续 3 次调用 os_mutex_recursive_lock():
第 1 次:lock_count 0→1,设置 owner=自己,挂 hold_node
第 2 次:lock_count 1→2(owner 已是自己,跳过设置)
第 3 次:lock_count 2→3(递归加锁)第三步:锁被其他任务持有 —— 等待或返回忙
else
{
if (timeout == OS_NO_WAIT)
{
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&mutex->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
ret = OS_BUSY;
}
else
{
if ((OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type) &&
(current_task->current_priority < mutex->owner->current_priority))
{
(void)_k_mutex_set_owner_priority(mutex, current_task->current_priority);
}
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_TRUE);
}
else
{
ret = k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_FALSE);
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&mutex->lock, &irq_save);
k_kernel_spin_lock();
#else
OS_KERNEL_ENTER();
#endif
if ((OS_SUCCESS != ret) && (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type))
{
need_sched = _k_mutex_restore_priority(mutex);
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
os_spin_unlock_irq(&mutex->lock, irq_save);
if (need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
}
}详细解释:锁被其他任务持有(lock_count > 0 且 owner != current_task)时,与普通锁的加锁流程完全一致:
| 步骤 | 代码 | 说明 |
|---|---|---|
| ① 不等待 | ret = OS_BUSY | timeout == OS_NO_WAIT 时立即返回忙 |
| ② 优先级继承 | _k_mutex_set_owner_priority() | 等待者优先级高于持有者时,临时提升持有者优先级 |
| ③ 阻塞等待 | k_block_task(&mutex->lock, irq_save, &mutex->task_list_head, timeout, OS_TRUE/FALSE) | 把当前任务挂到锁的等待队列(task_list_head),最后参数决定 PRIO/FIFO 唤醒 |
| ④ 恢复优先级 | _k_mutex_restore_priority() | k_block_task 返回后,若超时/失败且为 PRIO 策略,恢复持有者优先级 |
| ⑤ 触发调度 | OS_KERNEL_EXIT_SCHED() / OS_KERNEL_EXIT() | 按 need_sched 决定是否触发调度 |
os_mutex_recursive_unlock() 完整源码与分步详解
来源: kernel/source/os_mutex.c
os_err_t os_mutex_recursive_unlock(os_mutex_id mutex_id)
{
os_task_t *current_task;
os_task_t *block_task;
os_bool_t need_sched;
os_mutex_t *mutex;
OS_KERNEL_INIT();
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_TRUE == mutex->is_recursive);
OS_ASSERT(OS_FALSE == os_is_irq_active());
need_sched = OS_FALSE;
current_task = k_task_self();
if (current_task != OS_NULL)
{
/* Mutex only can be unlocked by owner */
if (current_task != mutex->owner)
{
OS_KERN_LOG(KERN_ERROR,
MUTEX_TAG,
"The mutex(addr: %p, name: %s, owner: %s) can't unlock by task(%s)",
mutex,
mutex->name,
mutex->owner->name,
current_task->name);
OS_ASSERT((OS_FALSE));
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&mutex->lock, &irq_save);
k_kernel_spin_lock();
#else
OS_KERNEL_ENTER();
#endif
OS_ASSERT((mutex->lock_count > 0U));
if (mutex->lock_count > 1U)
{
mutex->lock_count--;
}
else
{
os_list_del(&mutex->hold_node);
/* Restore task priority */
if (OS_MUTEX_WAKE_TYPE_PRIO == mutex->wake_type)
{
need_sched = _k_mutex_restore_priority(mutex);
}
/* Get the new owner, if any */
if (!os_list_empty(&mutex->task_list_head))
{
block_task = os_list_first_entry(&mutex->task_list_head, os_task_t, task_node);
need_sched = k_unblock_task(block_task);
mutex->owner = block_task;
mutex->original_priority = block_task->current_priority;
os_list_add_tail(&mutex->owner->hold_mutex_list_head, &mutex->hold_node);
}
else
{
mutex->owner = OS_NULL;
mutex->lock_count = 0U;
}
}
if ((current_task->current_priority != current_task->backup_priority) &&
os_list_empty(¤t_task->hold_mutex_list_head))
{
OS_KERN_LOG(KERN_INFO,
MUTEX_TAG,
"New priority(%u) takes effect, old priority(%u)",
current_task->backup_priority,
current_task->current_priority);
k_readyq_remove(current_task);
current_task->current_priority = current_task->backup_priority;
k_readyq_put(current_task);
need_sched = OS_TRUE;
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
k_kernel_spin_unlock();
os_spin_unlock_irq(&mutex->lock, irq_save);
if (OS_TRUE == need_sched)
{
OS_KERNEL_ENTER();
OS_KERNEL_EXIT_SCHED();
}
#else
if (OS_TRUE == need_sched)
{
OS_KERNEL_EXIT_SCHED();
}
else
{
OS_KERNEL_EXIT();
}
#endif
}
return OS_SUCCESS;
}分步详解:
第一步:所有权检查
if (current_task != mutex->owner)
{
OS_KERN_LOG(KERN_ERROR, ...);
OS_ASSERT((OS_FALSE));
}详细解释:与普通锁一样,只有持有者能解锁,否则断言失败。
第二步:计数递减 —— 核心递归释放逻辑
OS_ASSERT((mutex->lock_count > 0U));
if (mutex->lock_count > 1U)
{
mutex->lock_count--; /* 还有嵌套,只减计数 */
}
else
{
os_list_del(&mutex->hold_node);
/* 恢复优先级 + 转交所有权或完全释放 */
...
}详细解释:这就是递归锁"计数归零才释放"的核心:
lock_count > 1:还有嵌套加锁,只lock_count--,锁仍由当前任务持有,不释放。lock_count == 1:这是最后一次解锁,此时才真正释放:从持有锁链表移除hold_node、恢复优先级、把锁转交给等待者(或清空 owner)。
示例:同一任务连续 3 次 recursive_lock 后,3 次 recursive_unlock:
第 1 次 unlock:lock_count 3→2(还有嵌套,不释放)
第 2 次 unlock:lock_count 2→1(还有嵌套,不释放)
第 3 次 unlock:lock_count 1→0(真正释放:转交等待者或清空 owner)为什么需要递归锁?
当代码存在嵌套/递归调用,且多个层级都需要访问同一个临界资源时,普通锁会因"同一任务重复加锁"而断言失败。递归锁允许这种场景:每一层递归加锁 +1,返回时解锁 -1,计数归零才真正释放。
void recursive_func(os_mutex_id mutex_id, int depth)
{
if (depth <= 0)
return;
os_mutex_recursive_lock(mutex_id, OS_WAIT_FOREVER); /* 每层递归加锁 */
printf("depth=%d\n", depth);
recursive_func(mutex_id, depth - 1); /* 递归调用 */
os_mutex_recursive_unlock(mutex_id); /* 返回时解锁 */
}2.6 查询函数
os_err_t os_mutex_get_owner(os_mutex_id mutex_id, os_task_id *tid);
os_bool_t os_mutex_check_exist(os_mutex_id mutex_id);
os_err_t os_mutex_is_recursive(os_mutex_id mutex_id, os_bool_t *is_recursive);
os_err_t os_mutex_set_wake_type(os_mutex_id mutex_id, uint8_t wake_type);| 函数 | 说明 |
|---|---|
os_mutex_get_owner | 获取当前持有锁的任务 ID |
os_mutex_check_exist | 检查互斥锁是否存在 |
os_mutex_is_recursive | 检查是否为递归锁 |
os_mutex_set_wake_type | 设置唤醒策略:OS_MUTEX_WAKE_TYPE_PRIO 或 OS_MUTEX_WAKE_TYPE_FIFO |
下面按上述顺序逐个讲解:
os_mutex_get_owner() —— 获取互斥锁持有者
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_get_owner(os_mutex_id mutex_id, os_task_id *tid)
{
os_mutex_t *mutex;
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != tid);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
*tid = (os_task_id)mutex->owner;
return OS_SUCCESS;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_TYPE_CONVERT(os_mutex_t *, mutex_id) | 把互斥锁 ID 转换为内部指针 |
OS_ASSERT(OS_NULL != tid) | 传出参数非空(防止写入非法地址) |
OS_ASSERT(OS_NULL != mutex) | 互斥锁指针非空 |
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited) | 互斥锁必须已初始化 |
*tid = (os_task_id)mutex->owner | 核心:把持有者任务指针转换为 os_task_id 并写入传出参数 |
return OS_SUCCESS | 返回成功 |
关键点:
tid是传出参数,返回的是互斥锁当前持有者的任务 ID。- 若锁当前空闲(无持有者),
mutex->owner为OS_NULL,*tid会被写成NULL。使用前应判空。
使用示例:
os_task_id owner;
os_mutex_get_owner(mutex_id, &owner);
if (owner != OS_NULL)
{
os_kprintf("Mutex owner: %p\r\n", owner);
}os_mutex_check_exist() —— 检查互斥锁是否存在
完整源码(kernel/source/os_mutex.c,与项目一致):
os_bool_t os_mutex_check_exist(os_mutex_id mutex_id)
{
os_mutex_t *iter_mutex;
os_list_node_t *node;
os_bool_t exist;
os_mutex_t *mutex;
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
exist = OS_FALSE;
os_spin_lock(&gs_os_mutex_resource_list_lock);
os_list_for_each(node, &gs_os_mutex_resource_list_head)
{
iter_mutex = os_list_entry(node, os_mutex_t, resource_node);
if (mutex == iter_mutex)
{
exist = OS_TRUE;
break;
}
}
os_spin_unlock(&gs_os_mutex_resource_list_lock);
return exist;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_TYPE_CONVERT(os_mutex_t *, mutex_id) | 把互斥锁 ID 转换为内部指针 |
OS_ASSERT(OS_NULL != mutex) | 互斥锁指针非空 |
exist = OS_FALSE | 初始默认"不存在" |
os_spin_lock(...) | 加自旋锁,保护全局资源链表 |
os_list_for_each(node, &gs_os_mutex_resource_list_head) | 遍历全局互斥锁资源链表 |
if (mutex == iter_mutex) | 判断链表中是否存在与该 ID 相同的互斥锁 |
exist = OS_TRUE | 存在则置 OS_TRUE |
os_spin_unlock(...) | 释放自旋锁 |
return exist | 返回是否存在 |
关键点:通过遍历全局资源链表判断互斥锁是否有效存在(未销毁),常用于使用前校验。
使用示例:
if (os_mutex_check_exist(mutex_id) == OS_TRUE)
{
os_mutex_unlock(mutex_id);
}os_mutex_is_recursive() —— 检查是否为递归锁
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_is_recursive(os_mutex_id mutex_id, os_bool_t *is_recursive)
{
os_mutex_t *mutex;
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT(OS_NULL != is_recursive);
*is_recursive = mutex->is_recursive;
return OS_SUCCESS;
}分步详解:
| 代码 | 作用 |
|---|---|
OS_TYPE_CONVERT(os_mutex_t *, mutex_id) | 把互斥锁 ID 转换为内部指针 |
OS_ASSERT(OS_NULL != mutex) | 互斥锁指针非空 |
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited) | 互斥锁必须已初始化 |
OS_ASSERT(OS_NULL != is_recursive) | 传出参数非空 |
*is_recursive = mutex->is_recursive | 核心:把递归标志写入传出参数 |
return OS_SUCCESS | 返回成功 |
关键点:返回 mutex->is_recursive 字段(创建时的 recursive 参数),OS_TRUE 为递归锁、OS_FALSE 为普通锁。查询它可判断该锁应使用 os_mutex_lock 还是 os_mutex_recursive_lock。
使用示例:
os_bool_t is_recursive;
os_mutex_is_recursive(mutex_id, &is_recursive);
os_kprintf("Is recursive: %s\r\n", is_recursive ? "YES" : "NO");os_mutex_set_wake_type() —— 设置唤醒策略
完整源码(kernel/source/os_mutex.c,与项目一致):
os_err_t os_mutex_set_wake_type(os_mutex_id mutex_id, uint8_t wake_type)
{
os_err_t ret;
os_mutex_t *mutex;
OS_KERNEL_INIT();
mutex = OS_TYPE_CONVERT(os_mutex_t *, mutex_id);
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT((OS_MUTEX_WAKE_TYPE_PRIO == wake_type) || (OS_MUTEX_WAKE_TYPE_FIFO == wake_type));
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_lock_irq(&mutex->lock, &irq_save);
#else
OS_KERNEL_ENTER();
#endif
if (OS_NULL == mutex->owner)
{
mutex->wake_type = wake_type;
ret = OS_SUCCESS;
}
else
{
ret = OS_BUSY;
}
#if defined(OS_USING_SMP) && !defined(OS_IPC_MONOLOCK_MODE)
os_spin_unlock_irq(&mutex->lock, irq_save);
#else
OS_KERNEL_EXIT();
#endif
return ret;
}分步详解:
第一步:参数校验
OS_ASSERT(OS_NULL != mutex);
OS_ASSERT(OS_KOBJ_INITED == mutex->object_inited);
OS_ASSERT((OS_MUTEX_WAKE_TYPE_PRIO == wake_type) || (OS_MUTEX_WAKE_TYPE_FIFO == wake_type));详细解释:互斥锁非空、已初始化,且 wake_type 必须是 PRIO 或 FIFO 之一。
第二步:仅在锁空闲时可修改
if (OS_NULL == mutex->owner)
{
mutex->wake_type = wake_type;
ret = OS_SUCCESS;
}
else
{
ret = OS_BUSY;
}详细解释:
- 锁空闲时(
owner == OS_NULL)才能修改唤醒策略,返回OS_SUCCESS。 - 锁被占用时返回
OS_BUSY——因为等待队列中已有任务按旧策略排序,中途改策略会导致排序错乱。
关键点:应在互斥锁空闲(创建后、未被获取前)时设置唤醒策略:
mutex_id = os_mutex_create(OS_NULL, "mutex", OS_FALSE);
os_mutex_set_wake_type(mutex_id, OS_MUTEX_WAKE_TYPE_FIFO); /* 在锁空闲时设置 */📝 本节小结
核心要点回顾
本节围绕互斥锁机制展开,核心要点如下:
1. 互斥锁的本质
- 拥有所有权的同步对象:同一时刻只有一个任务持有锁,只有持有者才能释放。
- 与信号量的核心区别:互斥锁有 owner、支持优先级继承、支持递归持有,不能在中断中使用。
2. 优先级继承(防优先级反转)
- 高优先级任务等锁时,把持有者优先级临时提升到等待者优先级。
- 解锁时恢复优先级(
_k_mutex_restore_priority),多锁场景会遍历计算正确优先级。
3. 加锁/解锁核心逻辑
| 操作 | 锁空闲 | 锁被占用 |
|---|---|---|
| lock | 获取所有权(owner=自己,lock_count=1) | 阻塞等待(PRIO 先继承优先级)或返回 OS_BUSY |
| unlock | — | 转交新持有者 或 释放锁(owner=NULL,count=0) |
4. 递归锁
os_mutex_recursive_lock()/unlock():允许同一任务多次获取锁。- 每次递归加锁计数 +1,解锁 -1,计数归零才真正释放。
5. 关键限制
- 不能在中断中使用互斥锁(阻塞 + 优先级继承)。
- 保护临界区时持有锁时间应尽量短。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 在中断中使用互斥锁 | 禁止,中断中应用自旋锁或信号量 post |
非持有者调用 os_mutex_unlock() | 触发断言失败,互斥锁只有持有者能解锁 |
用 os_mutex_lock 对普通锁递归加锁 | 触发断言,递归加锁要用 os_mutex_recursive_lock |
| 用互斥锁做资源计数 | 计数用信号量,互斥锁用于互斥保护 |
忘记检查 os_mutex_lock 返回值 | 超时/忙会返回非成功错误码,需处理 |
| 持有锁时间过长 | 影响系统实时性,临界区应尽量短 |
下一步
互斥锁用于互斥保护。接下来请学习下一小节:
- 4.3.3 事件:了解如何用事件标志实现多条件同步