文章目录
前言
信号量,我们在之前网络编程的时候就讲过它的详细概念了,感兴趣的朋友可以回顾一下,这里介绍一下其在Freertos中的用法。
一、信号量的基础知识
1.1 信号量与队列的关系
信号量主要的功能就是实现任务之间的同步与互斥,实现的方式主要就是依靠队列(信号量是特殊的队列)的任务阻塞机制。具体来说,信号量就是大小为1的队列,信号量的“获取”和“释放”操作对应着队列的“发送”和“接收”。与此同时这样,我们用于队列的很多函数,也就成信号量的底层函数,例如:xSemaphoreTake()实际上是 xQueueReceive() 的封装;xSemaphoreGive() 是 xQueueSend() 的封装;对于在中断中使用的信号量操作,如xSemaphoreGiveFromISR(),对应的是 xQueueSendFromISR()。
那么为什么我们以及学习了队列还要学习信号量呢?其实还是为了“省”,前面介绍过队列常用来进行数据的传递,但是我们在同步或者互斥的之后却只需要确定队列中还有没有成员。如此一来我们就可以利用信号量来简化代码结构,而无需为信号量的实现单独构建一个冗长的队列。
1.2 优先级反转
优先级反转指的是一个高优先级的任务由于等待某个共享资源,而被迫让位于低优先级的任务,进而导致系统中的中等优先级任务抢占 CPU 资源。这种情况下,系统中的优先级关系被打破,高优先级任务的执行反而延后了。
例如如果任务B(低优先级)持有一个共享资源,任务A(高优先级)试图获取该资源,但任务A无法获取而被阻塞。此时任务C(中优先级)由于优先级高于任务B而获得了CPU的控制权,继续执行,导致任务A无法继续执行,尽管它的优先级高于任务C。最终任务A的执行被任务C延迟,而这个过程违背了优先级调度的原则。
优先级反转会带来系统调度的混乱,尤其在实时系统中,高优先级任务可能是一个时间敏感的任务。如果优先级反转持续存在,它可能导致高优先级任务不能在规定时间内完成,甚至引发系统的故障或崩溃。
为了解决优先级反转问题,操作系统通常引入优先级继承机制,即当一个低优先级任务持有某个共享资源时,如果一个高优先级任务试图访问这个资源并被阻塞,操作系统会将低优先级任务的优先级“继承”为高优先级任务的优先级,直到它释放该资源。这意味着低优先级任务会立即完成其对资源的操作,进而释放资源,使得高优先级任务可以继续执行。
二、FreeRTOS信号量
在FreeRTOS中,信号量(Semaphore)是一种用于管理任务间同步和共享资源的机制,类似于Linux中的信号量机制。它可以用于控制对共享资源(如硬件外设、内存等)的访问,以及任务之间的同步。FreeRTOS的信号量可以分为几种主要类型,每种类型都有不同的应用场景:
- 二元信号量(Binary Semaphore):实现为长度为 1、项大小为 0 的队列。
- 计数信号量(Counting Semaphore):实现为长度为 N、项大小为 0 的队列,其中 N 是信号量的最大计数值。
- 互斥量(Mutex):也是一种特殊的二元信号量,具有优先级继承等特性。
2.1 二值信号量(Binary Semaphore)
二值信号量的行为类似于互斥锁或开关,它其实可以理解为一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空。它通常用于同步任务,或者保护对共享资源的独占访问。和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。
xSemaphoreCreateBinary()
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinary( void )
- 参数:无
- 返回值:
- 成功: 创建成功的二值信号量的句柄
- 失败: NULL
xSemaphoreGive()和xSemaphoreGiveFromISR()
xSemaphoreGive()用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:
BaseType_t xSemaphoreGive( xSemaphore )
- 参数:
- xSemaphore:要释放的信号量句柄。
- 返回值:
- 成功:pdPASS
- 失败:errQUEUE_FULL
xSemaphoreGiveFromISR()函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量。
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
- 参数:
- xSemaphore: 要释放的信号量句柄
- pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换
注:pxHigherPriorityTaskWoken变量的值不用手动进行设置,用户只需要提供一个变量来保存这个值就行了,当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
- 返回值:
- 成功:pdPASS
- 失败:errQUEUE_FULL
xSemaphoreTake()和xSemaphoreTakeFromISR()
xSemaphoreTake()函数用于获取二值信号量、计数型信号量或互斥信号量,函数原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime)
- 参数:
- xSemaphore:要获取的信号量句柄
- xBlockTime: 阻塞时间
- 返回值:
- 成功:pdTRUE
- 失败:pdFALSE
xQueueReceiveFromISR ()函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,此函数原型如下:
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
- 参数:
- xSemaphore: 要获取的信号量句柄
- pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换
- 返回值:
- 成功:pdTRUE
- 失败:pdFALSE
2.2 计数信号量(Counting Semaphore)
计数信号量允许多个任务或中断同时释放信号量,并且允许多个任务等待信号量。它的作用类似于一个计数器,可以在多个任务或中断中同时处理多个事件。它限制访问某个共享资源的并发任务数,也可以计数某个事件的发生次数,比如传感器数据的更新次数。
xSemaphoreCreateCounting()
此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
- 参数:
- uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
- uxInitialCount: 计数信号量初始值。
- 返回值:
- 成功:返回计数型信号量句柄
- 失败:NULL
注:互斥信号量、计数型信号量的释放和获取与二值信号量相同。
2.3 互斥信号量(Mutex)
互斥信号量用于实现任务间的互斥访问,确保只有一个任务能够访问某一共享资源。与二值信号量不同,互斥信号量可以防止优先级反转(priority inversion),即较低优先级任务占用资源时,高优先级任务被阻塞的问题。
xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
- 参数:无。
- 返回值:
- 成功: 创建成功的互斥信号量的句柄
- 失败:NULL: 互斥信号量创建失败
免责声明:本文参考了网上公开资料,仅用于学习交流,若有错误或侵权请联系笔者。