本章介绍FreeRTOS任务之间的同步和资源共享机制,以及二进制信号量。 二进制信号量是计数信号量的一种特殊形式,即共享资源为1。
FreeRTOS分别提供二值信号量和计数信号量,其中二值信号量可理解为计数
信号是一种特殊形式,即初始化为只有一个资源可用,但自由RTOS为两者提供了API
另外一方面,RTX、uCOS-II、III等函数仅提供一个信号功能,如果设定不同初始值,则能够分别实现二值信道
信号量和计数信号量。 当然,FreeRTOS使用计数信号量也能够实现同样的效果。 另外,为什么叫二值信号
量是多少? 由于获取了信号量资源,所以信号量的值为0,当释放信号量资源时,信号量的值为1,并且只有0
1和2种情况下的信号量称为二值信号量。
函数xSemaphoreCreateBinary
函数原型:
semaphore handle _ txsemaphorecreatebinary (void ) )。
函数说明:
函数xSemaphoreCreateBinary用于创建二值信号量。
如果FreeRTOSConfig.h文件的heap大小不同,则返回值返回二进制信号量句柄(如果成功创建)
如果无法为此二进制信号量提供足够的空间,则返回NULL。
互斥信号量(Mutex或Mutual Exclusion的缩写),是FreeRTOS的重要资源共享机制。
建议初学者先学习前两个信号量,然后再学习本章的排他信号量。
独占信号的主要作用是对资源的独占访问,也有使用二值信号实现独占访问的功能,但相互之间
排斥信号量和二值信号量存在差异。 首先,让我们举一个二进制信号量资源独占(独占访问)的例子
大家都有印象,引出进一步说明的互斥信号量。
执行条件:
在两个任务Task1和Task2上运行串行打印函数printf。 在这里,我们用二进制信号量实现对函数
printf的互斥访问。 如果不独占访问函数printf,串行打印容易发生乱码。
为了通过计数信号量实现二值信号量,将计数信号量的初始值设置为1即可。
代码实现:
有了上面的二值信号量识别后,互斥信号量和二值信号量有什么区别呢? 互斥信号量可以防止优越
电平反转在前面,不支持2值信号量,下面就优先顺序反转问题进行说明。
执行条件:
创建三个任务Task1、Task2和Task3。 优先级分别为3、2、1。 也就是说,任务1的优先顺序最高。
任务Task1和Task3排他地访问串行端口打印printf,通过二值信号实现排他访问。
最初,Task3以二值信号量调用printf,但被任务Task1抢占,开始执行任务Task1,即上图的开始位置。
执行步骤如下。
任务Task1执行的进程需要调用函数printf。 您可以看到任务Task3正在被调用,任务Task1锁定等
等待Task3发行函数printf。
调度程序执行任务Task3并在任务3运行时,由于任务Task2已就绪,任务3已断开
点击优先级反转的问题就在这里。 根据任务的执行现象,任务Task1需要等待任务2的执行
这与抢占时间表正好相反,通常,高优先级任务抢占低优先级任务
在此,任务的执行成为高优先级任务Task1,等待低优先级任务Task2的完成。 所以把这个叫做“
优先级反转问题。
任务Task2的执行完成后,重新开始任务Task3的执行,当Task3释放排他资源时,任务Task1获得排他资源,
可以继续执行。 FreeRTOS排他信号量的实现FreeRTOS排他信号量是如何实现的呢? 实际上,相对于二进制信号量,排他信号量解决了以下优先级
反转问题。 让我们用以下框图说明FreeRTOS互斥信号量的实现,并让他们有一个图像。
执行条件:
分别以1和3的优先顺序创建两个任务Task1和任务Task2。 也就是说,任务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层被保护的资源,用户可以在这里添加被保护资源rn");/*第1层被保护的资源里面嵌套被保护的资源*/xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
{/*------------------------------------------------------------------------*/
//假如这里是被保护的资源,第2层被保护的资源,用户可以在这里添加被保护资源
/*------------------------------------------------------------------------*/printf("任务vTaskMsgPro在运行,第2层被保护的资源,用户可以在这里添加被保护资源rn");/*第2层被保护的资源里面嵌套被保护的资源*/xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
{
printf("任务vTaskMsgPro在运行,第3层被保护的资源,用户可以在这里添加被保护资源rn");
bsp_LedToggle(1);
bsp_LedToggle(4);
}
xSemaphoreGiveRecursive(xRecursiveMutex);
}
xSemaphoreGiveRecursive(xRecursiveMutex);
}
xSemaphoreGiveRecursive(xRecursiveMutex);/*vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
可以前面的那个官方文档那样用if判断传递共享量:
也可以用我们递归互斥信号量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE时,会一直等待信号量,直到有信号量来才执行后面的语句。