FreeRTOS-7-互斥量

这是FreeRTOS系列学习文章的第六篇,主要介绍FreeRTOS中的互斥量。

前置知识

优先级的翻转:在操作系统的,每个任务的优先级都是确定好的。如果某个时间点低优先级持有互斥量,此时高优先级的任务就会阻塞。如果在低优先级执行时有唤醒了中优先级任务,此时这个中优先级任务就会抢占低优先级的任务。此时导致高优先级的任务变成了最后执行的任务。此时就导致了优先级的翻转问题。优先级高的任务在等待优先级的任务执行完毕。

优先级的继承:由于操作系统的会出现优先级的反转的情况。所以使用一个机制来缓解这个问题。在出现高优先级在等待低优先级的任务执行完毕时,操作系统会临时将低优先级的任务的优先级调至和高优先级的任务一样高。这就防止了低优先级任务被中优先级任务抢占。

互斥量与二值信号量最大的不同之处在于,互斥量具有优先级继承机制,而信号量没有。

互斥量控制块

和信号量等等一致,互斥量也通过控制块来进行控制。

互斥量的API函数实际上也都是宏,使用现有的队列机制,这些宏定义在semphr.h文件中。如果使用互斥量,则需要包含semphr.h头文件。FreeRTOS的互斥量控制块结构体与消息队列结构体是一样的,只不过结构体中某些成员变量代表的含义不同。这个和信号量是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 1 typedef struct QueueDefinition {
2 int8_t *pcHead;
3 int8_t *pcTail;
4 int8_t *pcWriteTo;
5
6 union {
7 int8_t *pcReadFrom;
8 UBaseType_t uxRecursiveCallCount;(1)
9 } u;
10
11 List_t xTasksWaitingToSend;
12 List_t xTasksWaitingToReceive;
13
14 volatile UBaseType_t uxMessagesWaiting;(2)
15 UBaseType_t uxLength;(3)
16 UBaseType_t uxItemSize;(4)
17
18 volatileint8_t cRxLock;
19 volatileint8_t cTxLock;
20
21 #if( ( configSUPPORT_STATIC_ALLOCATION == 1 )
22 && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
23 uint8_t ucStaticallyAllocated;
24 #endif
25
26 #if ( configUSE_QUEUE_SETS == 1 )
27 struct QueueDefinition *pxQueueSetContainer;
28 #endif
29
30 #if ( configUSE_TRACE_FACILITY == 1 )
31 UBaseType_t uxQueueNumber;
32 uint8_t ucQueueType;
33 #endif
34
35 } xQUEUE;
36
37 typedef xQUEUE Queue_t;

(1):pcReadFrom与uxRecursiveCallCount是一对互斥变量,使用联合体来确保两个互斥的结构体成员不会同时出现。

  • 如果结构体用于消息队列,则pcReadFrom指向出队消息空间的最后一个,即读取消息时是从pcReadFrom指向的空间读取消息内容;
  • 如果结构体用于互斥量,uxRecursiveCallCount用于计数,记录递归互斥量被“调用”的次数。

(2):

  • 如果控制块结构体用于消息队列,则uxMessagesWaiting用来记录当前消息队列的消息个数;
  • 如果控制块结构体用于互斥量时,这个值表示有效互斥量的个数。此值为1时表示互斥量有效,为0则表示互斥量无效。

(3):

  • 如果控制块结构体用于消息队列,则uxLength表示队列的长度,即能存放多少消息;
  • 如果控制块结构体用于互斥量时,uxLength表示最大的信号量可用个数。uxLength最大为1,因为信号量要么是有效的,要么是无效的。

(4):

  • 如果控制块结构体用于消息队列,则uxItemSize表示单个消息的大小;
  • 如果控制块结构体用于互斥量时,则无须分配存储空间,为0即可。

互斥量的相关函数

由于互斥量的优先级继承,所以一般将互斥量用作对资源的保护。

主要函数包括:

  • 创建
  • 删除
  • 发送消息
  • 接受消息

创建函数

xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄。该句柄的原型是一个void型的指针,在使用之前必须先由用户定义一个互斥量句柄。要想使用该函数,必须在FreeRTOSConfig.h中把宏configSUPPORT_DYNAMIC_ALLOCATION定义为1,即开启动态内存分配。其实该宏在FreeRTOS.h中默认定义为1,即所有FreeRTOS的对象在创建时都默认使用动态内存分配方案。同时还需要在FreeRTOSConfig.h中把configUSE_MUTEXES宏定义打开,表示使用互斥量。

xSemaphoreCreateMutex()这个函数实际上是一个宏:

1
2
#define xSemaphoreCreateMutex()    xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

可以看到互斥量和信号量一样,其实就是队列函数的包装、不同的参数上的使用。xSemaphoreCreateMutex()函数虽然没有直接调用xQueueGenericCreate()函数,但是在xQueueCreateMutex()中还是调用了函数xQueueGenericCreate()

递归互斥量创建函数

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量。非递归互斥量用函数xSemaphoreCreateMutex()xSemaphoreCreateMutexStatic()创建。静态创建函数一般不会使用。所以只写了动态创建函数在上面。

  • 非递归互斥量且只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。
  • 递归互斥量则相反,它可以被同一个任务获取很多次,获取多少次就需要释放多少次。

递归互斥量与互斥量一样,都实现了优先级继承机制,可以减少优先级翻转情况的发生。

有上面的图我们可以知道。这个函数实际上也是对于xQueueCreateMutex()函数的封装。

互斥量删除函数

这个函数和信号量使用的删除函数是一致的。

非递归互斥量获取函数

xSemaphoreTake( xSemaphore, xBlockTime )

1
#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

这个函数也是对于xQueueSemaphoreTake()的封装,这和信号量的获取函数是一样的,不同的是如果是信号量则没有优先级继承算法,如果是互斥量则有。

递归互斥量获取函数

非递归互斥量释放函数

递归互斥量释放函数


FreeRTOS-7-互斥量
https://ysc2.github.io/ysc2.github.io/2024/01/22/FreeRTOS-6-互斥量/
作者
Ysc
发布于
2024年1月22日
许可协议