0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解互斥信号量的概念和运行

开源嵌入式 来源:开源嵌入式 作者:开源嵌入式 2020-10-22 11:57 次阅读

1 、互 斥 信 号 量

1.1 互斥信号量的概念及其作用

互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。

运行条件:

让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数printf 的互斥访问。如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。

用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。

代码实现:

创建二值信号量

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 在运行 "); 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 在运行 "); 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

使用举例:

static SemaphoreHandle_t xMutex = NULL; * 函 数 名: 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 SemaphoreHandle_t xMutex = NULL; * 函 数 名: vTaskLED * 功能说明: 实现串口的互斥访问,防止多个任务同时访问造成串口打印乱码 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 2 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 在运行 "); 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层被保护的资源,用户可以在这里添加被保护资源 "); /* 第1层被保护的资源里面嵌套被保护的资源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { //假如这里是被保护的资源,第2层被保护的资源,用户可以在这里添加被保护资源 printf("任务vTaskMsgPro在运行,第2层被保护的资源,用户可以在这里添加被保护资源 "); /* 第2层被保护的资源里面嵌套被保护的资源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { printf("任务vTaskMsgPro在运行,第3层被保护的资源,用户可以在这里添加被保护资源 "); bsp_LedToggle(1); bsp_LedToggle(4); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); } xSemaphoreGiveRecursive(xRecursiveMutex); /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }

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

也可以用我们递归互斥信号量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE时,会一直等待信号量,直到有信号量来才执行后面的语句。

责任编辑:PSY

原文标题:嵌入式系统FreeRTOS — 互斥信号量

文章出处:【微信公众号:开源嵌入式】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式系统
    +关注

    关注

    41

    文章

    3587

    浏览量

    129436
  • FreeRTOS
    +关注

    关注

    12

    文章

    484

    浏览量

    62143
  • 互斥信号量
    +关注

    关注

    0

    文章

    3

    浏览量

    2027
收藏 人收藏

    评论

    相关推荐

    开关和模拟的基本概念、特点及应用

    开关和模拟是电气工程和自动化控制领域中的两个重要概念。开关通常指二进制信号,即只有两种状态:开或关,高或低,1或0。模拟
    的头像 发表于 08-30 11:10 804次阅读

    热控信号和保护装置能否正常运行

    热控信号和保护装置是确保电力系统安全、稳定、经济运行的重要手段。它们在电力系统中起着至关重要的作用,能够及时检测和处理各种异常情况,防止设备损坏和系统故障。 一、热控信号和保护装置的基本概念
    的头像 发表于 08-27 11:12 299次阅读

    PLC对模拟信号的处理过程及方法 详解

    模拟信号是自动化过程控制系统中最基本的过程信号(压力、温度、流量等)输入形式。系统中的过程信号通过变送器,将这些检测信号转换为统一的电压、
    的头像 发表于 07-30 16:31 403次阅读
    PLC对模拟<b class='flag-5'>量</b><b class='flag-5'>信号</b>的处理过程及方法 <b class='flag-5'>详解</b>版

    互斥锁和自旋锁的实现原理

    互斥锁和自旋锁是操作系统中常用的同步机制,用于控制对共享资源的访问,以避免多个线程或进程同时访问同一资源,从而引发数据不一致或竞争条件等问题。 互斥锁(Mutex) 互斥锁是一种基本的同步机制,用于
    的头像 发表于 07-10 10:07 484次阅读

    4到20ma模拟信号怎么测?及原理介绍

    模拟信号的测量方法、原理、特点以及注意事项。       一、4到20mA模拟信号的基本概念       1.1 4到20mA
    的头像 发表于 06-23 08:38 5118次阅读
    4到20ma模拟<b class='flag-5'>量</b><b class='flag-5'>信号</b>怎么测?及原理介绍

    4到20ma模拟信号怎么测

    的测量方法、原理、特点以及注意事项。 一、4到20mA模拟信号的基本概念 1.1 4到20mA信号的定义 4到20mA模拟
    的头像 发表于 06-20 11:37 1760次阅读

    星仪小课堂:开关与模拟详解

    在现场仪表使用中,开关与模拟是使用最多的两种输入输出方式。今天我们就用图文结合的方式,详细讲解一下开关与模拟的原理是怎样的。开关
    的头像 发表于 06-18 09:47 570次阅读
    星仪小课堂:开关<b class='flag-5'>量</b>与模拟<b class='flag-5'>量</b><b class='flag-5'>详解</b>

    使用STM32CUBEMX创建一个基于RTOS的工程,互斥创建不成功的原因?

    使用STM32CUBEMX创建一个基于RTOS的工程,使用了互斥,但互斥创建不成功
    发表于 05-15 07:22

    关于FreeRTOS互斥的用法求解

    对于串口发送,我们都普遍用中断方式发送, 可是在配合互斥的时候会遇到些问题, 互斥的使用 必须在同一个任务中 占用和释放, 我目前的做法是用二值
    发表于 04-24 08:03

    STM32F107+CubeMX+FreeRTOS+LWIP连接成功后,信号量无法使用怎么解决?

    各位大哥,遇到一个在FreeRTOS+LWIP使用信号量的问题。 项目工程是通过CubeMX生成的,使用FreeRTOS + LWIP。 简化代码,FreeRTOS初始化两个任务,一个默认任务、一个
    发表于 04-19 07:33

    嵌入式实时操作系统——二值信号量

    当用户需要使用停车资源时,它靠近屏障并按下请求按钮,在信号量术语中,该行为被定义为信号等待(wait)操作。由于资源处于空闲状态,故服务员抬起屏障并回答可以通过, 用户随即进入保护区域,然后屏障关闭。
    发表于 04-09 14:44 655次阅读
    嵌入式实时操作系统——二值<b class='flag-5'>信号量</b>

    STM32H747双核的HSEM运行FreeRtos系统会卡死是怎么回事?

    FreeRtos,CM7在中断中使用SemaphoreGiveFromISR或任务二值信号量这种信号量时,程序就会卡死在在configASSERT( pxQueue );中,去掉
    发表于 03-28 06:32

    如何在Semaphore(信号量)和Mutex(互斥)之间做选择?

    在单CPU系统中,处理器是一个共享资源。在多个进程之间共享处理器时,处理器的使用由调度程序控制,不存在竞争问题。
    的头像 发表于 03-05 11:35 968次阅读
    如何在Semaphore(<b class='flag-5'>信号量</b>)和Mutex(<b class='flag-5'>互斥</b>)之间做选择?

    plc模拟输出怎么接线 plc模拟输出是什么信号

    ,通常用于控制和调节外部设备的运行状态。下面将详细介绍PLC模拟输出的接线方法、信号类型、输出范围以及其在实际应用中的应用。 接线方法: PLC模拟输出需要通过模拟
    的头像 发表于 02-05 14:46 5606次阅读

    信号量实现原理介绍

    除了原子操作,中断屏蔽,自旋锁以及自旋锁的衍生锁之外,在Linux内核中还存在着一些其他同步互斥的手段。
    的头像 发表于 01-10 09:07 1182次阅读