一、信号量的概念
1、信号量的基本概念
消息队列是实现任务与任务或任务与中断间通信的数据结构,可类比裸机编程中的数组。
信号量是实现任务与任务或任务与中断间通信的机制,可以类比裸机编程中的标志位。
信号量 (semaphore) 可以实现任务与任务或任务与中断间的同步功能 (二值信号量)、资源管理(计数信号量)、临界资源的互斥访问(互斥信号量) 等。
信号量是一个非负正数,二值信号量与互斥信号量取值范围为 0-1,计数信号量取值范围是 0-N(N>1)。
0: 信号量为空,所有试图获取它的任务都将处于阻塞状态,直到超时退出或其他任务释放信号量。
正数: 表示有一个或多个信号量供获取 。
2、信号量的分类
- 二值信号量 (重点讲解同步应用)
- 计数信号量 (重点讲解资源管理)
- 互斥信号量 (重点讲解互斥访问)
- 递归互斥信号量 (简要了解即可)
二、二值信号量的定义与应用
1、二值信号量的定义
当信号量被获取了,信号量值变为 0;当信号量被释放了,信号量值变为 1。 把这种取值只有 0 与 1 两种状态的信号量称之为二值信号量。
创建二值信号量时,系统会为创建的二值信号量分配内存,二值信号量创建完成后的示意图如下:
从上图可以看出,二值信号量是一种长度为 1,消息大小为 0 的特殊消息队列。
因为这个队列只有空或满两种状态,而且消息大小为 0,因此在运用时,只需要知道队列中是否有消息即可,而无需关注消息是什么。
2、二值信号量的应用
在嵌入式操作系统中,二值信号量是任务与任务或任务与中断间同步的重要手段。
Note:
二值信号量也可以用于临界资源的访问,但不建议,因为存在任务优先级翻转问题,这个将在下一讲的互斥信号量 (具有优先级继承机制) 中进行详细讲解。
任务与任务中同步的应用场景:
假设有一个温湿度传感器,每 1s 采集一次数据,那么让它在液晶屏中显示数据,这个周期也是 1s,如果液晶屏刷新的周期是 100ms,那么此时的温湿度数据还没更新,液晶屏根本无须刷新,只需要在 1s 后温湿度数据更新时刷新即可,否则 CPU 就是白白做了多次的无效数据更新操作,造成 CPU 资源浪费。如果液晶屏刷新的周期是 10s,那么温湿度的数据都变化了 10 次,液晶屏才来更新数据,那么这个产品测得的结果就是不准确的,所以还是需要同步协调工作,在温湿度采集完毕之后进行液晶屏数据的刷新,这样得到的结果才是最准确的,并且不会浪费 CPU 的资源。
任务与中断中同步的应用场景:
在串口接收中,我们不知道什么时候有数据发送过来,但如果设置一个任务专门时刻查询是否有数据到来,将会浪费 CPU 资源,所以在这种情况下使用二值信号量是很好的办法:当没有数据到来时,任务进入阻塞态,不参与任务的调度; 等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后在运行时处理数据,这样系统的资源就会得到很好的利用。
三、二值信号量的运作机制
1、FreeRTOS 任务间二值信号量的实现
任务间二值信号量的实现是指各个任务之间使用信号量实现任务的同步功能。下面我们通过如下的框图来说明一下 FreeRTOS 二值信号量的实现,让大家有一个形象的认识。
运行条件:
- 创建 2 个任务 Task1 和 Task2。
- 创建二值信号量默认的初始值是 0,也就是没有可用资源。
运行过程描述如下:
任务 Task1 运行过程中调用函数 xSemaphoreTake 获取信号量资源,但是由于创建二值信号的初始值是 0,没有信号量可以用,任务 Task1 将由运行态转到阻塞状态。运行的过程中,任务 Task2 通过函数 xSemaphoreGive 释放信号量,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,实现 Task1 与 Task2 的同步功能。
上面就是一个简单的 FreeRTOS 任务间二值信号量的同步使用过程。
2、FreeRTOS 中断方式二值信号量的实现
FreeRTOS 中断方式二值信号量的实现是指中断与任务间使用信号量实现同步功能。下面我们通过如下的框图来说明一下 FreeRTOS 中断方式二值信号量的实现,让大家有一个形象的认识。
运行条件:
- 创建 1 个任务 Task1 和一个串口接收中断。
- 二值信号量的初始值为 0,串口中断调用函数 xSemaphoreGiveFromISR 释放信号量,任务 Task1 调用函数 xSemaphoreTake 获取信号量资源。
运行过程描述如下:
-
任务 Task1 运行过程中调用函数 xSemaphoreTake,由于信号量的初始值是 0,没有信号量资源可用,任务 Task1 由运行态进入到阻塞态。
-
Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数 xSemaphoreGiveFromISR 释放信号量资源,信号量数值加 1,此时信号量计数值为 1,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务 Task1 获得信号量后,信号量数值减 1,此时信号量计数值又变成了 0。
-
再次循环执行时,任务 Task1 调用函数 xSemaphoreTake 由于没有资源可用再次进入到挂起态,等待串口释放二值信号量资源,如此往复循环。
上面就是一个简单的 FreeRTOS 中断方式二值信号量同步过程。
实际应用中,中断方式的消息机制要注意以下四个问题:
- 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应
- 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行
- 中断服务程序中一定要调用专用于二值信号量设置函数,即以 FromISR 结尾的函数
- 如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的二值信号量的 API 函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换
四、二值信号量常用的 API 函数
1、使用二值信号量的典型流程如下:
- 创建二值信号量
- 释放二值信号量
- 获取二值信号量
- 删除二值信号量
2、常用 API 函数如下:
- xSemaphoreCreateBinary()
- xSemaphoreGive() 与 xSemaphoreGiveFromISR()
- xSemaphoreTake()
- vSemaphoreDelete()
3、二值信号量创建与删除
二值信号量控制块 (句柄)
如下图:二值信号量的句柄为消息队列的句柄,因为二值信号量是一种长度为 1,消息大小为 0 的特殊消息队列
二值信号量创建
函数原型:
SemaphoreHandle_t xSemaphoreCreateBinary(void)
函数描述:
函数 xSemaphoreCreateBinary 用于创建二值信号量。
- 返回值,如果创建成功会返回二值信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此二值信号量提供所需的空间会返回 NULL。
说明:此函数基于消息队列函数实现:
应用举例:
二值信号量删除
函数原型:
void vSemaphoreDelete(void)
函数描述:
函数 vSemaphoreDelete 可用于删除二值信号量。
4、任务中二值信号量释放
函数原型:
xSemaphoreGive(SemaphoreHandle_t xSemaphore); /* 信号量句柄 */
函数描述:
函数 xSemaphoreGive 用于在任务代码中释放信号量。
-
第 1 个参数是信号量句柄。
-
返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
使用这个函数要注意以下问题:
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是 xSemaphoreGiveFromISR。
- 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting() 创建了信号量。
- 此函数不支持使用 xSemaphoreCreateRecursiveMutex() 创建的信号量。
应用举例:
5、中断中二值信号量释放
函数原型:
xSemaphoreGiveFromISR ( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
signed BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */ )
函数描述:
函数 xSemaphoreGiveFromISR 用于中断服务程序中释放信号量。
- 第 1 个参数是信号量句柄。
- 第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,说明有高优先级任务要执行,否则没有。
- 返回值,如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。
使用这个函数要注意以下问题:
- 此函数是基于消息队列函数 xQueueGiveFromISR 实现的:#define xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken) \xQueueGiveFromISR(( QueueHandle_t) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
- 此函数是用于中断服务程序中调用的,故不可以任务代码中调用此函数,任务代码中中使用的是 xSemaphoreGive。
- 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary() 或者 xSemaphoreCreateCounting() 创建了信号量。
- 此函数不支持使用 xSemaphoreCreateMutex () 创建的信号量。
应用举例:
6、二值信号量获取
函数原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */
函数描述:
函数 xSemaphoreTake 用于在任务代码中获取信号量。
- 第 1 个参数是信号量句柄。
- 第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。 返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是 xSemaphoreTakeFromISR。
- 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
- 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。
应用举例:
五、二值信号量的应用编程 - 任务与任务
视频讲解
六、二值信号量的应用编程 - 中断与任务
视频讲解