FreeRTOS——信号量
文章目录
前言
如果我们不需要传输数据信息而只是传递某种状态的话,我们可以选择使用“信号量(Semaphore)”。
举个通俗的例子吧:
1.想象一家餐厅有限的餐桌,每个餐桌只能容纳一个顾客或家庭。这里的每个餐桌就像一个信号量。当顾客进入餐厅时,他们会尝试获取一个餐桌(获取信号量)。如果所有餐桌都被占用(信号量不可用),他们就需要等待,直到有人用餐完毕离开(释放信号量),这样新的顾客才能使用餐桌。
2.公共卫生间每个厕所位看作一个信号量,当有人要使用时就得检查有没有空闲的位置?如果被占用了那就只能排队等待,当有人用完离开后,等待的人才能使用。
稍微专业一点的表述:
生产者和消费者的关系,生产者负责生产资源(产生信号量,也叫释放信号量);消费者需要拿走资源(获取信号量)。这时候如果生产者没有提供信号量,此时消费者要么等待(阻塞),要么不等直接走了。生产者生产一个资源,意思就是信号量数值增加1,消费者拿走一个资源,意思就是信号量数值减少1。
一、信号量是什么?
信号量(Semaphore),用来保证两个或多个关键代码段不被并发使用,用于传递一个状态。其中,信号表示起到通知的作用,量表示资源的数量。
按照这个概念我们可以将信号量分为”二值信号量“和”计数型信号量“,它们具体是什么意思?怎么用呢?下面让我们来逐一击破!
二、二值信号量
二值信号量又称作二进制信号量,它的计数值只有0和1,也就是对于某个资源来说只存在被占用和空闲中(允许任务来占用它)两个状态。
如果已经有一个生产者生产了一个信号量,这时信号量的计数值为1,那么这时候如果没有消费者拿走这个信号量使它数值减1的话,别的生产者也不能生产这个信号量。
反过来也是一样的。
这个就像一间单间厕所,当没有人占用时它是空闲的,此时计数值为1,表示可以被占用;当有人进去后它被占用,计数值为0,别人也进不去了。
三、计数型信号量
它的计数值没有限定,生产者生产一个信号量时它的计数值加1,可以一直叠加;消费者拿走一个信号量时它的计数值减1,一直减到0为止。
这个就像早餐店的包子,店家可以提供很多个包子,每做出来一个包子现有库存(计数值)加1,可以一直生产;当有顾客买走包子,每买走一个包子现有库存减1,当把包子都买光后计数值为0,此时表示没有包子了。
四、两种信号量的区别
1.最大的区别就是它们计数值的上限不同,二值信号量的计数值只能为0和1,而计数型信号量的计数值可以是任意的。
2.还有一个小区别是,当二值信号量被创建时,它的计数值初始值为0,必须要生产者先生产后才能用;而计数型信号量被创建时,它的计数值初始值是自定义的。
五、常用的API函数
1.创建二值信号量(动态)
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateBinary( void );
参数:无
返回值:二值信号量的句柄
2.创建二值信号量(静态)
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
参数:
StaticSemaphore_t *pxSemaphoreBuffer:开发者自己定义一个StaticSemaphore_t * 类型的pxSemaphoreBuffer为二值信号量提供所需的内存空间,注意,这是不需要额外赋值的,只需要定义即可,API会给其开辟空间。
返回值:
二值信号量的句柄
3.创建计数型信号量(动态)
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
参数:
1.uxMaxCount:最大计数值
2.uxInitialCount:计数值初始值
返回值:
计数值信号量句柄
4.创建计数型信号量(静态)
代码如下(示例):
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t* pxSemaphoreBuffer );
参数:
1.uxMaxCount:最大计数值
2.uxInitialCount:计数值初始值
3.pxSemaphoreBuffer :
开发者自己定义一个StaticSemaphore_t * 类型的pxSemaphoreBuffer为二值信号量提供所需的内存空间,注意,这是不需要额外赋值的,只需要定义即可,API会给其开辟空间。
返回值:
计数值信号量句柄
5.删除信号量(通用)
代码如下(示例):
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
参数:xSemaphore :需要删除的信号量的句柄
返回值:无
6.Give 生产信号量(通用)
任务中:
代码如下(示例):
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
参数:
xSemaphore :进行give操作的信号量的句柄
返回值:
成功:pdTRUE 生产成功
失败:pdFAIL 当二值信号量的计数值已经为1则此时再进行give生产操作会失败;当计数值信号量的计数值达到我们创建它时指定的最大值则此时再进行give操作会失败。
中断中:
代码如下(示例):
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t* pxHigherPriorityTaskWoken
);
参数:
1.xSemaphore :进行give操作的信号量的句柄
2.pxHigherPriorityTaskWoken:
当在中断服务例程(ISR)中释放一个信号量时,可能会有一个因为这个信号量而阻塞的任务变得可运行。如果这个被解除阻塞的任务的优先级高于当前正在运行的任务,那么理想的行为是尽快切换到这个高优先级的任务执行。但是,在 ISR 中,我们不能直接进行任务切换。相反,我们设置一个标志来表示在 ISR 完成后是否需要进行任务调度。
当你调用 xSemaphoreGiveFromISR 时,如果释放信号量导致一个优先级更高的任务变得可运行,FreeRTOS 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。
如果没有更高优先级的任务被解除阻塞,*pxHigherPriorityTaskWoken 保持原来的值(通常是 pdFALSE)。返回值:
成功:pdTRUE 生产成功
失败:pdFAIL 当二值信号量的计数值已经为0则此时再进行take拿走操作会失败;当计数值信号量的计数值达到我们创建它时指定的最大值则此时再进行give操作会失败。
7.Take 拿走信号量(通用)
任务中:
代码如下(示例):
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
参数:
1.xSemaphore :进行take操作的信号量的句柄
2.xTicksToWait: 超时时间
返回值:
成功:pdTRUE 生产成功
失败:pdFAIL 当二值信号量的计数值已经为0,则此时再进行give生产操作且超时等待时间已结束还是0的话会失败;当计数值信号量的计数值为0则此时再进行give操作且超时时间结束时还是0的话会失败。
中断中:
代码如下(示例):
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
参数:
1.xSemaphore :进行give操作的信号量的句柄
2.pxHigherPriorityTaskWoken:同上
返回值:同上
8.二值信号量的Give和Take的基本用法(一个简单框架来演示一下give和take的操作而已)
代码如下(示例):
SemaphoreHandle_t xSemaphore = NULL;
void TaskA(void *pvParameters) {
/* 任务 A 的工作内容
进行某些功能的实现
完成工作后,释放信号量(因为二值信号量初始值为0,
除非有人先进行Give操作,否则谁也用不了它)*/
xSemaphoreGive(xSemaphore);
/*
此后,任务 A 可以继续执行其他工作或者结束
...*/
}
void TaskB(void *pvParameters) {
/*等待信号量*/
if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
/* 一旦获取到信号量,开始执行任务 B 的工作
...*/
}
}
int main(void) {
/* 创建信号量*/
xSemaphore = xSemaphoreCreateBinary();
/* 创建任务 A 和任务 B*/
xTaskCreate(TaskA, "Task A", 1000, NULL, 1, NULL);
xTaskCreate(TaskB, "Task B", 1000, NULL, 1, NULL);
/* 启动调度器*/
vTaskStartScheduler();
for( ;; );
}
9.计数型信号量的Give和Take的基本用法
代码如下(示例):
/*计数型信号量的句柄*/
SemaphoreHandle_t xCountingSemaphore;
void TaskA(void *pvParameters) {
/*TaskA 用于增加信号量的计数*/
while(1) {
/* 给出信号量,增加计数*/
xSemaphoreGive(xCountingSemaphore);
/* 做一些其他的工作...*/
vTaskDelay(1000 / portTICK_PERIOD_MS); // 假设的工作延时
}
}
void TaskB(void *pvParameters) {
/* TaskB 用于等待信号量*/
while(1) {
if(xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE) {
/* 成功获取信号量,计数减一*/
/* 执行一些需要信号量的工作...*/
}
}
}
int main(void) {
// 创建计数型信号量,初始计数为 0,最大计数为 5
xCountingSemaphore = xSemaphoreCreateCounting(5, 0);
// 创建任务
xTaskCreate(TaskA, "Task A", 1000, NULL, 1, NULL);
xTaskCreate(TaskB, "Task B", 1000, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
for( ;; );
}
六、基于STM32,用两个按键进行测试
1.二值信号量
代码如下(示例):
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "stdio.h"
SemaphoreHandle_t xBinarySemaphore;
void KeyScanTask(void *pvParameters) {
while(1) {
// 检测 Key1,如果按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET) {
// 防抖动延时
vTaskDelay(20 / portTICK_PERIOD_MS);
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET) {
// 尝试给出信号量
if(xSemaphoreGive(xBinarySemaphore) == pdPASS) {
printf("Key1 pressed, semaphore given.\n");
} else {
printf("Key1 pressed, but semaphore give failed.\n");
}
// 等待释放按键
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET);
}
}
// 检测 Key2,如果按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET) {
// 防抖动延时
vTaskDelay(20 / portTICK_PERIOD_MS);
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET) {
// 尝试获取信号量
if(xSemaphoreTake(xBinarySemaphore, 0) == pdPASS) {
printf("Key2 pressed, semaphore taken.\n");
} else {
printf("Key2 pressed, but semaphore take failed.\n");
}
// 等待释放按键
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET);
}
}
// 轮询延时
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
int main(void) {
// ... GPIO 初始化代码(省略)
// 创建二值信号量
xBinarySemaphore = xSemaphoreCreateBinary();
// 创建按键扫描任务
xTaskCreate(KeyScanTask, "KeyScanTask", 1000, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
for( ;; );
}
2.计数型信号量:
代码如下(示例):
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "stdio.h"
SemaphoreHandle_t xCountingSemaphore;
void KeyScanTask(void *pvParameters) {
while(1) {
// 检测 Key1,如果按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET) {
// 防抖动延时
vTaskDelay(20 / portTICK_PERIOD_MS);
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET) {
// 尝试给出信号量
if(xSemaphoreGive(xCountingSemaphore) == pdPASS) {
printf("Key1 pressed, semaphore given.\n");
} else {
printf("Key1 pressed, but semaphore give failed.\n");
}
// 等待释放按键
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) == RESET);
}
}
// 检测 Key2,如果按下
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET) {
// 防抖动延时
vTaskDelay(20 / portTICK_PERIOD_MS);
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET) {
// 尝试获取信号量
if(xSemaphoreTake(xCountingSemaphore, 0) == pdPASS) {
printf("Key2 pressed, semaphore taken.\n");
} else {
printf("Key2 pressed, but semaphore take failed.\n");
}
// 等待释放按键
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3) == RESET);
}
}
// 轮询延时
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
int main(void) {
// ... GPIO 初始化代码(省略)
// 创建计数型信号量,初始计数为 0,最大计数为 5
xCountingSemaphore = xSemaphoreCreateCounting(5, 0);
// 创建按键扫描任务
xTaskCreate(KeyScanTask, "KeyScanTask", 1000, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
for( ;; );
}
总结
本文主要记录了FreeRTOS中的信号量相关知识点,把概念和常用方法都讲了一遍。