您的位置:首页 > 其它

FreeRTOS 二值信号量,互斥信号量,递归互斥信号量

2017-07-21 08:43 169 查看
本章节讲解 FreeRTOS 任务间的同步和资源共享机制,二值信号量。 二值信号量是计数信号量的一种特殊形式,即共享资源为 1 的情况。

FreeRTOS 分别提供了二值信号量和计数信号量,其中二值信号量可以理解成计数
信号量的一种特殊形式,即初始化为仅有一个资源可以使用,只不过 FreeRTOS 对这两种都提供了 API
函数,而像 RTX,uCOS-II 和 III 是仅提供了一个信号量功能,设置不同的初始值就可以分别实现二值信
号量和计数信号量。 当然,FreeRTOS 使用计数信号量也能够实现同样的效果。 另外,为什么叫二值信号
量呢?因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0
和 1 两种情况的信号量称之为二值信号量。

函数 xSemaphoreCreateBinary
函数原型:
SemaphoreHandle_t xSemaphoreCreateBinary(void)
函数描述:
函数 xSemaphoreCreateBinary 用于创建二值信号量。
 返回值,如果创建成功会返回二值信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不
足,无法为此二值信号量提供所需的空间会返回 NULL。



FreeRTOS 重要的资源共享机制---互斥信号量(Mutex,即 Mutual Exclusion 的缩写)。
注意,建议初学者学习完前两个信号量后再学习本章节的互斥信号量。

互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互
斥信号量与二值信号量有区别。 下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让
大家有一个形象的认识,进而引出要讲解的互斥信号量。
运行条件:
 让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数
printf 的互斥访问。 如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。
 用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。
代码实现:











有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优
先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。



运行条件:
 创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。 也就是 Task1 的优先级最高。
 任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
 起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。
运行过程描述如下:
 任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等
待 Task3 释放函数 printf。
 在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3
的运行。 优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行
完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级
任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。 所以这种情况被称之为
优先级翻转问题。
 任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,
从而可以继续执行。

FreeRTOS 互斥信号量的实现
FreeRTOS 互斥信号量是怎么实现的呢?其实相对于二值信号量,互斥信号量就是解决了一下优先级
翻转的问题。 下面我们通过如下的框图来说明一下 FreeRTOS 互斥信号量的实现,让大家有一个形象的认识。


运行条件:
 创建 2 个任务 Task1 和 Task2,优先级分别为 1 和 3,也就是任务 Task2 的优先级最高。
 任务 Task1 和 Task2 互斥访问串口打印 printf。
 使用 FreeRTOS 的互斥信号量实现串口打印 printf 的互斥访问。
运行过程描述如下:

 低优先级任务 Task1 执行过程中先获得互斥资源 printf 的执行。 此时任务 Task2 抢占了任务 Task1
的执行,任务 Task1 被挂起。 任务 Task2 得到执行。
 任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优
先级会被提升到与 Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority
inheritance),这样就有效地防止了优先级翻转问题。 任务 Task2 被挂起,任务 Task1 有新的优先
级继续执行。
 任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。 由于互斥资源可以使用,任务
Task2 获得互斥资源后开始执行。

FreeRTOS 中断方式互斥信号量的实现
互斥信号量仅支持用在 FreeRTOS 的任务中,中断函数中不可使用。

互斥信号量 API 函数
函数 xSemaphoreCreateMutex
函数原型:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
函数描述:
函数 xSemaphoreCreateMutex 用于创建互斥信号量。
 返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不
足,无法为此互斥信号量提供所需的空间会返回 NULL。

使用这个函数要注意以下问题:
1. 此函数是基于函数 xQueueCreateMutex 实现的:
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
函数 xQueueCreateMutex 的实现是基于消息队列函数 xQueueGenericCreate 实现的。
2. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_MUTEXES 1

互斥信号量, xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用

应用举例:





经过测试,互斥信号量是可以被其他任务释放的,但是我们最好不要这么做,因为官方推荐的就是在同一个任务中接收和释放。如果在其他任务释放,不仅仅会让代码整体逻辑变得复杂,还会给使用和维护这套API的人带来困难。遵守规范,总是好的。

裸机编程的时候,我经常想一个问题,就是怎么做到当一个标志位触发的时候,立即执行某个操作,如同实现标志中断一样,在os编程之后,我们就可以让一个优先级最高任务一直等待某个信号量,如果获得信号量,就执行某个操作,实现类似标志位中断的作用(当然,要想正真做到中断效果,那就需要屏蔽所有可屏蔽中断,而临界区就可以做到)。

再说一下递归互斥信号量:递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量

eg:

static void vTaskMsgPro(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 1500;

/* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();

while(1)
{
/* 递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量 */
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
{
/* -------------------------------------- */
//假如这里是被保护的资源,第1层被保护的资源,用户可以在这里添加被保护资源
/* ---------------------------------------------------------------------------- */
printf("任务vTaskMsgPro在运行,第1层被保护的资源,用户可以在这里添加被保护资源\r\n");

/* 第1层被保护的资源里面嵌套被保护的资源 */
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
{
/* ------------------------------------------------------------------------ */
//假如这里是被保护的资源,第2层被保护的资源,用户可以在这里添加被保护资源
/* ------------------------------------------------------------------------ */
printf("任务vTaskMsgPro在运行,第2层被保护的资源,用户可以在这里添加被保护资源\r\n");

/* 第2层被保护的资源里面嵌套被保护的资源 */
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
{
printf("任务vTaskMsgPro在运行,第3层被保护的资源,用户可以在这里添加被保护资源\r\n");
bsp_LedToggle(1);
bsp_LedToggle(4);
}
xSemaphoreGiveRecursive(xRecursiveMutex);
}
xSemaphoreGiveRecursive(xRecursiveMutex);
}
xSemaphoreGiveRecursive(xRecursiveMutex);

/* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}


可以前面的那个官方文档那样用if判断传递共享量:



也可以用我们递归互斥信号量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE时,会一直等待信号量,直到有信号量来才执行后面的语句。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: