在FreeRTOS中,信号量是一种非常重要的同步机制,用于实现任务间的互斥访问和同步操作。通过信号量,不同的任务可以安全地共享资源,避免竞争和冲突,从而确保系统的稳定性和可靠性。本篇博客将介绍FreeRTOS中信号量的基本概念、使用方法和实际应用,帮助读者深入理解信号量的原理和在实际项目中的应用场景。我们将从信号量的创建、获取和释放等方面进行详细讲解,并结合实际的示例代码,帮助读者更好地掌握FreeRTOS中信号量的使用技巧和注意事项。通过本篇博客的学习,读者将能够更加灵活地运用信号量来实现任务间的同步和资源共享,提高系统的可靠性和效率。
文章目录
- 1.信号量简介
- 1.1 二值信号量
- 1.2 计数信号量
- 1.3 互斥信号量
- 1.4 递归信号量
- 1.5 信号量控制块
- 2.常用信号量API函数
- 2.1 创建信号量函数
- 2.1.1 创建二值信号量 xSemaphoreCreateBinary()
- 2.1.2 创建计数信号量 xSemaphoreCreateCounting()
- 2.2 信号量删除函数 vSemaphoreDelete()
- 2.3 信号量释放函数
- 2.3.1 xSemaphoreGive()
- 2.3.2 xSemaphoreGiveFromISR()
- 2.4 信号量获取函数
- 2.4.1 xSemaphoreTake()
- 2.4.2 xSemaphoreTakeFromISR()
- 3.例子说明
1.信号量简介
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
1.1 二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
只能取两个值:0和1。这种信号量通常用于实现互斥访问,即只有一个进程或线程可以访问共享资源。当一个进程或线程占用资源时,二值信号量的值为1,其他进程或线程需要等待,直到信号量的值变为0才能访问资源。
二值信号量通常用于解决临界区问题,即多个进程或线程需要访问共享资源时可能会导致数据不一致或竞争条件的问题。通过使用二值信号量,可以有效地控制对共享资源的访问,避免出现这些问题。
在实际应用中,二值信号量通常与互斥锁(mutex
)结合使用,以实现对共享资源的互斥访问。当一个进程或线程需要访问共享资源时,首先尝试对二值信号量进行加操作,如果成功则可以访问资源,否则需要等待。在访问完成后,再对二值信号量进行减操作,释放资源。
1.2 计数信号量
二进制信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。
一种可以取多个值的信号量,它用于控制对一组资源的访问数量。计数信号量的值可以大于等于0,表示可用的资源数量。当一个进程或线程需要访问资源时,它会尝试对计数信号量进行减操作,如果计数信号量的值大于0,则可以访问资源,同时计数信号量的值会减少;如果计数信号量的值为0,则需要等待,直到有其他进程或线程释放资源,使计数信号量的值大于0。
计数信号量通常用于实现对一组资源的并发访问控制,例如限制同时访问某个资源的进程或线程的数量。这种机制可以有效地控制资源的并发访问,避免资源被过度占用,提高系统的性能和稳定性。
在实际应用中,计数信号量可以用于实现线程池、连接池等并发控制的场景,以及限制对其他有限资源的并发访问。通过合理地管理计数信号量的值,可以有效地控制对资源的并发访问,避免出现竞争条件和数据不一致的问题。
1.3 互斥信号量
互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源。
只能取两个值:0和1。它用于实现对共享资源的互斥访问,即只有一个进程或线程可以访问共享资源。当一个进程或线程占用资源时,互斥信号量的值为1,其他进程或线程需要等待,直到信号量的值变为0才能访问资源。
互斥信号量通常与互斥锁(mutex)等同步机制结合使用,以实现对共享资源的互斥访问。在访问共享资源之前,进程或线程会尝试对互斥信号量进行减操作,如果成功则可以访问资源,否则需要等待。在访问完成后,再对互斥信号量进行加操作,释放资源。
互斥信号量是实现进程同步和互斥的重要工具,它能够有效地避免竞争条件和数据不一致的问题。在并发编程中,互斥信号量通常用于保护临界区,即一段代码在同一时间只能被一个线程执行,以确保数据的一致性和正确性。
1.4 递归信号量
允许同一线程多次对信号量进行加操作,而不会导致死锁。通常情况下,普通的信号量在同一线程内多次对信号量进行加操作可能会导致死锁,因为信号量的值会被多次减少,但只有一次加操作来释放资源。
递归信号量则允许同一线程多次对信号量进行加操作,每次加操作都会增加信号量的值,从而避免了死锁的情况。这种机制通常用于需要在递归函数中对共享资源进行加锁的情况,以及其他需要在同一线程内多次对资源进行加锁的情况。
1.5 信号量控制块
2.常用信号量API函数
2.1 创建信号量函数
2.1.1 创建二值信号量 xSemaphoreCreateBinary()
- 原型:
SemaphoreHandle_t xSemaphoreCreateBinary( void );
- 作用:用于创建一个二值信号量,用于实现对共享资源的互斥访问。
- 参数:无参数。
- 返回值:返回一个
SemaphoreHandle_t
类型的句柄,表示创建的二值信号量。
xSemaphoreCreateBinary()
函数的作用是创建一个二值信号量,二值信号量的初始值为0,用于实现对共享资源的互斥访问。它返回一个 SemaphoreHandle_t
类型的句柄,可以用于后续对该二值信号量进行操作。
2.1.2 创建计数信号量 xSemaphoreCreateCounting()
- 原型:
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );
- 作用:用于创建一个计数信号量,用于控制对一组资源的并发访问数量。
- 参数:
uxMaxCount
:计数信号量的最大计数值。uxInitialCount
:计数信号量的初始计数值。
- 返回值:返回一个
SemaphoreHandle_t
类型的句柄,表示创建的计数信号量。
xSemaphoreCreateCounting()
函数用于创建一个计数信号量,计数信号量的初始值为 uxInitialCount
,最大计数值为 uxMaxCount
。计数信号量通常用于限制对一组资源的并发访问数量,通过合理地管理计数信号量的值,可以有效地控制对资源的并发访问,避免出现竞争条件和数据不一致的问题。
2.2 信号量删除函数 vSemaphoreDelete()
- 原型:
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
- 作用:用于删除一个已经创建的信号量。
- 参数:
xSemaphore
:要删除的信号量的句柄。
- 返回值:无。
vSemaphoreDelete()
函数用于删除一个已经创建的信号量,通过传入要删除的信号量的句柄 xSemaphore
来实现。一旦删除了信号量,其句柄将不再有效,并且不能再对该信号量进行操作。
2.3 信号量释放函数
2.3.1 xSemaphoreGive()
- 原型:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
- 作用:用于释放一个二值信号量或增加一个计数信号量的计数值。
- 参数:
xSemaphore
:要释放的信号量的句柄。
- 返回值:如果释放成功,则返回 pdTRUE,否则返回 pdFALSE。
xSemaphoreGive()
函数用于释放一个二值信号量或增加一个计数信号量的计数值。对于二值信号量,调用该函数会将信号量的计数值从 0 增加到 1;对于计数信号量,调用该函数会增加信号量的计数值。如果有任务因等待该信号量而被唤醒,则调用 xSemaphoreGive()
会使其中一个等待的任务得以继续执行。
2.3.2 xSemaphoreGiveFromISR()
- 原型:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
- 作用:用于从中断服务程序中释放一个二值信号量或增加一个计数信号量的计数值。
- 参数:
xSemaphore
:要释放的信号量的句柄。pxHigherPriorityTaskWoken
:一个指向 BaseType_t 类型的指针,用于指示是否有一个高优先级任务被唤醒。
- 返回值:如果释放成功,则返回 pdTRUE,否则返回 pdFALSE。
xSemaphoreGiveFromISR()
函数与 xSemaphoreGive()
函数类似,用于从中断服务程序中释放信号量。它可以用于在中断服务程序中释放信号量,以唤醒等待该信号量的任务。与 xSemaphoreGive()
不同的是,xSemaphoreGiveFromISR()
可以唤醒一个等待该信号量的高优先级任务,并通过 pxHigherPriorityTaskWoken
参数指示是否有高优先级任务被唤醒。
2.4 信号量获取函数
2.4.1 xSemaphoreTake()
- 原型:
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
- 作用:用于获取一个二值信号量或减少一个计数信号量的计数值。
- 参数:
xSemaphore
:要获取的信号量的句柄。xTicksToWait
:等待获取信号量的最长时间,以时钟节拍数(tick)表示。
- 返回值:如果成功获取信号量,则返回 pdTRUE,否则返回 pdFALSE。
xSemaphoreTake()
函数用于获取一个二值信号量或减少一个计数信号量的计数值。如果信号量的计数值大于 0,则获取成功,计数值减一;如果信号量的计数值为 0,则任务将进入阻塞状态,直到信号量可用或超时。
xTicksToWait
参数用于指定任务在获取信号量时的最长等待时间。如果设置为 portMAX_DELAY,任务将一直等待直到信号量可用;如果设置为 0,则任务将立即返回获取结果而不进行等待;如果设置为其他数值,则任务将等待指定的时钟节拍数后返回获取结果。
2.4.2 xSemaphoreTakeFromISR()
- 原型:
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
- 作用:用于从中断服务程序中获取一个二值信号量或减少一个计数信号量的计数值。
- 参数:
xSemaphore
:要获取的信号量的句柄。pxHigherPriorityTaskWoken
:一个指向 BaseType_t 类型的指针,用于指示是否有一个高优先级任务被唤醒。
- 返回值:如果成功获取信号量,则返回 pdTRUE,否则返回 pdFALSE。
xSemaphoreTakeFromISR()
函数与 xSemaphoreTake()
函数类似,用于从中断服务程序中获取信号量。它可以用于在中断服务程序中获取信号量,以等待信号量可用或减少信号量的计数值。与 xSemaphoreTake()
不同的是,xSemaphoreTakeFromISR()
可以唤醒一个等待该信号量的高优先级任务,并通过 pxHigherPriorityTaskWoken
参数指示是否有高优先级任务被唤醒。
3.例子说明
当在STM32上使用FreeRTOS时,可以使用信号量来实现任务间的同步和互斥访问。以下是一个简单的示例,演示了如何在STM32上使用FreeRTOS中的信号量。
假设我们有两个任务:Task1 和 Task2,它们需要共享一个资源,我们可以使用信号量来进行同步。Task1 将获取资源并执行一些操作,然后释放资源;Task2 将等待资源可用,然后获取资源并执行操作。
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
// 定义一个全局的信号量句柄
SemaphoreHandle_t xSemaphore;
void Task1(void *pvParameters) {
while (1) {
// 尝试获取信号量,等待最长100个时钟节拍
if (xSemaphoreTake(xSemaphore, 100) == pdTRUE) {
// 信号量获取成功,执行任务1的操作
// ...
// 释放信号量
xSemaphoreGive(xSemaphore);
} else {
// 等待超时或者获取失败的处理
// ...
}
}
}
void Task2(void *pvParameters) {
while (1) {
// 尝试获取信号量,等待最长100个时钟节拍
if (xSemaphoreTake(xSemaphore, 100) == pdTRUE) {
// 信号量获取成功,执行任务2的操作
// ...
// 释放信号量
xSemaphoreGive(xSemaphore);
} else {
// 等待超时或者获取失败的处理
// ...
}
}
}
int main(void) {
// 创建一个二值信号量
xSemaphore = xSemaphoreCreateBinary();
// 创建任务 Task1
xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// 创建任务 Task2
xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
while (1) {
// 如果调度器启动失败,进行错误处理
// ...
}
}
在上面的示例中,我们首先创建了一个二值信号量 xSemaphore
,然后在 Task1
和 Task2
中使用 xSemaphoreTake()
和 xSemaphoreGive()
来获取和释放信号量。
在每个任务中,我们使用 xSemaphoreTake()
函数来尝试获取信号量。如果获取成功,任务就可以执行相应的操作;如果获取失败,任务将等待一段时间(这里是100个时钟节拍)然后再次尝试获取。获取成功后,任务执行完操作后使用 xSemaphoreGive()
来释放信号量。
这样,通过信号量的获取和释放,我们可以实现 Task1 和 Task2 之间的资源共享和同步。这样的设计可以确保两个任务之间的操作不会相互干扰,从而实现了任务间的同步和互斥访问。