目录

一.FreeRTOS的概述

1.文件

2.头文件

3.内存管理

4.数据类型和编程规范

数据类型

变量名

函数名

宏名

二.任务管理

1.创建任务

2.删除任务

3.优先级和Tick

4.任务状态(重要)

1.阻塞状态

2.暂停状态

3.就绪状态

4.运行状态

5.空闲任务及其钩子函数

三.同步互斥与通信

四.队列

1.创建

2.删除

3.写队列

4.读队列

五.信号量

1.信号量是特殊的队列

2.计数值

3.创建

4.删除

5.give/take

六.互斥量

1.创建

2.删除、give/take

3.互斥量与二进制信号量的不同

4.递归锁

一.FreeRTOS的概述

1.文件

FreeRTOS的最核心文件只有2个:

  • FreeRTOS/Source/tasks.c
  • FreeRTOS/Source/list.c

其他文件的作用也一起列表如下:

FreeRTOS/Source/下的文件 作用
tasks.c 必需,任务操作
list.c 必须,列表
queue.c 基本必需,提供队列操作、信号量(semaphore)操作
timer.c 可选,software timer
event_groups.c 可选,提供event group功能
croutine.c 可选,过时了

2.头文件

头文件 作用
FreeRTOSConfig.h FreeRTOS的配置文件,比如选择调度算法:configUSE_PREEMPTION
每个demo都必定含有FreeRTOSConfig.h
建议去修改demo中的FreeRTOSConfig.h,而不是从头写一个
FreeRTOS.h 使用FreeRTOS API函数时,必须包含此文件。
在FreeRTOS.h之后,再去包含其他头文件,比如:
task.h、queue.h、semphr.h、event_group.h

3.内存管理

文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。源码中默认提供了5个文件,对应内存管理的5种方法。

文件 优点 缺点
heap_1.c 分配简单,时间确定 只分配、不回收
heap_2.c 动态分配、最佳匹配 碎片、时间不定
heap_3.c 调用标准库函数 速度慢、时间不定
heap_4.c 相邻空闲内存可合并 可解决碎片问题、时间不定
heap_5.c 在heap_4基础上支持分隔的内存块 可解决碎片问题、时间不定

4.数据类型和编程规范

数据类型

每个移植的版本都含有自己的portmacro.h头文件,里面定义了2个数据类型:

  • TickType_t:
    • FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt
    • 每发生一次中断,中断次数累加,这被称为tick count
    • tick count这个变量的类型就是TickType_t
    • TickType_t可以是16位的,也可以是32位的
    • FreeRTOSConfig.h中定义configUSE_16_BIT_TICKS时,TickType_t就是uint16_t
    • 否则TickType_t就是uint32_t
    • 对于32位架构,建议把TickType_t配置为uint32_t
  • BaseType_t:
    • 这是该架构最高效的数据类型
    • 32位架构中,它就是uint32_t
    • 16位架构中,它就是uint16_t
    • 8位架构中,它就是uint8_t
    • BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE

变量名

变量名有前缀:

变量名前缀 含义
c char
s int16_t,short
l int32_t,long
x BaseType_t,
其他非标准的类型:结构体、task handle、queue handle等
u unsigned
p 指针
uc uint8_t,unsigned char
pc char指针

函数名

函数名的前缀有2部分:返回值类型、在哪个文件定义。

函数名前缀 含义
vTaskPrioritySet 返回值类型:void
在task.c中定义
xQueueReceive 返回值类型:BaseType_t
在queue.c中定义
pvTimerGetTimerID 返回值类型:pointer to void
在tmer.c中定义

宏名

宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义。

宏的前缀 含义:在哪个文件里定义
port (比如portMAX_DELAY) portable.h或portmacro.h
task (比如taskENTER_CRITICAL()) task.h
pd (比如pdTRUE) projdefs.h
config (比如configUSE_PREEMPTION) FreeRTOSConfig.h
err (比如errQUEUE_FULL) projdefs.h

通用的宏定义如下:

pdTRUE 1
pdFALSE 0
pdPASS 1
pdFAIL 0

二.任务管理

1.创建任务

动态创建任务

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
                        void * const pvParameters, // 调用任务函数时传入的参数
                        UBaseType_t uxPriority,    // 优先级
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

参数说明:

参数 描述
pvTaskCode 函数指针,可以简单地认为任务就是一个C函数。
它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName 任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。
长度为:configMAX_TASK_NAME_LEN
usStackDepth 每个任务都有自己的栈,这里指定栈大小。
单位是word,比如传入100,表示栈大小为100 word,也就是400字节。
最大值为uint16_t的最大值。
怎么确定栈的大小,并不容易,很多时候是估计。
精确的办法是看反汇编码。
pvParameters 调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority 优先级范围:0~(configMAX_PRIORITIES – 1)
数值越小优先级越低,
如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
pxCreatedTask 用来保存xTaskCreate的输出结果:task handle。
以后如果想操作这个任务,比如修改它的优先级,就需要这个handle。
如果不想使用该handle,可以传入NULL。
返回值 成功:pdPASS;
失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足)
注意:文档里都说失败时返回值是pdFAIL,这不对。
pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。

静态创建任务,比动态创建多了一个事先创建的栈

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,      // 任务函数指针
                                const char * const pcName,      // 任务名称
                                const uint32_t ulStackDepth,    // 栈大小(单位:word)
                                void * const pvParameters,      // 传入参数
                                UBaseType_t uxPriority,         // 任务优先级
                                StackType_t * const puxStackBuffer, // 栈缓冲区指针
                                StaticTask_t * const pxTaskBuffer ); // 任务控制块指针

2.删除任务

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明:

参数 描述
pvTaskCode 任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。
也可传入NULL,这表示删除自己。

3.优先级和Tick

优先级:

  • FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。 configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,configMAX_PRIORITIES的取值不能超过32。

使用uxTaskPriorityGet来获得任务的优先级:使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用vTaskPrioritySet 来设置任务的优先级:使用参数xTask来指定任务,设置为NULL表示设置自己的优先级; 参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。

void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

Tick:

对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。

  • 假设t1、t2、t3发生时钟中断
  • 两次中断之间的时间被称为时间片(time slice、tick period)
  • 时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms

image-20210731130348561

vTaskDelay(2);  // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100));	 // 等待100ms

4.任务状态(重要)

1.阻塞状态

在开发中,我们不会让任务持续占用CPU进行轮询,而是采用事件驱动(Event-Driven)的设计模式:

  • 任务等待某个事件的发生

  • 事件发生后,任务才被唤醒并执行

  • 在等待事件的过程中,任务不消耗CPU资源

当任务等待事件时,它进入阻塞状态(Blocked)。在阻塞状态下,任务可以等待两类事件:

· 时间相关事件:

相对时间等待:等待一段指定的时间(如“等待2分钟”)

绝对时间等待:等待直到某个绝对时刻(如“等到下午3点”)

· 同步事件:

由其他任务或中断服务程序产生

用于任务间的协调与通信

2.暂停状态

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

要退出暂停状态,只能由别人来操作:

  • 别的任务调用:vTaskResume
  • 中断程序调用:xTaskResumeFromISR

3.就绪状态

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

4.运行状态

这个任务正在运行。

5.空闲任务及其钩子函数

空闲任务(Idle任务)的作用:释放被删除的任务的内存。

在使用vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。注意的是钩子函数

  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用vTaskDelete() 来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。

三.同步互斥与通信

同步:协调多个任务/线程的执行顺序,确保它们在正确的时间点执行正确的操作。

互斥:确保同一时刻,只有一个任务/线程能够访问某个共享资源,防止数据被破坏。

内核对象 生产者 消费者 数据/状态 说明
队列 ALL ALL 数据:若干个数据
谁都可以往队列里扔数据,
谁都可以从队列里读数据
用来传递数据,
发送者、接收者无限制,
一个数据只能唤醒一个接收者
事件组 ALL ALL 多个位:或、与
谁都可以设置(生产)多个位,
谁都可以等待某个位、若干个位
用来传递事件,
可以是N个事件,
发送者、接受者无限制,
可以唤醒多个接收者:像广播
信号量 ALL ALL 数量:0~n
谁都可以增加一个数量,
谁都可消耗一个数量
用来维持资源的个数,
生产者、消费者无限制,
1个资源只能唤醒1个接收者
任务通知 ALL 只有我 数据、状态都可以传输,
使用任务通知时,
必须指定接受者
N对1的关系:
发送者无限制,
接收者只能是这个任务
互斥量 只能A开锁 A上锁 位:0、1
我上锁:1变为0,
只能由我开锁:0变为1
就像一个空厕所,
谁使用谁上锁,
也只能由他开锁

四.队列

1.创建

动态分配内存:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
返回值 非0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为内存不足

静态分配内存:

QueueHandle_t xQueueCreateStatic(
                           UBaseType_t uxQueueLength,
                           UBaseType_t uxItemSize,
                           uint8_t *pucQueueStorageBuffer,
                           StaticQueue_t *pxQueueBuffer
                       );
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
pucQueueStorageBuffer 如果uxItemSize非0,pucQueueStorageBuffer必须指向一个uint8_t数组,
此数组大小至少为"uxQueueLength * uxItemSize"
pxQueueBuffer 必须执行一个StaticQueue_t结构体,用来保存队列的数据结构
返回值 非0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为pxQueueBuffer为NULL

2.删除

只能删除使用动态方法创建的队列,它会释放内存

void vQueueDelete( QueueHandle_t xQueue );

3.写队列

/* 等同于xQueueSendToBack
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSend(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );


/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );
参数 说明
xQueue 队列句柄,要写哪个队列
pvItemToQueue 数据指针,这个数据的值会被复制进队列,
复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait 如果队列满则无法写入新数据,可以让任务进入阻塞状态,
xTicksToWait表示阻塞的最大时间(Tick Count)。
如果被设为0,无法写入数据时函数会立刻返回;
如果被设为portMAX_DELAY,则会一直阻塞直到有空间可写
返回值 pdPASS:数据成功写入了队列
errQUEUE_FULL:写入失败,因为队列满了。

4.读队列

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );

BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );
参数 说明
xQueue 队列句柄,要读哪个队列
pvBuffer bufer指针,队列的数据会被复制到这个buffer
复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait 果队列空则无法读出数据,可以让任务进入阻塞状态,
xTicksToWait表示阻塞的最大时间(Tick Count)。
如果被设为0,无法读出数据时函数会立刻返回;
如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写
返回值 pdPASS:从队列读出数据入
errQUEUE_EMPTY:读取失败,因为队列空了。

五.信号量

1.信号量是特殊的队列

队列 信号量
可以容纳多个数据,
创建队列时有2部分内存: 队列结构体、存储数据的空间
只有计数值,无法容纳其他数据。
创建信号量时,只需要分配信号量结构体
生产者:没有空间存入数据时可以阻塞 生产者:用于不阻塞,计数值已经达到最大时返回失败
消费者:没有数据时可以阻塞 消费者:没有资源时可以阻塞

2.计数值

限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。

差别列表如下:

二进制信号量 技术型信号量
被创建时初始值为0 被创建时初始值可以设定
其他操作是一样的 其他操作是一样的

3.创建

使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。

对于二进制信号量、计数型信号量,它们的创建函数不一样:

二进制信号量 计数型信号量
动态创建 xSemaphoreCreateBinary
计数值初始值为0
xSemaphoreCreateCounting
vSemaphoreCreateBinary(过时了)
计数值初始值为1
静态创建 xSemaphoreCreateBinaryStatic xSemaphoreCreateCountingStatic
/* 创建一个二进制信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );




/* 创建一个计数型信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, 
                                                 UBaseType_t uxInitialCount, 
                                                 StaticSemaphore_t *pxSemaphoreBuffer );

4.删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

5.give/take

二进制信号量、计数型信号量的give、take操作函数是一样的。

在任务中使用 在ISR中使用
give xSemaphoreGive xSemaphoreGiveFromISR
take xSemaphoreTake xSemaphoreTakeFromISR

Give:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
参数 说明
xSemaphore 信号量句柄,释放哪个信号量
返回值 pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败
BaseType_t xSemaphoreGiveFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );
参数 说明
xSemaphore 信号量句柄,释放哪个信号量
pxHigherPriorityTaskWoken 如果释放信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE
返回值 pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

Take:

BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );
参数 说明
xSemaphore 信号量句柄,获取哪个信号量
xTicksToWait 如果无法马上获得信号量,阻塞一会:
0:不阻塞,马上返回
portMAX_DELAY: 一直阻塞直到成功
其他值: 阻塞的Tick个数,可以使用pdMS_TO_TICKS()来指定阻塞时间为若干ms
返回值 pdTRUE表示成功
BaseType_t xSemaphoreTakeFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );
参数 说明
xSemaphore 信号量句柄,获取哪个信号量
pxHigherPriorityTaskWoken 如果获取信号量导致更高优先级的任务变为了就绪态,
则*pxHigherPriorityTaskWoken = pdTRUE
返回值 pdTRUE表示成功

六.互斥量

互斥量是一种特殊的二进制信号量,用来保护共享资源,通常我们也成为上锁资质。

1.创建

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

//配置
#define configUSE_MUTEXES 1


/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

2.删除、give/take

要注意的是,互斥量不能在ISR中使用。

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

3.互斥量与二进制信号量的不同

假设系统中有三个任务:高优先级任务A中等优先级任务B低优先级任务C。它们共享一个用互斥量保护的资源。

1.首先,低优先级任务C获得了互斥量,正在使用共享资源。

2.此时,高优先级任务A需要这个资源,于是请求互斥量,但因为被C持有,所以A被阻塞,等待C释放资源。

3.问题来了:中等优先级任务B(不需要这个资源)开始运行,它抢占了低优先级任务C的CPU时间。

4.结果就是:高优先级任务A在等待低优先级任务C释放资源,但C无法执行(被B抢占了),所以A一直在等待。

5.B运行完后,C才能继续执行,释放资源,最后A才能执行。

为了解决这个问题,互斥量Mutex提供了优先级继承(Priority Inheritance)机制:

当高优先级任务A因为等待低优先级任务C持有的互斥量而被阻塞时,系统会临时把C的优先级提升到A的水平(甚至更高)。这样一来:

1.低优先级任务C被提升到高优先级,中等优先级任务B就无法抢占C了。

2.C继续执行,尽快完成工作,释放互斥量。

3.C释放互斥量后,优先级恢复原状。

4.此时,高优先级任务A终于获得互斥量,继续执行。

5.最后任务B才执行。

优先级反转:高优先级任务因等待低优先级任务持有的资源,而被中等优先级任务间接阻塞的现象。

优先级继承:为了解决优先级反转,系统临时提升低优先级任务的优先级到高优先级任务的水平。

4.递归锁

1.假设有2个互斥量M1、M2,2个任务A、B:

  • A获得了互斥量M1
  • B获得了互斥量M2
  • A还要获得互斥量M2才能运行,结果A阻塞
  • B还要获得互斥量M1才能运行,结果B阻塞
  • A、B都阻塞,再无法释放它们持有的互斥量
  • 死锁发生!

2.自我死锁

  • 任务A获得了互斥锁M
  • 它调用一个库函数
  • 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
  • 死锁发生!

3.解决方法可以使用递归锁,递归锁实现了:谁上锁就由谁解锁。

递归锁 一般互斥量
创建 xSemaphoreCreateRecursiveMutex xSemaphoreCreateMutex
获得 xSemaphoreTakeRecursive xSemaphoreTake
释放 xSemaphoreGiveRecursive xSemaphoreGive
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐