互斥信号量可以在资源保护的时候很有帮助。用于控制在两个或多个任务间访问共享资源。任务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);

以下参考:

https://mp.weixin.qq.com/s?src=11&timestamp=1565745402&ver=1789&signature=qe1QZ6HPJRk6tu9AxDhfQCwtlsxg-7pc3ce9vjSpWHkNrvz-epufym9ZTh1qxLs71Y9Kl5Jj4xvcAr*JWBTv0oHXYA7-t1Or9kDhsiJm2VL6TEBoyGBXx8qH7UL8vJ-V&new=1

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 — 互斥信号量的更多相关文章

  1. FreeRTOS互斥信号量

    API函数 #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) #define xSemaphoreCreateMutex() xQueueCreateMutex ...

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

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节讲解 FreeRTOS 任务间的同步和资源共享机制,二值信号量. 二值信号量是计数信号量的一种特殊形式 ...

  3. 【零基础学习FreeRTOS嵌入式系统】之一:FreeRTOS环境搭建

    [零基础学习FreeRTOS嵌入式系统]之一:FreeRTOS环境搭建 一:FreeRTOS系统下载 在官网上https://www.freertos.org/,找到下载入口. 或直接进入下载地址ht ...

  4. FreeRTOS 计数信号量

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节开始讲解 FreeRTOS 任务间的同步和资源共享机制,计数信号量. FreeRTOS 中计数信号量的 ...

  5. 嵌入式系统Linux内核开发工程师必须掌握的三十道题(转)

    嵌入式系统Linux内核开发工程师必须掌握的三十道题 如果你能正确回答以下问题并理解相关知识点原理,那么你就可以算得上是基本合格的Linux内核开发工程师,试试看! 1) Linux中主要有哪几种内核 ...

  6. ucos互斥信号量解决优先级反转问题

    在可剥夺性的内核中,当任务以独占方式使用共享资源的时候,会出现低优先级任务高于高优先级任务运行的情况,这种情况叫做优先级反转,对于实时操作系统而言,这是一场灾难,下面我们来说说优先级反转的典型环境. ...

  7. ucos之互斥信号量及优先级反转

    在ucos常使用共享资源来作为任务之间的通信方式,其中有:消息队列,信号量,邮箱,事件.信号量中又分二值信号,多值信号,互斥信号.这次主要讲二值信号与互斥信号之间区别和使用. 首先了解一下ucos的任 ...

  8. 【iCore4 双核心板_uC/OS-II】例程七:互斥信号量

    一.实验说明: 在介绍互斥信号量前,我们先简单地描述一下什么是优先级反转.使用实时内核心,优先级反转问题是实时系统中出现得最多的问题.假设任务H优先级高于任务M,任务M优先级高于任务L.任务H和任务M ...

  9. 嵌入式:FreeRTOS的使用(未完)

    为了方便与UCOS对比,顺序按照UCOS那篇编写. 0.一些移植.系统相关 1.框架写法(个人习惯相关) 1-1.main 函数里创建一个开始任务 int main(void) { 初始化外设 xTa ...

随机推荐

  1. 洛谷 题解 P2010 【回文日期】

    因为有8个字符,所以可得出每一年只有一个回文日期. 因此只要判断每一年就行了. 做法: 我们先把年倒过来,例如2018年就倒为8102,就得出8102就是回文日期的后四个字符,我们只要判断一下有没有这 ...

  2. Javascript性能优化阅读笔记

    第一章 加载和执行 大多数浏览器都是用单一进程处理UI界面的刷新和JavaScript的脚本执行,所以同一时间只能做一件事,Javascript执行过程耗时越久,浏览器等待响应的时间就越长. 所以,H ...

  3. 剑指offer29:最小的k个数

    1 题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 2 思路和方法,C++核心代码 2.1 sort()函数,ve ...

  4. 网络编程[第二篇]基于udp协议的套接字编程

    udp协议下的套接字编程 一.udp是无链接的    不可靠的 而上篇的tcp协议是可靠的,会有反馈信息来确认信息交换的完成与否 基于udp协议写成的服务端与客户端,各司其职,不管对方是否接收到信息, ...

  5. MySql字段类型及字节

    字段类型:TINYINT-----------------一个很小的整数.有符号的范围是-128到127,无符号的范围是0到255. SMALLINT--------------一个小整数.有符号的范 ...

  6. .Net C# Dictionary 和参数字符串互转

    #region Parse #region Dictionary Parse To String /// <summary> /// Dictionary Parse To String ...

  7. Oracle导入数据后中文乱码的解决方法

    解决方法: 方法一. 1.在运行命令行输入regedit,打开注册表编辑器 2.找到HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraDb11g_home1 3.看N ...

  8. NetScaler Logs Collection Guide

    NetScaler Logs Collection Guide 来源  https://support.citrix.com/article/CTX227560 Article | Authentic ...

  9. YoloV3 训练崩溃

    经过排查  发现是这里出了问题 然后发现是标注文件里有 x=0 y=0  这样的数据,46_Jockey_Jockey_46_576.txt ,  那么肯定是标注文件出了问题!! 删除该标注文件即可. ...

  10. arcgis js之点击获取featureLayer中的点

    arcgis js之点击获取featureLayer中的点 代码: this.view.on('click', (evt) => { let layer = this.map.findLayer ...