STM32 进阶封神之路(四十) FreeRTOS 队列、信号量、互斥锁精讲|任务通信、同步、资源保护(超详细图文版)

0 阅读6分钟

STM32 进阶封神之路(四十)

FreeRTOS 队列、信号量、互斥锁精讲|任务通信、同步、资源保护(超详细图文版)

前言

在前面的章节中,我们已经完整掌握了 FreeRTOS 任务管理、调度机制、临界区、挂起恢复、内核底层原理。这些内容解决了 “多任务如何跑起来” 的问题。

但在真正的项目中,任务之间并不是孤立运行的 —— 它们需要传递数据、同步执行顺序、保护共享资源。这就必须依靠 FreeRTOS 最核心的三大机制:队列、信号量、互斥锁

本篇将用最系统、最通俗、最工程化的方式,把这三大模块一次性讲透,从原理、API、使用场景、区别对比到代码实战,全部超详细展开,让你彻底理解多任务系统如何做到安全、有序、稳定。


一、为什么需要任务通信与同步机制?

在裸机中,我们用全局变量、标志位完成数据传递。但在多任务 RTOS 中,如果多个任务随意读写全局变量,会出现大量严重问题:

  • 数据被覆盖、读一半被打断
  • 串口打印乱码、拼接
  • IIC/SPI 时序错乱、外设不响应
  • 传感器读取错误、数据异常
  • 系统死机、卡死、重启

这些问题统称为:资源竞争执行不同步数据不安全

FreeRTOS 提供了一套线程安全的机制,专门解决这些问题:

  1. 队列(Queue) :任务与任务、中断与任务之间传递数据
  2. 信号量(Semaphore) :任务与任务、中断与任务之间同步、通知
  3. 互斥锁(Mutex) :对共享硬件、变量独占保护

二、队列(Queue)—— FreeRTOS 最基础的通信机制

2.1 什么是队列?

队列是一种 FIFO(先进先出) 的数据缓存容器,用于在任务与任务之间、中断与任务之间安全传递数据。

你可以把它理解为:一个线程安全的数据管道

特点:

  • 可以传递任意类型数据:char、int、float、结构体、指针
  • 多任务可以操作同一个队列
  • 队列空 → 读任务阻塞
  • 队列满 → 写任务阻塞
  • 自带临界区,无需手动加锁

2.2 队列工作流程

  1. 创建队列,指定长度(能存几条消息)
  2. 指定每条数据大小(字节)
  3. 任务 A 发送数据 → 存入队列
  4. 任务 B 读取数据 → 从队列取出
  5. 队列空:读任务进入阻塞态
  6. 队列满:写任务进入阻塞态

2.3 队列核心 API(超详细)

(1)创建队列

c

运行

QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,    // 队列长度(最多存几条数据)
    UBaseType_t uxItemSize         // 每条数据大小(字节)
);

返回值:队列句柄,失败返回 NULL。

(2)任务中发送数据到队列

c

运行

xQueueSend(
    QueueHandle_t xQueue,           // 目标队列
    const void *pvItemToQueue,      // 要发送的数据指针
    TickType_t xTicksToWait         // 队列满时,阻塞等待时间
);
(3)任务中从队列读取数据

c

运行

xQueueReceive(
    QueueHandle_t xQueue,           // 要读取的队列
    void *pvBuffer,                  // 接收数据的缓冲区
    TickType_t xTicksToWait         // 队列空时,阻塞等待时间
);
(4)中断中发送数据(必须用这个!)

c

运行

xQueueSendFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);
(5)中断中读取数据

c

运行

xQueueReceiveFromISR(
    QueueHandle_t xQueue,
    void *pvBuffer,
    BaseType_t *pxHigherPriorityTaskWoken
);

2.4 队列最经典实战:串口接收 + 队列解析

流程:

  1. 串口中断收到 1 个字节
  2. 中断中将字节发送到队列
  3. 任务从队列读取数据
  4. 任务进行数据解析、处理

这是工业级最标准、最稳定的串口处理方案


三、信号量(Semaphore)—— 多任务同步神器

信号量不传递数据,只传递 “事件发生了” 的通知。

3.1 二值信号量(Binary Semaphore)

只有两个状态:有信号 / 无信号

最典型用途:

  • 中断通知任务
  • 按键触发任务
  • 传感器数据就绪通知
  • 任务之间同步执行
API

创建:

c

运行

SemaphoreHandle_t xSemaphoreCreateBinary(void);

等待信号量(任务阻塞等待):

c

运行

xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,
    TickType_t xTicksToWait
);

释放信号量(发出通知):

c

运行

xSemaphoreGive(SemaphoreHandle_t xSemaphore);

中断中释放:

c

运行

xSemaphoreGiveFromISR(...);

3.2 计数信号量(Counting Semaphore)

可以设置一个最大值,用于:

  • 资源池管理(3 个资源允许多任务访问)
  • 生产消费模型
  • 事件计数
  • 缓冲区管理
API

创建:

c

运行

xSemaphoreCreateCounting(max, initial);

取信号量(占用资源):

c

运行

xSemaphoreTake();

还信号量(释放资源):

c

运行

xSemaphoreGive();

3.3 二值信号量 vs 计数信号量

  • 二值信号量:0/1,用于同步、通知
  • 计数信号量:0~N,用于资源管理、限流

四、互斥锁(Mutex)—— 资源独占、防止竞争(最重要)

4.1 什么是互斥锁?

互斥锁 = 互斥信号量,用于保护共享资源,保证同一时间只有一个任务在使用。

需要保护的资源:

  • 串口 printf
  • IIC、SPI 总线
  • W25Q64 Flash
  • LCD 屏幕
  • 全局变量、结构体

4.2 互斥锁的特点

  • 同一时间只能被一个任务持有
  • 自动支持优先级继承
  • 防止优先级翻转
  • 不能在中断中使用

4.3 优先级翻转(面试必考)

现象:

  • 低优先级任务正在使用资源
  • 高优先级任务请求资源 → 阻塞
  • 中优先级任务抢占 CPU → 疯狂运行
  • 高优先级任务长时间无法执行

互斥锁通过优先级继承自动解决:临时把低优先级任务提升到和高优先级一样高,让中优先级无法抢占。

4.4 互斥锁 API

创建:

c

运行

SemaphoreHandle_t xSemaphoreCreateMutex(void);

加锁(拿锁):

c

运行

xSemaphoreTake(mutex, portMAX_DELAY);

解锁(归还锁):

c

运行

xSemaphoreGive(mutex);

使用模板:

c

运行

xSemaphoreTake();
// 访问共享资源:printf、IIC、SPI、Flash
xSemaphoreGive();

五、队列、二值信号量、互斥锁 核心区别(超清晰总结)

队列

  • 传递:数据
  • 方向:任务↔任务、中断↔任务
  • 特点:FIFO、可存多条数据

二值信号量

  • 传递:通知、同步
  • 方向:中断→任务、任务→任务
  • 特点:只有 0/1,无数据

互斥锁

  • 作用:资源独占保护
  • 特点:优先级继承、防优先级翻转
  • 不能在中断使用

六、工程中最标准的使用规范

队列使用场景

  • 串口数据接收
  • 按键消息分发
  • 传感器数据传递
  • 指令下发

二值信号量使用场景

  • 中断唤醒任务
  • 任务同步执行
  • 事件触发

互斥锁使用场景

  • 保护 printf
  • 保护 IIC/SPI
  • 保护 Flash
  • 保护 LCD

七、常见错误与避坑指南

7.1 中断中不能用普通 API

必须用 FromISR 结尾的函数。

7.2 互斥锁不能用在中断

会直接死机。

7.3 队列读取必须判断返回值

判断是否读取成功。

7.4 信号量必须先 Give 再 Take

二值信号量创建后默认为无信号,必须先 Give 一次。

7.5 锁必须成对

加锁一次,必须解锁一次,不能嵌套。


八、本篇总结

本篇我们完整、系统、深入地学习了 FreeRTOS 三大核心机制:

  1. 队列:任务之间安全传递数据
  2. 二值信号量:中断与任务同步、通知
  3. 计数信号量:资源计数、管理
  4. 互斥锁:资源保护、防优先级翻转

它们是多任务系统稳定运行的基石,也是嵌入式开发面试、笔试、项目开发中最高频、最重要的知识点。

掌握本篇内容,你已经具备了设计线程安全、稳定可靠、工业级多任务系统的核心能力。