当前位置: 首页 > news >正文

FreeRTOS 同步互斥与任务协作 学习笔记

目录

FreeRTOS 同步机制

一、知识总览

二、核心概念分步拆解​编辑

(一)同步的本质

1. 一句话理解

2. 生活场景映射(抢厕所 / 报表示例 )

(二)代码中的同步实现(以 CalcTask 和 LcdPrintTask 为例 )

1. 同步信号:g_calc_end

2. 任务 1:CalcTask(生产同步信号 )

3. 任务 2:LcdPrintTask(等待同步信号 )

(三)同步的意义与延伸​编辑

三、知识串联(从生活到代码 )

四、易错点 

FreeRTOS 互斥机制 

一、知识总览

二、核心概念分步拆解(按截图倒序,从代码问题到解决方案 )

(一)临界资源冲突问题​编辑

​编辑1. 场景:多任务抢 LCD(临界资源 )

2. 硬件级打断:中断影响

(二)互斥的本质:保护临界区​编辑

1. 互斥量(Mutex )的作用

2. FreeRTOS 互斥量的实现

(三)正确实现互斥:代码修正与流程

1. 修正 LCD_PrintString:用互斥思想保护资源

2. 任务级互斥:LcdPrintTask 修正

(四)FreeRTOS 标准互斥量 API

1. 常用 API 介绍

2. 替换自定义逻辑:用标准 API 更可靠

三、知识串联(从问题到解决方案 )

四、易错点 & 补充说明

(一)易错点

(二)补充拓展

FreeRTOS 同步机制

一、知识总览

聚焦 FreeRTOS 中同步机制,通过生活场景类比与代码实践,解析多任务间 “协调执行顺序” 的逻辑。核心是理解 “任务等待 - 唤醒” 协作模式,让程序按预期流程推进,像计算任务完成后触发显示任务执行,解决多任务依赖问题。

二、核心概念分步拆解

(一)同步的本质

1. 一句话理解

同步是 “任务间的执行顺序协调” ,即 “任务 B 需等任务 A 完成某动作后,再执行自己的动作”,类似生活里 “经理等同事写完报表才汇报”“B 等 A 用完厕所再使用”。

2. 生活场景映射(抢厕所 / 报表示例 )
  • 以 “同事写报表、经理汇报” 为例:
    同事 A 写报表(任务 A 执行 ),经理 B 要等 A 写完(同步等待 ),A 写完后通知 B(同步唤醒 ),B 才去汇报。
    对应伪代码逻辑
    void 写报表/用厕所(void) {if (资源/动作未完成) 等待; // 经理/B等待执行动作; // 同事A/任务A执行通知等待者; // 唤醒经理/B
    }
    
    本质是通过 “等待 - 唤醒”,让任务按依赖关系有序执行。

(二)代码中的同步实现(以 CalcTask 和 LcdPrintTask 为例 )

在代码里,CalcTask(计算任务 )和LcdPrintTask(显示任务 )通过全局标志 + 循环检查实现同步,拆解如下:

1. 同步信号:g_calc_end
  • 定义:static volatile int g_calc_end = 0;
    • volatile 关键作用:告诉编译器 “该变量会被多任务 / 中断修改”,禁止编译器优化,确保任务间访问的是最新值。
    • 功能:作为计算任务是否完成的 “同步标志” ,CalcTask完成计算后置1LcdPrintTask通过检查它决定是否执行显示。
2. 任务 1:CalcTask(生产同步信号 )
void CalcTask(void *params) {uint32_t i = 0;g_time = system_get_ns(); // 记录开始时间(纳秒)for(i = 0; i < 10000000; i++) {g_sum += i; // 执行耗时计算(模拟业务)}g_calc_end = 1; // 【核心】计算完成,置同步标志g_time = system_get_ns() - g_time; // 计算耗时vTaskDelete(NULL); // 任务完成,自我删除
}
  • 角色:“被等待的任务” ,执行完计算后,通过修改g_calc_end,向其他任务(如LcdPrintTask )发送 “我做完了” 的信号。
3. 任务 2:LcdPrintTask(等待同步信号 )
void LcdPrintTask(void *params) {int len;while(1) {LCD_PrintString(0,0,"Waiting"); // 先显示等待状态vTaskDelay(3000); // 延迟,模拟其他逻辑或降低检查频率// 【同步核心】循环检查,等CalcTask完成while(g_calc_end == 0); // 同步条件满足,执行显示if(g_LCDCanUse) { g_LCDCanUse = 0; // (可结合互斥,这里先聚焦同步)// ... 执行LCD显示g_sum和g_time的逻辑 ...g_LCDCanUse = 1; }vTaskDelete(NULL); // 显示完自我删除(实际场景可优化)}
}
  • 角色:“等待的任务” ,通过while(g_calc_end == 0);循环检查同步标志,直到CalcTask完成(g_calc_end1 ),才执行后续显示操作,实现 “等计算完再显示” 的同步逻辑。

(三)同步的意义与延伸

  • 意义:解决多任务间的执行依赖,让程序逻辑符合 “先完成前提动作,再执行后续动作” 的需求。比如:
    • 计算任务算完结果,显示任务才显示(当前代码 );
    • 任务 A、B 写完队列数据,任务 C、D 才读取(上图队列模型,也是 “写 - 读” 的同步 )。
  • 延伸:FreeRTOS 还提供更高效的同步机制(如信号量、事件组 ),但核心逻辑都是 “等待 - 唤醒”,当前代码用全局变量 + 轮询是最基础的实现,便于理解本质。

三、知识串联(从生活到代码 )

  • 生活场景:通过 “等厕所、等报表” 理解 “等待 - 唤醒” 的同步逻辑。
  • 代码实践:g_calc_end作为同步标志,CalcTask置标志、LcdPrintTask轮询标志,实现 “计算完才显示” 的任务协作。
  • 框架关联:FreeRTOS 复杂同步机制(信号量等 ),本质是对 “等待 - 唤醒” 的封装优化,解决轮询效率低等问题,但核心逻辑与当前代码一致。

四、易错点 

  1. 忘记加volatile:若g_calc_end没加volatile,编译器可能优化为 “只读一次内存”,导致LcdPrintTask永远等不到标志变化,同步失效。
  2. 轮询效率低:当前代码用while(g_calc_end == 0);轮询,会持续占用 CPU。实际项目常用 FreeRTOS 提供的阻塞式同步 API(如xSemaphoreTakexTaskNotifyWait ),让等待任

FreeRTOS 互斥机制 

一、知识总览

聚焦 FreeRTOS 中互斥(Mutex )的核心逻辑,通过代码实践(LCD_PrintString 函数、LcdPrintTask 任务 )+ 手绘流程,解析多任务如何安全争抢 “临界资源”(如 LCD 显示器 ) 。核心是理解 “互斥量保护临界区”,避免多任务同时操作资源导致混乱,保证程序正确性。

二、核心概念分步拆解(按截图倒序,从代码问题到解决方案 )

(一)临界资源冲突问题

1. 场景:多任务抢 LCD(临界资源 )
  • 临界资源:LCD 显示器同一时间只能被一个任务 “安全使用”(类似 “厕所”,多任务同时操作会导致显示混乱 )。
  • 问题代码LcdPrintTask 任务中,多个任务(A、B )可能同时执行 LCD_PrintString,触发冲突。
    static int g_LCDCanUse = 1; 
    void LcdPrintTask(void *params) {while(1) {if (g_LCDCanUse) { g_LCDCanUse = 0; // 【危险】多任务可能同时进入,操作LCDLCD_PrintString(...); g_LCDCanUse = 1; }mdelay(500);}
    }
    
  • 冲突原因:任务 A 执行到 g_LCDCanUse = 0 后,若被任务 B 打断,任务 B 会检测到 g_LCDCanUse = 0 吗?不会! 因为任务切换可能发生在 “检查 - 修改” 的间隙,导致多个任务同时认为资源可用,最终同时操作 LCD,引发显示混乱。
2. 硬件级打断:中断影响

  • 代码片段LCD_PrintString 函数中,用 bCanUse 标记资源是否可用,但未正确处理中断。
    int LCD_PrintString(int x, int y, char *str) {static int bCanUse = 1; disable_irq();    // 关闭中断bCanUse--;        // 【步骤1:标记资源占用】enable_irq();     // 开启中断if (bCanUse == 0) { // 使用LCD(临界区)bCanUse++;    return 0;} else {disable_irq();bCanUse++;    enable_irq();return -1;}
    }
    
  • 问题:任务执行 bCanUse--(步骤 1 )时,若被中断 / 其他任务打断,会导致 bCanUse 状态混乱。比如:
    • 任务 A 执行 bCanUse--(值变为 0 ),但未进入 “使用 LCD” 逻辑就被打断;
    • 任务 B 执行 bCanUse--(值变为 -1 ),后续判断逻辑完全失效,资源保护彻底失控。

(二)互斥的本质:保护临界区

1. 互斥量(Mutex )的作用
  • 核心逻辑:通过一个 “标志位(0 或 1 )”,让临界资源同一时间仅被一个任务访问
  • 类比生活:厕所的 “门锁”,有人用则锁门(标志位 0 ),没人用则开门(标志位 1 )。任务要使用资源,先检查 “门锁”,锁着就等待,没锁就占用并锁门。
2. FreeRTOS 互斥量的实现
  • 数据结构:互斥量本质是一个 “二进制信号量”,值为 1(资源可用 )或 0(资源占用 )。
  • 操作流程
    • 上锁(Take ):任务要使用资源时,尝试 “获取互斥量”。若值为 1,则置为 0(占用资源 );若值为 0,则任务进入阻塞,等待资源释放。
    • 解锁(Give ):任务使用完资源后,“释放互斥量”,将值置为 1,唤醒等待的任务。

(三)正确实现互斥:代码修正与流程

1. 修正 LCD_PrintString:用互斥思想保护资源
  • 关键优化:确保 “检查 - 修改 - 使用” 的原子性(不能被打断 )。
  • 修正后代码
    int LCD_PrintString(int x, int y, char *str) {static int bCanUse = 1; disable_irq();    // 关闭中断(保证操作不被打断)if (bCanUse) {    // 检查资源是否可用bCanUse = 0;  // 标记为“占用”enable_irq(); // 开启中断// 使用LCD(临界区,安全操作)// ... LCD 操作代码 ...bCanUse = 1;  // 释放资源return 0;} else {enable_irq(); // 开启中断return -1;    // 资源忙,无法使用}
    }
    
  • 原理:通过 disable_irq() 和 enable_irq() 关闭 / 开启中断,保证 “检查 bCanUse → 修改 bCanUse → 使用 LCD” 的过程不会被中断或其他任务打断,实现 “互斥访问”。
2. 任务级互斥:LcdPrintTask 修正
  • 修正后代码
    static int g_LCDCanUse = 1; 
    void LcdPrintTask(void *params) {while(1) {disable_irq();          // 关闭中断,保证原子操作if (g_LCDCanUse) {      // 检查资源g_LCDCanUse = 0;    // 标记占用enable_irq();       // 开启中断// 安全使用LCD(临界区)LCD_PrintString(...); g_LCDCanUse = 1;    // 释放资源} else {enable_irq();       // 开启中断}mdelay(500);}
    }
    
  • 效果:任务访问 LCD 前,通过关闭中断确保 “检查 - 占用” 操作不被打断,避免多任务同时进入临界区,实现互斥。

(四)FreeRTOS 标准互斥量 API

1. 常用 API 介绍
  • 创建互斥量

    SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); 
    
     
    • 返回一个互斥量句柄,用于后续操作。
  • 获取互斥量(上锁 )

    if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdPASS) { // 临界区:安全操作资源(如 LCD)// ... xSemaphoreGive(xMutex); // 释放互斥量
    }
    
     
    • xSemaphoreTake 尝试获取互斥量,若资源被占用则阻塞,直到超时或资源释放。
    • portMAX_DELAY 表示无限等待。
  • 释放互斥量(解锁 )

    xSemaphoreGive(xMutex); 
    
     
    • 将互斥量置为 1,唤醒等待的任务。
2. 替换自定义逻辑:用标准 API 更可靠
  • 原自定义逻辑:用 g_LCDCanUse 手动标记资源,需自己处理中断、原子操作,容易出错。
  • 标准 API 优势:FreeRTOS 互斥量内部已实现线程安全、阻塞管理、优先级继承(避免优先级翻转 ),更可靠、高效。

三、知识串联(从问题到解决方案 )

  1. 问题暴露:多任务同时操作临界资源(LCD ),因 “检查 - 修改” 被打断,导致资源冲突。
  2. 硬件级风险:中断 / 任务切换可能破坏资源标记(bCanUse ),引发更严重的混乱(。
  3. 互斥本质:通过 “原子操作 + 标志位”,保证临界资源同一时间仅被一个任务访问。
  4. 正确实现:用 disable_irq() 保证原子性,或直接使用 FreeRTOS 标准互斥量 API,安全保护临界区。

四、易错点 & 补充说明

(一)易错点

  1. 忘记恢复中断:在 disable_irq() 后,若因逻辑分支 / 错误未执行 enable_irq(),会导致系统中断全部关闭,引发严重问题。
  2. 手动标记的缺陷:用 g_LCDCanUse 这类全局变量手动实现互斥,无法处理复杂场景(如任务阻塞 ),推荐直接使用 FreeRTOS 标准 API。
  3. 优先级翻转:高优先级任务等待低优先级任务释放互斥量时,若低优先级任务被中等优先级任务抢占,会导致高优先级任务长时间阻塞。FreeRTOS 互斥量默认支持优先级继承,可缓解此问题。

(二)补充拓展

  • 递归互斥量(Recursive Mutex ):允许同一任务多次获取互斥量(避免自己阻塞自己 ),需注意释放次数与获取次数一致。
  • 互斥量 vs 二进制信号量
    • 互斥量用于保护临界资源,支持优先级继承;
    • 二进制信号量用于同步事件(如任务间通知 ),不支持优先级继承。
  • 临界区最小化:尽量缩短 xSemaphoreTake 和 xSemaphoreGive 之间的代码,减少任务阻塞时间,提升系统效率。

通过 “问题暴露→硬件风险→互斥本质→正确实现→标准 API” 的倒序拆解,互斥的核心逻辑(保护临界资源不被多任务同时访问 )就清晰了!关键记住 “原子操作 + 标志位” 的思想,实际项目优先用 FreeRTOS 标准互斥量 API,避免手动实现的隐患~

http://www.dtcms.com/a/352220.html

相关文章:

  • 【Protues仿真】定时器
  • 对讲联动电梯门禁系统通过深度集成对讲、梯控、身份认证三大模块,在提升便捷性的同时,以“权限后置发放+电梯状态闭环检测“为核心,实现安全性与可靠性的双重突破。
  • 解决VSCode无法下载服务器端 Server问的题
  • 当 C++ 用于嵌入式开发:优点和缺点
  • .gitignore 文件相关使用配置
  • 【Redis】安装和基础命令
  • 十、Java面向对象编程入门指南:继承与多态
  • 利用 OpenTelemetry 建设尾部采样
  • 大模型全栈学习路线:4 - 6 个月从入门到实战,打通技术与业务闭环
  • [灵动微电子 霍尔FOC MM32BIN560C]从引脚到应用
  • 《黑客帝国》解构:白帽黑客的极客思维宇宙
  • vue3写一个简单的时间轴组件
  • 【python】python利用QQ邮箱SMTP发送邮件
  • k8s pod resources: {} 设置的含义
  • 支持向量机(第二十九节课内容总结)
  • TensorFlow 面试题及详细答案 120道(61-70)-- 高级特性与工具
  • 如何在项目中集成XXL-JOB
  • uniapp 引入使用u-view 完整步骤,u-view 样式不生效
  • 重复文件删除查找工具 Duplicate Files Search Link v10.7.0
  • 【深度学习】Transformer 注意力机制与 LoRA target_modules 详解
  • 如何安装 VS2019 和 .NET Core SDK 2.2.301(winx64)?完整操作步骤(附安装包下载)
  • 基于YOLOv11训练无人机视角Visdrone2019数据集
  • 区块链技术探索与应用:从密码学奇迹到产业变革引擎
  • 从入门到理解:支持向量机的核心原理与实战思路
  • 计数组合学7.21(有界部分大小的平面分拆)
  • 车载铁框矫平机:一辆“会熨衣服”的工程车
  • 高性能异步任务编排框架:Gobrs-Async
  • 【项目】深房数据通——深圳房价可视化系统
  • 嵌入式第三十七课!!!TCP机制与HTTP协议
  • 【学习笔记】系统时间跳变会影响time接口解决措施