目录

概述

什么是互斥锁

互斥锁的核心作用

互斥锁核心原理

优先级继承避免优先级反转

CubeMX配置

创建互斥锁osMutexNew (const osMutexAttr_t *attr)

osMutexNew函数调用

osMutexId_t TestMutexHandle

获取锁

释放锁

获取锁osMutexAcquire(TestMutexHandle,osWaitForever);

释放锁osMutexRelease(TestMutexHandle);

本章结束


概述

在FreeRTOS实时操作系统中,多任务并发执行时,必然会出现多个任务竞争同一独占性资源(如全局变量、硬件外设、串口等)的场景。若不对资源访问进行控制,会导致数据错乱、硬件操作异常等问题,互斥锁(Mutex,全称Mutual Exclusion Semaphore)便是解决该问题的核心机制。它本质是一种带有所有权优先级继承特性的特殊二值信号量,专门用于实现任务间对临界资源的独占式访问,最大限度规避优先级反转问题,保障系统实时性与稳定性。

什么是互斥锁

互斥锁可以类 比为“资源访问钥匙”:当一个任务需要访问临界资源时,必须先“获取钥匙”(获取互斥锁);访问完成后,必须“归还钥匙”(释放互斥锁)。同一时刻,只有一把“钥匙”可用,即只有一个任务能持有互斥锁并访问资源,其他试图获取该互斥锁的任务会进入阻塞态,等待“钥匙”被归还后再竞争获取。

与普通二值信号量不同,互斥锁具有两个关键特性,这也是其区别于信号量、专门用于互斥场景的核心原因:

  • 所有权仅持有互斥锁的任务才能释放它,其他任务(或中断服务函数)释放无效,避免误释放导致的资源混乱。

  • 优先级继承:当高优先级任务等待低优先级任务持有的互斥锁时,低优先级任务会被临时提升至与高优先级任务相同的优先级,直到释放互斥锁,以此减少优先级反转的影响(注意:优先级继承不能完全解决优先级反转,仅能将影响降至最低)。

互斥锁的核心作用

互斥锁的核心目标是解决“临界区问题”,即保证多个任务对临界资源(同一时刻只能被一个任务安全访问的资源)的独占式访问,具体作用包括:

  • 共享全局变量/数据结构:

    • 两个任务(例如,一个数据采集任务和一个数据处理任务)都需要读写同一个全局缓冲区或链表。如果没有互斥量,当一个任务正在写入时,另一个任务可能开始读取,导致读取到不完整或错误的数据。

  • 外设访问:

    • 多个任务可能需要向同一个串口发送数据。如果没有互斥量,不同任务的输出可能会交织在一起,导致数据混乱。

    • 多个任务可能需要操作同一个I2C设备或SPI设备。互斥量可以确保总线在同一时间只被一个任务使用。

  • 缓解优先级反转

    • 通过优先级继承机制,减少低优先级任务阻塞高优先级任务的时间,保障系统实时性(FreeRTOS实现的是简化版优先级继承,优化内存占用和执行效率)。

互斥锁核心原理

互斥锁底层基于二值信号量实现,内核会维护一个“锁状态”(可用:1 / 已占用:0)和一个等待任务列表,其核心工作流程分为3个阶段:

  • 初始化阶段:互斥锁创建后,默认处于“可用状态”(锁状态=1),等待任务列表为空。

  • 获取阶段(Take):任务尝试获取互斥锁时,若锁状态为1(可用),则将锁状态设为0(已占用),任务持有互斥锁并继续执行;若锁状态为0(已占用),则该任务进入阻塞态,被加入等待任务列表,等待锁被释放。

  • 释放阶段(Give):持有互斥锁的任务释放锁时,将锁状态设为1(可用),内核从等待任务列表中唤醒优先级最高的任务,该任务会立即获取互斥锁并执行。

关键说明:互斥锁的等待任务列表采用“优先级抢占”机制,即锁释放时,优先唤醒优先级最高的等待任务,确保高优先级任务能及时访问资源,符合实时系统的优先级调度原则。

优先级继承避免优先级反转

优先级反转(Priority Inversion):当一个高优先级任务需要访问一个被低优先级任务占用的共享资源时,高优先级任务会被阻塞,直到低优先级任务释放资源。如果此时,一个中等优先级任务开始运行(因为它的优先级高于低优先级任务),并且不需要访问该共享资源,它可能会抢占低优先级任务的CPU ,从而间接地阻止低优先级任务释放资源,导致高优先级任务被“无限期”阻塞。

  • 以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过 信号量机制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后,Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过 程中,中等优先级任务M抢占了任务L,从而延迟了信号量的释放时间,导致Task H阻塞了更长时间,这种现象称为优先级倒置或优先级反转(翻转)。

  • 互斥量的优先级继承机制:FreeRTOS的互斥量通过优先级继承机制来解决这个问题。当一个低优先级任务持有互斥量,并且一个高优先级任务尝试获取这个互斥量而被阻塞时,持有互斥量的低优先级任务的优先级会被临时提升到阻塞它的高优先级任务的优先级。这样,中等优先级的任务就无法抢占这个“被提升了优先级”的低优先级任务,确保低优先级任务能尽快执行并释放互斥量,从而解除高优先级任务的阻塞。一旦低优先级任务释放了互斥量,它的优先级就会恢复到原来的值。

CubeMX配置

创建互斥锁osMutexNew (const osMutexAttr_t *attr)

/**
 * @brief 创建并初始化一个互斥锁(Mutex)
 * @param attr 互斥锁属性配置(可为 NULL,使用默认配置)
 * @return 成功返回互斥锁ID,失败返回 NULL
 *
 * @note 这是 CMSIS-RTOS2 的封装函数,内部根据属性配置调用 FreeRTOS 的对应 API
 *       支持普通互斥锁和递归互斥锁,支持动态和静态内存分配
 *       关键特性:通过句柄最低位标记递归锁类型
 */
osMutexId_t osMutexNew(const osMutexAttr_t *attr) {
    SemaphoreHandle_t hMutex;  // FreeRTOS 原生互斥锁句柄
    uint32_t type;             // 属性位掩码副本
    uint32_t rmtx;             // 递归锁标志:1=递归锁,0=普通锁
    int32_t  mem;              // 内存分配标志:-1=错误,0=动态,1=静态
    #if (configQUEUE_REGISTRY_SIZE > 0)
    const char *name;          // 互斥锁名称(用于调试注册)
    #endif

    hMutex = NULL;  // 初始化句柄为 NULL

    // 互斥锁不能在中断服务程序(ISR)中创建
    // 因为创建过程可能涉及动态内存分配
    if (!IS_IRQ()) {
        // 解析属性配置
        if (attr != NULL) {
            type = attr->attr_bits;  // 使用用户提供的属性
        } else {
            type = 0U;               // 使用默认属性(普通锁、动态分配)
        }

        // 检查是否为递归锁
        if ((type & osMutexRecursive) == osMutexRecursive) {
            rmtx = 1U;  // 是递归锁
        } else {
            rmtx = 0U;  // 是普通锁
        }

        // ===== Robust 锁处理(关键点)=====
        // FreeRTOS 原生不支持 CMSIS 定义的 Robust Mutex 语义
        // 如果设置了 osMutexRobust 标志,直接跳过创建逻辑,返回 NULL
        if ((type & osMutexRobust) != osMutexRobust) {
            
            // ===== 内存分配策略判断 =====
            mem = -1;  // 默认为错误状态
            
            if (attr != NULL) {
                // 情况1:用户提供了静态内存控制块
                if ((attr->cb_mem != NULL) && (attr->cb_size >= sizeof(StaticSemaphore_t))) {
                    mem = 1;  // 静态分配
                }
                // 情况2:用户明确使用动态分配(未提供内存且大小为0)
                else if ((attr->cb_mem == NULL) && (attr->cb_size == 0U)) {
                    mem = 0;  // 动态分配
                }
                // 其他情况:参数冲突,保持 mem=-1(错误)
            }
            else {
                // attr 为 NULL 时,默认使用动态分配
                mem = 0;
            }

            // ===== 静态内存分配分支 =====
            if (mem == 1) {
                #if (configSUPPORT_STATIC_ALLOCATION == 1)
                    if (rmtx != 0U) {
                        // 创建静态递归互斥锁
                        #if (configUSE_RECURSIVE_MUTEXES == 1)
                        hMutex = xSemaphoreCreateRecursiveMutexStatic(attr->cb_mem);
                        #endif
                    }
                    else {
                        // 创建静态普通互斥锁
                        hMutex = xSemaphoreCreateMutexStatic(attr->cb_mem);
                    }
                #endif
            }
            // ===== 动态内存分配分支 =====
            else {
                if (mem == 0) {
                    #if (configSUPPORT_DYNAMIC_ALLOCATION == 1)
                        if (rmtx != 0U) {
                            // 创建动态递归互斥锁
                            #if (configUSE_RECURSIVE_MUTEXES == 1)
                            hMutex = xSemaphoreCreateRecursiveMutex();
                            #endif
                        } else {
                            // 创建动态普通互斥锁
                            hMutex = xSemaphoreCreateMutex();
                        }
                    #endif
                }
            }

            // ===== 调试注册(可选)=====
            #if (configQUEUE_REGISTRY_SIZE > 0)
            if (hMutex != NULL) {
                // 获取互斥锁名称
                if (attr != NULL) {
                    name = attr->name;
                } else {
                    name = NULL;
                }
                // 将互斥锁注册到队列注册表,便于调试工具识别
                vQueueAddToRegistry(hMutex, name);
            }
            #endif

            // ===== 关键技巧:递归锁句柄标记 =====
            // 由于指针在32位系统中总是4字节对齐,最低位始终为0
            // 利用这一特性,将最低位置1来标记这是一个递归锁
            // 这样在 osMutexAcquire/Release 中就可以通过检查最低位来区分锁类型
            if ((hMutex != NULL) && (rmtx != 0U)) {
                hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U);
            }
        }
    }

    // 返回 CMSIS 互斥锁ID(可能带有递归锁标记)
    return ((osMutexId_t)hMutex);
}

osMutexNew函数调用

osMutexId_t TestMutexHandle;
const osMutexAttr_t TestMutex_attributes = {
  .name = "TestMutex"
};

void MX_FREERTOS_Init(void) {

TestMutexHandle = osMutexNew(&TestMutex_attributes);

}

osMutexId_t TestMutexHandle

定义一个互斥锁句柄变量,用来保存由 osMutexNew() 创建出来的 Mutex 对象地址。它本质是一个指针,通过这个句柄,任务才能调用 osMutexAcquire()osMutexRelease() 去操作对应的互斥锁。

  • 获取锁

osMutexAcquire(TestMutexHandle,osWaitForever);
  • 释放锁

osMutexRelease(TestMutexHandle);

获取锁osMutexAcquire(TestMutexHandle,osWaitForever);

/**
 * @brief 获取互斥锁(Mutex)
 * @param mutex_id 互斥锁ID(由 osMutexNew 返回,可能带有递归锁标记)
 * @param timeout 超时时间(单位:系统节拍,osWaitForever 表示永久等待)
 * @return 操作状态
 *         - osOK: 成功获取锁
 *         - osErrorISR: 在中断中调用(不允许)
 *         - osErrorParameter: 参数错误(如 mutex_id 为 NULL)
 *         - osErrorTimeout: 超时未获取到锁
 *         - osErrorResource: 非阻塞模式下锁不可用
 *
 * @note 这是 CMSIS-RTOS2 的封装函数,内部根据 mutex_id 的最低位判断锁类型
 *       并调用 FreeRTOS 对应的 API(普通锁或递归锁)
 */
osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) {
    SemaphoreHandle_t hMutex;  // FreeRTOS 原生互斥锁句柄
    osStatus_t stat;           // 返回状态
    uint32_t rmtx;             // 递归锁标志(1=递归锁,0=普通锁)

    // 关键操作:清除 mutex_id 最低位的标记,获取真实的 FreeRTOS 句柄
    // 在 osMutexNew 中,递归锁的句柄最低位被置 1,这里需要还原
    hMutex = (SemaphoreHandle_t)((uint32_t)mutex_id & ~1U);

    // 提取最低位:判断是否为递归锁
    // 如果最低位为 1,则是递归锁;否则是普通锁
    rmtx = (uint32_t)mutex_id & 1U;

    stat = osOK;  // 默认返回成功状态

    // 检查是否在中断服务程序(ISR)中调用
    // 互斥锁不能在 ISR 中使用(可能导致优先级反转等问题)
    if (IS_IRQ()) {
        stat = osErrorISR;
    }
    // 检查互斥锁句柄是否有效
    else if (hMutex == NULL) {
        stat = osErrorParameter;
    }
    else {
        // 根据锁类型调用不同的 FreeRTOS API
        if (rmtx != 0U) {
            // ===== 递归锁分支 =====
            // 递归锁允许同一线程多次获取,内部维护嵌套计数
            #if (configUSE_RECURSIVE_MUTEXES == 1)
            // 调用 FreeRTOS 递归锁获取函数
            if (xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) {
                // 获取失败:根据超时参数判断失败原因
                if (timeout != 0U) {
                    // 设置了超时时间但未获取到锁 -> 超时
                    stat = osErrorTimeout;
                } else {
                    // 非阻塞模式(timeout=0)下锁不可用
                    stat = osErrorResource;
                }
            }
            #endif
        }
        else {
            // ===== 普通锁分支 =====
            // 普通锁不允许同一线程重复获取(会导致死锁)
            if (xSemaphoreTake(hMutex, timeout) != pdPASS) {
                // 获取失败:根据超时参数判断失败原因
                if (timeout != 0U) {
                    // 设置了超时时间但未获取到锁 -> 超时
                    stat = osErrorTimeout;
                } else {
                    // 非阻塞模式(timeout=0)下锁不可用
                    stat = osErrorResource;
                }
            }
        }
    }

    return stat;
}

作用:请求获取一个互斥锁,如果锁当前被其他任务占用,则根据 timeout 参数决定等待还是立即返回。

osMutexAcquire() 做的事情可以概括成:

  • 接收一个 Mutex 句柄,根据句柄判断它是哪一种锁,然后调用 FreeRTOS 底层 xSemaphoreTake() 获取资源。如果资源被占用,根据 timeout 参数让当前任务等待或者立即返回。

释放锁osMutexRelease(TestMutexHandle);

/**
 * @brief 释放互斥锁(Mutex)
 * @param mutex_id 互斥锁ID(由 osMutexNew 返回,可能带有递归锁标记)
 * @return 操作状态
 *         - osOK: 成功释放锁
 *         - osErrorISR: 在中断中调用(不允许)
 *         - osErrorParameter: 参数错误(如 mutex_id 为 NULL)
 *         - osErrorResource: 释放失败(如当前线程未持有锁,或递归锁嵌套计数不为0)
 *
 * @note 这是 CMSIS-RTOS2 的封装函数,内部根据 mutex_id 的最低位判断锁类型
 *       并调用 FreeRTOS 对应的 API(普通锁或递归锁)
 * @warning 只有锁的当前持有者才能释放它
 */
osStatus_t osMutexRelease(osMutexId_t mutex_id) {
    SemaphoreHandle_t hMutex;  // FreeRTOS 原生互斥锁句柄
    osStatus_t stat;           // 返回状态
    uint32_t rmtx;             // 递归锁标志(1=递归锁,0=普通锁)

    // 关键操作:清除 mutex_id 最低位的标记,获取真实的 FreeRTOS 句柄
    // 该标记在 osMutexNew 中被设置,用于区分普通锁和递归锁
    hMutex = (SemaphoreHandle_t)((uint32_t)mutex_id & ~1U);

    // 提取最低位:判断是否为递归锁
    // 该位在 osMutexNew 中被设置(递归锁为1,普通锁为0)
    rmtx = (uint32_t)mutex_id & 1U;

    stat = osOK;  // 默认返回成功状态

    // 安全检查1:不能在中断服务程序(ISR)中释放互斥锁
    // 互斥锁有线程所有权概念,ISR 不属于任何线程,因此不能操作互斥锁
    if (IS_IRQ()) {
        stat = osErrorISR;
    }
    // 安全检查2:检查互斥锁句柄是否有效
    else if (hMutex == NULL) {
        stat = osErrorParameter;
    }
    else {
        // 根据锁类型调用不同的 FreeRTOS API
        if (rmtx != 0U) {
            // ===== 递归锁分支 =====
            // 递归锁维护一个嵌套计数,每次获取增加计数,每次释放减少计数
            // 只有当嵌套计数减为0时,锁才会真正释放给其他线程
            #if (configUSE_RECURSIVE_MUTEXES == 1)
            // 调用 FreeRTOS 递归锁释放函数
            if (xSemaphoreGiveRecursive(hMutex) != pdPASS) {
                // 释放失败的情况:
                // 1. 当前线程不是锁的持有者
                // 2. 递归锁的嵌套计数在释放后不为0(仍在占用)
                // 3. 其他内部错误
                stat = osErrorResource;
            }
            #endif
        }
        else {
            // ===== 普通锁分支 =====
            // 普通锁没有嵌套计数,一次释放即完全释放锁
            if (xSemaphoreGive(hMutex) != pdPASS) {
                // 释放失败的情况:
                // 1. 当前线程不是锁的持有者(普通锁有严格的所有权)
                // 2. 锁未被获取(重复释放)
                // 3. 其他内部错误
                stat = osErrorResource;
            }
        }
    }

    return stat;
}

本章结束

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐