嵌入式系统FreeRTOS — 互斥信号量
互斥信号量可以在资源保护的时候很有帮助。用于控制在两个或多个任务间访问共享资源。任务1里面用互斥,那么任务2只能等任务1访问完再访问同一个变量。
比如全局变量double gADC_value[CH_ADC_NUM] = {0}; //7 adc channals
任务1中:周期100ms的采样任务,并保存数据都全局数组中。
osMutexWait(myMutex01Handle, osWaitForever);
gADC_value[adc_index] = Get_ADS1259_ADC_Value();
osMutexRelease(myMutex01Handle);
任务2中: 周期1000ms的采样任务,并读取保存在全局数组中的adc数据。
osMutexWait(myMutex01Handle, osWaitForever);
TC355_ADC_Value = gADC_value[CH_TMCOM2];
TC314_ADC_Value = gADC_value[CH_TMCOM1];
osMutexRelease(myMutex01Handle);
以下参考:
1 、互 斥 信 号 量
1.1 互斥信号量的概念及其作用
互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。
运行条件:
让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数printf 的互斥访问。如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。
用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。
代码实现:
创建二值信号量
通过二值信号量实现对 printf 函数互斥访问的两个任务
static SemaphoreHandle_t xSemaphore = NULL;
// * 函 数 名: AppObjCreate
// * 功能说明: 创建任务通信机制
// * 形 参: 无
// * 返 回 值: 无
static void AppObjCreate (void)
{
/* 创建二值信号量,首次创建信号量计数值是 0 */
xSemaphore = xSemaphoreCreateBinary();
if (xSemaphore == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
}/* 先释放一次,将初始值改为 1,利用二值信号量实现互斥功能 */
//xSemaphoreGive(xSemaphore);这句可以不用
}
//通过二值信号量实现对 printf 函数互斥访问的两个任务
static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 300;/* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();
while (1)
{
/* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
printf("任务 vTaskLED 在运行\r\n");
bsp_LedToggle(1);
bsp_LedToggle(4);
xSemaphoreGive(xSemaphore);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// * 函 数 名: vTaskMsgPro
// * 功能说明: 实现对串口的互斥访问
// * 形 参: pvParameters 是在创建该任务时传递的形参
// * 返 回 值: 无
// * 优 先 级: 3
static void vTaskMsgPro(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 300; /* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();
while (1)
{
/* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */
xSemaphoreTake(xSemaphore, portMAX_DELAY);
printf("任务 vTaskMsgPro 在运行\r\n");
bsp_LedToggle(2);
bsp_LedToggle(3);
xSemaphoreGive(xSemaphore);
/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。
1.2 优先级翻转问题
下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。
创建 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 得到互斥资源,从而可以继续执行。
上面就是一个产生优先级翻转问题的现象。
1.3 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 互斥信号量的实现过程。
1.4 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 函数 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
使用举例:
// * 函 数 名: AppObjCreate
// * 功能说明: 创建任务通信机制
// * 形 参: 无
// * 返 回 值: 无
static void AppObjCreate (void)
{
/* 创建互斥信号量 */xMutex = xSemaphoreCreateMutex();
if (xSemaphore == NULL)
{/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */}
}
2.2 函数 xSemaphoreGive
函数原型:
xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */
函数描述:
函数 xSemaphoreGive 用于在任务代码中释放信号量。
第 1 个参数是信号量句柄。
返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
使用这个函数要注意以下问题:
1. 此函数是基于消息队列函数 xQueueGenericSend 实现的:
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR。
3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。
4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。
2.3 函数 xSemaphoreTake
函数原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */
函数描述:
函数 xSemaphoreTake 用于在任务代码中获取信号量。
第 1 个参数是信号量句柄。
第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xSemaphoreTakeFromISR。
2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。
使用举例:
static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime; const TickType_t xFrequency = 200;/* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();
while (1)
{
/* 互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用 */
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("任务 vTaskLED 在运行\r\n");
bsp_LedToggle(2);
bsp_LedToggle(3);
xSemaphoreGive(xMutex);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用
经过测试,互斥信号量是可以被其他任务释放的,但是我们最好不要这么做,因为官方推荐的就是在同一个任务中接收和释放。如果在其他任务释放,不仅仅会让代码整体逻辑变得复杂,还会给使用和维护这套API的人带来困难。遵守规范,总是好的。
裸机编程的时候,我经常想一个问题,就是怎么做到当一个标志位触发的时候,立即执行某个操作,如同实现标志中断一样,在os编程之后,我们就可以让一个优先级最高任务一直等待某个信号量,如果获得信号量,就执行某个操作,实现类似标志位中断的作用(当然,要想正真做到中断效果,那就需要屏蔽所有可屏蔽中断,而临界区就可以做到)。
再说一下递归互斥信号量:递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量
使用举例:
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时,会一直等待信号量,直到有信号量来才执行后面的语句。
嵌入式系统FreeRTOS — 互斥信号量的更多相关文章
- FreeRTOS互斥信号量
API函数 #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) #define xSemaphoreCreateMutex() xQueueCreateMutex ...
- FreeRTOS 二值信号量,互斥信号量,递归互斥信号量
以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节讲解 FreeRTOS 任务间的同步和资源共享机制,二值信号量. 二值信号量是计数信号量的一种特殊形式 ...
- 【零基础学习FreeRTOS嵌入式系统】之一:FreeRTOS环境搭建
[零基础学习FreeRTOS嵌入式系统]之一:FreeRTOS环境搭建 一:FreeRTOS系统下载 在官网上https://www.freertos.org/,找到下载入口. 或直接进入下载地址ht ...
- FreeRTOS 计数信号量
以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节开始讲解 FreeRTOS 任务间的同步和资源共享机制,计数信号量. FreeRTOS 中计数信号量的 ...
- 嵌入式系统Linux内核开发工程师必须掌握的三十道题(转)
嵌入式系统Linux内核开发工程师必须掌握的三十道题 如果你能正确回答以下问题并理解相关知识点原理,那么你就可以算得上是基本合格的Linux内核开发工程师,试试看! 1) Linux中主要有哪几种内核 ...
- ucos互斥信号量解决优先级反转问题
在可剥夺性的内核中,当任务以独占方式使用共享资源的时候,会出现低优先级任务高于高优先级任务运行的情况,这种情况叫做优先级反转,对于实时操作系统而言,这是一场灾难,下面我们来说说优先级反转的典型环境. ...
- ucos之互斥信号量及优先级反转
在ucos常使用共享资源来作为任务之间的通信方式,其中有:消息队列,信号量,邮箱,事件.信号量中又分二值信号,多值信号,互斥信号.这次主要讲二值信号与互斥信号之间区别和使用. 首先了解一下ucos的任 ...
- 【iCore4 双核心板_uC/OS-II】例程七:互斥信号量
一.实验说明: 在介绍互斥信号量前,我们先简单地描述一下什么是优先级反转.使用实时内核心,优先级反转问题是实时系统中出现得最多的问题.假设任务H优先级高于任务M,任务M优先级高于任务L.任务H和任务M ...
- 嵌入式:FreeRTOS的使用(未完)
为了方便与UCOS对比,顺序按照UCOS那篇编写. 0.一些移植.系统相关 1.框架写法(个人习惯相关) 1-1.main 函数里创建一个开始任务 int main(void) { 初始化外设 xTa ...
随机推荐
- 模型预测控制 MPC
使用MPC的原因:
- 亿级Web系统搭建――单机到分布式集群 转载
当一个Web系统从日访问量10万逐步增长到1000万,甚至超过1亿的过程中,Web系统承受的压力会越来越大,在这个过程中,我们会遇到很多的问题.为了解决这些性能压力带来问题,我们需要在Web系统架构层 ...
- linux_mysql学习系列
Linux&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html 网站架构系列文章:http://www.cnblogs.co ...
- H3C 交换机配置ssh登陆
1.开启ssh服务,创建密钥. <D05-S5048-02>system-view [D05-S5048-02]ssh server enable //开启ssh服务 [D05-S5048 ...
- 关于Linux操作系统中的一些易忘记的命令与作用
1.改变文件或文件夹的权限,例如:chmod options mode file :[ugoa...] [+-=] [rwxXstugo],其中字符的含义如下: 第一组[ugoa...]:文件(夹)权 ...
- 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)
--喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...
- Java数组定义及方法
数组的描述 在数组中每个元素都具有相同的数据类型,是有序数据的集合.通俗的说数组就相当于一个容器.数组分为一维数组.二维数组和多维数组. 数组的特点: 数组类型是从抽象基类 Array 派生的引用 ...
- Android Studio中怎么使用DDMS工具
随着android studio的广泛使用,开发人员对相关工具的使用需求更加凸显.昨天在一个android studio教程网站上,看到一篇有关DDMS工具使用的相关知识,感觉很不错,分享给大家,一起 ...
- 图数据库-Neo4j-初探
图数据库-Neo4j-初探 2018-08-17 本次初探主要学习如何安装Neo4j,以及Cypher的基本语法. 1. 安装Neo4j Desktop版本 neo4j-desktop Server版 ...
- 记笔记的软件(vnote)
前面我们已经把我们的 Ubuntu 系统在物理机上运行起来了,也做了一些简单的优化,教了大家怎么使用 Ubuntu 系统自带的应用商店和 apt 安装和卸载软件.接着我们安装了搜狗输入法,现在我们的系 ...