FreeRTOS 笔记_互斥信号量
互斥信号量(Mutex,即 Mutual Exclusion )是FreeRTOS 重要的资源共享机制。
1 互斥信号量的概念及其作用
互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。
下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子:
运行条件:
◆ 让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数 printf 的互斥访问。如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。
◆ 用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。
◆ 通过二值信号量实现对 printf 函数互斥访问的两个任务
任务一:
任务二:
2 优先级翻转问题
有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?
互斥信号量可以防止优先级翻转,而二值信号量不支持,下面了解一下优先级翻转问题:

运行条件:
◆ 创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。 Task1 的优先级最高。
◆ 任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
◆ 起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。
运行过程描述如下:
◆ 任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。
◆ 在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3 的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。
◆ 任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源, 从而可以继续执行。 上面就是一个产生优先级翻转问题的现象。
3 FreeRTOS 互斥信号量的实现
相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题。
下图来说明一下 FreeRTOS 互斥信号量的实现:

运行条件:
◆ 创建 2 个任务 Task1 和 Task2,优先级分别为 1 和 3,任务 Task2 的优先级最高。
◆ 任务 Task1 和 Task2 互斥访问串口打印 printf。
◆ 使用 FreeRTOS 的互斥信号量实现串口打印 printf 的互斥访问。
运行过程描述如下:
◆ 低优先级任务Task1 执行过程中先获得互斥资源 printf 的执行。此时任务Task2 抢占了任务 Task1 的执行,任务 Task1 被挂起。任务Task2 得到执行。
◆ 任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优先级会被提升到与Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务 Task2 被挂起,任务 Task1 有新的优先级继续执行。
◆ 任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务 Task2 获得互斥资源后开始执行。
上面就是一个简单的 FreeRTOS 互斥信号量的实现过程。
4 FreeRTOS 中断方式互斥信号量的实现
重点的说以下 3 个函数:
◆ xSemaphoreCreateMutex ()
◆ xSemaphoreGive ()
◆ xSemaphoreTake ()
函数 xSemaphoreCreateMutex 用于创建互斥信号量
◆ 返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不 足,无法为此互斥信号量提供所需的空间会返回 NULL。
使用这个函数要注意以下问题:
1. 此函数是基于函数 xQueueCreateMutex 实现的:
函数 xQueueCreateMutex 的实现是基于消息队列函数 xQueueGenericCreate 实现的。
2. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_MUTEXES 1
举例:
函数 xSemaphoreGive 用于在任务代码中释放信号量
◆ 第 1 个参数是信号量句柄。
◆ 返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
使用这个函数要注意以下问题:
1. 此函数是基于消息队列函数 xQueueGenericSend 实现的:
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR。
3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。
4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。
举例:
函数 xSemaphoreTake 用于在任务代码中获取信号量。
◆ 第 1 个参数是信号量句柄。
◆ 第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
◆ 返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是 xSemaphoreTakeFromISR。
2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。