互斥量(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):

  1. 任务首次 xSemaphoreTake 拿到互斥量 → 内核把持有者记进队列的 pxMutexHolder
  2. 别的任务 Take 抢不到要阻塞时 → 调 vTaskPriorityInherit(),把持锁任务临时提升到等待者中最高的优先级
  3. 持锁任务 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 互斥量。