一:概述
FreeRTOS 提供了信号量和互斥锁,用于任务间的同步和资源共享管理。信号量更偏向于任务同步,而互斥锁用于保护共享资源。
FreeRTOS 中的 信号量 (Semaphore) 是一种用于 任务间通信 和 同步 的机制。
你可以把它简单理解为:一个用来传递状态的“令牌”或“钥匙”。它本质上是一个队列 (Queue),但是这个队列不存储具体的数据,只存储“计数值”(有多少个令牌)。
1. 信号量的三种主要类型
A. 二值信号量 (Binary Semaphore)
B. 计数型信号量 (Counting Semaphore)
C. 互斥信号量 (Mutex, Mutual Exclusion)
2.信号量的含义
信号量这个名字很恰当:
- 信号:起通知作用
- 量:还可以用来表示资源的数量
- 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
- 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
- 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
3.计数值是什么




二: 信号量的三种主要类型讲解
Take 减一 -1 Give 加1 +1

1. 二值信号量 (Binary Semaphore)

2. 计数型信号量 (Counting Semaphore)

3. 互斥信号量 (Mutex / Mutual Exclusion)


三.Give/Take 组合
Take 减一 -1 Give 加1 +1






四:信号量函数
做为初学者,信号量函数只需要掌握三类核心操作:创建(Create)、获取(Take)、释放(Give),外加一个特别需要注意的中断版(FromISR)。
1. 创建
使用信号量之前,要先创建,得到一个句柄(Handle);使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:
| 函数名 | 描述 | 老师的备注 |
xSemaphoreCreateBinary() | 创建二值信号量 | 注意: 创建出来默认是“空”的(0),你需要先 Give 一次才能被 Take。 |
xSemaphoreCreateCounting(...) | 创建计数信号量 | 需要两个参数:最大计数值(Max)和初始值(Init)。 |
xSemaphoreCreateMutex() | 创建互斥量 | 创建出来默认是“满”的(可用),可以直接 Take。 |
示范代码:
SemaphoreHandle_t mySem; // 1. 定义句柄变量
void main() {
// 2. 创建
mySem = xSemaphoreCreateBinary();
// 3. 检查是否创建成功(非常重要!)
if (mySem == NULL) {
// 内存不足,创建失败
}
}




静态之后搜下吧,感觉先不是很重要
代码示范
// 1. 定义句柄(全局变量)
SemaphoreHandle_t myBinarySem = NULL;
SemaphoreHandle_t myCountingSem = NULL;
SemaphoreHandle_t myMutex = NULL;
void main_init_function() {
// --- 创建二值信号量 ---
myBinarySem = xSemaphoreCreateBinary();
if (myBinarySem != NULL) {
// 创建成功,但现在是空的(0),需要先 Give 才能用,或者等中断 Give
// xSemaphoreGive(myBinarySem); // 如果需要让他默认可用,就手动Give一次
}
// --- 创建计数信号量 ---
// 比如:一个停车场,最多停5辆车,一开始里面也是空的(0辆车)
myCountingSem = xSemaphoreCreateCounting(5, 0);
// --- 创建互斥量 ---
myMutex = xSemaphoreCreateMutex();
// 创建成功后,它就是可用的。不需要手动 Give。
}
2 删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
3. give/take
二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版本:给任务使用,给ISR使用。列表如下:
| 在任务中使用 | 在ISR中使用 | |
|---|---|---|
| give | xSemaphoreGive | xSemaphoreGiveFromISR |
| take | xSemaphoreTake | xSemaphoreTakeFromISR |
一、 任务版函数 (Task API)
这两个函数只能在任务(Task)中调用,绝对不能在中断(ISR)里用。因为它们允许“阻塞”(也就是睡觉等待),而中断是不能睡觉的。


二、 中断版函数 (ISR API)
中断函数最大的特点是:没有阻塞时间参数(不能等),并且多了一个**“切换上下文”的机制**。
核心机制:pxHigherPriorityTaskWoken 这个参数的变量定义是由你(用户)写的,



代码示范:
假设我们有一个按键中断,当按键按下时,ISR 释放信号量,唤醒一个负责处理按键逻辑的任务(KeyTask)。
步骤 1:定义信号量和任务
SemaphoreHandle_t xKeySemaphore; // 定义信号量句柄
// 在 main 或初始化中创建二进制信号量
xKeySemaphore = xSemaphoreCreateBinary();
步骤 2:中断服务程序 (ISR)
这是 ISR 版本的核心写法,请注意 xHigherPriorityTaskWoken 的用法:
void EXTI0_IRQHandler(void)
{
// 1. 定义一个变量来保存是否需要切换任务的状态,初始化为 pdFALSE
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 清除中断标志位(具体取决于硬件平台,这里仅为伪代码)
Clear_IT_Flag();
// 2. 发送信号量(Give)
// 告诉 FreeRTOS:“资源有了,如果有任务在等,把它唤醒”
// 如果唤醒的任务优先级比当前被打断的任务高,xHigherPriorityTaskWoken 会变为 pdTRUE
xSemaphoreGiveFromISR(xKeySemaphore, &xHigherPriorityTaskWoken);
// 3. 强制上下文切换
// 如果 xHigherPriorityTaskWoken 为 pdTRUE,这里会挂起 PendSV 中断
// 使得 ISR 退出后直接运行高优先级任务,而不是回到原来的低优先级任务
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); [cite: 4449]
}
步骤 3:接收任务 (Task)
任务中使用普通的阻塞函数等待信号量:
void KeyProcessTask(void *pvParameters)
{
while(1)
{
// 阻塞等待信号量,portMAX_DELAY 表示死等
if(xSemaphoreTake(xKeySemaphore, portMAX_DELAY) == pdTRUE)
{
// 只有当 ISR 执行了 Give 操作,程序才会运行到这里
printf("按键被按下了,处理业务逻辑...\r\n");
}
}
}
4. 总结

| 在任务中使用 | 在ISR中使用 | |
|---|---|---|
| give | xSemaphoreGive | xSemaphoreGiveFromISR |
| take | xSemaphoreTake | xSemaphoreTakeFromISR |
5.问题提问
pxHigherPriorityTaskWoken 这个参数的变量定义是由你(用户)写的
问题1






问题2


问题3


五:对于初学者要掌握的
对于第12章 信号量 (Semaphore),你不需要背下所有的函数参数和复杂的内部机制。作为初学者,你只需要掌握以下 4 个核心点 就能应对 80% 的开发需求:

六:信号量是用于哪些方面的
根据文档第12章的内容,信号量(Semaphore)主要用于解决资源管理和任务同步这两个核心问题。
你可以把它理解为一个**“全局计数器”**,用于在多任务之间传递状态或管理数量。具体应用场景分为以下三类:



总结
| 信号量类型 | 核心作用 | 典型生活例子 | 操作逻辑 |
| 计数型 | 资源管理 | 停车场、电影票 | 有票就进,没票排队 |
| 计数型 | 事件计数 | 生产流水线 | 生产一个+1,消费一个-1 |
| 二进制 | 同步/通知 | 裁判发令枪 | 枪响(Give),运动员跑(Take) |
| 二进制 | 互斥(锁) | 公用电话亭 | 我用时你别进(注:建议改用Mutex) |




被折叠的 条评论
为什么被折叠?



