FreeRTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的(只能取0或1)也可以是计数型的(可以是任意正整数)。信号量的基本操作包括"获取"和"释放"。是为了解决同一时刻下资源的争抢问题。
我们用CubeMX生成的Rtos工程中,自带了一个默认的任务启动任务,我们创建信号量时,一般是先创建信号量,随后创建自己的业务任务,最后删除启动任务自身。
信号量有二值信号量和计数型信号量,我们先来看看二值型信号量:
二值信号量:
二值型信号量的内部就是一个长度为1的队列,然后他的值只能取0和1。
创建二值信号量的函数原型如下:
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void ); //创建后不会主动释放一次信号量
void vSemaphoreCreateBinary( SemaphoreHandle_t ); //创建后会主动释放一次信号量
他们还有两个区别就是:x开头的不需要参数,他会返回一个队列的句柄,内部把队列创建好了。但是v开头的需要传入队列句柄
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
释放二值信号量:(也就是放入二值信号量)
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);

首先就是我们自己定义一个队列句柄
//二值信号传入的参数:队列句柄*/
QueueHandle_t Sem_Handle;
void StartDefaultTask(void *argument)
{
/* 在启动函数中创建二值函数 */
vSemaphoreCreateBinary(Sem_Handle);
if(Sem_Handle == NULL)
{
printf("Create binary semaphore failed\r\n");
}
/* 业务任务代码 */
}
/* 用于按键扫描,当按键检测到按键1按下时,释放二值信号量(放入) */
void Task1(void *argument)
{
uint8_t key;
while(1)
{
key = Key_Scan();
if(key == KEY1_PRESS)
{
/* 释放二值信�? */
BaseType_t res = xSemaphoreGive(Sem_Handle); //内部调用的是队列的发送,所以这里需要传入一�?队列参数
if(res == pdPASS)
{
printf("Give two num success\r\n");
}
else
{
printf("Give two num failed\r\n");
}
}
vTaskDelay(500);
}
}
/* 获取二值信号,当成功获取后打印提示信息 */
void Task2(void *argument)
{
while(1)
{
/* 获取二值信号量 */
BaseType_t res = xSemaphoreTake(Sem_Handle, portMAX_DELAY); //他需要传队列句柄和阻塞时间
if(res == pdPASS)
{
printf("success\r\n");
}
else
{
printf("failed\r\n");
}
vTaskDelay(500);
}
}
计数型信号量:
二值型信号量是数量为1的队列,但是计数型信号量是可以计数的队列。我们在创建时,会规定计数最大值和初始值。
假设我有三个任务:ABC,然后计数型信号量数值是3,我们每个任务执行的时候,都会先获取一个计数值,让计数值-1,任务执行完毕后,在释放一次,让计数值+1.
(本质上他们是全局变量的替代,因为全局变量会导致同时访问时造成混乱)
创建计数型信号量:
//二值信号传入的参数:队列句柄
QueueHandle_t Sem_Count_Handle;
/* 创建计数型信号量 */
Sem_Count_Handle = xSemaphoreCreateCounting(100, 0); //参数:最大计数值,初始计数值 返回值:队列句柄
在获取和释放是,使用的API跟二值信号量是一样的
//释放
xSemaphoreGive(队列句柄)
返回值paDASS则说明成功
//接收
xSemaphoreTake(队列句柄)
返回值paDASS则说明成功
倒反天罡(优先级翻转)
优先级翻转是一个在实时系统中可能出现的问题,特别是在多任务环境中。该问题指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。
举个例子:
大家都知道二值型信号量只有一个值,比如我现在一个低优先级刚开始执行,获取了信号量,随后他被高优先级打断,然后高优先级也需要获取这个信号量,那他就等不到这个信号量,如果设置的获取信号量是阻塞模式的话,就会导致高优先级一直被阻塞。

以此图为例;刚开始taskB运行,他获取了信号量,随后被taskA抢占,但是taskB没有释放信号量,随后taskA就阻塞,等待taskB释放信号量,随后来了一个中等优先级的taskC,那么此时没有人能打断taskC,一直运行到taskC完毕为止,随后执行taskB,当taskB释放信号量时,taskA才开始执行。(由于获取信号量的任务优先级较低,那么任务一个任务都能打断他,一打断他,那么他释放信号量就越晚,高优先级就越晚执行)