11.1 信号量说明
信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步, FreeRTOS中信号量又分为二值信号量、 计数型信号量、互斥信号量和递归互斥信号量。信号量在实际应用中最广泛的两个用途是:
- 临界资源的锁机制:用于控制共享资源访问的场景相当于一个上锁机制, 代码只有获得了这个锁的钥匙才能够执行。
- 多个任务同步机制:用于任务与任务或中断与任务之间的同步。
在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话会影响的中断的实时性。 裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能, 当中断发生的时候就释放信号量,中断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间非常短。 这个例子就是中断与任务之间使用信号量来完成同步,当然了, 任务与任务之间也可以使用信号量来完成同步。
在实际4G/WiFi等网络应用中,一般最简单的方法就是使用一个任务去查询 4G/WiFi 模块是否有数据到来,当有数据的时候就处理这个网络数据。但这样使用轮询的方式是很浪费CPU 资源的,而且也阻止了其他任务的运行。一种理想的解决方法应该是当没有网络数据的时候网络任务就进入阻塞态,把 CPU 让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入阻塞态,而网络中断服务函数通过释放信号量来通知任务以太网外设接收到了网络数据,网络任务可以去提取处理了。网络任务只是在一直的获取二值信号量,它不会释放信号量,而中断服务函数是一直在释放信号量,它不会获取信号量。
接下来,我们以二值信号量为例,讲解信号量实现任务同步的大致流程。其实,二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的。二值信号量通常用于互斥访问或任务同步,它与互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
- 任务获取信号量有效
如下图所示,任务Task通过调用 xSemaphoreTake() 函数获取信号量,但此时二值信号量无效,所以任务Task进入到阻塞态。 - 中断释放信号量
当有数据到来产生中断后,在中断服务处理程序中通过调用 xSemaphoreGiveFromISR() 函数释放信号量,此后二值信号量有效。 - 任务获取信号量有效
由于此时信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相关的处理过程。 - 任务再次进入阻塞
由于任务一般是一个大的死循环,所以在任务做完相关处理以后就会再次调用 xSemaphoreTake() 函数获取信号量,但此时信号量已经失效,因此任务将进入到状态1下阻塞等待信号量有效。
11.2 信号量API说明
11.2.1 获取信号量
不管是二值信号量、计数型信号量还是互斥信号量,它们都使用以下两个函数获取信号量。
/*
参数:
xSemaphore: 要获取的信号量句柄。
xBlockTime: 阻塞时间。
返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
*/
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
/*
说明: 此函数用于在中断服务函数中获取信号量, 此函数用于获取二值信号量和计数型信号量,绝对不能使用此函数来获取互斥信号量。
参数: xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,
用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
回值: pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。
*/
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken)
11.2.2 释放信号量
释放信号量分为任务级和中断级。不管是二值信号量、计数型信号量还是互斥信号量,它们都使用以下两个函数释放信号量。
/*
参数:xSemaphore: 要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功;
errQUEUE_FULL: 释放信号量失败。
*/
BaseType_t xSemaphoreGive( xSemaphore )
/*
说明: 此函数用于在中断中释放信号量, 此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量。
参数: xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,
用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
*/
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t * pxHigherPriorityTaskWoken)
11.2.3 创建二值信号量
在FreeRTOS中,有两个函数可以创建二值信号量:
/*
说明: 使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void )
/*
说明: 此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的RAM需要由用户来分配
参数: pxSemaphoreBuffer: 此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
11.2.4 创建互斥信号量
在FreeRTOS中,有两个函数可以创建互斥信号量:
/*
说明:此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。
参数:无
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void )
/*
说明: 此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的RAM 需要由用户来分配
参数: pxMutexBuffer: 此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
11.2.5 创建计数信号量
在FreeRTOS中,有两个函数可以创建计数信号量:
/*
说明: 此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,UBaseType_t uxInitialCount )
/*
说明: 此函数也是用来创建计数型信号量的,使用此函数创建计数型信号量的时候所需要的内存需要由用户分配。
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
pxSemaphoreBuffer: 指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型号量创建成功,返回计数型信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,UBaseType_t uxInitialCount,StaticSemaphore_t * pxSemaphoreBuffer )
11.2.6 递归互斥信号量
递归互斥信号量可以看是一种特殊的互斥信号量,已经获取了信号量的任务就不能再次获取这个互斥量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量,次数不限。也就是在同一个任务中可以无限次获取递归互斥信号量,中途不需要释放,而互斥信号量获取一次后就失效,需要再次释放才有效。注意:任务获取多少次递归互斥信号量,就要释放多少次递归信号量。
/*
说明: 动态创建递归互斥信号量
参数: 无
返回值:创建失败NULL,创建成功返回信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
/*
说明: 静态创建递归互斥信号量
参数: pxMutexBuffer:指向StaticSemaphore_t类型的变量,用来保存信号量结构体。
返回值: 创建失败NULL,创建成功返回信号量句柄。
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )
#define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) xQueueCreateMutexStatic( queueQUEUE_TYPE_RECURSIVE_MUTEX, pxStaticSemaphore )
/*
说明: 释放递归互斥信号量
参数: xMutex: 信号量句柄
返回值:
pdPASS: 释放信号量成功
pdFAIL: 释放信号量失败
*/
xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex )
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
/*
说明:获取递归互斥信号量
参数:
xMutex: 信号量句柄
xBlockTime:阻塞时间
返回值:
pdPASS: 获取信号量成功
pdFAIL: 获取信号量失败
*/
xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex, TickType_t xBlockTime );
#define xSemaphoreTakeRecursive( xMutex, xBlockTime )
xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
11.3 信号量多任务同步实例
在这里我们将创建两个Led线程,其中蓝色Led线程每隔200ms闪烁一次,而红色Led线程则每隔1200ms闪烁一次。然后我们使用二值信号量的机制,让两个Led线程同步交替闪烁一次。
11.3.1 创建两个线程任务
如下图所示,单击窗格顶部的 “New Thread” 按钮,添加两个线程分别命名为 thread_led1 和 thread_led2 ,其它的保持默认配置即可,并重新生成代码。
如下图所示,单击窗口的 “New Object” 按钮,选择 “Binary Semaphore” 添加一个二值信号量,然后修改该信号量的名称为 g_led_semaphore ,并重新生成代码。
11.3.2 修改信号量实例代码
修改 thread_led1_entry.c 源码如下:
#include "thread_led1.h"
/* Led Thread entry function */
/* pvParameters contains TaskHandle_t */
void thread_led1_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED (pvParameters);
R_BSP_PinAccessEnable(); /* Enable access to the PFS registers. */
/* TODO: add your own code here */
while (1)
{
xSemaphoreTake( g_led_semaphore, portMAX_DELAY );
R_BSP_PinWrite(LedRed, BSP_IO_LEVEL_HIGH);
vTaskDelay (600);
R_BSP_PinWrite(LedRed, BSP_IO_LEVEL_LOW);
vTaskDelay (600);
}
}
- 如果不加信号量操作部分代码,红色Led将每隔1200ms闪烁一次;
- 这里调用了xSemaphoreTake() 函数来获取信号量 g_led_semaphore,如果 thread_led2 线程没有运行并释放信号量的话,它将会阻塞;
- xSemaphoreTake() 函数在获取到信号之后,将会让红色Led闪烁一次;
- 之后该线程将会再次调用了xSemaphoreTake() 函数,等待 thread_led2 线程释放信号;
修改 thread_led2_entry.c 源码如下:
#include "thread_led2.h"
/* Led Thread entry function */
/* pvParameters contains TaskHandle_t */
void thread_led2_entry(void *pvParameters)
{
FSP_PARAMETER_NOT_USED (pvParameters);
R_BSP_PinAccessEnable(); /* Enable access to the PFS registers. */
/* TODO: add your own code here */
while (1)
{
if( pdTRUE == xSemaphoreGive( g_led_semaphore) )
{
R_BSP_PinWrite(LedBlue, BSP_IO_LEVEL_HIGH);
vTaskDelay (100);
R_BSP_PinWrite(LedBlue, BSP_IO_LEVEL_LOW);
vTaskDelay (100);
}
}
}
- 如果不加信号量操作部分代码,蓝色Led将每隔200ms闪烁一次;
- 这里调用了xSemaphoreGive() 函数释放信号量 g_led_semaphore,用来同步通知 thread_led1 线程运行;
- xSemaphoreGive() 函数在释放信号之后,如果 thread_led1 没有来得及获取信号量的话,则会返回pdFALSE;
- 这样,如果 thread_led1 没有运行的话,蓝色Led也不会闪烁了,从而实现了两个线程中的Led同步交替闪烁一次效果了;
-
操作系统
+关注
关注
37文章
6818浏览量
123320 -
FreeRTOS
+关注
关注
12文章
484浏览量
62159 -
信号量
+关注
关注
0文章
53浏览量
8336
发布评论请先 登录
相关推荐
评论