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

FreeRTOS学习 --- 消息队列

队列简介

        队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递

         全局变量的弊端数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损

        使用队列的情况如下:

 

        读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用!

        FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、

二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。

        在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。

        在创建队列时,就要指定队列长度以及队列项目的大小!

FreeRTOS队列特点:

1、数据入队出队方式

        队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;

2、数据传递方式

        FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递

3、多任务访问

        队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息

4、出队、入队阻塞

        当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队

        ① 若阻塞时间为0  :直接返回不会等待;
        ② 若阻塞时间为0~port_MAX_DELAY  :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
        ③ 若阻塞时间为port_MAX_DELAY  :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

入队阻塞:

        

队列满了,此时写不进去数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToSend

出队阻塞:

队列为空,此时读取不了数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToReceive

问题:当多个任务写入消息给一个满队列时,这些任务都会进入阻塞状态,也就是说有多个任务    在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?

答:

          1、优先级最高的任务

          2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态

队列结构体介绍

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函数介绍

        使用队列的主要流程:创建队列 ---> 写队列 ---> 读队列。

创建队列相关API函数介绍:

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

         动态和静态创建队列之间的区别:队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。

创建队列函数入口参数解析:

#define xQueueCreate (  uxQueueLength,   uxItemSize  )   			 \					
	   xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) 

        此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配

        前面说 FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:

#define queueQUEUE_TYPE_BASE                  	( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  	( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 	( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       	( ( uint8_t ) 4U )	/* 递归互斥信号量 */

往队列写入消息API函数:

函数

描述

xQueueSend()

往队列的尾部写入消息

xQueueSendToBack()

xQueueSend()

xQueueSendToFront()

往队列的头部写入消息

xQueueOverwrite()

覆写队列消息(只用于队列长度为 1 的情况)

xQueueSendFromISR()

在中断中往队列的尾部写入消息

xQueueSendToBackFromISR()

xQueueSendFromISR()

xQueueSendToFrontFromISR()

在中断中往队列的头部写入消息

xQueueOverwriteFromISR()

在中断中覆写队列消息(只用于队列长度为 1 的情况)

 队列写入消息:

#define  xQueueSend(  xQueue,   pvItemToQueue,   xTicksToWait  )	 					\    
	    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define  xQueueSendToBack(  xQueue,   pvItemToQueue,   xTicksToWait  )					 \    
	    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define  xQueueSendToFront(  xQueue,   pvItemToQueue,   xTicksToWait  ) 					\   
	    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define  xQueueOverwrite(  xQueue,   pvItemToQueue  ) 								\    
	    xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

        可以看到这几个写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置!

        队列一共有 3 种写入位置 :

#define queueSEND_TO_BACK         ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT        ( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE            ( ( BaseType_t ) 2 )		/* 覆写队列*/

        注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用

往队列写入消息函数入口参数解析:

BaseType_t  xQueueGenericSend(  QueueHandle_t 	    xQueue,
                                const void * const 	pvItemToQueue,
                                TickType_t 		    xTicksToWait,
                                const BaseType_t 	xCopyPosition   );

从队列读取消息API函数:

函数

描述

xQueueReceive()

从队列头部读取消息,并删除消息

xQueuePeek()

从队列头部读取消息

xQueueReceiveFromISR()

在中断中从队列头部读取消息,并删除消息

xQueuePeekFromISR()

在中断中从队列头部读取消息

队列读取消息函数入口参数解析:

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

        此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。

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

        此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息

队列相关API函数工作流程具体解析:

创建队列:xQueueCreate( )

        实际执行的是xQueueGenericCreate( )

xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

        1、计算队列需要多大内存 xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize )

        2、为队列申请内存,申请大小:sizeof( Queue_t ) + xQueueSizeInBytes ,前面部分存放结构体成员,后面就是队列项大小

        3、判断内存是否申请成功,成功即计算出队列项存储区的首地址

        4、调用prvInitialiseNewQueue()初始化新队列pxNewQueue

                (1)、初始化队列结构体成员变量

                (2)、调用xQueueGenericReset()复位队列

                        1)、初始化其他队列结构体成员变量

                        2)、判断要复位的队列是否为新创建的队列

                                不是新创建队列,那就复位它,将列表xTasksWaitingToSend移除,是新创建的队列,那就初始化这两个列表xTasksWaitingToSend和xTasksWaitingToReceive

往队列写入数据(入队):xQueueSend( )

        实际执行的是:xQueueGenericSend( )

xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

        1、进入临界区(关中断)

        2、判断队列是否已满?

        3、队列有空闲位置
                (1)、只有在队列有空闲位置或为覆写的情况才能写入消息

                (2)、当有空闲位置或覆写时:将待写入消息按指定写入方式复制到队列中

                (3)、判断是否有因为读不到消息而阻塞的任务,有的话,将解除阻塞态,通过这个函数

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理
                (4)、退出临界区(开中断)

        3、队列已满

                (1)、此时不能写入消息,因此要将任务阻塞

                (2)、如果阻塞时间为0 ,代表不阻塞,直接返回队列满错误

                (3)、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿(补偿就是计算剩余的阻塞时间还有多久)

                (4)、判断阻塞时间补偿后,是否还需要阻塞

                        需要:将任务的事件列表项添加到等待发送列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器。不需要:队列解锁,恢复调度器,返回队列满错误。

                        不需要:队列解锁,恢复调度器,返回队列满错误
 

  从队列读取数据(出队):xQueueReceive( )

        1、进入临界区(关中断)

        2、判断队列是否为空

        3、有数据

                (1)、使用函数prvCopyDataFromQueue( )拷贝数据

                (2)、队列项目个数减一

                (3)、因为前面已经减了一个队列项,所以队列已经有空位了,如果xTasksWaitingToSend等待发送列表中,有任务,则解除阻塞态,通过这个函数xTaskRemoveFromEventList( )

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理

                (4)、退出临界区(开中断)

        4、为空

                (1)、此时读取不到消息,因此要将任务阻塞

                (2)、如果阻塞时间为0 ,代表不阻塞,直接返回队列空错误

                (3)、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿

                (4)、判断阻塞时间补偿后,是否还需要阻塞

                        需要:将任务的事件列表项添加到等待接收列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器。

                        不需要:队列解锁,恢复调度器,返回队列空错误


http://www.kler.cn/a/530114.html

相关文章:

  • 操作系统和中间件的信息收集
  • 本地部署DeepSeek教程(Mac版本)
  • 小程序项目-购物-首页与准备
  • 【Python-办公自动化】实现自动化输出json数据类型的分析报告和正逆转换
  • 无心剑七绝《深度求索》
  • Linux中 端口被占用如何解决
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.11 视图与副本:内存优化的双刃剑
  • leetcode 2856. 删除数对后的最小数组长度
  • 【TypeScript】扩展:装饰器
  • 在Arm芯片苹果Mac系统上通过homebrew安装多版本mysql并解决各种报错,感谢deepseek帮助解决部分问题
  • Python 原子操作:使用 `atomic` 模块保证线程安全
  • web前端12--表单和表格
  • 科技快讯 | 领英“隐私风波”告一段落;华为余承东智驾 1345 公里返工,称智界 R7 打赢“鸡蛋保卫战”;谷歌翻译将增“提问”功能
  • 利用Spring Batch简化企业级批处理应用开发
  • 【漫话机器学习系列】075.隐含层(Hidden Layer)
  • Git如何避免推送.idea文件夹
  • 使用 vllm 搭建推理加速大模型服务
  • OpenAI 实战进阶教程 - 第二节:生成与解析结构化数据:从文本到表格
  • 想品客老师的第天:类
  • Java集合+并发(部分)
  • MultiResUNet学习笔记(2019 Neural Networks【SCI 1区】)
  • 用结构加法3ax+1预测第4点的分布
  • 掌握Spring MVC异常处理的艺术
  • ICLR 2025收录论文:为什么动作分块对于机器人灵活性至关重要?
  • makailio-alias_db模块详解
  • 蓝桥杯备考:六大排序算法