freeRTOS学习笔记(十二)--信号量

文章目录

一、同步和互斥

1.1 同步

在FreeRTOS中,同步(Synchronization) 指的是多个任务之间(或任务与中断之间)为了协调执行节奏、共享事件或传递状态而进行的时序协作机制。其核心目的是确保任务按照预期的逻辑顺序执行,避免因执行时机错乱导致的数据错误、资源冲突或功能失效。

同步的核心场景与目标

在实时操作系统(RTOS)中,多任务并发执行时,往往需要通过同步解决以下问题:

  1. "等待事件":一个任务需要等待另一个任务(或中断)完成某个操作后才能继续执行(例如,传感器采集任务完成后,数据处理任务才能启动)。
  2. "步调一致":多个任务需按照特定顺序协同工作(例如,A任务生成数据→B任务处理数据→C任务存储数据,需严格按A→B→C的顺序执行)。
  3. "资源有序访问":当多个任务共享有限资源(如缓冲区、外设)时,通过同步确保资源使用的时序合理性(区别于"互斥",同步更侧重"顺序"而非"独占")。

FreeRTOS中常用的同步机制

FreeRTOS提供了多种同步工具,每种工具适用于不同场景:

1. 二进制信号量(Binary Semaphore)
  • 原理 :类似"开关",只有两种状态(0或1),通常用于单次事件同步

  • 场景

    • 中断服务程序(ISR)通知任务处理事件(例如,串口接收数据后,ISR释放信号量,任务获取信号量后处理数据)。
    • 任务A完成初始化后,释放信号量允许任务B启动。
    c 复制代码
    // 示例:ISR中释放信号量,任务中等待信号量
    void UART_ISR(void) {
        xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); // ISR释放
    }
    
    void vDataProcessTask(void *pvParam) {
        while(1) {
            xSemaphoreTake(xBinarySemaphore, portMAX_DELAY); // 等待信号量
            // 处理数据
        }
    }
2. 计数信号量(Counting Semaphore)
  • 原理 :信号量值可在0到最大值之间变化,用于统计可用资源数量或事件次数
  • 场景
    • 管理资源池(如5个缓存区,任务获取信号量使用缓存,释放后信号量值加1)。
    • 累计事件次数(如计数按键按下次数,任务批量处理)。
3. 事件标志组(Event Groups)
  • 原理:通过一个32位整数的不同位表示不同事件,任务可等待"任意一个事件"或"所有事件"发生。

  • 场景

    • 任务等待多个事件中的一个(如"传感器A触发"或"传感器B触发")。
    • 任务等待多个事件同时发生(如"温度达标"且"湿度达标"后启动风扇)。
    c 复制代码
    // 示例:等待两个事件同时发生
    EventBits_t xBits = xEventGroupWaitBits(
        xEventGroup,          // 事件组
        (1 << 0) | (1 << 1),  // 等待事件0和事件1
        pdTRUE,               // 获取后清除标志位
        pdTRUE,               // 需两个事件同时发生
        portMAX_DELAY         // 无限等待
    );
    if( (xBits & ((1 << 0) | (1 << 1))) == ((1 << 0) | (1 << 1)) ) {
        // 两个事件均发生,执行操作
    }
4. 任务通知(Task Notifications)
  • 原理:通过直接向任务发送通知(替代信号量/事件组),效率更高(无需创建额外内核对象)。
  • 场景:轻量级同步(如替代二进制信号量,减少内存开销)。
5. 消息队列(Message Queues)
  • 原理:通过队列传递数据,同时隐含同步功能(发送方和接收方需等待队列有空间/有数据)。
  • 场景:同步+数据传递(如任务A发送数据到队列,任务B等待队列有数据后处理)。

同步与互斥的区别

需注意,同步与"互斥(Mutex)"是不同概念:

  • 同步:解决"时序协调"问题,确保任务按逻辑顺序执行(如"先生产后消费")。
  • 互斥:解决"资源独占"问题,防止多个任务同时访问共享资源(如用互斥锁保护全局变量)。

例如:两个任务共享打印机时,互斥 确保同一时间只有一个任务使用打印机;而同步可能确保"任务A打印完文档1后,任务B才能打印文档2"。

总结

FreeRTOS中的同步是多任务协作的核心机制,通过信号量、事件标志组等工具,确保任务(或中断)按照预期的时序执行,避免因并发导致的逻辑混乱。实际开发中,需根据具体场景(如事件数量、是否传递数据、资源类型)选择合适的同步工具,以保证系统的实时性和可靠性。

1.2 互斥

在FreeRTOS(或更广泛的操作系统领域)中,互斥(Mutual Exclusion,简称"互斥") 是一种解决多个任务对共享资源竞争访问 的机制,核心目标是确保同一时间只有一个任务能访问特定的共享资源,从而避免因并发操作导致的数据错乱、逻辑冲突或系统异常。

互斥的核心问题:资源竞争与数据不一致

在多任务系统中,多个任务可能同时需要访问"共享资源"(如全局变量、硬件外设、缓冲区等)。如果缺乏协调,可能出现以下问题:

  • 例如,任务A和任务B同时向同一个串口发送数据,最终输出的内容会变成两者的混合乱码;
  • 任务A读取全局变量count=5并执行count++(计划改为6),但执行中途被任务B抢占,任务B读取count=5并改为6,随后任务A恢复执行,继续将count改为6------最终结果应为7,却错误地变成6。

这种因"同时访问"导致的错误,称为资源竞争(Race Condition)。互斥机制的作用就是通过"独占访问"避免此类问题。

FreeRTOS中互斥的实现:互斥锁(Mutex)

FreeRTOS通过互斥锁(Mutex) 实现互斥功能,其本质是一种特殊的信号量,具有以下特性:

  1. 独占性

    互斥锁只有"持有"和"释放"两种状态。当一个任务获取(take)互斥锁后,其他任务必须等待(block),直到该任务释放(give)互斥锁,才能有机会获取。

    c 复制代码
    // 示例:创建互斥锁并使用
    SemaphoreHandle_t xMutex;
    
    void vInitTask(void *pvParam) {
        xMutex = xSemaphoreCreateMutex(); // 创建互斥锁
        // ... 创建其他任务
    }
    
    void vTaskA(void *pvParam) {
        while(1) {
            // 尝试获取互斥锁,最多等待100ms
            if(xSemaphoreTake(xMutex, 100) == pdTRUE) {
                // 成功获取锁,安全访问共享资源(如全局变量、串口)
                shared_data = 123;
                printf("TaskA: %d\n", shared_data);
                
                xSemaphoreGive(xMutex); // 释放锁
            }
            vTaskDelay(100);
        }
    }
    
    void vTaskB(void *pvParam) {
        while(1) {
            // 尝试获取互斥锁,无限等待
            if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
                // 安全访问共享资源
                shared_data = 456;
                printf("TaskB: %d\n", shared_data);
                
                xSemaphoreGive(xMutex); // 释放锁
            }
            vTaskDelay(150);
        }
    }
  2. 优先级继承(Priority Inheritance)

    这是FreeRTOS互斥锁的关键特性,用于解决"优先级反转"问题:

    • 优先级反转:低优先级任务持有互斥锁时,中等优先级任务抢占CPU,导致高优先级任务(等待该锁)被长时间阻塞,违背实时性要求。
    • 优先级继承:当高优先级任务等待低优先级任务持有的互斥锁时,低优先级任务的优先级会临时提升到与高优先级任务相同,直到释放锁,从而避免被中等优先级任务抢占,保证高优先级任务的响应速度。
  3. 所有权

    互斥锁具有"所有权"概念------只有获取锁的任务才能释放锁(信号量则无此限制)。这避免了其他任务误释放锁导致的混乱。

互斥与同步的核心区别

维度 互斥(Mutex) 同步(如信号量、事件组)
核心目标 解决"资源独占"问题,防止竞争访问 解决"时序协调"问题,确保任务按逻辑顺序执行
关注点 资源的"安全性"(数据一致、操作正确) 任务的"有序性"(先做什么,后做什么)
典型场景 多个任务访问同一串口、全局变量、硬件外设等 任务A等待任务B完成初始化、中断通知任务处理数据等
关键特性 优先级继承、所有权 信号量计数、事件标志位组合等

互斥的使用原则

  1. 最小持有时间:获取互斥锁后,应尽快完成对共享资源的操作并释放锁,减少其他任务的等待时间。
  2. 避免嵌套死锁:不要在持有一个互斥锁时尝试获取另一个互斥锁(或同一锁),否则可能导致任务相互等待(死锁)。
  3. 适配场景:仅对"需要独占访问的共享资源"使用互斥锁,无需保护的资源(如只读变量)无需加锁,避免过度消耗系统资源。

总结

互斥是FreeRTOS中保障共享资源安全访问的核心机制,通过互斥锁实现"同一时间仅一个任务访问资源",并通过优先级继承解决实时性问题。它与同步机制(如信号量)相辅相成:同步确保任务"按顺序执行",互斥确保资源"被安全访问",共同支撑多任务系统的稳定运行。

相关推荐
脑洞代码4 小时前
ADXL345 SPI加速度传感器Linux驱动开发笔记
linux·驱动开发·笔记
新子y4 小时前
【小白笔记】 while 与 for + break 的比较分析
笔记·python
せいしゅん青春之我4 小时前
【JavaEE初阶】网络经典面试题小小结
java·网络·笔记·网络协议·tcp/ip·java-ee
南♡黎(・ิϖ・ิ)っ4 小时前
JavaEE初阶,文件IO(2)
java·笔记·java-ee
野老杂谈4 小时前
如何快速学习智能合约开发语言 Solidity
开发语言·学习·智能合约·solidity·以太坊·区块链开发
Han.miracle5 小时前
Java线程的学习—多线程(一)
java·开发语言·学习
是店小二呀5 小时前
Trilium非线性笔记测评:本地知识库+远程协作,构建你的第二大脑!
笔记
忧郁奔向冷的天5 小时前
视觉SLAM十四讲2nd—学习笔记(二)20250817
笔记·学习
立志成为大牛的小牛5 小时前
数据结构——三十一、最小生成树(王道408)
数据结构·学习·程序人生·考研·算法