FreeRTOS的学习系列文章目录
FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
前言
信号量可以认为是队列的一种表达形式,他的存在给予了任务和任务,任务和中断之间的资源访问形式。
- 信号量常被用于控制对共享资源的访问和任务同步,可以对资源的变化进行计数,或者判断是否使用某资源等。
- 另外信号量还常用于任务同步,用于任务于任务或者中断与任务之间的同步。
FreeRTOS中具有非常多的信号量,比如计数信号量,二值信号量,互斥信号量和递归互斥信号量,其存在在某些情况下亦可以用队列的功能去替换。
1 二值信号量简介
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号量更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,故也就是二值信号量的概念由来。信号量的API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。不过与队列不同的是,释放信号量的API函数并没有阻塞时间的概念,因为任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。不过由于存在多个任务等待同一个信号量的原因,所以获取二值信号量是需要设置阻塞时间的。
2 二值信号量的创建
二值信号量的创建函数如下:
- vSemaphoreCreateBinary(),动态创建二值信号量(老版本),可自动释放一个信号量。
- xSemaphoreCreateBinary(),动态创建二值信号量(新版本),自动生成一个空的信号量,不释放。
- xSemaphoreCreateBinaryStatic(),静态创建二值信号量。
2.1 vSemaphoreCreateBinary
函数的定义如下:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \
} \
}
#endif
可以看到实际上创建的过程中就是调用了队列的通用创建函数。并且在创建成功后会调用xSemaphoreGive( ( xSemaphore ) ),也就是释放该二值信号量。
2.2 xSemaphoreCreateBinary
函数的定义如下:
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的。
2.3 xSemaphoreCreateBinaryStatic
函数的定义如下:
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore ) xQueueGenericCreateStatic( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif /* configSUPPORT_STATIC_ALLOCATION */
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的RAM需要由用户来分配。
3 信号量的释放和获取
3.1 释放信号量
释放信号量的函数有两个:
- xSemaphoreGive(),任务级信号量释放函数。
- xSemaphoreGiveFromISR(),中断级信号量释放函数。
任务级释放信号量函数为xSemaphoreGive,该函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成。
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息,阻塞时间为 0(宏 semGIVE_BLOCK_TIME为 0),入队方式采用的后向入队。
入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当 uxMessagesWaiting 为1 的话说明二值信号量有效,为 0 就无效。
实质上二值信号量的就是长度为1的队列罢了。
3.2 获取信号量
获取信号量的函数有两个:
- xSemaphoreTake(),任务级获取信号量函数。
- xSemaphoreTakeFromISR(),中断级获取信号量函数。
同释放信号量的API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量都可用上述函数获取。
函数xSemaphoreTake如下:
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueSemaphoreTake(),这与旧版的代码是不同的,旧版本的源码是由通用的出队函数实现的(xQueueSemaphoreTake),新版本,则区分了不同的情况,列出了单独的函数,这点在前面的队列出队分析中也提到了。
获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。二值信号量不在乎队列中的内容是什么,而是要知道队列中是否有信息,通过这种二值的状态来同步任务之间的信息。
如果队列为空并且阻塞时间为 0 的话就立即返回 errQUEUE_EMPTY,表示队列空。如果队列为空并且阻塞时间不为 0 的话就将任务添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一(这个步骤见下面的代码),然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出队成功。
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
互斥信号量涉及到优先级继承,处理方式不同,在获取函数中做了单独的判断。
4 写在后面
关于信号量的讲解也分了几个部分,后面会继续放其他的信号量分析。