当前位置: 首页 > news >正文

FREERTOS信号量详解

信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步,资源管理其实就是用变量来标记现有资源的数量,任务同步其实就是用标志位来控制任务的先后执行顺序,这些概念在操作系统中以及裸机开发中都有所涉及。

FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但有些应用场景是可以互换着使用的。

信号量简介

信号量常常用于控制对共享资源的访问和任务同步。举一个很常见的例子,某个停车场有 100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。

假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例:使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。信号量用于控制共享资源访问的场景相当于一个上锁机制,代码只有获得了这个锁的钥匙 才能够执行。

上面我们讲了信号量在共享资源访问中的使用,信号量的另一个重要的应用场合就是任务 同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话会影响的中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间非常短。这个例子就是中断与任务之间使用信号量来完成同步,当然了,任务与任务之间也可以使用信号量来完成同步。

FreeRTOS 中还有一些其他特殊类型的信号量,比如互斥信号量和递归互斥信号量,这些具体遇到的时候再讲解。有关信号量的知识在 FreeRTOS 的官网上都有详细的讲解,包括二值信号量、计数型信号量、互斥信号量和递归互斥信号量,我们下面要讲解的这些涉及到理论性的知识都是翻译自 FreeRTOS 官方资料,感兴趣的可以去官网看原版的英文资料。

其用法就是取代裸机中的全局变量,使得可以被操作系统进行统一管理。

要注意体会队列和信号量的不同之处,队列像是全局变量中的数据传递使用,属于数据类型;而信号量更像是全局变量中的标志位使用,起到控制和设置作用。

二值信号量

二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号量更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问,有关互斥信号量的内容后面会专门讲解,本节只讲解二值信号量在同步中的应用。

和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。

二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。

在实际应用中通常会使用一个任务来处理 MCU 的某个外设,比如网络应用中,一般最简单的方法就是使用一个任务去轮询的查询 MCU ETH(网络相关外设,如 STM32 的以太网MAC)外设是否有数据,当有数据的时候就处理这个网络数据。这样使用轮询的方式是很浪费CPU 资源的,而且也阻止了其他任务的运行。最理想的方法就是当没有网络数据的时候网络任务就进入阻塞态,把CPU 让给其他的任务,当有数据的时候网络任务才去执行。现在使用二值信号量就可以实现这样的功能,任务通过获取信号量来判断是否有网络数据,没有的话就进入阻塞态,而网络中断服务函数(大多数的网络外设都有中断功能,比如STM32 MAC 专用 DMA 中断,通过中断可以判断是否接收到数据)通过释放信号量来通知任务以太网外设接收到了网络数据,网络任务可以去提取处理了。网络任务只是在一直的获取二值信号量,它不会释放信号量,而中断服务函数是一直在释放信号量,它不会获取信号量。

在中断服务函数中发送信号量可以使用函数 xSemaphoreGiveFromISR(),也可以使用任务通知功能来替代二值信号量,而且使用任务通知的话速度更快,代码量更少,有关任务通知的内容后面会有专门的章节介绍。

使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来替代二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。下面几个步骤演示了二值信号量的工作过程。

由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数 xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR()释放信号量。

这一过程和裸机中标志位的使用是一样的思路,只不过操作系统对其进行了管理。

创建二值信号量

同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数如下表所示:

此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的,由此也可知,二值信号量确实是一个队列。

函数原型如下:

SemaphoreHandle_t xSemaphoreCreateBinary( void )

返回值: 

NULL: 二值信号量创建失败。

其他值: 创建成功的二值信号量的句柄。

二值信号量创建过程分析

看一下新版本的二值信号量创建函数 xSemaphoreCreateBinary(),函数代码如下:

可以看出新版本的二值信号量创建函数也是使用函数 xQueueGenericCreate()来创建一个类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE、长度为 1、队列项长度为 0 的队列。这一步和老版本的二值信号量创建函数一样,唯一不同的就是新版本的函数在成功创建二值信号量以后不会立即调用函数 xSemaphoreGive()释放二值信号量。也就是说新版函数创建的二值信号量默认是无效的,而老版本是有效的。

大家注意看,创建的队列是个没有存储区的队列,前面说了使用队列是否为空来表示二值信号量,而队列是否为空可以通过队列结构体的成员变量 uxMessagesWaiting 来判断。

释放信号量

释放信号量的函数有两个,如下表所示:

同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号量还是互斥信号量,它们都使用上表中的函数释放信号量,递归互斥信号量有专用的释放函数。

注意:释放信号量是说给一个信号量,不是说把信号量搞没。这名字容易让人产生误解。

函数 xSemaphoreGive()

此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:

BaseType_t xSemaphoreGive( xSemaphore )

参数:

xSemaphore:要释放的信号量句柄。

返回值: 

pdPASS: 释放信号量成功。

errQUEUE_FULL: 释放信号量失败。

我们再来看一下函数 xSemaphoreGive()的具体内容,此函数在文件 semphr.h 中有如下定义:

可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息,阻塞时间为0(semGIVE_BLOCK_TIME 0),入队方式采用的后向入队。具体入队过程之前已经做了详细的讲解,入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当uxMessagesWaiting 1 的话说明二值信号量有效,为 0 就无效。如果队列满的话就返回错误值 errQUEUE_FULL,提示队列满,入队失败。

函数xSemaphoreGiveFromISR()

此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数 xQueueGiveFromISR(),

在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队函数 xQueueGenericSendFromISR()极其类似!只是针对信号量做了微小的改动。函数xSemaphoreGiveFromISR()不能用于在中断中释放互斥信号量,因为互斥信号量涉及到优先级继承的问题,而中断不属于任务,没法处理中断优先级继承。

获取信号量

获取信号量也有两个函数,如下表所示:

此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成的,函数原型如下:

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime)

参数:

xSemaphore:要获取的信号量句柄。

xBlockTime: 阻塞时间。 

再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:

获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。之前讲解函数 xQueueGenericReceive()的时候说过如果队列为空并且阻塞时间为 0 的话就立即返回errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一,然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出对成功。

互斥信号量涉及到优先级继承,处理方式不同,后面讲解互斥信号量的时候在详细的讲解。

函数 xSemaphoreTakeFromISR ()

此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝对不能使用此函数来获取互斥信号量!此函数是一个宏,真正执行的是函数xQueueReceiveFromISR ()

在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数pxHigherPriorityTaskWoken 设置为pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!这个函数还是很简单的。

二值信号量的使命就是同步,完成任务与任务或中断与任务之间的同步。大多数情况下都是中断与任务之间的同步。类似于裸机中的标志位。

计数型信号量

有些资料中也将计数型信号量叫做数值信号量,二值信号量相当于长度为 1 的队列,那么计数型信号量就是长度大于 1 的队列,也是通过队列结构体成员uxMessagesWaiting来实现的。同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列结构体成员uxMessagesWaiting当前数值为多少即可。

计数型信号量通常用于如下两个场合:

1、事件计数

在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数 值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量 uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0

2、资源管理

在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。 一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减

一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100

创建计数型信号量 

FreeRTOS 提供了两个计数型信号量创建函数,如下表所示:

函数 xSemaphoreCreateCounting()

此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateCountingSemaphore(),此函数原型如下:

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )

参数:

uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。

uxInitialCount: 计数信号量初始值。

返回值:

NULL: 计数型信号量创建失败。

其他值: 计数型信号量创建成功,返回计数型信号量句柄。

计数型信号量创建过程分析

这里只分析动态创建计数型信号量函数 xSemaphoreCreateCounting(),此函数是个宏,定义

如下:

可以看出,真正干事的是函数 xQueueCreateCountingSemaphore()(并没有直接调用函数xQueueGenericCreate()),此函数在文件 queue.c 中,有如下定义:

(1)、计数型信号量也是在队列的基础上实现的,所以需要调用函数 xQueueGenericCreate()

创建一个队列,队列长度为uxMaxCount , 队列项长度为 queueSEMAPHORE_QUEUE_ITEM_LENGTH( 此宏为0) , 队 列 的 类 型 为

queueQUEUE_TYPE_COUNTING_SEMAPHORE,表示是个计数型信号量。

(2)、队列结构体的成员变量 uxMessagesWaiting 用于计数型信号量的计数,根据计数型信 号量的初始值来设置 uxMessagesWaiting

释放和获取计数信号量 

计数型信号量的释放和获取与二值信号量相同。

优先级翻转

在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能

会导致严重的后果。

所谓的优先级翻转,就是本来应该是高优先级抢占低优先级任务,当有高优先级任务执行时,低优先级的任务就只能等着。但是,如果出现优先级翻转,就会有低优先级任务一直在执行,导致高优先级任务得不到执行,这显然是不符合我们的预期的。

下图所示就是一个优先级翻转的例子:

这个图要说明的就是,当存在高中低三种优先级的任务,其中高和低优先级任务都需要用到一个相同的信号量,但是中优先级的不用这个信号量,此时,低优先级任务先获取该信号量,然后高优先级任务优先级最高就会因此抢占执行,但是因为该信号量此时还被低优先级任务占用未释放,所以高优先级任务就会阻塞,此时,中等优先级任务就会抢占低优先级任务开始执行,只有当中等优先级任务执行完,再等低优先级任务执行完并释放信号量,高优先任务获取到信号量了,才会开始执行。

这么一来,本该优先执行的高优先任务,却是最后一个执行完的。

其过程如下所示:

(1) 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。

(2) 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。

(3) 任务 L 获得信号量并开始使用该共享资源。

(4) 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L CPU 使用权。

(5) 任务 H 开始运行。

(6) 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。

(7) 任务 L 继续运行。

(8) 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务L 的 CPU 使用权。

(9) 任务 M 处理该处理的事。

(10) 任务 M 执行完毕后,将 CPU 使用权归还给任务 L

(11) 任务 L 继续运行。

(12) 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换。

(13) 任务 H 得到该信号量并接着运行。

在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。

当一个低优先级任务和一个高优先级任务同时使用同一个信号量,而系统中还有其他中等优先级任务时。如果低优先级任务获得了信号量,那么高优先级的任务就会处于等待状态,但是,中等优先级的任务可以打断低优先级任务而先于高优先级任务运行 (此时高优先级的任务在等待信号量 ,所以不能运行),这是就出现了优先级翻转的现象。

既然优先级翻转是个很严重的问题,那么有没有解决方法呢?有!这就要引出另外一种信号量——互斥信号量!  

互斥信号量

互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在 互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。

不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞,不过虽然如此,此时这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承,从而使得信号量尽快被释放。

优先级继承尽可能地降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。

优先级继承并不能完全消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。

硬实时应用应该在设计之初就要避免优先级翻转的发生。

互斥信号量不能用于中断服务函数中,原因如下:

● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。

● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。

创建互斥信号量

FreeRTOS 提供了两个互斥信号量创建函数,如下表所示:

 函数  xSemaphoreCreateMutex()

此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质

是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex(),此函数原型如下:

SemaphoreHandle_t xSemaphoreCreateMutex( void )

返回值:

NULL: 互斥信号量创建失败。

其他值: 创建成功的互斥信号量的句柄。   

互斥信号量创建过程分析

这里只分析动态创建互斥信号量函数 xSemaphoreCreateMutex (),此函数是个宏,定义如下:

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

可以看出,真正干事的是函数 xQueueCreateMutex(),此函数在文件 queue.c 中有如下定义,

(1)、调用函数 xQueueGenericCreate()创建一个队列,队列长度为 1,队列项长度为 0,队列类型为参数 ucQueueType。由于本函数是创建互斥信号量的,所以参数 ucQueueType

queueQUEUE_TYPE_MUTEX

(2)、调用函数 prvInitialiseMutex()初始化互斥信号量。

互斥信号量创建成功以后会调用函数 xQueueGenericSend()释放一次信号量,说明互斥信号量默认就是有效的!

释放互斥信号量

释 放 互 斥 信 号 量 的 时 候 和 二 值 信 号 量 、 计 数 型 信 号 量 一 样 , 都 是 用 的 函 数

xSemaphoreGive()(实际上完成信号量释放的是函数 xQueueGenericSend())

不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数 xSemaphoreGive()释放信号 量 最 重 要 的 一 步 就 是 将 uxMessagesWaiting 加 一 , 而 这 一 步 就 是 通 过 函 数prvCopyDataToQueue() 来完成的,释放信号量的函数xQueueGenericSend() 会调用prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数 prvCopyDataToQueue()中完成的。

获取互斥信号量

获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数 xQueueGenericReceive()在文件 queue.c 中有定义,可自行查阅。

互斥访问

如果想要多个任务对某个函数进行互斥访问,那么就可以给该函数加上互斥信号量。 

其实就是操作系统课程里说的互斥锁的一种。

函数进入时就上锁,即获取信号量,相当于手上拿了一把通行钥匙,钥匙只有一把,别的任务获取不了信号量,就会阻塞,等待该信号量被释放,函数执行结束,就释放信号量,即返还钥匙,让别的任务能拿着钥匙进入。

互斥信号量使用完成以后一定要释放!否则就会出现钥匙丢失,后面谁都没法进入了。

递归互斥信号量

递归互斥信号量可以看作是一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以

再次获取这个递归互斥信号量,而且次数不限!一个任务使用函数xSemaphoreTakeRecursive() 成功的获取了多少次递归互斥信号量就得使用函数xSemaphoreGiveRecursive()释放多少次!比如某个任务成功的获取了 5 次递归信号量,那么这个任务也得同样的释放 5 次递归信号量。

递归互斥信号量也有优先级继承的机制,所以当任务使用完递归互斥信号量以后一定要记

得释放。同互斥信号量一样,递归互斥信号量不能用在中断服务函数中。

● 由于优先级继承的存在,就限定了递归互斥信号量只能用在任务中,不能用在中断服务函数中!

● 中断服务函数不能设置阻塞时间。

要使用递归互斥信号量的话宏 configUSE_RECURSIVE_MUTEXES 必须为 1

创建递归互斥信号量

FreeRTOS 提供了两个互斥信号量创建函数,如表 14.10.2.1 所示:

 

函数 xSemaphoreCreateRecursiveMutex()

此函数用于创建一个递归互斥信号量,所需要的内存通过动态内存管理方法分配。此函数

本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex ()

递归信号量创建过程分析

这里只分析动态创建互斥信号量函数 xSemaphoreCreateRecursiveMutex (),此函数是个宏,定义如下:

可以看出,真正干事的是函数 xQueueCreateMutex(),互斥信号量的创建也是用的这个函数,只是在创建递归互斥信号量的时候类型选择为queueQUEUE_TYPE_RECURSIVE_MUTEX

释放递归互斥信号量

递归互斥信号量有专用的释放函数:xSemaphoreGiveRecursive(),此函数为宏,如下:

#define xSemaphoreGiveRecursive( xMutex )       xQueueGiveMutexRecursive( ( xMutex ) )

函数的参数就是就是要释放的递归互斥信号量,真正的释放是由函数 xQueueGiveMutexRecursive()来完成的。

由于递归互斥信号量可以被一个任务重复的获取,因此在释放的时候也要释放多次,但是只有在最后一次释放的时候才会调用函数 xQueueGenericSend()完成真正的释放。其他释放的话只是简单的将 uxRecursiveCallCount 减一。

获取递归互斥信号量 

递归互斥信号量的获取使用函数 xSemaphoreTakeRecursive(),此函数是个宏,定义如下

函数第一个参数是要获取的递归互斥信号量句柄,第二个参数是阻塞时间。真正的获取过程是由函数 xQueueTakeMutexRecursive()来完成的。

相关文章:

FREERTOS信号量详解

信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步,资源管理其实就是用变量来标记现有资源的数量,任务同步其实就是用标志位来控制任务的先后执行顺序,这些概念在操作系统中以及裸机开发中都有所涉及。 FreeR…...

每天学习一个Linux命令之vim

每天学习一个Linux命令之vim Vim是一款功能强大的文本编辑器,在Linux系统中广泛使用。本篇博客将介绍一些常用的Vim命令及其选项,帮助您更好地使用Vim进行文本编辑。 命令及选项 以下是Vim的常用命令及其可用选项: 1. 打开文件 $ vim fi…...

linux环境部署

war包环境 在Linux系统上部署准备war包环境 查看linux当前版本和系统类型 [rootlocalhost ~]# uname -a Linux localhost.localdomain 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linuxlinux 打包文件夹 使用tar命令&#xff1…...

上位机图像处理和嵌入式模块部署(qmacvisual图像预处理)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 不管大家是在读书的时候学习的图像处理,还是在后来的工作中,重新学习了图像处理,相信大家对图像预处理的概念并…...

C语言内存函数详解

文章目录 前言一、memcpy函数(内存拷贝函数)二、memmove重叠拷贝函数三.memset内存设置函数四.memcmp内存比较函数总结 前言 我们之前按学习了C语言标准库中提供了一系列的字符和字符串库函数,接下来我们就学习一下关于内存相关的一些函数。…...

详解Redis的持久化RDB和AOF

Redis的持久化是将内存中的数据同步到硬盘的过程 具体来说,Redis支持两种主要的持久化方式:RDB 和 AOF。 RDB(Redis Database) 简介 默认持久化方式 RDB会将内存中的数据快照保存到磁盘上的一个二进制文件中。这个文件包含了…...

详细分析Js中的Promise.all基本知识(附Demo)

目录 1. 基本知识2. Demo3. 实战 1. 基本知识 Promise.all 是 JavaScript 中的一个方法,它接受一个由 Promise 对象组成的数组作为参数,并在所有 Promise 对象都变为 resolved(已完成)状态时才返回一个新的 Promise 对象&#xf…...

const,static深度总结——c++穿透式分析

前言;c类和对象的知识点中除了几种默认函数, 比较重要的还有使用const和static修饰成员相关知识点。const在c中特性很简单。 但是在使用中, 比较容易疏忽大意出现问题。 static特性也很简单, 但是比起const来要直接的多。 在使用中…...

快速搭建一个一元二次方程flask应用

新建flask_service目录、templates子目录 flask_service —— app.py —— templates —— —— index.html app.py from flask import Flask, request, jsonify, render_template import random import matplotlib.pyplot as plt from io import BytesIO import base64app F…...

O2OA红头文件流转与O2OA版式公文编辑器基本使用

O2OA开发平台在流程管理中,提供了符合国家党政机关公文格式标准(GB/T 9704—2012)的公文编辑组件,可以让用户在包含公文管理的项目实施过程中,轻松地实现标准化公文格式的在线编辑、痕迹保留、手写签批等功能。并且可以…...

软件测试:C++ Google Test单元测试框架GTest

目录 编译和安装框架使用AssertionsGoogle TestingGoogle MockingMatchersActions 运行结果 最近在写项目的时候,学到了许多关于软件测试的知识,也不断的使用新的测试框架和测试工具,每次总是机械式的拼接其他人的代码,代码发生错…...

大数据面试题 —— HBase

目录 什么是HBase简述HBase 的数据模型HBase 的读写流程HBase 在写的过程中的region的split的时机HBase 和 HDFS 各自的使用场景HBase 的存储结构HBase 中的热现象(数据倾斜)是怎么产生的,以及解决办法有哪些HBase rowkey的设计原则HBase 的列…...

SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现GWO-TCN-BiGRU-Attention灰狼算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程序…...

SpringMVC的执行原理

SpringMVC的执行原理可以简单地概括为以下几个步骤: 客户端发送请求:客户端(一般是浏览器)发送HTTP请求到服务器,请求特定的URL资源。 前端控制器(DispatcherServlet)接收请求:在Sp…...

Qt + HTTP 线程交互类封装

介绍 QT的HTTP模块封装的交互类&#xff0c;线程运行。使用时添加自己的业务逻辑即可 代码 头文件 /*** file httpcontroller.h* brief 云台相机的协议交互类* author xintong-zhou* date 2024-03-13*/#ifndef HTTPCONTROLLER_H #define HTTPCONTROLLER_H#include <QNet…...

GitHub Copilot+ESP开发实战-串口

上篇文章讲了GitHub Copilot在应用中可能遇到的问题&#xff0c;接下来小启就简单介绍下GitHub Copilot在ESP32开发中C语言实现串口功能&#xff0c;感兴趣的可以看看。 一、向Copilot提问&#xff1a; 1. ESP32用C语言实现串口初始化&#xff1b; 2.配置uart为1&#xff0c…...

C# 使用ffmpeg将图片保存为mp4视频

使用 FFmpeg 这个强大的多媒体处理工具&#xff0c;可以轻松地将一系列图片转换为一个 MP4 视频文件。以下是一个基本的命令行示例来完成这个任务&#xff1a; ffmpeg -framerate 25 -i image-%03d.jpg -c:v libx264 -r 30 -pix_fmt yuv420p output.mp4 命令参数说明&#xf…...

Java安全技术及代码审计技巧

概述 Java安全编码和代码审计是确保Java应用程序安全性的重要环节。本文旨在介绍Java中常见的Web漏洞、安全编码示例以及一些常见漏洞函数&#xff0c;并提供一个自动化查找危险函数的Python脚本。 1. XML外部实体 (XXE) 漏洞 介绍 XML文档结构包括XML声明、DTD文档类型定义&…...

C# 使用OpenCvSharp4将Bitmap合成为MP4视频的环境

环境安装步骤&#xff1a; 在VS中选中项目或者解决方案&#xff0c;鼠标右键&#xff0c;选择“管理Nuget包”&#xff0c;在浏览窗口中搜索OpenCVSharp4 1.搜索OpenCvSharp4,选择4.8.0版本&#xff0c;点击安装 2.搜索OpenCvSharp4.runtime.win,选择4.8.0版本&#xff0c;点…...

[游戏开发][Unity] 导出Xcode工程,完成调试与发布

Unity导出Xcode工程(模拟器版本与真机调试) [游戏开发][Unity] 打包Xcode工程模拟器真机调试_unity5 打包xcod-CSDN博客 Unity导出发布版本Xcode工程&#xff0c;上传app到官网&#xff0c;正式发布或创建TestFlight Xcode发布AppStore与TestFlight全流程_xcode 上传到testfit-…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...