FreeRTOS互斥信号量解决优先级翻转实战教程

news2025/4/22 11:37:23

FreeRTOS互斥信号量解决优先级翻转实战教程

大家好!今天我们来深入探讨FreeRTOS中的优先级翻转问题,并通过互斥信号量来解决这个问题。上一篇文章我们已经了解了优先级翻转的现象,今天我们将动手实践,通过代码对比来直观感受互斥信号量的作用。

🚀 本文是FreeRTOS学习系列的一部分。完整教程请访问我的GitHub仓库:https://github.com/Despacito0o/FreeRTOS,从入门到精通的全面指南!

一、互斥信号量解决优先级翻转

了解完互斥信号量可以解决优先级翻转的问题,我们来实际体验一下。我们将使用前面优先级翻转的案例,把它改造成使用互斥信号量的版本,然后观察效果。

首先,我们直接拷贝之前的012工程,重命名为013:

工程复制

二、从二值信号量切换到互斥信号量

我们有三个任务,三个任务的逻辑不需要修改,只需要将创建的信号量类型从二值信号量换成互斥信号量。

要使用互斥信号量,必须先在FreeRTOSConfig.h中开启相关配置:

#define configUSE_MUTEXES                       1  // 是否启用互斥信号量

启用互斥信号量

三、创建互斥信号量

接下来,我们将二值信号量的创建函数替换为互斥信号量的创建函数:

xSemaphoreCreateMutex();

创建互斥信号量

让我们深入看看这个函数的实现:

#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_MUTEXES == 1 ) )
    #define xSemaphoreCreateMutex()    xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif

进一步查看xQueueCreateMutex函数的实现:

#if ( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
    {
        QueueHandle_t xNewQueue;
        const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

        xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        prvInitialiseMutex( ( Queue_t * ) xNewQueue );

        return xNewQueue;
    }

#endif /* configUSE_MUTEXES */

最后看看prvInitialiseMutex函数:

static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
    if( pxNewQueue != NULL )
    {
        /* 队列创建函数会为通用队列正确设置所有队列结构成员,
         * 但这个函数是创建互斥量。覆盖那些需要不同设置的成员 - 
         * 特别是优先级继承所需的信息。 */
        pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
        pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

        /* 为递归互斥量做准备 */
        pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;

        traceCREATE_MUTEX( pxNewQueue );

        /* 以预期状态开始信号量 */
        ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
    }
    else
    {
        traceCREATE_MUTEX_FAILED();
    }
}

可以看到,创建完互斥信号量后,会自动调用一次xQueueGenericSend,相当于主动释放了一次资源,这与我们之前介绍的"互斥信号量默认创建完会自动释放一次资源"一致。

因此,我们只需要简单地替换创建函数即可:

myPrintfQueueHandler = xSemaphoreCreateMutex(); // 创建互斥信号量,创建成功会自动释放一次信号量

替换信号量创建函数

四、编译调试,观察现象

我们编译、调试、运行代码:

编译运行

现在,我们通过串口输出分析互斥信号量的效果。

在没有使用互斥信号量的情况下,当高优先级任务3尝试获取已被低优先级任务1占用的信号量时,任务3会阻塞;此时中优先级任务2可以抢占任务1执行,导致任务1无法快速释放信号量,任务3长时间等待,出现优先级翻转。

而使用互斥信号量后,我们可以看到:当任务3(高优先级)尝试获取被任务1(低优先级)持有的互斥信号量时,任务1的优先级会被临时提升到与任务3相同的级别,这样任务2(中优先级)就无法抢断任务1的执行了。

通过串口输出,我们可以发现:

  1. 任务1获取互斥信号量后开始执行
  2. 当任务3尝试获取同一个互斥信号量时,任务1的优先级被提升
  3. 此时任务2无法抢占任务1,任务1可以继续执行并最终释放互斥信号量
  4. 任务1释放互斥信号量后,其优先级被恢复为原来的值
  5. 任务3获取互斥信号量并执行

这就验证了互斥信号量通过优先级继承机制解决优先级翻转的工作原理。

五、代码分析

让我们详细分析一下任务执行的完整过程:

  1. 首先,高优先级的任务3先完整执行一轮,然后进入延时状态(vTaskDelay(1000))
  2. 接着,中优先级的任务2开始执行,它需要执行1.5秒
  3. 但在任务2执行1秒后,任务3的延时结束,抢占执行权,获取互斥信号量,执行自己的逻辑,然后释放互斥信号量并再次进入延时状态
  4. 任务2继续执行剩余的0.5秒,然后也进入延时状态
  5. 这时轮到低优先级的任务1执行,它获取互斥信号量并开始执行
  6. 0.5秒后,任务3的延时结束,尝试获取互斥信号量,但发现信号量被任务1占用
  7. 关键点:此时任务1的优先级被临时提升到与任务3相同的级别
  8. 由于任务1现在具有与任务3相同的高优先级,任务2无法抢断其执行
  9. 任务1可以继续执行直到释放互斥信号量
  10. 任务1释放互斥信号量后,其优先级恢复原值,任务3获取信号量并开始执行
  11. 任务3执行完毕后,再次轮到任务2执行,而不是任务1,这证明任务1的优先级已被恢复

这整个过程清晰地展示了互斥信号量如何通过优先级继承机制解决优先级翻转问题。

六、完整代码

下面是完整的示例代码,使用互斥信号量解决优先级翻转:

#include "stm32f10x.h"                  // Device header
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "semphr.h"
#include "delay.h"

TaskHandle_t myTaskHandler1;
TaskHandle_t myTaskHandler2;
TaskHandle_t myTaskHandler3;
TaskHandle_t startTaskHandler;

QueueHandle_t myPrintfQueueHandler;

void myTask1(void *arg)
{
    BaseType_t res = 0;
    while (1)
    {
        taskENTER_CRITICAL();
        /* 获取信号量 */
        printf("The low-priority Task1 gets the semaphore\r\n");
        res = xSemaphoreTake(myPrintfQueueHandler, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task1 failed to get semaphore\r\n");
        }

        /* 执行其他逻辑 */
        printf("A low-priority Task1 is being executed\r\n");
        
        Delay_ms(3000);

        /* 释放信号量 */
        printf("Low-priority Task1 releases semaphores\r\n");
        res = xSemaphoreGive(myPrintfQueueHandler);
        if (res != pdPASS)
        {
            printf("Task1 failed to release semaphore\r\n");
        }

        taskEXIT_CRITICAL();
        
        vTaskDelay(1000);
    }
}

void myTask2(void *arg)
{
    while (1)
    {
        taskENTER_CRITICAL();
        printf("Task 2 of medium priority is being executed\r\n");
        Delay_ms(1500);
        printf("task2 is executed once\r\n");
        taskEXIT_CRITICAL();
        vTaskDelay(1000);
    }
}

void myTask3(void *arg)
{
    BaseType_t res = 0;
    while (1)
    {
        taskENTER_CRITICAL();
        
        /* 获取信号量 */
        printf("The high priority Task3 gets the semaphore\r\n");
        res = xSemaphoreTake(myPrintfQueueHandler, portMAX_DELAY);
        if (res != pdPASS)
        {
            printf("Task3 failed to get semaphore\r\n");
        }

        /* 执行其他逻辑 */
        printf("High Priority Task3 is executing...\r\n");
        Delay_ms(1000);

        /* 释放信号量 */
        printf("A high-priority Task3 releases a semaphore\r\n");
        res = xSemaphoreGive(myPrintfQueueHandler);
        if (res != pdPASS)
        {
            printf("Failed to release semaphore in Task3\r\n");
        }

        taskEXIT_CRITICAL();
        
        vTaskDelay(1000);
    }
}

void startTask(void *arg)
{
    taskENTER_CRITICAL();
    printf("startTask runnig\n");
    taskEXIT_CRITICAL();
    xTaskCreate(myTask1,"myTask1",128,NULL,2,&myTaskHandler1);
    xTaskCreate(myTask2,"myTask2",128,NULL,3,&myTaskHandler2);  // 注意这里的优先级设置
    xTaskCreate(myTask3,"myTask3",128,NULL,4,&myTaskHandler3);  // 任务名应为myTask3
    vTaskDelete(NULL);
}

int main(void)
{
    USART_Config();
    
    myPrintfQueueHandler = xSemaphoreCreateMutex(); // 创建互斥信号量
    
    if(myPrintfQueueHandler == NULL)
    {
        printf("The semaphore is created failed\r\n");
    }
    else 
    {
        printf("The semaphore is created successfully\r\n");
    }
    
    xTaskCreate(startTask,"startTask",128,NULL,2,&startTaskHandler);
    
    vTaskStartScheduler();
    
    while (1)
    {
        
    }
}

StaticTask_t        IdleTaskTCB;
StackType_t         IdleTaskStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
                                   StackType_t ** ppxIdleTaskStackBuffer,
                                   uint32_t * pulIdleTaskStackSize )
{
    * ppxIdleTaskTCBBuffer = &IdleTaskTCB;
    * ppxIdleTaskStackBuffer = IdleTaskStack;
    * pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

注意:在startTask函数中,我修正了三个任务的优先级设置,分别为2、3、4,确保优先级递增,且修正了第三个任务的名称应为"myTask3"。

七、总结与拓展

通过本文的实践,我们清晰地看到了互斥信号量是如何通过优先级继承机制解决优先级翻转问题的。当高优先级任务等待低优先级任务持有的资源时,低优先级任务的优先级被临时提升,以防止中优先级任务抢占执行权。

互斥信号量与二值信号量的区别

  1. 优先级继承:互斥信号量支持优先级继承,二值信号量不支持
  2. 用途:互斥信号量专门用于互斥访问共享资源,二值信号量更适合任务同步
  3. 所有权:互斥信号量有所有权概念,只有获取者才能释放;二值信号量没有所有权概念
  4. 递归性:FreeRTOS还提供递归互斥信号量,允许同一任务多次获取同一互斥量

优先级继承的局限性

优先级继承机制虽然能解决优先级翻转问题,但仍有一些局限性:

  1. 只能解决单个互斥量的情况,对于多个互斥量形成的死锁,无能为力
  2. 增加了系统开销,尤其是频繁的优先级调整
  3. 在某些极端情况下可能导致系统行为不可预测

优先级天花板协议

除了优先级继承,还有一种解决优先级翻转的方法是"优先级天花板协议"(Priority Ceiling Protocol)。该协议为每个互斥信号量指定一个优先级天花板,任何获取该互斥量的任务都会临时提升到这个优先级。虽然FreeRTOS没有直接实现这一协议,但我们可以通过适当的设计来模拟它。

🔥 完整的FreeRTOS学习项目请访问:https://github.com/Despacito0o/FreeRTOS,从入门到精通的全面指南!

这个开源项目提供了从入门到精通的FreeRTOS学习资源,包括详细的移植教程、丰富的示例代码和深入浅出的文档说明,欢迎大家star和fork!


如果你对FreeRTOS有更多疑问或建议,欢迎在评论区留言,也欢迎关注我获取更多嵌入式开发内容!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一篇:从哲学到管理——实践论与矛盾论如何重塑企业思维

引言:当革命哲学照亮现代商业 1937年,毛泽东在战火中写就的《实践论》《矛盾论》,为中国共产党提供了认识世界的方法论。今天,这两部著作正成为企业破解管理困局的“思维操作系统”: 战略模糊:据Gartner统…

14.电容的高频特性在EMC设计中的应用

电容的高频特性在EMC设计中的应用 1. 电容自谐振频率特性对EMC的作用2. 退耦电容的选型3. Y电容选型注意事项4. 储能电容与电压跌落的瞬时中断5. 穿心电容对EMC滤波的作用 1. 电容自谐振频率特性对EMC的作用 电容的高频特性等效模型如下: 其自谐振成因如下&#x…

网络编程4

day4 一、Modbus 1.分类 (1).Modbus RTU: 运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛。(2).Modbus ASCII: 运行在串口上的协议,采用ASCII码传输,并且利用特殊字符作为其字节的开始…

Java 性能优化:如何利用 APM 工具提升系统性能?

Java 性能优化:如何利用 APM 工具提升系统性能? 在当今竞争激烈的软件开发领域,系统性能至关重要。随着应用规模的扩大和用户需求的增加,性能问题逐渐凸显,这不仅影响用户体验,还可能导致业务损失。而 APM…

AI音乐解决方案:1分钟可切换suno、udio、luno、kuka等多种模型,suno风控秒切换 | AI Music API

你有没有觉得,suno风控来了,就要停服了? 你有没有觉得,对接多种音乐模型,让你很疲乏? 你有没有觉得,音乐模型,中文咬字不清楚,让你很苦恼? 别怕&#xff0…

一键升级OpenSSH/OpenSSL修复安全漏洞

在服务器安全运维过程中,我们经常面临这样的问题:收到高危漏洞通报(如最近的OpenSSH多个CVE漏洞),但Ubuntu系统无法通过apt直接升级到修复版本。这种情况下,传统方法需要手动编译源码,处理依赖关…

健康养生,开启新生活

在饮食上,应遵循 “均衡搭配、清淡少盐” 的原则。主食不要只吃精米白面,可适当加入燕麦、糙米等全谷物,为身体补充膳食纤维;每天保证一斤蔬菜半斤水果,深色蔬菜如菠菜、西兰花富含维生素与矿物质,水果则选…

VLAN间通讯技术

多臂路由 路由器使用多条物理线路,每条物理线路充当一个 VLAN 的网管 注意:路由器对端的交换机接口,需要设定 Access 类型,因为路由器的物理接口无法处理 VLAN 标签 。 单臂路由 使用 以太网子接口 (sub-interface) 实现。 …

人工智能在慢病管理中的具体应用全集:从技术落地到场景创新

一、AI 赋能慢病管理:技术驱动医疗革新 1.1 核心技术原理解析 在当今数字化时代,人工智能(AI)正以前所未有的态势渗透进医疗领域,尤其是在慢性病管理方面,展现出巨大的潜力和独特优势。其背后依托的机器学习、深度学习、自然语言处理(NLP)以及物联网(IoT)与可穿戴设…

B+树节点与插入操作

B树节点与插入操作 设计B树节点 在设计B树的数据结构时,我们首先需要定义节点的格式,这将帮助我们理解如何进行插入、删除以及分裂和合并操作。以下是对B树节点设计的详细说明。 节点格式概述 所有的B树节点大小相同,这是为了后续使用自由…

线性回归之多项式升维

文章目录 多项式升维简介简单案例实战案例多项式升维优缺点 多项式升维简介 多项式升维(Polynomial Expansion)是线性回归中一种常用的特征工程方法,它通过将原始特征进行多项式组合来扩展特征空间,从而让线性模型能够拟合非线性关…

颠覆传统!毫秒级响应的跨平台文件同步革命,远程访问如本地操作般丝滑

文章目录 前言1. 安装Docker2. Go File使用演示3. 安装cpolar内网穿透4. 配置Go File公网地址5. 配置Go File固定公网地址 前言 在这个信息爆炸的时代,谁不曾遭遇过类似的窘境呢?试想,当你正于办公室中埋首案牍时,手机突然弹出一…

CrewAI Community Version(一)——初步了解以及QuickStart样例

目录 1. CrewAI简介1.1 CrewAI Crews1.2 CrewAI Flows1.3 Crews和Flows的使用情景 2. CrewAI安装2.1 安装uv2.2 安装CrewAI CLI 3. 官网QuickStart样例3.1 创建CrewAI Crews项目3.2 项目结构3.3 .env3.4 智能体角色及其任务3.4.1 agents.yaml3.4.2 tasks.yaml 3.5 crew.py3.6 m…

Nginx下搭建rtmp流媒体服务 并使用HLS或者OBS测试

所需下载地址: 通过网盘分享的文件:rtmp 链接: https://pan.baidu.com/s/1t21J7cOzQR1ASLrsmrYshA?pwd0000 提取码: 0000 window: 解压 win目录下的 nginx-rtmp-module-1.2.2.zip和nginx 1.7.11.3 Gryphon.zip安装包,解压时选…

Lateral 查询详解:概念、适用场景与普通 JOIN 的区别

1. 什么是Lateral查询? Lateral查询(也称为横向关联查询)是一种特殊的子查询,允许子查询中引用外层查询的列(即关联引用),并在执行时逐行对外层查询的每一行数据执行子查询。 语法上通常使用关…

【springsecurity oauth2授权中心】简单案例跑通流程 P1

项目被拆分开,需要一个授权中心使得每个项目都去授权中心登录获取用户权限。而单一项目里权限使用的是spring-security来控制的,每个controller方法上都有 PreAuthorize("hasAuthority(hello)") 注解来控制权限,想以最小的改动来实…

spark—SQL3

连接方式 内嵌Hive: 使用时无需额外操作,但实际生产中很少使用。 外部Hive: 在虚拟机下载相关配置文件,在spark-shell中连接需将hive-site.xml拷贝到conf/目录并修改url、将MySQL驱动copy到jars/目录、把core-site.xml和hdfs-sit…

一文了解相位阵列天线中的真时延

本文要点 真时延是宽带带相位阵列天线的关键元素之一。 真时延透过在整个信号频谱上应用可变相移来消除波束斜视现象。 在相位阵列中使用时延单元或电路板,以提供波束控制和相移。 市场越来越需要更快、更可靠的通讯网络,而宽带通信系统正在努力满…

linux学习 5 正则表达式及通配符

重心应该放在通配符的使用上 正则表达式 正则表达式是用于 文本匹配和替换 的强大工具 介绍两个交互式的网站来学习正则表达式 regexlearn 支持中文 regexone 还有一个在线测试的网址 regex101 基本规则 符号作用示例.匹配任何字符除了换行a.b -> axb/a,b[abc]匹配字符…

基于超启发鲸鱼优化算法的混合神经网络多输入单输出回归预测模型 HHWOA-CNN-LSTM-Attention

基于超启发鲸鱼优化算法的混合神经网络多输入单输出回归预测模型 HHWOA-CNN-LSTM-Attention 随着人工智能技术的飞速发展,回归预测任务在很多领域得到了广泛的应用。尤其在金融、气象、医疗等领域,精确的回归预测模型能够为决策者提供宝贵的参考信息。为…