1 、信 号 量
1.1 信号量的概念及其作用
信号量(semaphores)是 20 世纪 60 年代中期 Edgser Dijkstra 发明的。使用信号量的最初目的是为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
实际的应用中,信号量的作用又该如何体现呢?比如有个 30 人的电脑机房,我们就可以创建信号量的初始化值是 30,表示 30 个可用资源,不理解的初学者表示信号量还有初始值?是的,信号量说白了就是共享资源的数量。另外我们要求一个同学使用一台电脑,这样每有一个同学使用一台电脑,那么信号量的数值就减一,直到 30 台电脑都被占用,此时信号量的数值就是 0。如果此时还有几个同学没有电脑可以使用,那么这几个同学就得等待,直到有同学离开。有一个同学离开,那么信号量的数值就加 1,有两个就加 2,依此类推。刚才没有电脑用的同学此时就有电脑可以用了,有几个同学用,信号量就减几,直到再次没有电脑可以用,这么一个过程就是使用信号量来管理共享资源的过程。
平时使用信号量主要实现以下两个功能:
两个任务之间或者中断函数跟任务之间的同步功能,这个和前面章节讲解的事件标志组是类似的。其实就是共享资源为 1 的时候。
多个共享资源的管理,就像上面举的机房上机的例子。
针对这两种功能,FreeRTOS 分别提供了二值信号量和计数信号量,其中二值信号量可以理解成计数信号量的一种特殊形式,即初始化为仅有一个资源可以使用,只不过 FreeRTOS 对这两种都提供了 API函数,而像 RTX,uCOS-II 和 III 是仅提供了一个信号量功能,设置不同的初始值就可以分别实现二值信号量和计数信号量。当然,FreeRTOS 使用计数信号量也能够实现同样的效果。
另外,为什么叫二值信号量呢?因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0和 1 两种情况的信号量称之为二值信号量。
实际上信号量还有很多其它用法,而且极具挑战性,可以大大的开拓大家的视野,有兴趣的可以阅读一下《The Little Book Of Semaphores》,作者是 Allen B. Downy。
1.2 FreeRTOS 任务间计数信号量的实现
任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能。下面我们通过如下的框图来说明一下 FreeRTOS 计数信号量的实现,让大家有一个形象的认识。
运行条件:
创建 2 个任务 Task1 和 Task2。
创建计数信号量可用资源为 1。
运行过程描述如下:
任务 Task1 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task2占用,Task1 将直接获取资源。如果信号量被 Task2 占用,任务 Task1 将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。
任务 Task2 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task1占用,Task2 将直接获取资源。如果信号量被 Task1 占用,任务 Task2 将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。
上面就是一个简单的 FreeRTOS 任务间计数信号量的使用过程。
1.3 FreeRTOS 中断方式计数信号量的实现
FreeRTOS 中断方式信号量的实现是指中断函数和 FreeRTOS 任务之间使用信号量。信号量的中断方式主要是用于实现任务同步,与前面章节讲解事件标志组中断方式是一样的。
下面我们通过如下的框图来说明一下 FreeRTOS 中断方式信号量的实现,让大家有一个形象的认识。
运行条件:
创建一个任务 Task1 和一个串口接收中断。
信号量的初始值为 0,串口中断调用函数 xSemaphoreGiveFromISR 释放信号量,任务 Task1 调用函数 xSemaphoreTake 获取信号量资源。
运行过程描述如下:
任务 Task1 运行过程中调用函数 xSemaphoreTake,由于信号量的初始值是 0,没有信号量资源可用,任务 Task1 由运行态进入到阻塞态。
Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数xSemaphoreGiveFromISR 释放信号量资源,信号量数值加 1,此时信号量计数值为 1,任务 Task1由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务 Task1 获得信号量后,信号量数值减 1,此时信号量计数值又变成了 0。
再次循环执行时,任务 Task1 调用函数 xSemaphoreTake 由于没有资源可用再次进入到阻塞态,等待串口释放信号量资源,如此往复循环。
上面就是一个简单的 FreeRTOS 中断方式信号量同步过程。实际应用中,中断方式的消息机制要注意以下四个问题:
中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
中断服务程序中一定要调用专用于中断的信号量设置函数,即以 FromISR 结尾的函数。
在操作系统中实现中断服务程序与裸机编程的区别。
如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的信号量 API 函数,与裸机编程是一样的。
如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的信号量 API 函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程稍有区别。
另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407,F429的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
1.4 FreeRTOS 任务间二值信号量的实现
任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能。下面我们通过如下的框图来说明一下 FreeRTOS 二值信号量的实现,让大家有一个形象的认识。
上面就是一个简单的 FreeRTOS 任务间二值信号量的使用过程。
2 、计 数 信 号API 函 数
使用如下 18 个函数可以实现 FreeRTOS 的信号量(含计数信号量,二值信号量和互斥信号):
xSemaphoreCreateBinary()
xSemaphoreCreateBinaryStatic()
vSemaphoreCreateBinary()
xSemaphoreCreateCounting()
xSemaphoreCreateCountingStatic()
xSemaphoreCreateMutex()
xSemaphoreCreateMutexStatic()
xSem'CreateRecursiveMutex()
xSem'CreateRecursiveMutexStatic()
vSemaphoreDelete()
xSemaphoreGetMutexHolder()
uxSemaphoreGetCount()
xSemaphoreTake()
xSemaphoreTakeFromISR()
xSemaphoreTakeRecursive()
xSemaphoreGive()
xSemaphoreGiveRecursive()
xSemaphoreGiveFromISR()
关于这 18 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:
2.1 函数 xSemaphoreCreateCounting
使用举例:
static SemaphoreHandle_t xSemaphore = NULL;
/*
*********************************************************************************************************
* 函 数 名: AppObjCreate
* 功能说明: 创建任务通信机制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
/* 初始化有 1 个可用资源,当前可用资源为 0,此时计数信号量的功能等同二值信号量 */
xSemaphore = xSemaphoreCreateCounting(1, 0);
if(xSemaphore == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
}
}
2.2 函数 xSemaphoreGive
2.3 函数 xSemaphoreGiveFromISR
使用举例:
static SemaphoreHandle_t xSemaphore = NULL;
/*
*********************************************************************************************************
* 函 数 名: TIM2_IRQHandler
* 功能说明: 定时器中断。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void TIM2_IRQHandler (void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 中断消息处理,此处省略 */
……
/* 发送同步信号 */
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
/* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
2.4 函数 xSemaphoreTake
未完待续~~~(实验任务间通信,中断方式)