欢迎光临
我们一直在努力

FreeRTOS Semaphore详解:二值信号量、计数信号量与ISR同步

信号量 Semaphore

1. Semaphore 是什么

Semaphore(信号量)本质上是一种用于任务同步和事件通知的内核对象。

它最重要的作用不是传递具体数据,而是表达:

某个资源是否可用,或者某个事件是否已经发生。

在 FreeRTOS 中,Semaphore 的底层实现与 Queue 有密切关系。

可以先建立一个总的认识:

事件 / 资源发生变化

Give 信号量

Semaphore状态发生变化

等待中的Task被唤醒

Take信号量

Task处理事件

因此,Semaphore最核心的两个动作就是:

Give

释放 / 发布信号

Take

获取 / 等待信号


2. Semaphore 与 Queue 的关系

2.1 FreeRTOS 中的底层关系

FreeRTOS 的 Semaphore 并不是完全独立设计的一套底层数据结构。

它使用了 Queue 的底层机制。

可以理解为:

FreeRTOS


Queue

┌─────────┴─────────┐
↓ ↓
Queue API Semaphore API

┌───────────┼───────────┐
↓ ↓ ↓
二值信号量 计数信号量 互斥量

所以前面学习 Queue 时看到的很多底层概念,在 Semaphore 中仍然存在。


2.2 为什么 Semaphore 可以使用 Queue 的底层逻辑

Queue关注:

有没有数据、数据是什么。

Semaphore关注:

有没有信号、资源数量是多少。

因此 Semaphore 不需要真正关心数据内容。

例如:

Queue:

发送:
数据A → Queue

接收:
Queue → 数据A

而二值信号量:

Semaphore:

Give

有信号

Take

获取信号

它不关心:

“这个信号里面到底存了什么数据”


2.3 二值信号量的特殊性

二值信号量只有两种状态:

0 = 不可用 / 没有信号
1 = 可用 / 有信号

从底层 Queue 的角度理解:

uxLength = 1
uxItemSize = 0

这里:

uxLength

表示只能容纳一个信号。

而:

uxItemSize = 0

表示它不真正存储数据项。

所以二值信号量可以理解为:

只借用了 Queue 的“有没有东西”的逻辑,而不需要真正保存数据。


3. Semaphore 的核心思想

Semaphore 最重要的不是 API,而是下面这个模型:

事件产生者


Give


Semaphore


Take


事件处理者

例如:

温度超过阈值

Give

温度处理任务

Take

执行故障处理

这里必须注意:

Semaphore 本身不是事件源。

真正的事件来源可能是:

按键
ADC
DMA
定时器
CAN接收
UART接收
硬件中断
其他任务

Semaphore 只是负责把:

“事件发生了”

这个信息传递给等待它的任务。


4. 二值信号量 Binary Semaphore

4.1 二值信号量的状态

二值信号量只有:

0
1

可以理解成:

0 → 没有信号

1 → 有信号

状态变化:

Give
0 ─────────────→ 1

│ Take

0

因此它特别适合:

一个事件发生 → 通知另一个任务处理一次。


4.2 创建二值信号量

API:

// 创建一个二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void);

典型代码:

// 创建二值信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();

// 判断信号量是否创建成功
if (xSemaphore == NULL)
{
// 创建失败,进入错误处理
Error_Handler();
}

这里需要特别注意:

动态创建可能失败。

原因包括:

FreeRTOS Heap空间不足
动态内存分配失败
堆管理配置问题

所以工程代码不能默认:

创建一定成功

而应该检查:

// 判断信号量句柄是否为空
if (xSemaphore == NULL)
{
// 创建失败
Error_Handler();
}


5. 为什么创建后不能认为已经有信号

这是理解 Semaphore 的一个关键点。

创建:

// 创建二值信号量
xSemaphore = xSemaphoreCreateBinary();

并不等于:

事件已经发生

通常创建后:

Semaphore

不可用

count = 0

因此任务执行:

// 尝试获取信号量
xSemaphoreTake(xSemaphore, 0);

通常会:

Take

没有信号

返回失败

这正是合理的。

例如:

系统刚启动

还没有发生按键事件

按键任务等待

Blocked

而不是一启动就让任务误认为:

“已经发生了一次按键事件”


6. 二值信号量的 Give

6.1 普通任务上下文 Give

API:

// 在任务上下文中释放一个信号量
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);

例如:

// 释放二值信号量
BaseType_t result = xSemaphoreGive(xSemaphore);

// 判断释放是否成功
if (result == pdTRUE)
{
// 信号量释放成功
}
else
{
// 信号量释放失败
}


6.2 Give 到底发生了什么

假设:

Semaphore = 0

执行:

Give

结果:

0 → 1

如果此时有任务正在:

Take

并且因为没有信号而进入 Blocked,那么 Give 会使等待任务获得被唤醒的机会:

Task
Blocked

Give

Task
Ready

注意:

Blocked → Ready 不等于 Ready → Running。

真正是否马上运行,还需要经过调度器判断。


7. 为什么 Give 与任务之间能够联动

这是 Semaphore 最容易被误解的地方。

不能理解成:

Give

莫名其妙

另一个Task运行

正确过程是:

事件发生

事件产生者执行Give

Semaphore变为可用

等待该Semaphore的Task
Blocked → Ready

调度器重新选择任务

如果满足运行条件

Task Running

Take成功


8. 一个真实的任务间同步例子

假设有两个任务。

ProducerTask

负责检测温度。

温度超过阈值

Give

ConsumerTask

负责处理故障。

Take

处理故障

整体流程:

ProducerTask

│ 检测温度

温度超过阈值


Give


Semaphore


ConsumerTask

│ Take

处理故障

这里 Semaphore 的作用就是:

把“温度异常事件”从产生者传递给处理者。


9. 任务等待信号量

核心 API:

// 等待并尝试获取信号量
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime
);

其中最重要的是:

xBlockTime

它决定:

如果当前没有信号,任务最多愿意等待多久。


10. xSemaphoreTake() 的三种等待方式

10.1 不等待

// 立即尝试获取信号量,不进行阻塞等待
xSemaphoreTake(xSemaphore, 0);

过程:

Take

有信号?
├── 是 → 成功
└── 否 → 立即失败

任务不会进入 Blocked。


10.2 有限时间等待

例如:

// 最多等待100个Tick获取信号量
xSemaphoreTake(xSemaphore, 100);

如果当前没有信号:

Take

没有信号

进入Blocked

等待

如果100 Tick内发生 Give:

Give

Blocked → Ready

调度器

Task运行

Take成功

如果100 Tick内一直没有 Give:

Blocked

100 Tick超时

Ready

Take失败

返回pdFALSE


10.3 无限等待

// 无限等待信号量
xSemaphoreTake(xSemaphore, portMAX_DELAY);

含义:

任务愿意长期等待这个事件。

不是:

CPU一直等待

而是:

Task

Blocked

CPU运行其他Ready任务

事件发生

Give

Task Ready

所以:

portMAX_DELAY 描述的是任务的等待行为,不代表 CPU 忙等。


11. Take 的完整决策流程

xSemaphoreTake()


Semaphore是否可用?
┌─────┴─────┐
│ │
是 否
│ │
↓ ↓
Take成功 检查BlockTime

┌────────┴────────┐
│ │
0 > 0
│ │
↓ ↓
立即失败 Blocked

┌──────────┴──────────┐
│ │
Give 超时
│ │
↓ ↓
Blocked→Ready Ready
│ │
└──────────┬──────────┘

调度器

Task运行

这是必须掌握的核心流程。


12. Semaphore 与任务状态

必须严格区分:

Blocked
Ready
Running

例如:

Task A
Blocked

因为:

Take

没有信号

允许阻塞

当另一个执行者 Give:

Blocked

Ready

但:

Ready ≠ Running

只有调度器最终选择 Task A:

Ready

调度

Running


13. 为什么阻塞不会占用 CPU

错误理解:

Task A

等待信号量

CPU一直等Task A

正确理解:

Task A

Take

没有信号

Blocked

此时:

CPU

运行其他Ready任务

例如:

Task A → Blocked

Task B → Ready
Task C → Ready

CPU

Task B

Task C

Task B

所以阻塞等待的意义就是:

让出CPU,而不是忙等。


14. Semaphore 的 Give / Take 速度不匹配

二值信号量只有一个有效状态。

因此它不能像 Queue 一样保存大量事件。

例如:

Semaphore = 0

第一次Give
0 → 1

第二次Give
已经是1

不能继续累计

所以:

Give
Give
Give
Give

不能理解成:

4个事件全部排队等待

二值信号量只能表达:

“现在至少有一个事件尚未被消费”

因此如果:

生产速度 > 消费速度

就可能出现:

事件丢失 / 无法累计

如果需求是:

必须记录累计发生了多少次事件

那么二值信号量可能不合适。


15. Counting Semaphore

计数信号量用于表达:

当前累计有多少个可用资源 / 未处理事件。

它与二值信号量最大的区别:

Binary Semaphore:

0 / 1
Counting Semaphore:

0 / 1 / 2 / 3 / …


16. 创建计数信号量

API:

// 创建一个计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount
);

例如:

// 创建最大计数为5、初始计数为0的计数信号量
SemaphoreHandle_t xSemaphore =
xSemaphoreCreateCounting(5, 0);

这里:

uxMaxCount = 5

表示:

最大只能计数到5。

而:

uxInitialCount = 0

表示:

创建时当前没有可用信号。


17. Counting Semaphore 的计数变化

假设:

uxMaxCount = 3
count = 0

连续 Give:

第一次Give:

0 → 1

第二次Give:

1 → 2

第三次Give:

2 → 3

此时:

count = uxMaxCount = 3

继续 Give:

3 → ?

不能继续增加。

因此:

达到 uxMaxCount 后不能继续增加。


18. 为什么达到最大值后 Give 失败

原因不是:

FreeRTOS内存不足

而是:

计数信号量的逻辑计数已经达到上限。

例如:

uxMaxCount = 3

当前count = 3

此时:

Give

发现已经达到最大计数

无法增加

Give失败

所以:

Give失败

不等于:

内存不足

这两个概念必须区分。


19. Counting Semaphore 的 Take

假设:

count = 3

连续 Take:

第一次:

3 → 2

第二次:

2 → 1

第三次:

1 → 0

第四次 Take:

0

没有信号

如果允许阻塞:

Task

Blocked

等待下一次 Give。


20. Counting Semaphore 的工程意义

假设生产者产生事件:

事件1
事件2
事件3

每发生一次:

Give

那么:

count:

0

1

2

3

消费者每处理一个:

Take

那么:

3

2

1

0

因此 Counting Semaphore 可以表示:

还有多少个事件 / 资源等待处理。


21. Binary Semaphore 与 Counting Semaphore 对比

特性Binary SemaphoreCounting Semaphore
状态 0 / 1 0 ~ uxMaxCount
是否累计 不能累计多个 可以累计
Give上限 1 uxMaxCount
Take 消耗一个信号 计数减1
典型用途 事件通知 资源/事件计数
是否保存数据
是否适合传递具体数据

简单记忆:

Binary:

“有没有?”

Counting:

“有几个?”


22. Semaphore 与 Queue 的核心区别

Queue

Queue主要解决:

数据传递。

例如:

Task A

发送ADC数据

Queue

Task B

接收ADC数据

Queue中真正存在:

数据


Semaphore

Semaphore主要解决:

同步 / 事件通知 / 资源计数。

例如:

DMA完成

Semaphore Give

ADC Task Take

Semaphore本身不负责保存ADC数据。


23. 数据与事件必须分开理解

这是工程中非常重要的一点。

例如 ADC + DMA:

ADC数据

DMA

ADC_Buffer[]

这是:

数据通道

而:

DMA完成

Semaphore

这是:

事件通道

最终:

数据通道:

ADC → DMA → Buffer

事件通道:

DMA完成 → ISR → Semaphore → Task

所以:

Semaphore负责告诉任务“什么时候可以处理”,Buffer负责告诉任务“处理什么数据”。


24. ISR 释放信号量

在中断服务函数中不能简单按照普通任务代码思考。

任务上下文:

// 在任务上下文中释放信号量
xSemaphoreGive(xSemaphore);

ISR上下文:

// 在ISR上下文中释放信号量
xSemaphoreGiveFromISR(
xSemaphore,
&xHigherPriorityTaskWoken
);

核心区别:

任务上下文

xSemaphoreGive()

ISR上下文

xSemaphoreGiveFromISR()


25. 为什么 ISR 必须使用 FromISR 版本

ISR与普通Task的执行上下文不同。

FreeRTOS专门提供:

xxxFromISR()

形式的API用于中断环境。

因此:

DMA ISR

xSemaphoreGiveFromISR()

而不是:

DMA ISR

xSemaphoreGive()

这是 FreeRTOS API 使用中的基本规则。


26. xHigherPriorityTaskWoken

典型代码:

// 保存是否因为Give操作唤醒了更高优先级任务
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

// 在ISR中释放信号量
xSemaphoreGiveFromISR(
xSemaphore,
&xHigherPriorityTaskWoken
);

这个变量用于告诉 FreeRTOS:

这次ISR中的Give是否使一个更高优先级的任务从Blocked变成Ready。

如果是:

xHigherPriorityTaskWoken != pdFALSE

通常会:

// 如果唤醒了更高优先级任务
if (xHigherPriorityTaskWoken != pdFALSE)
{
// 请求在ISR退出后进行任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

其逻辑:

ISR

GiveFromISR

等待任务被唤醒

Blocked → Ready

发现它优先级更高

请求任务切换

ISR退出后调度


27. ISR 与 Task 的完整同步流程

这是 Semaphore 在实时系统中非常重要的模型:

硬件事件


ISR


xSemaphoreGiveFromISR()


Semaphore


等待中的Task被唤醒

Blocked → Ready


调度器


Running


xSemaphoreTake()


处理事件


28. ADC + DMA + Semaphore

这是本章最重要的工程应用之一。

假设:

ADC采集64个数据

DMA负责:

ADC

DMA

ADC_Buffer[64]

DMA完成后:

DMA Transfer Complete

DMA ISR

xSemaphoreGiveFromISR()

ADC处理任务:

xSemaphoreTake()

等待

因此:

ADC Task
Blocked

DMA完成:

Blocked

Ready

调度器选择该任务:

Ready

Running

最终:

Take成功

读取ADC_Buffer[]

处理ADC数据


29. ADC DMA 完成通知完整流程图

┌──────────────┐
│ ADC │
│ 产生采样值 │
└──────┬───────┘

┌──────────────┐
│ DMA │
│ 自动搬运数据│
└──────┬───────┘

┌──────────────┐
│ ADC_Buffer[] │
│ 保存数据 │
└──────┬───────┘

DMA传输完成

┌──────────────┐
│ DMA ISR │
└──────┬───────┘

GiveFromISR()

┌──────────────┐
│ Semaphore │
│ 事件同步 │
└──────┬───────┘

Blocked → Ready

┌──────────────┐
│ ADC处理任务 │
└──────┬───────┘

Take()

读取ADC_Buffer[]

处理ADC数据


30. ADC DMA 示例代码

30.1 定义 Buffer 和 Semaphore

// 定义ADC DMA缓冲区长度
#define ADC_BUFFER_SIZE 64

// 保存DMA采集的ADC数据
static uint16_t ADC_Buffer[ADC_BUFFER_SIZE];

// 保存ADC DMA完成信号量句柄
static SemaphoreHandle_t xADC_Semaphore = NULL;


30.2 创建信号量

// 创建一个二值信号量
xADC_Semaphore = xSemaphoreCreateBinary();

// 判断信号量是否创建成功
if (xADC_Semaphore == NULL)
{
// 信号量创建失败
Error_Handler();
}


30.3 ADC处理任务

// ADC数据处理任务
static void ADC_Process_Task(void *argument)
{
// 当前任务不使用传入参数
(void)argument;

// 任务持续运行
while (1)
{
// 无限等待ADC DMA完成事件
if (xSemaphoreTake(
xADC_Semaphore,
portMAX_DELAY) == pdTRUE)
{
// DMA已经完成数据搬运
uint32_t sum = 0;

// 遍历所有ADC采样值
for (uint32_t i = 0; i < ADC_BUFFER_SIZE; i++)
{
// 累加ADC采样值
sum += ADC_Buffer[i];
}

// 计算ADC平均值
uint16_t average = sum / ADC_BUFFER_SIZE;

// 对ADC平均值进行后续处理
BMS_ADC_Process(average);
}
}
}


30.4 DMA ISR

// DMA1 Channel1中断服务函数
void DMA1_Channel1_IRQHandler(void)
{
// 保存是否唤醒了更高优先级任务
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

// 判断DMA传输完成中断是否发生
if (DMA_GetITStatus(DMA1_IT_TC1) != RESET)
{
// 清除DMA传输完成中断标志
DMA_ClearITPendingBit(DMA1_IT_TC1);

// 通知ADC处理任务DMA已经完成
xSemaphoreGiveFromISR(
xADC_Semaphore,
&xHigherPriorityTaskWoken
);
}

// 判断是否唤醒了更高优先级任务
if (xHigherPriorityTaskWoken != pdFALSE)
{
// 请求在ISR退出后进行任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}


31. 为什么 ISR 不应该负责复杂 ADC 数据处理

错误结构:

DMA ISR

读取64个ADC值

计算平均值

滤波

最大值/最小值计算

复杂BMS算法

问题:

ISR执行时间变长

其他中断响应受到影响

系统实时性下降

更合理:

DMA ISR

清中断标志

GiveFromISR

退出ISR

然后:

ADC Task

Take

复杂数据处理


32. Semaphore 并不能解决所有并发问题

这是工程中必须注意的边界。

Semaphore解决:

“什么时候可以处理?”

但不自动解决:

“共享Buffer如何保证数据一致?”

例如:

DMA

正在写Buffer

Task读取Buffer

如果DMA已经开始覆盖下一批数据,而Task还没有处理完上一批数据,就可能产生数据竞争。

因此实际工程可能进一步使用:

双缓冲
Ping-Pong Buffer
半传输/全传输机制
Buffer状态管理

这里需要记住:

Semaphore 是同步机制,不是万能的数据保护机制。


33. Binary Semaphore 的典型使用场景

适合:

一个事件

通知一个任务

例如:

按键

EXTI

GiveFromISR

KeyTask Take
DMA完成

DMA ISR

GiveFromISR

ADC Task Take
温度超过阈值

Give

故障处理Task Take


34. Counting Semaphore 的典型使用场景

适合:

多个事件发生

需要累计数量

例如:

事件1 → Give
事件2 → Give
事件3 → Give

得到:

count = 3

处理:

Take → 2
Take → 1
Take → 0

因此:

Binary Semaphore
→ “有没有事件?”

Counting Semaphore
→ “有多少个事件?”


35. Semaphore 不适合传递具体数据

如果需要:

ADC值
CAN帧
温度值
结构体
命令参数

那么应该考虑:

Queue

因为 Queue 的核心能力是:

数据生产者

Queue

数据消费者

而 Semaphore:

事件生产者

Semaphore

事件消费者


36. Semaphore 与 Queue 的选择

可以用下面的判断:

我要传递什么?

┌─────────┴─────────┐
↓ ↓
数据 事件/资源
│ │
↓ ↓
Queue Semaphore

┌─────────┴─────────┐
↓ ↓
是否累计? 只需0/1?
│ │
↓ ↓
Counting Binary

简单判断:

“我要把什么东西传过去?”

Queue

“我只想告诉另一个任务事情发生了。”

Binary Semaphore

“我还要知道累计发生了多少次。”

Counting Semaphore


37. Semaphore 常见错误

37.1 创建后直接认为已经有信号

错误认识:

Create

Semaphore已经有效

正确:

Create

等待真正事件发生

Give

Semaphore有效


37.2 ISR中使用普通Give

错误:

// 错误示例:ISR中直接使用任务版本API
xSemaphoreGive(xSemaphore);

正确:

// 在ISR中使用ISR安全版本API
xSemaphoreGiveFromISR(
xSemaphore,
&xHigherPriorityTaskWoken
);


37.3 把Blocked理解成占用CPU

错误:

Task Blocked

CPU一直等待

正确:

Task Blocked

任务不参与CPU运行

其他Ready任务获得CPU


37.4 把Blocked → Ready理解成马上运行

错误:

Blocked

Ready

立即Running

正确:

Blocked

Ready

调度器

根据优先级等条件决定

Running


37.5 用Binary Semaphore累计大量事件

例如:

Give
Give
Give
Give

不能理解成:

4个事件全部保存

二值信号量最多表达:

0 / 1

如果必须累计事件数量:

Counting Semaphore

或者根据具体数据需求考虑 Queue 等机制。


37.6 认为Semaphore可以传递数据

错误:

Semaphore → ADC数据

正确:

Buffer → ADC数据

Semaphore → ADC完成事件


37.7 ISR做大量工作

错误:

ISR

复杂计算

长时间占用CPU

更合理:

ISR

快速处理

GiveFromISR

Task处理复杂业务


38. 一个完整的工程模型

以BMS中的ADC采集为例:

┌─────────────┐
│ ADC │
│ 采集电池数据 │
└──────┬──────┘

┌─────────────┐
│ DMA │
│ 自动搬运数据 │
└──────┬──────┘

┌─────────────┐
│ ADC_Buffer │
│ 保存数据 │
└──────┬──────┘

DMA完成中断

┌─────────────┐
│ ISR │
└──────┬──────┘

GiveFromISR()

┌─────────────┐
│ Semaphore │
│ 事件通知 │
└──────┬──────┘

Task Ready

调度器选择

┌─────────────┐
│ ADC Task │
└──────┬──────┘

Take()

读取ADC_Buffer

数据处理/计算


39. Semaphore 的核心知识体系

把整个章节压缩成一棵知识树:

Semaphore

├── 1. 本质
│ ├── 同步
│ ├── 事件通知
│ └── 资源计数

├── 2. 底层关系
│ └── 基于Queue机制

├── 3. Binary Semaphore
│ ├── 0 / 1
│ ├── Create
│ ├── Give
│ └── Take

├── 4. Counting Semaphore
│ ├── 0 ~ uxMaxCount
│ ├── Give → count + 1
│ └── Take → count – 1

├── 5. Task等待
│ ├── xBlockTime = 0
│ ├── 有限Tick
│ └── portMAX_DELAY

├── 6. ISR释放
│ ├── xSemaphoreGiveFromISR()
│ ├── xHigherPriorityTaskWoken
│ └── portYIELD_FROM_ISR()

├── 7. Task状态
│ ├── Blocked
│ ├── Ready
│ └── Running

├── 8. 工程应用
│ ├── 按键事件
│ ├── DMA完成
│ ├── ADC DMA
│ └── 任务间事件通知

└── 9. 与Queue区别
├── Queue → 数据
└── Semaphore → 事件/同步/资源


40. 面试高频问题

40.1 Semaphore和Queue有什么区别?

标准回答:

Queue主要用于任务之间的数据传递,能够保存具体的数据项;Semaphore主要用于任务同步、事件通知或者资源计数,本身不关注具体数据内容。FreeRTOS中Semaphore底层使用了Queue的相关机制。


40.2 二值信号量为什么只有0和1?

因为它只需要表达:

0 → 没有信号
1 → 有信号

不需要保存具体数据。


40.3 xSemaphoreTake() 阻塞的时候CPU是不是一直等?

不是。

任务进入:

Blocked

不会占用CPU。

调度器会运行其他Ready任务。


40.4 Give之后任务是不是马上运行?

不是必然。

流程:

Give

Blocked → Ready

调度器

判断优先级等条件

决定是否运行


40.5 为什么ISR中使用 xSemaphoreGiveFromISR()?

因为ISR属于中断上下文,FreeRTOS提供专门的 FromISR API供中断环境使用。


40.6 为什么不能在ISR中进行复杂计算?

因为ISR执行时间过长会增加中断延迟,影响系统实时性。

更合理:

ISR

快速通知

Task

复杂处理


40.7 Binary Semaphore和Counting Semaphore区别?

核心区别:

Binary:

0 / 1

Counting:

0 ~ uxMaxCount

Binary适合:

是否发生事件。

Counting适合:

累计发生了多少事件 / 有多少资源。


40.8 ADC DMA为什么适合使用Semaphore?

因为DMA负责搬运数据,DMA完成后只需要通知任务:

“这一批数据已经准备好了。”

具体数据已经存储在ADC Buffer中,因此Semaphore负责事件同步,Buffer负责数据保存。


最终记忆模型

如果以后忘记Semaphore,不要从API开始回忆。

先问自己:

我要解决的是数据传输,还是事件同步?

如果是:

数据

Queue

如果是:

事件

Semaphore

然后继续判断:

只有“有没有事件”?

Binary Semaphore
需要知道“有多少个事件”?

Counting Semaphore

如果事件来自ISR:

ISR

GiveFromISR

Task Take

如果任务等待事件:

Take

没有信号

Blocked

CPU运行其他任务

事件发生:

Give

Blocked → Ready

调度器

Running

Take成功

最终形成:

Semaphore

┌────────────────┼────────────────┐
↓ ↓ ↓
Binary Counting ISR同步
│ │ │
0 / 1 0~MaxCount GiveFromISR
│ │ │
└────────────────┼────────────────┘

Take

Task等待事件

Blocked

Give

Ready

调度器

Running

一句话总结:

Semaphore不是用来传数据的,而是用来表达“事件发生了、资源可用了、还有多少个事件/资源”的同步机制;Task通过Take等待,事件产生者通过Give发布,ISR则使用FromISR版本进行通知。

赞(0)
未经允许不得转载:171主机测评 » FreeRTOS Semaphore详解:二值信号量、计数信号量与ISR同步
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址