FreeRTOS入门总结
目录
一.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

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 |
更多推荐
所有评论(0)