立志最细,FreeRtos中的信号量Semaphore教程详解!!!

前言:本篇教程,参考韦东山,开发文档,连接放在最后

目录

Semaphore基本概念

[二值信号量(Binary Semaphore)](#二值信号量(Binary Semaphore))

[计数信号量(Couting Semaphore)](#计数信号量(Couting Semaphore))

互斥信号量(Mutex)

信号量释放和使用函数


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 );//里面的参数填写句柄

欢迎指正,希望对你,有所帮助!!!

[9-2-2]_信号量实验_优先级反转_哔哩哔哩_bilibili

相关推荐
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
ChoSeitaku6 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___6 小时前
不使用递归的决策树生成算法
算法
我爱工作&工作love我6 小时前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子7 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower7 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯7 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui17 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农7 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲8 小时前
浏览器是加载ES6模块的?
javascript·算法