本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。适用于快速了解FreeRTOS并进行开发、突击面试、对新手小白非常友好。期待您的后续关注和订阅!
目录
信号量介绍
信号量可以看作为一种工具,用于协调多个任务对共享资源的访问。在日常生活中,可以将信号量类比为停车场的停车位或者交通灯,它们帮助确保资源的有序使用和避免冲突。
1 信号量的作用
- 任务同步:任务A完成某个操作后,通过释放信号量通知任务B继续执行。
- 互斥访问:多个任务需要访问同一个资源时,使用信号量确保每次只有一个任务能访问该资源,避免数据混乱或冲突。
2 信号量的类型
- 二值信号量:只有两个状态(0或1),类似于交通灯的红绿状态。常用于任务同步。
- 计数型信号量:可以有多个计数值,类似于有多个停车位的停车场。常用于资源管理,表示可用资源的数量。
- 互斥信号量:一种特殊的二值信号量,具有优先级继承机制,防止优先级翻转问题。常用于保护共享资源的访问。
3 举例示范
停车位:
- 停车场信号量:假设一个停车场有10个停车位,每个停车位代表一个信号量的计数值。每当一辆车进入停车场,占用一个停车位时,信号量的计数值减1;当一辆车离开停车场,释放一个停车位时,信号量的计数值加1。
- 资源管理:如果所有停车位都被占用,新的车就不能进入停车场(信号量为0,表示没有可用资源)。车主必须等待直到有停车位空出(信号量大于0,表示有可用资源)。
交通灯:
- 交通信号量:在十字路口,交通灯帮助管理车辆的通行顺序。红灯相当于信号量为0,禁止通行;绿灯相当于信号量为1,允许通行。
- 同步:交通灯确保不同方向的车辆不会同时进入交叉路口,从而避免事故。这类似于信号量确保多个任务不会同时访问同一资源,避免资源冲突。
4 信号量类型
- 二值信号量:只有两个状态(0或1),类似于交通灯的红绿状态。常用于任务同步。
- 计数型信号量:可以有多个计数值,类似于有多个停车位的停车场。常用于资源管理,表示可用资源的数量。
- 互斥信号量:一种特殊的二值信号量,具有优先级继承机制,防止优先级翻转问题。常用于保护共享资源的访问。
1 二值信号量
1.1 简介
(1)本质
二值信号量实际上是一个长度为1的队列,该队列只有空和满两种状态。
(2)作用
二值信号量通常用于任务同步和简单的资源互斥,但由于没有优先级继承机制,可能会导致优先级翻转的问题。因此,二值信号量更适合用于同步场景。
1.2 相关API函数
函数 | 作用 |
---|---|
xSemaphoreCreateBinary() | 创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
(1)xSemaphoreCreateBinary()
函数作用:创建一个二值信号量
函数原型:SemaphoreHandle_t xSemaphoreCreateBinary(void)
参数解析:无参数
返回值:
NULL
:创建失败。- 其他值:成功返回二值信号量的句柄。
函数举例:
// 创建一个二值信号量
SemaphoreHandle_t binarySemaphore = xSemaphoreCreateBinary();
if (binarySemaphore == NULL) {
// 处理创建失败的情况
// 可以打印错误信息或采取其他措施
}
(2)xSemaphoreGive()
函数作用:释放信号量
函数原型:BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要释放的信号量句柄 |
返回值:
pdPASS
:释放信号量成功。errQUEUE_FULL
:释放信号量失败。
函数举例:
// 释放二值信号量
if (xSemaphoreGive(binarySemaphore) != pdPASS) {
// 处理释放失败的情况
// 可以打印错误信息或采取其他措施
}
(3)xSemaphoreGiveFromISR()
函数作用:在中断服务例程中释放信号量
函数原型:BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要释放的信号量句柄 |
pxHigherPriorityTaskWoken | 指向一个布尔值,如果释放信号量后需要唤醒更高优先级的任务,该值会被设置为pdTRUE |
返回值:
pdPASS
:释放信号量成功。errQUEUE_FULL
:释放信号量失败。
函数举例:
void ISR_Handler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 初始化为pdFALSE
// 在中断服务例程中释放二值信号量
if (xSemaphoreGiveFromISR(binarySemaphore, &xHigherPriorityTaskWoken) != pdPASS) {
// 处理释放失败的情况
// 可以打印错误信息或采取其他措施
}
// 如果需要唤醒更高优先级的任务,则触发任务切换
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR(); // 触发任务切换
}
}
(4)xSemaphoreTake()
函数作用:获取信号量
函数原型:BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要获取的信号量句柄 |
xTicksToWait | 阻塞时间,以滴答计时 |
返回值:
pdTRUE
:获取信号量成功。pdFALSE
:超时,获取信号量失败。
函数举例:
// 获取二值信号量,阻塞时间为最大值
if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量
// 可以继续执行需要同步的操作
} else {
// 处理获取失败的情况
// 可以打印错误信息或采取其他措施
}
(5)xSemaphoreTakeFromISR()
函数作用:在中断服务例程中获取信号量
函数原型:BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要获取的信号量句柄 |
pxHigherPriorityTaskWoken | 指向一个布尔值,如果获取信号量后需要唤醒更高优先级的任务,该值会被设置为pdTRUE |
返回值:
pdTRUE
:获取信号量成功。pdFALSE
:获取信号量失败。
1.3 代码实例
实验设计
本实验旨在演示如何在FreeRTOS中创建并使用二值信号量来实现任务间的同步。实验包括两个任务:一个任务模拟按键扫描,当检测到按键按下时释放信号量;另一个任务等待获取信号量,当成功获取到信号量后执行相应操作。
任务功能
-
任务1(按键扫描任务):
- 模拟按键扫描。
- 当检测到按键按下时释放信号量。
-
任务2(处理任务):
- 等待获取信号量。
- 成功获取信号量后执行处理操作。
代码示例
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
// 任务句柄和信号量句柄
TaskHandle_t Task1_Handler;
TaskHandle_t Task2_Handler;
SemaphoreHandle_t BinarySemaphore;
// 任务函数声明
void Task1(void *pvParameters);
void Task2(void *pvParameters);
int main(void)
{
// 创建二值信号量
BinarySemaphore = xSemaphoreCreateBinary();
if (BinarySemaphore == NULL) {
// 处理创建失败的情况
printf("Failed to create binary semaphore.\n");
while(1); // 信号量创建失败时,程序陷入死循环
}
// 创建任务1(按键扫描任务)
xTaskCreate((TaskFunction_t)Task1, "Task1", 128, NULL, 2, &Task1_Handler);
// 创建任务2(处理任务)
xTaskCreate((TaskFunction_t)Task2, "Task2", 128, NULL, 2, &Task2_Handler);
// 启动调度器
vTaskStartScheduler();
// 主函数不会再执行到这里
while(1);
}
// 任务1:按键扫描任务
void Task1(void *pvParameters)
{
while(1)
{
// 模拟按键按下
printf("Button pressed.\n");
// 释放信号量
if (xSemaphoreGive(BinarySemaphore) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore.\n");
}
// 模拟按键按下间隔
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒
}
}
// 任务2:处理任务
void Task2(void *pvParameters)
{
while(1)
{
// 等待获取信号量
if (xSemaphoreTake(BinarySemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量,执行处理操作
printf("Semaphore taken, performing action.\n");
} else {
// 处理获取失败的情况
printf("Failed to take semaphore.\n");
}
}
}
2 计数型信号量
2.1 简介
(1)本质
计数型信号量相当于长度大于1的队列,能够容纳多个资源。
(2)作用
计数型信号量常用于事件计数和资源管理。例如,事件计数每次事件发生后释放信号量,任务获取信号量进行处理;资源管理中,信号量表示资源数目,任务获取信号量才能使用资源,用完资源后释放信号量。
2.2 相关API函数
以下API函数为计数型信号量的常用函数,其中后面四个我们在前文已经进行介绍过
函数 | 描述 |
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
(1)xSemaphoreCreateCounting()
函数作用:使用动态方法创建计数型信号量
函数原型:SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)
参数解析:
参数 | 描述 |
---|---|
uxMaxCount | 计数型信号量的最大计数值 |
uxInitialCount | 计数型信号量的初始计数值 |
返回值:
NULL
:创建失败。- 其他值:成功返回计数型信号量的句柄。
函数举例:
// 创建一个最大计数值为10,初始计数值为0的计数型信号量
SemaphoreHandle_t countingSemaphore = xSemaphoreCreateCounting(10, 0);
if (countingSemaphore == NULL) {
// 处理创建失败的情况
printf("Failed to create counting semaphore.\n");
}
(2)xSemaphoreCreateCountingStatic()
函数作用:使用静态方法创建计数型信号量
函数原型:SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxStaticSemaphore)
参数解析:
参数 | 描述 |
---|---|
uxMaxCount | 计数型信号量的最大计数值 |
uxInitialCount | 计数型信号量的初始计数值 |
pxStaticSemaphore | 指向静态信号量结构体的指针 |
返回值:
NULL
:创建失败。- 其他值:成功返回计数型信号量的句柄.
函数举例:
// 定义一个静态信号量结构体
StaticSemaphore_t myStaticSemaphore;
// 创建一个最大计数值为10,初始计数值为0的静态计数型信号量
SemaphoreHandle_t countingSemaphore = xSemaphoreCreateCountingStatic(10, 0, &myStaticSemaphore);
if (countingSemaphore == NULL) {
// 处理创建失败的情况
printf("Failed to create static counting semaphore.\n");
}
(3)uxSemaphoreGetCount()
函数作用:获取信号量的计数值。
函数原型:UBaseType_t uxSemaphoreGetCount(SemaphoreHandle_t xSemaphore)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 信号量句柄 |
返回值:
- 当前信号量的计数值。
函数举例:
// 获取信号量的计数值
UBaseType_t count = uxSemaphoreGetCount(countingSemaphore);
printf("Semaphore count: %u\n", count);
2.3 代码实例
实验设计
本实验旨在演示如何在FreeRTOS中创建并使用计数型信号量来实现任务间的资源管理。实验包括三个任务:一个任务用于初始化创建任务和信号量,另一个任务模拟事件发生,每次事件发生时释放信号量,第三个任务等待获取信号量,获取到信号量后执行相应操作。
任务功能
-
任务1(初始化任务)
- 创建任务2和任务3。
- 创建计数型信号量。
-
任务2(事件产生任务)
- 模拟事件发生,每次事件发生时释放信号量。
-
任务3(事件处理任务)
- 等待获取信号量,获取到信号量后执行处理操作。
代码示例
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
// 任务句柄和信号量句柄
TaskHandle_t InitTask_Handler;
TaskHandle_t EventTask_Handler;
TaskHandle_t ProcessingTask_Handler;
SemaphoreHandle_t CountingSemaphore;
// 任务函数声明
void InitTask(void *pvParameters);
void EventTask(void *pvParameters);
void ProcessingTask(void *pvParameters);
int main(void)
{
// 创建初始化任务
xTaskCreate((TaskFunction_t)InitTask, "InitTask", 128, NULL, 1, &InitTask_Handler);
// 启动调度器
vTaskStartScheduler();
// 主函数不会再执行到这里
while(1);
}
// 任务1:初始化任务
void InitTask(void *pvParameters)
{
// 创建计数型信号量,最大计数值为10,初始计数值为0
CountingSemaphore = xSemaphoreCreateCounting(10, 0);
if (CountingSemaphore == NULL) {
// 处理创建失败的情况
printf("Failed to create counting semaphore.\n");
while(1); // 信号量创建失败时,程序陷入死循环
}
// 创建事件产生任务
xTaskCreate((TaskFunction_t)EventTask, "EventTask", 128, NULL, 2, &EventTask_Handler);
// 创建事件处理任务
xTaskCreate((TaskFunction_t)ProcessingTask, "ProcessingTask", 128, NULL, 2, &ProcessingTask_Handler);
// 删除初始化任务
vTaskDelete(InitTask_Handler);
}
// 任务2:事件产生任务
void EventTask(void *pvParameters)
{
while(1)
{
// 模拟事件发生
printf("Event occurred.\n");
// 释放计数型信号量
if (xSemaphoreGive(CountingSemaphore) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore.\n");
}
// 模拟事件发生间隔
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒
}
}
// 任务3:事件处理任务
void ProcessingTask(void *pvParameters)
{
while(1)
{
// 等待获取信号量
if (xSemaphoreTake(CountingSemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量,执行处理操作
printf("Semaphore taken, processing event.\n");
} else {
// 处理获取失败的情况
printf("Failed to take semaphore.\n");
}
}
}
3 互斥信号量
在学习互斥信号量之前,我们先了解一下优先级翻转什么?
优先级翻转是指高优先级任务被低优先级任务阻塞,导致高优先级任务得不到及时调度。这种情况在实时操作系统中是不允许的,因为会破坏任务的预期顺序,可能导致严重后果。
解决方案:通过使用互斥信号量并引入优先级继承机制可以有效减少优先级翻转的影响。当低优先级任务持有互斥信号量时,如果高优先级任务也需要这个信号量,低优先级任务的优先级会被暂时提升到与高优先级任务相同,直到低优先级任务释放信号量。
3.1 简介
互斥信号量其实就是拥有优先级继承的二值信号量。
优先级继承:当一个低优先级任务持有互斥信号量时,高优先级任务尝试获取信号量会被阻塞,但高优先级任务会将低优先级任务的优先级提升到与自己相同,直到低优先级任务释放信号量。
注意:
- 互斥信号量不能用于中断(中断不是任务没有优先级)
- 中断服务函数不能因为等待互斥信号量而设置阻塞时间进入阻塞态
3.2 相关API函数
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 创建互斥信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreTake() | 获取信号量 |
(1)xSemaphoreCreateMutex()
函数作用:创建互斥信号量
函数原型:SemaphoreHandle_t xSemaphoreCreateMutex(void)
参数解析:
参数 | 描述 |
---|---|
无 | 该函数不接受任何参数。 |
返回值:
NULL
:创建失败。- 其他值:成功返回互斥信号量的句柄。
函数举例:
// 创建一个互斥信号量
SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
if (mutex == NULL) {
// 处理创建失败的情况
printf("Failed to create mutex semaphore.\n");
}
(2)xSemaphoreGive()
函数作用:释放信号量
函数原型:BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要释放的信号量句柄 |
返回值:
pdPASS
:释放信号量成功。errQUEUE_FULL
:释放信号量失败。
函数举例:
// 释放互斥信号量
if (xSemaphoreGive(mutex) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore.\n");
}
(3)xSemaphoreTake()
函数作用:获取信号量。
函数原型:BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)
参数解析:
参数 | 描述 |
---|---|
xSemaphore | 要获取的信号量句柄 |
xTicksToWait | 阻塞时间,以滴答计时 |
返回值:
pdTRUE
:获取信号量成功。pdFALSE
:超时,获取信号量失败。
函数举例:
// 获取互斥信号量,阻塞时间为最大值
if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量
// 可以继续执行需要同步的操作
} else {
// 处理获取失败的情况
printf("Failed to take semaphore.\n");
}
3.3 代码实例
实验设计
本实验旨在演示如何在FreeRTOS中创建并使用互斥信号量来实现任务间的互斥访问。实验包括四个任务:一个任务用于初始化创建任务和信号量,另外三个任务模拟资源访问,但它们不能同时访问资源。
任务功能
-
任务1(初始化任务):
- 创建任务2、任务3和任务4。
- 创建互斥信号量。
-
任务2(资源访问任务1):
- 获取互斥信号量,模拟访问共享资源,释放互斥信号量。
-
任务3(资源访问任务2):
- 获取互斥信号量,模拟访问共享资源,释放互斥信号量。
-
任务4(资源访问任务3):
- 获取互斥信号量,模拟访问共享资源,释放互斥信号量。
代码示例
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
// 任务句柄和信号量句柄
TaskHandle_t InitTask_Handler;
TaskHandle_t ResourceTask1_Handler;
TaskHandle_t ResourceTask2_Handler;
TaskHandle_t ResourceTask3_Handler;
SemaphoreHandle_t MutexSemaphore;
// 任务函数声明
void InitTask(void *pvParameters);
void ResourceTask1(void *pvParameters);
void ResourceTask2(void *pvParameters);
void ResourceTask3(void *pvParameters);
int main(void)
{
// 创建初始化任务
xTaskCreate((TaskFunction_t)InitTask, "InitTask", 128, NULL, 1, &InitTask_Handler);
// 启动调度器
vTaskStartScheduler();
// 主函数不会再执行到这里
while(1);
}
// 任务1:初始化任务
void InitTask(void *pvParameters)
{
// 创建互斥信号量
MutexSemaphore = xSemaphoreCreateMutex();
if (MutexSemaphore == NULL) {
// 处理创建失败的情况
printf("Failed to create mutex semaphore.\n");
while(1); // 信号量创建失败时,程序陷入死循环
}
// 创建资源访问任务1
xTaskCreate((TaskFunction_t)ResourceTask1, "ResourceTask1", 128, NULL, 2, &ResourceTask1_Handler);
// 创建资源访问任务2
xTaskCreate((TaskFunction_t)ResourceTask2, "ResourceTask2", 128, NULL, 2, &ResourceTask2_Handler);
// 创建资源访问任务3
xTaskCreate((TaskFunction_t)ResourceTask3, "ResourceTask3", 128, NULL, 2, &ResourceTask3_Handler);
// 删除初始化任务
vTaskDelete(InitTask_Handler);
}
// 任务2:资源访问任务1
void ResourceTask1(void *pvParameters)
{
while(1)
{
// 获取互斥信号量
if (xSemaphoreTake(MutexSemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量,模拟访问共享资源
printf("ResourceTask1 is accessing the resource.\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟资源访问需要1秒
// 释放互斥信号量
if (xSemaphoreGive(MutexSemaphore) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore in ResourceTask1.\n");
}
} else {
// 处理获取失败的情况
printf("Failed to take semaphore in ResourceTask1.\n");
}
vTaskDelay(pdMS_TO_TICKS(500)); // 任务间隔500ms
}
}
// 任务3:资源访问任务2
void ResourceTask2(void *pvParameters)
{
while(1)
{
// 获取互斥信号量
if (xSemaphoreTake(MutexSemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量,模拟访问共享资源
printf("ResourceTask2 is accessing the resource.\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟资源访问需要1秒
// 释放互斥信号量
if (xSemaphoreGive(MutexSemaphore) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore in ResourceTask2.\n");
}
} else {
// 处理获取失败的情况
printf("Failed to take semaphore in ResourceTask2.\n");
}
vTaskDelay(pdMS_TO_TICKS(500)); // 任务间隔500ms
}
}
// 任务4:资源访问任务3
void ResourceTask3(void *pvParameters)
{
while(1)
{
// 获取互斥信号量
if (xSemaphoreTake(MutexSemaphore, portMAX_DELAY) == pdTRUE) {
// 成功获取信号量,模拟访问共享资源
printf("ResourceTask3 is accessing the resource.\n");
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟资源访问需要1秒
// 释放互斥信号量
if (xSemaphoreGive(MutexSemaphore) != pdPASS) {
// 处理释放失败的情况
printf("Failed to give semaphore in ResourceTask3.\n");
}
} else {
// 处理获取失败的情况
printf("Failed to take semaphore in ResourceTask3.\n");
}
vTaskDelay(pdMS_TO_TICKS(500)); // 任务间隔500ms
}
}
本专栏将对FreeRTOS进行快速讲解,带你了解并使用FreeRTOS的各部分内容。期待诸君的关注和订阅!