FreeRTOS学习笔记(三)—— 消息队列,信号量,事件标志组,任务通知
文章目录
FreeRTOS学习笔记系列
【FreeRTOS(MDK、STM32CUBEIDE)学习笔记,超详细!!!(合集)】
【FreeRTOS学习笔记(一)—— 裸机和RTOS,Freertos移植(MDK),stm32cubeIDE使用Freertos】
【FreeRTOS学习笔记(二)—— 任务,挂起,临界区,中断,任务调度和切换】
【FreeRTOS学习笔记(三)—— 消息队列,信号量,事件标志组,任务通知】
【FreeRTOS学习笔记(四)—— 延时函数,列表,软件定时器,低功耗模式,内存管理】
1、消息队列(集)
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损
队列
概念
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
在队列中可以存储数量有限、大小固定的数据。
在创建队列时,就要指定 队列长度 以及 队列项目的大小 !
队列项目:队列中的每一个数据
队列长度 :队列能够存储 “队列项目” 的最大数量
FreeRTOS队列特点
- 1、数据入队出队方式
队列通常采用“先进先出”(FIFO)的数据存储缓冲机制
也可以配置为“后进先出”LIFO方式 - 2、数据传递方式
FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递
可以传递指针,在传递较大的数据的时候采用指针传递 - 3、多任务访问
队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息 - 4、出队、入队阻塞
当任务向一个队列发送消息时,可以指定一个阻塞时间
假设此时当队列已满无法入队
①若阻塞时间为0:直接返回不会等待;
②若阻塞时间为O~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
③若阻塞时间为port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
阻塞的时候多个任务等待同一个队列的空间
当队列中有空间时,优先级最高的任务 先进入就绪态
如果优先级相同,等待时间最久的任务 先进入就绪态
结构体
/* 消息队列结构体 */
typedef struct QueueDefinition
{
int8_t * pcHead /* 存储区域的起始地址 */
int8_t * pcWriteTo; /* 下一个写入的位置 */
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u ;
List_t xTasksWaitingToSend; /* 等待发送列表 */
List_t xTasksWaitingToReceive; /* 等待接收列表 */
volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */
UBaseType_t uxLength; /* 队列长度 */
UBaseType_t uxItemSize; /* 队列项目的大小 */
volatile int8_t cRxLock; /* 读取上锁计数器 */
volatile int8_t cTxLock; /* 写入上锁计数器 */
…………/* 其他的一些条件编译 */
} xQUEUE;
/* 当用于队列使用时 */
typedef struct QueuePointers
{
int8_t * pcTail; /* 存储区的结束地址 */
int8_t * pcReadFrom; /* 最后一个读取队列的地址 */
} QueuePointers_t;
/* 当用于互斥信号量和递归互斥信号量时 */
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 互斥信号量持有者 */
UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;
API函数
函数 | 描述 |
---|---|
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建队列 |
xQueueSend() | 往队列的尾部写入消息 |
xQueueSendToBack() | 同 xQueueSend() |
xQueueSendToFront() | 往队列的头部写入消息 |
xQueueOverwrite() | 覆写队列消息(只用于队列长度为1的情况) |
xQueueSendFromISR() | 在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() | 同 xQueueSendFromISR() |
xQueueSendToFrontFromISR() | 在中断中往队列的头部写入消息 |
xQueueOverwriteFromISR() | 在中断中覆写队列消息(只用于队列长度为1的情况) |
xQueueReceive() | 从队列头部读取消息,并删除消息 |
xQueuePeek() | 从队列头部读取消息 |
xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() | 在中断中从队列头部读取消息 |
一个队列只允许传递同一种数据类型的消息
一个队列只允许传递同一种数据类型的消息
一个队列只允许传递同一种数据类型的消息
如果需要传递不同数据类型的消息,就需要使用队列集 !
队列集
对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让任务退出阻塞状态
对应的宏:configUSE_QUEUE_SETS
API函数
函数 | 描述 |
---|---|
xQueueCreateSet() | 创建队列集 |
xQueueAddToSet() | 队列添加到队列集中 |
xQueueRemoveFromSet() | 从队列集中移除队列 |
xQueueSelectFromSet() | 获取队列集中有有效消息的队列 |
xQueueSelectFromSetFromISR() | 在中断中获取队列集中有有效消息的队列 |
注意!!!,添加到队列集的队列,必须没有有效的消息
注意!!!,添加到队列集的队列,必须没有有效的消息
注意!!!,添加到队列集的队列,必须没有有效的消息
注意!!!,从队列集移除的队列,必须没有有效的消息
注意!!!,从队列集移除的队列,必须没有有效的消息
注意!!!,从队列集移除的队列,必须没有有效的消息
2、信号量
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问
信号量:用于传递状态
当计数值大于0,代表有信号量
当释放信号量,信号量计数值(资源数)加一
当获取信号量,信号量计数值(资源数)减一
资源信号量的计数值都有限定最大值
二值信号量:限定最大值被限定为1
计数型信号量:限定最大值不是1
二值信号量
二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况
函数 | 描述 |
---|---|
xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
计数型信号量
计数型信号量相当于队列长度大于1 的队列
函数 | 描述 |
---|---|
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
互斥信号量
对应的宏:configUSE_MUTEXES
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。
优先级继承并不能完全的消除优先级翻转的问题,只是尽可能的降低优先级翻转带来的影响
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
互斥信号量不能用于中断服务函数
互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级
中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态
PS:优先级继承
当一个互斥信号量正在被一个低优先级的任务持有时
如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞
不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
一定程度上降到了优先级翻转的危害
PS:优先级翻转
什么是优先级反转?
优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时
该信号量已被一低优先级任务占有
造成高优先级任务被许多中优先级任务阻塞,实时性难以保证
而且中优先任务比高优先级任务先执行。
通俗来讲:一个高优先级任务(H任务)正在执行,但是执行过程中高优先级要使用的资源的信号量被一个低优先级(L任务)占用,这时候高优先级任务只能进入阻塞,将cpu使用权给低优先级,等低优先级任务级释放信号量,低优先级任务继续执行任务,不巧的是,低优先级任务还没有释放信号量,中优先级任务(M任务)进入就绪态,然后抢占了低优先级任务的cpu使用权,此时必须先执行完中优先级任务。此时没有任何瓜葛的中优先级任务和高优先级任务执行先后顺序就翻转了
操作系统如何解救优先级反转?
优先级天花板(priority ceilings)
指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。
(这个优先级称为该资源的优先级天花板) 。
这种方法简单易行, 不必进行复杂的判断
不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。
通俗来讲:就是将低优先级任务(L任务)的优先级暂时提升,优先级会提升至定义的天花板优先级(大于或等于H任务的优先级)。这个天花板优先级通常高于所有在该信号量上可能阻塞的任务优先级,如此中优先级任务就没有机会抢占cpu使用权了
3、事件标志组
事件标志位:用一个位,来表示事件是否发生
事件标志组是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
使用了 32 位无符号的数据类型变量来存储事件标志
高8位:用作存储事件标志组的控制信息
低24位:用作存储事件标志
一个事件组最多可以存储 24 个事件标志
函数 | 描述 |
---|---|
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
xEventGroupSync() | 设置事件标志位,并等待事件标志位 |
4、任务通知
任务通知:用来通知任务的
任务结构体的成员变量——任务控制块TCB
任务控制块结构体两个重要成员变量:通知值,通知状态
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 /* 定义任务通知数组的大小, 默认: 1 */
typedef struct tskTaskControlBlock
{
… …
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue [ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 通知值 */
volatile uint8_t ucNotifyState [ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 通知状态 */
#endif
… …
} tskTCB;
跟队列、信号量、事件标志组相比
相似
合理灵活的利用任务通知,部分情况可以替代队列、信号量、事件标志组
任务通知值的更新方式如下:
- 不覆盖接受任务的通知值(队列)
- 覆盖接受任务的通知值(队列)
- 更新接受任务通知值的一个或多个bit(事件标志组)
- 增加接受任务的通知值(信号量)
区别
队列、信号量、事件标志组通讯需要中间媒介(对应的结构体)
任务通知不需要中间媒介,任务结构体TCB包含了内部对象,可以直接接收别人发过来的"通知"
优劣
优
效率更高:使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多
使用内存更小:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
劣
无法发送数据给ISR:ISR没有任务结构体,无法接受数据。但是ISR可以发数据给任务。
无法广播给多个任务:任务通知只能指定的一个任务来接收
无法缓存多个数据:任务通知通过更新任务通知值来发送数据,任务结构体中只有一个任务通知值,只能保持一个数据。
发送受阻不支持阻塞:发送方无法进入阻塞状态等待
API函数
函数 | 描述 |
---|---|
xTaskNotify() | 发送通知,带有通知值 (队列、信号量、事件标志组) |
xTaskNotifyAndQuery() | 发送通知,带有通知值,保留接收任务的原通知值(队列、信号量、事件标志组) |
xTaskNotifyGive() | 发送通知,不带通知值(信号量) |
xTaskNotifyFromISR() | 在中断中发送通知,带有通知值 |
xTaskNotifyAndQueryFromISR() | 在中断中发送通知,带有通知值,保留接收任务的原通知值 |
vTaskNotifyGiveFromISR() | 在中断中发送通知,不带通知值 |
uITaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候,将任务通知值清零或者减一。(信号量) |
xTaskNotifyWait() | 获取任务通知,可获取 通知值 和 清除通知值的指定位(队列、事件标志组) |
┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈ ┈┈┈┈▕▔╲┈┈┈┈┈┈┈┈
┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈┈┈┈┈▏▕┈┈┈┈┈┈┈ ┈
┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈┈┈┈┈▏ ▕▂▂▂▂▂┈┈┈
▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈ ▂▂▂▂╱┈┈▕▂▂▂▂▏┈┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈ ▉▉▉┈┈┈┈▕▂▂▂▂▏ ┈
▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈ ▔▔▔▔╲▂▂▕▂▂▂▂▏┈┈