互斥量(Mutex)— 会做优先级继承的排他锁
← SVC | ← FreeRTOS内核实现与应用开发实战指南
FreeRTOS 里互斥量 = “排他锁 + 三条硬保证”:只能由持有者释放、支持递归、自动做优先级继承。目的就是排他访问共享资源,并顺带抑制优先级反转。
为什么有信号量还不够
信号量只保证”同时只有一个人进”,但谁都能 Give、不做优先级继承——共享资源加锁就得用互斥量:
| 维度 | 互斥量 | 二值信号量 |
|---|---|---|
| 用途 | 排他访问临界区(锁) | 排他 + 同步(可当锁/当”事件位”) |
| 谁能释放 | 只有持有者(内核记了 pxMutexHolder) | 任何任务 / ISR 都能 Give |
| 优先级继承 | ✅ 自动 | ❌ 无 |
| 递归加锁 | ✅(Recursive 版本) | ❌ 自己再 Take 会把自己堵死 |
| 中断里用 | ❌ 不可 | ✅ 用 FromISR 版本 |
| 本质 | 特殊队列,额外记持有者 | 0 长度队列,只记计数值 |
编译开关:
configUSE_MUTEXES(互斥量)、configUSE_RECURSIVE_MUTEXES(递归互斥量),都在queue.c/FreeRTOSConfig.h联动。
优先级继承(核心)
场景:高优先级任务 H 等低优先级任务 L 持有的锁;若无保护,中优先级任务 M 会趁机把 CPU 抢走,L 迟迟放不出锁,H 被 M 卡住 → 优先级反转。
互斥量做法:H Take 失败转阻塞前,内核临时把持锁的 L 提到 H(及所有等待者的最高)优先级,M 抢不走 CPU → L 赶紧执行完、释放锁 → H 立刻拿到,L 再降回原优先级。加锁/解锁瞬间发生,任务无感。
实现层面(queue.c + tasks.c):
- 任务首次
xSemaphoreTake拿到互斥量 → 内核把持有者记进队列的pxMutexHolder - 别的任务 Take 抢不到要阻塞时 → 调
vTaskPriorityInherit(),把持锁任务临时提升到等待者中最高的优先级 - 持锁任务
xSemaphoreGive()→ 调vTaskPriorityDisinherit()恢复原优先级,唤醒最高优先级等待者
⚠️ 继承能消反转,但不能防死锁:H 等 L、L 又等 H 时照样僵死(那是死锁,解法看加锁顺序)。互斥量多层嵌套(同时持/等多把锁)时可能需要链式继承,FreeRTOS 的继承是即时、局部的,嵌套多时并不总是继承完整——所以嵌套锁仍要按统一顺序加解锁。
加锁/解锁纪律
- 谁拿谁放:同一把锁只在持有它的任务里释放(互斥量的”只有持有者能释放”就是为这条兜底)
- 多把锁:全系统约定同一加锁顺序(如按资源编号递增),解锁按相反顺序(LIFO)——防死锁靠加锁顺序,LIFO 是好习惯
- 拿多个资源时用带超时的
xSemaphoreTake,失败就放手已持有的锁,别死等
防死锁理论机制见操作系统—死锁、饥饿与优先级反转;同步原语总述见操作系统—同步、互斥与通信。这套概念在 SVC/队列层面怎么走,见链表和调度的”信号任务的调度”。
代码用法(机制对照)
/* configUSE_MUTEXES = 1 */
SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); /* 创建,互斥量初始为"已释放" */
if( xSemaphoreTake( mutex, pdMS_TO_TICKS( 100 ) ) == pdTRUE )
{
/* 临界区:只有拿到锁的任务能进 */
xSemaphoreGive( mutex ); /* 只有持有者能释放 */
}递归版本(同一任务可多次 Take,计数到零才真正解锁,跨任务不能 Give):
/* configUSE_RECURSIVE_MUTEXES = 1 */
SemaphoreHandle_t rmutex = xSemaphoreCreateRecursiveMutex();
xSemaphoreTakeRecursive( rmutex, portMAX_DELAY );
/* 里面还能再 TakeRecursive 一次 */
xSemaphoreGiveRecursive( rmutex ); /* 配平才真正释放 */
xSemaphoreGiveRecursive( rmutex );具体工程封装(OSAL、创建配置)见术—FreeRTOS 互斥量。