FreeRTOS Semaphore信号量-笔记
FreeRTOS Semaphore信号量-笔记
- **一、信号量与互斥量的核心区别**
- **二、二值信号量(Binary Semaphore)**
- **1. 功能与使用场景**
- **2. 示例:ADC中断与任务同步**
- **三、计数信号量(Counting Semaphore)**
- **1. 功能与使用场景**
- **2. 示例:ADC双缓冲区管理**
- **四、互斥量(Mutex)**
- **1. 功能与使用场景**
- **2. 示例:保护共享资源**
- **五、关键注意事项**
- **六、总结**
队列的功能是将进程间需要传递的数据存在其中。所以在有的RTOS系统里,队列也被称为邮箱。
一、信号量与互斥量的核心区别
特性 | 信号量(Semaphore) | 互斥量(Mutex) |
---|---|---|
用途 | 进程间同步(如事件通知)或资源计数(如ADC双缓冲区) | 互斥访问共享资源(如保护全局变量或外设) |
所有权 | 无所有权(任何任务或ISR均可释放) | 有所有权(仅持有者可释放) |
优先级继承机制 | 无(可能导致优先级翻转) | 有(缓解优先级翻转) |
是否可中断使用 | 可(通过 xSemaphoreGiveFromISR ) | 不可(ISR中无法使用) |
初始值 | 二值信号量(0或1)或计数信号量(任意值) | 固定为1(表示资源可用) |
信号量和互斥量。信号量和互斥量的实现都是基于队列的,信号量更适用于进程间同步,而互斥量更适用于共享资源的互斥性访问。
二、二值信号量(Binary Semaphore)
如果不使用二值信号量,而是使用一个自定义标志变量来实现以上的同步过程,则任务需要不断的查询标志变量的值,而不是像使用二值信号那样可以使任务进入阻塞动态状态。所以使用二值信号量进行进程间同步的效率更高。
1. 功能与使用场景
- 功能:作为“标志”实现任务同步,例如:
- ADC中断通知任务:当ADC中断写入缓冲区后,释放二值信号量通知任务处理数据。
- 任务等待事件:任务阻塞等待信号量,避免忙等待。
- 创建函数:
// 动态分配xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );//这是一个宏函数。调用的函数类似于队列(xQueueGenericCreate)创建队列时,调用的也是这个函数。
参数解析
-
( UBaseType_t ) 1
- 队列长度:表示队列最多能容纳的“消息”数量。
- 二进制信号量特性:二进制信号量只能处于“空”(0)或“满”(1)状态,因此队列长度设为 1。
-
semSEMAPHORE_QUEUE_ITEM_LENGTH
- 队列项大小:定义队列中每个消息的字节长度。
- 二进制信号量特殊性:该宏被定义为 0(#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )),表示信号量不存储实际数据,仅用队列的空/满状态表示信号量状态。
-
queueQUEUE_TYPE_BINARY_SEMAPHORE
- 队列类型:指定此队列用于二进制信号量,而非普通队列或其他类型(如互斥锁或计数信号量)。
// 静态分配SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函数:
- 释放信号量(任务):
xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
参数解析
-
(QueueHandle_t)(xSemaphore)
- 信号量即队列:FreeRTOS 的信号量句柄 xSemaphore 实际上是队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
-
NULL
- 无实际数据:信号量本身不传递数据,仅用队列的空/满状态表示信号量状态。因此,发送的“消息”无需有效数据,使用 NULL 即可。
-
semGIVE_BLOCK_TIME
- 阻塞时间:定义为 0(#define semGIVE_BLOCK_TIME (0)),表示调用 xSemaphoreGive 时不等待。如果队列已满(信号量已处于“可用”状态),则直接返回错误 errQUEUE_FULL。
-
queueSEND_TO_BACK
- 入队位置:将“消息”添加到队列的尾部。信号量的顺序无关紧要,因此使用尾部入队是合理的。
-
返回类型为BaseType_t,pdFALSE或者pdTRUE。
- 释放信号量(ISR):
xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
参数解析
- xSemaphore
- 信号量句柄:实际是一个队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
- pxHigherPriorityTaskWoken
- 优先级唤醒标志:
- 类型:BaseType_t *(指向布尔值的指针)。
- 功能:如果释放信号量导致更高优先级的任务被唤醒,该指针会被设置为 pdTRUE,表示需要在退出中断前请求上下文切换。
- 可选参数:从 FreeRTOS V7.3.0 开始,此参数可以设为 NULL。
- 注意:如果释放信号量导致了一个任务解锁,而解锁的任务比当前任务的优先级高,这里就会返回pdTRUE。这就需要在退出ISR之前申请任务调度,以便及时的解锁高优先级的任务。
- 获取信号量(任务):
xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
参数解析
- xSemaphore
- 信号量句柄:实际是一个队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
- 类型:SemaphoreHandle_t(底层为 QueueHandle_t)。
● xBlockTime - 阻塞时间:以 Tick 为单位指定任务等待信号量的最长时间。
- portMAX_DELAY:无限期等待,直到信号量可用。
- 0:非阻塞模式,立即返回。
- 转换:使用 pdMS_TO_TICKS(ms) 将毫秒转换为 Tick(例如 pdMS_TO_TICKS(100))。
2. 示例:ADC中断与任务同步
// 创建二值信号量
SemaphoreHandle_t xADCSemaphore = xSemaphoreCreateBinary();// ADC中断服务程序
void ADC_IRQHandler(void) {// 写入数据到缓冲区WriteDataToBuffer();// 释放信号量通知任务xSemaphoreGiveFromISR(xADCSemaphore, NULL);
}// 数据处理任务
void vDataProcessingTask(void *pvParameters) {while (1) {// 等待信号量xSemaphoreTake(xADCSemaphore, portMAX_DELAY);// 处理缓冲区数据ProcessData();}
}
三、计数信号量(Counting Semaphore)
资源类比成一个餐馆中的四个餐桌。
管理多个共享资源。例如ADC连续数据采集时,一般使用双缓冲区,就可以使用计数信号量来进行管理。
1. 功能与使用场景
- 功能:管理多个同类型资源(如ADC双缓冲区),允许同时访问多个资源。
- 创建函数:
// 动态分配 SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); // 静态分配 SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函数:
- 释放资源:
xSemaphoreGive(xSemaphore); // 资源计数+1
- 获取资源:
xSemaphoreTake(xSemaphore, xTicksToWait); // 资源计数-1
- 释放资源:
2. 示例:ADC双缓冲区管理
// 创建计数信号量(2个缓冲区)
SemaphoreHandle_t xADCBufferSemaphore = xSemaphoreCreateCounting(2, 2);// ADC中断服务程序
void ADC_IRQHandler(void) {// 获取一个缓冲区if (xSemaphoreTakeFromISR(xADCBufferSemaphore, NULL) == pdTRUE) {// 写入数据到缓冲区WriteDataToBuffer();// 释放信号量(资源可用)xSemaphoreGiveFromISR(xADCBufferSemaphore, NULL);}
}// 数据处理任务
void vDataProcessingTask(void *pvParameters) {while (1) {// 获取缓冲区资源xSemaphoreTake(xADCBufferSemaphore, portMAX_DELAY);// 处理缓冲区数据ProcessData();// 释放缓冲区资源xSemaphoreGive(xADCBufferSemaphore);}
}
四、互斥量(Mutex)
二值信号量更适用于进程间同步,而互斥量更适用于控制对互斥型资源的访问。二值信号量没有优先级继承机制,将二值信号量用于互斥型资源访问时,容易出现优先级翻转问题。而互斥量有优先级继承机制,可以减缓优先级翻转问题。
1. 功能与使用场景
- 功能:保护共享资源的独占访问(如串口),避免数据竞争。
- 创建函数:
// 动态分配 SemaphoreHandle_t xSemaphoreCreateMutex(void); // 静态分配 SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxSemaphoreBuffer);
- 操作函数:
- 释放互斥量:(可以释放二值信号量、计数信号量或者是互斥量。)
xSemaphoreGive(xSemaphore);
- 获取互斥量:
xSemaphoreTake(xSemaphore, xTicksToWait);
- 递归互斥量(允许任务多次获取同一互斥量):
// 创建 SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); // 释放 xSemaphoreGiveRecursive(xSemaphore); // 获取 xSemaphoreTakeRecursive(xSemaphore, xTicksToWait);
- 释放互斥量:(可以释放二值信号量、计数信号量或者是互斥量。)
2. 示例:保护共享资源
// 创建互斥量
SemaphoreHandle_t xSharedResourceMutex = xSemaphoreCreateMutex();// 任务A
void vTaskA(void *pvParameters) {while (1) {xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);// 访问共享资源SharedResource++;xSemaphoreGive(xSharedResourceMutex);}
}// 任务B
void vTaskB(void *pvParameters) {while (1) {xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);// 访问共享资源SharedResource--;xSemaphoreGive(xSharedResourceMutex);}
}
五、关键注意事项
-
优先级翻转问题:
- 二值信号量:无优先级继承机制,可能导致低优先级任务阻塞高优先级任务。
- 互斥量:通过优先级继承机制缓解问题,但仍需谨慎设计任务优先级。
-
ISR中的使用限制:
- 信号量:可在ISR中使用(
xSemaphoreGiveFromISR
)。 - 互斥量:禁止在ISR中使用(因其依赖优先级继承)。
- 信号量:可在ISR中使用(
-
内存分配选择:
- 动态分配:适合资源充足的系统,但可能产生内存碎片。
- 静态分配:适合资源受限的嵌入式系统,需手动管理内存。
-
调试工具:
uxSemaphoreGetCount()
:获取信号量当前值(适用于计数信号量)。xSemaphoreGetMutexHolder()
:检查互斥量当前持有者。
六、总结
其他相关函数
● xSemaphoreGiveRecursive(用于递归互斥锁的释放)。
●xSemaphoreTakeRecursive()获取二值信号量、计数信号量或者是互斥量。释放递归互斥量依然有一个专用的函数。
●uxSemaphoreGetCount()返回传进去的这个信号量的当前值。
●xSemaphoreGetMutexHolder()作用:返回当前持有指定互斥锁(mutex)的任务的句柄(TaskHandle)。如果互斥锁未被任何任务持有,则返回 NULL。
xSemaphoreTakeFromISR()
- 选择信号量还是互斥量:
- 同步需求(如事件通知)→ 信号量。
- 资源互斥访问(如保护共享变量)→ 互斥量。
- 避免常见错误:
- 不要在ISR中释放互斥量。
- 确保每次获取互斥量后最终释放。
- 使用递归互斥量时,获取与释放需严格配对。
通过合理使用信号量和互斥量,可以高效实现FreeRTOS中的任务同步与资源共享,提升系统的实时性和稳定性。