前言:本篇教程,参考韦东山,开发文档,连接放在最后
目录
[二值信号量(Binary Semaphore)](#二值信号量(Binary Semaphore))
[计数信号量(Couting Semaphore)](#计数信号量(Couting Semaphore))
Semaphore基本概念
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间的同步或者互斥访问,主要分为,二值信号量(Binary Semaphore) ,计数信号量(Couting Semaphore) ,互斥信号量(Mutex)。
与queue传输数据不同的是Semaphore利用本身信号量计数值用来传递决定Task能不能进行访问的状态,同时用这种状态来完成,Task的同步,互斥,限制访问资源Task数量。
信号量的API函数实际上使用的都是宏,使用来自现有的队列机制,二者也是有相似的地方的。
|------------|------------------|----------------------|
| FreeRtos机制 | 队列(Queue) | 信号量(Semaphore) |
| 容纳数据 | 可以容纳多个数据 | 只能存放信号量计数值,对其进行操作 |
| 写操作 | Queue没有空间写数据可以阻塞 | 计数值最大时,返回不进入阻塞状态 |
| 读操作 | Queue中没有数据可以阻塞 | 没有信号量时可以阻塞 |
表格里面需要注意的是,信号量(Semaphore)在进行释放的时候,如果达到规定值,这个时候是没有阻塞状态的,只有等待信号量的时候有阻塞状态。
二值信号量(Binary Semaphore)
这种类型信号量,调用函数创建,信号量计数值默认初始化为0,同时计数值,取值只有0 或1 ,通常用来实现Task之间的互斥。
cpp
SemaphoreHandle_t xSemaphoreCreateBinary( void ); //动态创建
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );//静态创建 需要结构体类型存储信号量
同样的Semaphore(信号量)的创建也分为动态创建和静态创建,区别也是静态创建需要结构体来存储数据。需要注意,这里在创建使用的时候,同时需要声明句柄,来创建,和后续通过句柄来完成对信号量的释放和使用操作。
cpp
static SemaphoreHandle_t Binary_Semaphore_Handle;//创建句柄承接创建函数的返回值
Binary_Semaphore_Handle = xSemaphoreCreateBinary();//使用函数创建二进制信号量
计数信号量(Couting Semaphore)
技术型信号量最大的特定是,创建的时候可以确定,信号量 的最大值,与初始值,计数信号量,通常用来限制访问互斥资源的数量。
cpp
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);// uxMaxCount: 信号量最大值 uxInitialCount 信号量初始值
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
同样的计数型信号量的创建也分为动态创建和静态创建,区别也是静态创建需要创建存放信号量的数据结构。
cpp
static SemaphoreHandle_t counting_Semaphore_Handle; //创建句柄
counting_Semaphore_Handle = xSemaphoreCreateCounting(3,2);//信号量最大值为3,初始值为2的计数型信号量
这里调用需要注意的是,最大值是不能小于信号量的初始值的。
|--------|------------------------------|--------------------------------|
| 信号量 | 二进制信号量量 | 计数型信号量 |
| 动态创建 | xSemaphoreCreateBinary | xSemaphoreCreateCounting |
| 静态创建 | xSemaphoreCreateBinaryStatic | xSemaphoreCreateCountingStatic |
| 信号量初始值 | 二进制信号量初始值为0 | 计数型信号量初始值自己设定 |
| 释放申请删除 | 操作相同 | 操作相同 |
优先级反转问题
优先级反转指的是,高优先级任务因为等待低优先级任务释放信号量进入阻塞,低优先级任务被中优先级任务抢占了CPU,没有办法执行任务释放,信号量导致了整体的系统延迟的情况。
任务A(高优先级)
任务B(低优先级)
任务C(中优先级)
任务B持有资源:任务B(低优先级)首先获得了一个共享资源,比如某个互斥信号量,用于保护一个共享数据。
任务A请求资源:此时,任务A(高优先级)需要使用相同的共享资源,但由于任务B正在使用该资源,任务A必须等待任务B释放互斥信号量。于是,任务A进入阻塞状态,等待资源变得可用。
任务C抢占CPU:在任务A等待资源的同时,任务C(中优先级)不依赖该资源,但由于它的优先级高于任务B,它会在任务A和任务B之间获得CPU的执行机会,抢占任务B的CPU时间。
上面这种情况就会导致了,优先级反转问题的产生。
使用二进制信号量在多任务系统访问资源中,有概率会出现这个问题,需要防止这个问题的出现,当创建Task的时候可以尽量避免,中优先级的任务创建,或者使用互斥信号量后者的优先级继承问题可以有效的避免,优先级反转问题的出现。
cpp
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "stdio.h"
// 任务句柄
TaskHandle_t xHighPriorityTaskHandle, xMediumPriorityTaskHandle, xLowPriorityTaskHandle;
// 信号量句柄
SemaphoreHandle_t xMutex;
// 低优先级任务,持有共享资源
void vLowPriorityTask(void *pvParameters) {
// 获取互斥信号量,模拟持有共享资源
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("Low Priority Task: Obtained the mutex and using the resource.\n");
// 模拟任务占用资源较长时间
vTaskDelay(pdMS_TO_TICKS(2000));
printf("Low Priority Task: Releasing the mutex.\n");
// 释放互斥信号量
xSemaphoreGive(xMutex);
// 任务结束
vTaskDelete(NULL);
}
// 高优先级任务,等待获取共享资源
void vHighPriorityTask(void *pvParameters) {
vTaskDelay(pdMS_TO_TICKS(500)); // 延迟以确保低优先级任务先执行
printf("High Priority Task: Attempting to obtain the mutex...\n");
// 试图获取互斥信号量
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("High Priority Task: Obtained the mutex and using the resource.\n");
// 模拟资源使用时间
vTaskDelay(pdMS_TO_TICKS(1000));
// 释放互斥信号量
xSemaphoreGive(xMutex);
printf("High Priority Task: Released the mutex.\n");
// 任务结束
vTaskDelete(NULL);
}
// 中优先级任务,不需要共享资源,只是执行一段时间
void vMediumPriorityTask(void *pvParameters) {
for (int i = 0; i < 5; i++) {
printf("Medium Priority Task: Executing...\n");
vTaskDelay(pdMS_TO_TICKS(500)); // 模拟占用CPU时间
}
// 任务结束
vTaskDelete(NULL);
}
int main(void) {
// 创建互斥信号量
xMutex = xSemaphoreCreateMutex();
if (xMutex != NULL) {
// 创建低优先级任务,优先级为1
xTaskCreate(vLowPriorityTask, "Low Priority Task", configMINIMAL_STACK_SIZE, NULL, 1, &xLowPriorityTaskHandle);
// 创建高优先级任务,优先级为3
xTaskCreate(vHighPriorityTask, "High Priority Task", configMINIMAL_STACK_SIZE, NULL, 3, &xHighPriorityTaskHandle);
// 创建中优先级任务,优先级为2
xTaskCreate(vMediumPriorityTask, "Medium Priority Task", configMINIMAL_STACK_SIZE, NULL, 2, &xMediumPriorityTaskHandle);
// 启动调度器
vTaskStartScheduler();
}
// 如果代码运行到这里,说明调度器未能启动
for(;;);
}
互斥信号量(Mutex)
互斥信号量(Mutex)是信号量的一种特殊类型,专门用来保护临界资源,确保只有一个任务可以访问该资源,特别的是互斥信号量拥有优先级继承机制,这种机制是二进制信号量和计数信号量没有的。
优先级继承机制特点在于,当低优先级人物持有互斥信号量,同时高优先级任务等待获取该信号量,低优先级任务会短暂提升自身优先级与高优先级任务优先级相同,直到释放互斥信号量,自身优先级会被取消,这个机制来保证因为任务优先级导致释放资源延迟程序的情况。
通常用于保护共享资源,防止任务并发访问。特别适合用于需要防止数据竞争的场景,并且系统中可能存在优先级反转问题的情况。
这种优先级集成机制,可以有效的避免优先级反转问题造成的,Task被阻止或者严重延迟。
cpp
SemaphoreHandle_t xSemaphoreCreateMutex( void );
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
上面是创建Mutex的原型函数,同样的静态创建需要定义结构体。
cpp
SemaphoreHandle_t Mutex_Handle; //声明句柄
Mutex_Handle = xSemaphoreCreateMutex();//创建Mutex
信号量操作函数
上文提及了,信号量不同的类型,当创建完信号量之后还要通过对信号量进行释放,使用,删除来完成特定任务。
cpp
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );//参数xSemaphore信号量句柄
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);//xTicksToWait 0:不阻塞, portMAX_DELAY: 一直阻塞直到成功
这里需要注意的是使用信号量函数,多一个参数可选,可以选择阻塞等待信号量和不等待信号量,如果是在中断中使用则是下面的函数。
cpp
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
需要注意的是,在二进制信号量 中,多次使用释放信号量函数 ,二进制信号量的值依旧是1,不会因为多次调用函数,从而导致信号量值的提高。
创建的信号量,在不需要的时候,同样的可以通过函数来删除,这里函数的参数是需要删除信号量的句柄。
cpp
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );//里面的参数填写句柄
欢迎指正,希望对你,有所帮助!!!