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; //1
UBaseType_t uxLength; //2
UBaseType_t uxItemSize; //3
volatile 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");
else
printf("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");
else
printf("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()(中断)
它不能用于获取互斥,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起
作用, 而在中断中毫无意义。