18.5 信号量控制块
信号量 API 函数实际上都是宏,它使用现有的队列机制, 这些宏定义在 semphr.h 文件中, 如果使用信号量或者互斥量,需要包含 semphr.h 头文件。 所以 FreeRTOS 的信号量控制块结构体与消息队列结构体是一模一样的, 只不过结构体中某些成员变量代表的含义不一样而已。
typedef struct QueueDefinition{int8_t *pcHead; // pcHead 指向队列消息存储区起始位置,即第一个消息空间。int8_t *pcTail; // pcTail 指向队列消息存储区结束位置地址。int8_t *pcWriteTo; // pcWriteTo 指向队列消息存储区下一个可用消息空间。union{int8_t *pcReadFrom;UBaseType_t uxRecursiveCallCount;} u;List_t xTasksWaitingToSend;List_t xTasksWaitingToReceive;volatile UBaseType_t uxMessagesWaiting; //1UBaseType_t uxLength; //2UBaseType_t uxItemSize; //3volatile int8_t cRxLock;volatile int8_t cTxLock;#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated;#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif} xQUEUE;typedef xQUEUE Queue_t;
如果控制块结构体是用于消息队列: uxMessagesWaiting 用来记录当前消息队列的消息个数; 如果控制块结构体被用于信号量的时候, 这个值就表示有效信号量个数,有以下两种情况:
- 如果信号量是二值信号量、互斥信号量,这个值是 1 则表示有可用信号量,如果是 0 则表示没有可用信号量。
- 如果是计数信号量,这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始值 uxMaxCount
果控制块结构体是用于消息队列: uxLength 表示队列的长度,也就是能存放多少消息; 如果控制块结构体被用于信号量的时候, uxLength 表示最大的信号量可用个数, 会有以下两种情况:
- 如果信号量是二值信号量、互斥信号量, uxLength 最大为 1,因为信号量要么是有效的,要么是无效的
- 如果是计数信号量,这个值表示最大的信号量个数,在创建计数信号量的时候将由用户指定这个值 uxMaxCount。
- 如果控制块结构体是用于消息队列: uxItemSize 表示单个消息的大小; 如果控制块结构体被用于信号量的时候,则无需存储空间,为 0 即可。
18.6 常会有信号量函数接口详解
18.6.1 创建信号量函数
1. 创建二值信号量xSemaphoreCreateBinary()
xSemaphoreCreateBinary()用于创建一个二值信号量, 并返回一个句柄。 其实二值信号量和互斥量都共同使用一个类型 SemaphoreHandle_t 的句柄(.h 文件 79 行) , 该句柄的原型 是 一 个 void 型 的 指 针。
使 用 该 函数 创 建 的 二 值信 号 量 是 空的 , 在 使 用函 数xSemaphoreTake()获取之前必须先调用函数 xSemaphoreGive()释放后才可以获取。 如果是使用老式的函数 vSemaphoreCreateBinary()创建的二值信号量,则为 1, 在使用之前不用先释放。
configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateBinary() \xQueueGenericCreate( ( UBaseType_t ) 1, \semSEMAPHORE_QUEUE_ITEM_LENGTH, \queueQUEUE_TYPE_BINARY_SEMAPHORE )#endif
实 际 使 用 的 函 数 就 是xQueueGenericCreate()函数,和队列使用的是一样的,但是参数不同。
QueueHandle_t xQueueGenericCreate(const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,const uint8_t ucQueueType)
const UBaseType_t uxQueueLength:为 1 表示创建的队列长度为 1,其实用作信号量就表示信号量的最大可用个数
const UBaseType_t uxItemSize:emSEMAPHORE_QUEUE_ITEM_LENGTH 其实是一个宏定义,其值为 0,
const uint8_t ucQueueType:ucQueueType 表示的是创建消息队列的类型, 在 queue.h 中有定义。
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )
二值信号量的释放和获取都是通过操作队列结控制块构体成员 uxMessageWaiting 来实现的,它表示信号量中当前可用的信号量个数。 在信号量创建之后,变量 uxMessageWaiting 的值为 0,这说明当前信号量处于无效状态, 此时的信号量是无法被获取的, 在获取信号之前,应先释放一个信号量。
2. 创建计数信号量xSemaphoreCreateCounting()
xSemaphoreCreateCounting ()用于创建一个计数信号量。 和上面创建类似的。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )#endif
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ){QueueHandle_t xHandle;configASSERT( uxMaxCount != 0 );configASSERT( uxInitialCount <= uxMaxCount );xHandle = xQueueGenericCreate( uxMaxCount,queueSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_COUNTING_SEMAPHORE );if( xHandle != NULL ){( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;traceCREATE_COUNTING_SEMAPHORE();}else{traceCREATE_COUNTING_SEMAPHORE_FAILED();}return xHandle;}#endif
仍然是调用函数xQueueGenericCreate()来创建一个计数信号量,信号量最大个数由参数 uxMaxCount 指定。
每个消息空间的大小由宏 queueSEMAPHORE_QUEUE_ITEM_LENGTH 指定,这个宏被定义为0。
如果创建成功,还会将消息队列控制块中的 uxMessagesWaiting 成员变量赋值为用户指定的初始可用信号量个数 uxInitialCount,如果这个值大于 0,则表示此时有 uxInitialCount 个计数信号量是可用的,这点与二值信号量的创建不一样,二值信号量在创建成功的时候是无效的。
二值信号量创建函数 xSemaphoreCreateBinary()使用实例:
SemaphoreHandle_t xSemaphore = NULL;void vATask( void * pvParameters ){/* 尝试创建一个信号量 */xSemaphore = xSemaphoreCreateBinary();if ( xSemaphore == NULL ) {/* 内存不足,创建失败 */} else {/* 信号量现在可以使用,句柄存在变量 xSemaphore 中这个时候还不能调用函数 xSemaphoreTake()来获取信号量因为使用 xSemaphoreCreateBinary()函数创建的信号量是空的在第一次获取之前必须先调用函数 xSemaphoreGive()先提交*/}}
计数信号量创建函数 xSemaphoreCreateCounting()使用实例:
void vATask( void * pvParameters ){SemaphoreHandle_t xSemaphore;xSemaphore = xSemaphoreCreateCounting( 5, 5 );if ( xSemaphore != NULL ) {/* 计数信号量创建成功 */}}
18.6.2 信号量删除函数vSemaphoreDelete()
vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。
注意:如果有任务阻塞在该信号量上,那么不要删除该信号量。 
删除信号量过程其实就是删除消息队列过程, 因为信号量其实就是消息队列, 只不过是无法存储消息的队列而已。
#define vSemaphoreDelete( xSemaphore )vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
18.6.3 信号量释放函数
信号量的释放可以在任务、中断中使用,需要调用不同的API。
让信号量有效:
- 创建的时候进行初始化,将它可用的信号量个数设置一个初始值
- FreeRTOS 提供了信号量释放函数,每调用一次该函数就释放一个信号量
注意:
- 要注意可用信号量的范围,当用作二值信号量的时候,必须确保其可用值在 0~1 范围内
- 用作计数信号量的话,其范围是由用户在创建时指定 uxMaxCount,其最大可用信号量不允许超出uxMaxCount,
- 可以调用,但是无法释放成功。要注意代码的严谨。
1.xSemaphoreGive()(任务)
xSemaphoreGive()是一个用于释放信号量的宏, 真正的实现过程是调用消息队列通用发送函数。
释放的信号量对象必须是已经被创建的, 可以用于二值信号量、 计数信号量、 互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量
释放信号量实际上是一次入队操作,并且是不允许入队阻塞,因为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。#define xSemaphoreGive( xSemaphore ) \xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \NULL, \semGIVE_BLOCK_TIME, \queueSEND_TO_BACK)
简化过程:如果信号量未满, 控制块结构体成员 uxMessageWaiting 就会加 1, 然后判断是否有阻塞的任务, 如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满, 则返回错误代码(err_QUEUE_FULL) 。
使用示例:
static void Send_Task(void* parameter){BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */while (1) {/* K1 被按下 */if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if ( xReturn == pdTRUE )printf("BinarySem_Handle 二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle 二值信号量释放失败!\r\n");if ( Key_Scan(KEY2_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量if ( xReturn == pdTRUE )printf("BinarySem_Handle 二值信号量释放成功!\r\n");elseprintf("BinarySem_Handle 二值信号量释放失败!\r\n");}}
2. xSemaphoreGiveFromISR()(中断)
用于释放一个信号量,带中断保护。被释放的信号量可以是二进制信号量和计数信号量。和普通版本的释放信号量 API 函数有些许不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用, 互斥量的优先级继承机制只能在任务中起作用,而在中断中毫无意义。
#define xSemaphoreGiveFromISR( xSemaphore, \pxHigherPriorityTaskWoken ) \xQueueGiveFromISR(( QueueHandle_t ) \( xSemaphore ), \( pxHigherPriorityTaskWoken ) )
一个或者多个任务有可能阻塞在同一个信号量上,调用函数 xSemaphoreGiveFromISR()可能会唤醒阻塞在该信号量上的任务,如果被唤醒的任务的优先级大于当前任务的优先级,那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE, 然后在中断退出前执行一次上下文切换。
使用示例:
void vTestISR( void ){BaseType_t pxHigherPriorityTaskWoken;uint32_t ulReturn;/* 进入临界段,临界段可以嵌套 */ulReturn = taskENTER_CRITICAL_FROM_ISR();/* 判断是否产生中断 */{/* 如果产生中断,清除中断标志位 *///释放二值信号量,发送接收到新数据标志,供前台程序查询xSemaphoreGiveFromISR(BinarySem_Handle,&pxHigherPriorityTaskWoken);//如果需要的话进行一次任务切换,系统会判断是否需要进行切换portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);}/* 退出临界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn );}
18.6.4 信号量的获取
与消息队列的操作一样,信号量的获取可以在任务、中断(中断中使用并不常见) 中使用,所以需要有不一样的 API 函数在不一样的上下文环境中调用。
1.xSemaphoreTake()(任务)

从该宏定义可以看出释放信号量实际上是一次消息出队操作, 阻塞时间由用户指定xBlockTime。
获取一个信号量的过程简化:如果有可用信号量, 控制块结构体成员 uxMessageWaiting 就会减 1,然后返回获取成功信息(pdPASS);如果信号量无效并且阻塞时间为 0, 则返回错误代码(errQUEUE_EMPTY) ;如果信号量无效并且用户指定了阻塞时间, 则任务会因为等待信号量而进入阻塞状态,任务会被挂接到延时列表中。
使用实例:
static void Receive_Task(void* parameter){BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */while(1) {//获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */portMAX_DELAY); /* 等待时间 */if (pdTRUE == xReturn)printf("BinarySem_Handle 二值信号量获取成功!\n\n");LED1_TOGGLE;}}
2 xSemaphoreTakeFromISR()(中断)
它不能用于获取互斥,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起
作用, 而在中断中毫无意义。 
