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 — 互斥信号量

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

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

    关注

    40

    文章

    3431

    浏览量

    128225
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61349
  • 互斥信号量
    +关注

    关注

    0

    文章

    3

    浏览量

    1974
收藏 人收藏

    评论

    相关推荐

    关于FreeRTOS互斥的用法求解

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

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

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

    FreeRTOS信号量的使用与实例

    在嵌入式系统中,任务管理是一个重要的部分,它涉及到任务之间的通信和同步,信号量,队列,互斥锁和事件标志组等概念。本文将以 FreeRTOS 为例,详细讲解这些内容。
    的头像 发表于 12-12 15:25 641次阅读

    互斥锁、条件变量、读写锁、自旋锁及信号量介绍

    一、互斥锁(同步) 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西
    的头像 发表于 11-10 16:16 266次阅读
    <b class='flag-5'>互斥</b>锁、条件变量、读写锁、自旋锁及<b class='flag-5'>信号量</b>介绍

    关于RTOS中的信号量问题

    信号量是操作系统里的一个基本概念 我现在了解信号量是做什么的,怎么做的。 限于工作经验,只能用到二值信号量。计数型信号量用在什么场合呢? 请
    发表于 10-31 06:25

    怎么使用ucos的信号量

    应该怎么使用ucos的信号量,在什么情况下使用二进制信号量和数值型信号量
    发表于 10-07 07:41

    STM32H7 HSEM硬件信号量

    管理访问权限和同步• 不同的进程运行在相同的CPU上• 不同的CPU• 共32信号量• 两种锁机制• 2-step write, read back lock• 1-step read lock• 信号量释放会生成中断
    发表于 09-11 06:50

    释放信号量唤醒线程后,程序死机Hard_fault如何解决?

    系统在运行过程中出现死机,经过cmbacktrace 回溯发现在释放信号量的地方出错, 程序设计为,A线程释放一个信号量后唤醒B线程,但在发送信号量切换线程以后出现了bug。 扩大被唤
    发表于 08-20 16:50

    TThread完整版学习(RCEA考试练习)之信号量使用

    信号量是线程间同步的一种方式。在rtthread中用于线程间同步的还有互斥量和事件集。
    的头像 发表于 07-26 16:43 850次阅读
    TThread完整版学习(RCEA考试练习)之<b class='flag-5'>信号量</b>使用

    Semaphore信号量概念及其介绍

    信号量即Semaphore。信号量主要用于控制和保护任务对特定资源的访问。
    的头像 发表于 07-25 15:40 880次阅读
    Semaphore<b class='flag-5'>信号量</b><b class='flag-5'>概念</b>及其介绍

    互斥锁不能在中断中使用?

    互斥锁是一种特殊的二值信号量,因为它解决了优先级翻转的问题。
    的头像 发表于 07-25 15:36 1406次阅读

    FreeRTOS四种信号量详细介绍

    1、二值信号量 二值信号量通常用于互斥访问或同步,二值信号量互斥信号量非常类似,但是还是有一些
    的头像 发表于 07-06 17:14 1215次阅读

    FreeRTOS信号量介绍

    1、信号量用于控制对共享资源的访问 举一个很常见的例子,某个停车场有100个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。假设现在这个停车场正常运行,你要把
    的头像 发表于 07-06 17:09 404次阅读

    请教一个AT组件的信号量的问题

    我现在使用AT组件中的EC20模块,程序初始化以后at_client_getchar函数就在等待信号量client->rx_notice,如下面第一个图所示,但是我搜了一下
    发表于 05-11 14:22

    AT组件中的at_skt0 等等的信号量是什么情况下产生的?怎么产生的?

    ,socket打开就不正常了,然后就会多出几个at_skt0,at_skt1等等的信号量,有的时候我的线程还会挂起在这些信号量上,我在工程里搜也没有搜到相关的定义和内容,请问这些信号量是什么情况下产生的?怎么产生的?我想看看为什
    发表于 05-11 09:57