IustitiaeのBlog
首页项目归档照片墙音乐说说笔记关于
封面
📓 笔记/📁 嵌入式/FreeRTOS信号量与回调函数——任务间的对话

FreeRTOS信号量与回调函数——任务间的对话

2026-06-27 17:00:00
# FreeRTOS
# STM32
# RTOS
# 信号量
# 回调函数

引言

多任务操作系统中,任务不是孤岛——它们需要通信、需要同步、需要共享资源。如果说上一篇的"任务管理"让每个任务独立运行,那么本文的"信号量"就是任务之间对话的语言。

一、信号量(Semaphore)—— 四种类型

1.1 二值信号量(Binary Semaphore)—— 任务同步 / 中断通知

最常用的信号量类型,只有 0 和 1 两个状态,用于任务间同步或中断通知任务。

创建:

SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 注意:创建后初始值为 0(空)!

任务中等待(Take):

if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
    // 获取到信号量,执行同步操作
}

中断中释放(GiveFromISR):

BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);  // 判断是否需要任务切换

典型应用场景:

  • 按键中断通知任务处理
  • DMA 传输完成中断通知任务
  • 两个任务之间的握手同步

1.2 计数信号量(Counting Semaphore)—— 资源池管理

可以管理多个同类资源,计数值表示可用资源的数量。

// 最大计数 5,初始计数 3(有 3 个资源可用)
SemaphoreHandle_t xCountSem = xSemaphoreCreateCounting(5, 3);

// 消耗一个资源
xSemaphoreTake(xCountSem, portMAX_DELAY);

// 归还一个资源
xSemaphoreGive(xCountSem);

典型应用场景:

  • 管理固定大小的缓冲区池
  • 限制同时访问某资源的任务数量
  • 多生产者-多消费者模型

1.3 互斥锁(Mutex)—— 资源独占 + 优先级继承

用于保护共享资源(全局变量、外设、通信总线),确保同一时刻只有一个任务访问。

// 创建互斥锁
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();

// 使用模式(必须在同一任务中 Take 和 Give)
xSemaphoreTake(xMutex, portMAX_DELAY);
    // 访问共享资源(printf、I2C、SPI、全局变量)
    // ...
xSemaphoreGive(xMutex);

⚠️ 关键限制:

  • 互斥锁不能在中断中使用!
  • Take 和 Give 必须在同一个任务中成对出现
  • 互斥锁自动支持优先级继承,防止优先级翻转问题

优先级翻转问题:低优先级任务持有锁 → 高优先级任务等待锁 → 中优先级任务抢走 CPU。互斥锁的优先级继承机制会将持有锁的低优先级任务临时提升,避免这种情况。

1.4 递归互斥锁(Recursive Mutex)

普通互斥锁同一任务只能 Take 一次,递归互斥锁允许同一任务多次 Take。

SemaphoreHandle_t xRecursiveMutex = xSemaphoreCreateRecursiveMutex();

xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);  // 第一次获取
    xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);  // 再次获取(OK!)
    xSemaphoreGiveRecursive(xRecursiveMutex);                  // 释放一层
xSemaphoreGiveRecursive(xRecursiveMutex);                      // 完全释放

典型应用场景:嵌套函数调用中,每个函数都可能需要获取同一个锁。

二、信号量类型对比总结

类型Take 次数Give 次数中断可用优先级继承典型用途二值信号量1 次(变 0)1 次(变 1)✅ Give❌任务同步、中断通知计数信号量N 次(减到 0)N 次(加到最大)✅ Give❌资源池管理互斥锁1 次1 次(同一任务)❌✅资源独占保护递归互斥锁多次(同任务)对应次数❌✅嵌套函数锁

三、软件定时器与回调函数

FreeRTOS 提供软件定时器,允许在定时器到期时执行回调函数。这些回调函数在定时器服务任务的上下文中运行。

创建软件定时器

// 定时器回调函数(不能有死循环!不能有阻塞调用!)
void TimerCallback(TimerHandle_t xTimer) {
    // 定时执行的代码
    // 只能调用带 FromISR 后缀或不阻塞的 API
}

// 创建定时器
TimerHandle_t xTimer = xTimerCreate(
    "MyTimer",                      // 名称
    pdMS_TO_TICKS(100),             // 周期 100ms
    pdTRUE,                         // pdTRUE=周期模式, pdFALSE=单次触发
    (void *)0,                      // 定时器 ID(可传参)
    TimerCallback                   // 回调函数
);

// 启动定时器
xTimerStart(xTimer, 0);

CubeMX CMSIS-RTOS 封装方式

osTimerDef(MyTimer, TimerCallback);
osTimerId timerHandle = osTimerCreate(osTimer(MyTimer), osTimerPeriodic, NULL);
osTimerStart(timerHandle, 5);  // 5ms 周期

回调函数注意事项

注意点说明不能有死循环回调函数必须快速返回不能阻塞不能调用 vTaskDelay、xSemaphoreTake(带超时的也不行)不能调用非 FromISR 的 API可以使用 xSemaphoreGiveFromISR运行在定时器服务任务中优先级通常较高,耗时操作会影响所有定时器

四、任务通知 —— 更轻量的替代方案

如果只需要简单的任务同步,任务通知(Task Notification) 比信号量更快,占用 RAM 更少——但一个任务只能阻塞在一个通知上。

// 发送通知(任务中)
xTaskNotifyGive(TaskHandle);

// 发送通知(中断中)
vTaskNotifyGiveFromISR(TaskHandle, &xHigherPriorityTaskWoken);

// 等待通知
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// pdTRUE  → 收到通知后将值清零(类似二值信号量)
// pdFALSE → 收到通知后将值减 1(类似计数信号量)

任务通知 vs 信号量:

  • 任务通知:更快、RAM 更少,但一个任务只能等待一个通知源
  • 信号量:稍慢但更灵活,可以等待任意多个信号量

五、信号量 vs 队列 vs 任务通知

机制用途传递数据中断安全优先级继承队列任务间数据传输✅ 数据✅❌二值信号量任务同步/中断通知❌ 仅通知✅❌计数信号量资源计数管理❌ 计数✅❌互斥锁资源独占保护❌ 锁❌✅任务通知轻量同步可选 32位值✅❌

六、关键避坑指南

  1. 中断中不能用互斥锁,只能用信号量(带 FromISR 后缀的 API)
  2. 中断释放信号量后必须调用 portYIELD_FROM_ISR() 检查是否需要上下文切换
  3. 互斥锁的 Take 和 Give 必须在同一个任务中成对出现
  4. 二值信号量创建后初始为空(0),如需初始可用需额外调用一次 xSemaphoreGive()
  5. 软件定时器回调中不能阻塞或死循环
  6. 共享资源必须加锁,否则会出现数据覆盖、串口乱码、I2C/SPI 时序错乱

总结

信号量是 FreeRTOS 任务间通信的基石。二值信号量实现同步,计数信号量管理资源,互斥锁保护临界区,软件定时器回调处理周期任务——这四把武器掌握好了,绝大部分 RTOS 应用场景都能从容应对。

交流创造秩序,共享需要规则。 —— Iustitiae


avatar

Iustitiae

在代码、单片机,电机控制模拟间穿梭的普通人。近期正埋头于 freerots学习。

2026年6月

一
二
三
四
五
六
日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Recent Records

比较器基础——输出级、输入范围与颤振的解决之道

2026-08-03 12:00:00

MOS管基础——从电压控制的电子开关到电源开关与防反接

2026-08-03 12:00:00

三极管基础——从小电流撬动大电流的放大器到电子开关

2026-07-29 12:00:00