FreeRTOS学习:消息队列

堆是向上生长,栈是向下生长。 

队列简介

队列介绍

        队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制,简单来说就是用来传递消息的。

在裸机中虽然我们用全局变量也可以进行传递消息,但是由于FreeRTOS我们创建多个任务时,全局变量会出现在其他任务中修改的问题,也就是数据是无保护易受损的

通过读写队列进行消息传递的话,FreeRTOS对读写队列已经做好了保护,可以防止多任务同时访问的冲突(其实就是加入了临界区)。

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

队列示意图如下,

 创建队列时,需要规定队列的长度和每个队列项目的大小。

队列特点

  • 数据入队出队:队列通常采用 FIFO(先进先出)的存储缓冲机制,,先入队的先读出(First Input First Output),当然也可以配置成LIFO方式。
  • 数据传递:FreeRTOS采用实际值传递,也就是拷贝数据传递,当然,也可以传递指针(传递大数据)。
  • 多任务访问:队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列中读取消息。
  • 出入队阻塞:在任务从队列读取消息时,可以指定一个阻塞超时时间,出入队阻塞相似
    •  阻塞时间为0:直接返回不等待   
    •  阻塞时间0-port_MAX_DELAY:等待设定的阻塞时间,超时直接返回
    •  阻塞时间port_MAX_DELAY:死等,一直等到入队为止   

备注:当多个任务给一个满队列写数据,优先级高的先进入就绪态,优先级相同的话,等待时间最久的任务进入就绪态。(出队时也一样)

队列操作过程 

队列操作过程分为四步,如下,

队列结构体

队列结构体的结构如下所示,

typedef struct QueueDefinition
{
    int8_t * pcHead; /* 存储区域的起始地址 */
    int8_t * pcWriteTo; /* 下一个写入的位置 */

    /* 信号量是由队列实现的,
    * 此结构体能用于队列和信号量,
    * 当用于队列时,使用联合体中的 xQueue,
    * 当用于信号量时,使用联合体中的 xSemaphore
    */
    union
    {
        QueuePointers_t xQueue;
        SemaphoreData_t xSemaphore;
    } u;

    List_t xTasksWaitingToSend; /* 写入阻塞任务列表,也叫等待发送列表 */
    List_t xTasksWaitingToReceive; /* 读取阻塞任务列表,等待接收列表 */

    volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */

    UBaseType_t uxLength; /* 队列的长度 */
    UBaseType_t uxItemSize; /* 队列项目的大小 */

    /* 锁用于在任务因队列操作被阻塞前,防止中断或其他任务操作队列。
    * 上锁期间,队列可以写入和读取消息,但不会操作队列阻塞任务列表,
    * 当有消息写入时, cTxLock 加 1,当有消息被读取时, cRxLock 加 1,
    * 在解锁时,会统一处理队列的阻塞任务列表
    */

    volatile int8_t cRxLock; /* 读取上锁计数器 */
    volatile int8_t cTxLock; /* 写入上锁计数器 */

    /*其他一些条件编译*/
} xQUEUE;

        

        在队列的结构中,有两个联合体 QueuePointers_t和 SemaphoreData_t分别表示用于队列还是信号量,他们的结构分别如下,

typedef struct QueuePointers
{
    int8_t * pcTail; /* 存储区域的结束地址 */
    int8_t * pcReadFrom; /* 最后一次读取队列的位置 */
} QueuePointers_t;
typedef struct SemaphoreData
{
    TaskHandle_t xMutexHolder; /* 互斥信号量的持有者 */
    UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量被递归获取计数器 */
} SemaphoreData_t;

总体的队列示意图如下,

队列相关函数

创建队列 

xQueueCreate():

#define xQueueCreate(   uxQueueLength,
                        uxItemSize ) 

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


参数

  • uxQueueLength:队列长度
  • uxItemSize:队列项目大小

     返回值:如果队列创建成功,则返回队列起始地址;创建失败,返回NULL。

可以看到,队列创建函数xQueueCreate()是基于xQueueGenericCreate()函数创建的,在这个函数中多了一个宏 queueQUEUE_TYPE_BASE,用来代表创建不同功能,介绍如下,

#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 ) /* 递归互斥信号量 */

队列写入消息

 

对于队列写入函数,看起来很多,其实分为两类,任务级和中断级,尾缀是FromISR的表示中断级的。

函数如下,

队列写入消息的方式分为三种,尾部写入,头部写入和覆写。

从上述的队列写入消息函数来看,我们可以看到,所有的写于函数都是调用同一个函数xQueueGenericSend(),只是指定了不同的写入位置。

参数

  • xQueue:待写入队列
  • pvItemToQueue:待写入的消息
  • xTicksToWait:阻塞超时时间

返回值为pdTRUE时,则表示队列写入成功;否则表示写入失败。 

备注:覆写时,队列中只有一个队列项目,写入消息时将之前的消息覆盖,所以不需要阻塞时间。 

队列读取消息 

xQueueReceive():

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

参数:

  • xQueue:待读取的队列
  • pvBuffer:消息读取缓冲区,其实就是一个内存
  • xTicksToWait:阻塞超时时间

返回pdTRUE表示读取成功;反之读取失败。 

函数xQueuePeek()的参数和xQueueReceive()一样,区别就是,此函数在成功读取消息后,并不会移除已读取的消息。

函数内部实现

队列创建

队列创建函数内部总结如下,

在前三步以后,最后调用函数prvInitialiseNewQueue()初始化新队列pxNewQueue,过程如下,

  1. 初始化其他队列结构体成员变量
  2. 判断要复位的队列是否为新创建的队列,

 

队列消息写入

 队列消息写入时,首先进入临界区(关闭中断),然后判断队列是否已满,如果有空闲位置的话,进行下面的操作,

在函数xTaskRemoveFromEventList中,判断调度器是否挂起,如下,

如果队列已满的话,进行以下操作,

队列消息读取

        队列消息读取时,首先进入临界区(关闭中断),然后判断队列是否为空,如果有数据的话,进行下面的操作,

 

在函数xTaskRemoveFromEventList中,判断调度器是否挂起,如下,

当没有数据,也就是为空时,进行以下操作,

以上就是三个API函数内部源码总结,建议朋友们还是对照源码阅读效果更好哈!!

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值