FreeRTOS--队列

目录

简介:

队列的特征 :

FreeRTOS队列特点:

​编辑

入队阻塞:

出队阻塞:

队列操作的基本过程:

队列结构体介绍:

队列结构体整体示意图:

队列相关API函数介绍:

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

对列写入消息API函数:

从队列读取消息API函数:

队列相关API函数解析:

队列的创建API函数:xQueueCreate()

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

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


简介:

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

其作用与全局变量类似,但又存在差距。

      假设一个全局变量a = 0;现在两个任务都在写这个变量,且均在执行a++程序

                   任务一()                                                         任务二()     

                {                                                                          {

                 a++;                                                                     a++;

                }                                                                           }

       如果任务一与任务二各执行一次,理论上a应该等于2。但是呢a++在c语言中内部又分为多个操作:

       1.寄存器读数据(r0 <= a);

       2.寄存器修改数据(r0 = r0 + 1);

       3.变量读取寄存器数据(r0 => a);

      在裸机操作中,并没有创建的多个任务进行干扰此过程,而在OS中,便会存在这种可能,比如:

任务一执行到a++内部操作的第二步时,被更高优先级的任务二打断,那么会出现什么情况呢?

此时r0寄存器已经读取到了变量a的值,r0+1此时r0的值为1,但是变量a还未从r0中读取数据,而这时候CPU便去执行优先级更高的任务二,任务二同样的也是这三个过程,此时的变量a的值还是0,执行完毕后a = 1;任务二进入阻塞态,CPU又重新回到任务一的第三步,这时候r0 = 1,a赋值后仍然等于1。这样的话,执行了两个任务,但是它的实际效果a=1,造成了数据受损。所以,全局变量在OS中存在一个弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据容易受损。

       那么在队列中,此过程又是怎么实现的呢?

此时任务A与任务B也是同时写队列,是否也会存在干扰呢?然而写队列内部函数的操作中会进入临界区,也就是关中断。

       任务调度是由PendSV引起的,而PendSV的中断优先级在正常情况下设置的是最低的,此时进入临界区关掉了所有的中断,此时PendSV不可能触发。所以任务A在写队列的时候,任务B是不可能打断其运行。此时数据的安全性得到了保障,相同的,读队列也是同样的道理。

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

       FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解FreeRTOS的队列。

队列的特征 :

        在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。(跟c语言中的数组有些类似)

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

FreeRTOS队列特点:

如果设置的阻塞时间为0:且恰好所有的队列项目中均有数据,此时便会直接返回退出不会进行等待;

如果设置的阻塞时间为0~port_MAX_DELAY(0xFFFFFFFF):等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;

如果设置的阻塞时间为port_MAX_DELAY:死等,一直等到队列空出一个位置可以进行入队操作为止。

出队阻塞与入队阻塞类似。

入队阻塞:

此时队列已满,写不进数据:

1.将该任务的状态列表项挂载在pxDelayedTaskList(阻塞列表)中;

2.将该任务的事件列表项挂载在xTaskWaitingToSend(等待发送列表)中;

出队阻塞:

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

1.将该任务的状态列表项挂载在pxDelayedTaskList(阻塞列表)中;

2.将该任务的事件列表项挂载在xTaskWaitingToReceive(等待读取列表)中;

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

答:

       1.优先级最高的任务

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

队列操作的基本过程:

队列结构体介绍:

typedef struct QueueDefinetion
{

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;

当用于队列使用时:

typedef struct QueuePointers

{

    int8_t * pcTail;                                        /*存储区的结束地址*/

    int8_t * pcReadFrom;                             /*最后一个读取队列的地址*/

}QueuePointers_t;

当用于互斥信号量和递归互斥信号量时:

typedef struct SemaphoreData

{
    TaskHandle_t xMutexHolder;                   /*互斥信号量持有者(信号量属于谁)*/

    UBaseType_t uxRecursiveCallCount;      /*递归互斥信号量的获取计数器*/

}SemaphoreData_t;

队列结构体整体示意图:

        pcHead指向起始地址;pcWriteTo(下一次写入的位置)也是从起始地址开始;xQueue.pcTail是结束地址;xQueue.pcReadFrom是最后一个读取的地址; xTasksWaitingToSend为等待发送列表; xTasksWaitingToReceive为等待接收列表;uxMessagesWaiting非空闲队列数目,此时队列项均为空闲,值为0;uxLength队列长度,即队列项数目;uxItemSize一个队列项的大小,此时为32字节;queueUNLOCKED表示两个列锁均未上锁。

队列相关API函数介绍:

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

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

                函数                             描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

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

         此函数用于·使用动态创建队列,队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配,动态创建队列函数xQueueCreate()实际上是一个宏,真正起作用的是xQueueGenericCreate()函数

                       形参                         描述
xQueueLength队列长度
uxItemSize队列项目的大小
                    返回值                                          描述                 
NULL队列创建失败
其他值队列创建成功,返回队列句柄

前面说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的情况)

 

       在这里可以看成两部分,前四个为一部分,在任务中使用,后四个在中断中使用。下面只展示任务集使用的函数,中断函数可查阅相关手册

       四个函数首先都是宏,可以看出这几个写入函数调用的是同一个函数xQueueGenericSend(),只是指定了不同的写入位置,即最后一个参数。最后一个函数没有阻塞时间可以设置,默认为0。覆写指只有一个队列项目,且里面已经存放数据,也可覆盖写入数据,不会进入阻塞。

 队列一共有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    pvltemToQueue,

                                                            TickType_t               xTicksToWait,

                                                            const BaseType_t    xCopyPostion )

                       形参                         描述
xQueue待写入消息的队列
pvItemToQueue待写入消息
xTicksToWait阻塞超时时间
xCopyPostion 写入的位置
                    返回值                                          描述                 
pdTRUE队列写入成功
errQUEUE_FULL队列写入失败

从队列读取消息API函数:

                函数                               描述

xQueueReceive()

从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息

BaseType_t   xQueueReceive( QueueHandle_t      xQueue,

                                                      void * const             pvBuffer,

                                                      TickType_t               xTicksToWait );

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

                  形参                            描述         
xQueue待读取的队列
pvBuffer信息读取缓冲区
xTicksToWait 阻塞超时时间
                    返回值                                          描述                 
pdTRUE读取成功
pdFALSE读取失败

      BaseType_t   xQueuePeek  ( QueueHandle_t      xQueue,

                                                      void * const             pvBuffer,

                                                      TickType_t               xTicksToWait );

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

                  形参                            描述         
xQueue待读取的队列
pvBuffer信息读取缓冲区
xTicksToWait 阻塞超时时间
                    返回值                                          描述                 
pdTRUE读取成功
pdFALSE读取失败

 

队列相关API函数解析:

队列的创建API函数:xQueueCreate()

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

在queue.h文件中,xQueueCreate()函数是以宏定义的形式出现,有两个入口参数,分别是队列长度以及队列项目大小 。实际操作的是xQueueGenericCreate()函数,参数多了一个创建队列类型,queueQUEUE_TYPE_BASE 表示创建的是队列。

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue = NULL;
        size_t xQueueSizeInBytes;
        uint8_t * pucQueueStorage;

        if( ( uxQueueLength > ( UBaseType_t ) 0 ) &&
            /*首先对队列长度进行判断,判断是否大于0 */
            ( ( SIZE_MAX / uxQueueLength ) >= uxItemSize ) &&
            /* 后两个均是判断队列是否溢出 */
            ( ( SIZE_MAX - sizeof( Queue_t ) ) >= ( uxQueueLength * uxItemSize ) ) )
            /* if作用:判断数据合法性*/
        {
            /*计算队列项长度的总大小 */
            xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*一个队列项大 
                                                                    小 * 总的队列项数目 */

            /* 开始申请内存,队列内存内部总共分为两部分,一个是队列结构体存储区,另一个是队列项 
            (消息存储区),申请完内存后把首地址传给pxNewQueue */
            pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*Queue_t 表示结构体存储区, xQueueSizeInBytes 表示队列项存储区*/

            if( pxNewQueue != NULL )/*判断是否申请成功*/
            {
                /*首先让pucQueueStorage等于首地址,然后向上偏移到队列结构体存储区的末尾也就是队 
                  列项的首地址*/
                pucQueueStorage = ( uint8_t * ) pxNewQueue;
                pucQueueStorage += sizeof( Queue_t ); /*此时 pucQueueStorage 等于队列项一的 
                                                        起始地址*/

                #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                    {
                        /* Queues can be created either statically or dynamically, so
                         * note this task was created dynamically in case it is later
                         * deleted. */
                        pxNewQueue->ucStaticallyAllocated = pdFALSE;
                    }
                #endif /* configSUPPORT_STATIC_ALLOCATION */
                /*初始化队列,五个入口参数分别是队列项个数、队列项大小、队列项(消息存储区)首地 
                  址、创建的队列类型、队列首地址*/
                prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
            }
            else
            {
                traceQUEUE_CREATE_FAILED( ucQueueType );
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            configASSERT( pxNewQueue );
            mtCOVERAGE_TEST_MARKER();
        }

        return pxNewQueue;
    }

初始化队列 prvInitialiseNewQueue()函数的解析:

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,
                                   const UBaseType_t uxItemSize,
                                   uint8_t * pucQueueStorage,
                                   const uint8_t ucQueueType,
                                   Queue_t * pxNewQueue )
{
    /* Remove compiler warnings about unused parameters should
     * configUSE_TRACE_FACILITY not be set to 1. */
    ( void ) ucQueueType;
    /*首先判断队列项大小是不是等于0,若等于0,说明此时并非队列而是信号量*/
    if( uxItemSize == ( UBaseType_t ) 0 )
    {
        /*把头地址指向结构体存储区首地址 */
        pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
    }
    else
    {
        /* 若不等于0,把头指针指向消息存储区首地址 */
        pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
    }

    /* 接着赋值队列项长度与大小 */
    pxNewQueue->uxLength = uxQueueLength;
    pxNewQueue->uxItemSize = uxItemSize;
    /*调用函数,复位队列,此时队列可能是新旧队列两种情况,pdTRUE代表新创建的队列*/
    ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

    #if ( configUSE_TRACE_FACILITY == 1 )
        {
            pxNewQueue->ucQueueType = ucQueueType;
        }
    #endif /* configUSE_TRACE_FACILITY */

    #if ( configUSE_QUEUE_SETS == 1 )
        {
            pxNewQueue->pxQueueSetContainer = NULL;
        }
    #endif /* configUSE_QUEUE_SETS */

    traceQUEUE_CREATE( pxNewQueue );
}

复位队列函数xQueueGenericReset()解析:


BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
                               BaseType_t xNewQueue )
{
    BaseType_t xReturn = pdPASS;
    /*将队列首地址赋值*/
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );
    /*判断内存是否申请成功,并且长度大于等于1*/
    if( ( pxQueue != NULL ) &&
        ( pxQueue->uxLength >= 1U ) &&
        /* 判断是否有溢出 */
        ( ( SIZE_MAX / pxQueue->uxLength ) >= pxQueue->uxItemSize ) )
    {
        taskENTER_CRITICAL();/*进入临界区*/
        {
            /*对成员变量进行赋值*/
            pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); /*pcHead此时为消息存储区首地址,加上队列项总长度,此时pcTail 指向队列末尾 */
            pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;/*非空闲队列项数目*/
            pxQueue->pcWriteTo = pxQueue->pcHead; /*下一个要写入的位置,此时指向头*/
            pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); /*头地址+ (队列大小-1)* 队列项大小 ,表示指向最后一个元素*/
            /*队列锁均未上锁*/
            pxQueue->cRxLock = queueUNLOCKED;
            pxQueue->cTxLock = queueUNLOCKED;
              
            /*判断是否为旧队列*/
            if( xNewQueue == pdFALSE )
            {
                /* 如果为旧列表,判断当前等待发送列表是不是有任务在里面,若有任务,则直接移除 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                /* 若为新列表,直接初始化等待发送与等待接收两个列表*/
                vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
                vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
            }
        }
        taskEXIT_CRITICAL();/*退出临界区*/
    }
    else
    {
        xReturn = pdFAIL;
    }

    configASSERT( xReturn != pdFAIL );

    /* 返回一个返回值 */
    return xReturn;
}

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

在任务中往队列写入消息的函数有函数 xQueueSend() 、 xQueueSendToBack() 、 xQueueSendToFront()、xQueueOverwrite(),这 4 个函数实际上都是宏定义,在 queue.h 文件中有 定义,具体的代码如下所示:

#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)

从上面的代码中可以看到,函数 xQueueSend()、函数 xQueueSendToBack()、函数 xQueueSendToFront()和函数 xQueueOverwrite()实际上都是调用了函数 xQueueGenericSend(),只 是指定了不同的写入位置,队列一共有 3 种写入位置,在 queue.h 文件中有定义,具体的代码如下

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

 要注意的是,覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用,这在下文 讲解函数 xQueueGenericSend()时,会提到。 函 数 xQueueGenericSend() 用 于 在 任 务 中 往 队 列 的 指 定 位 置 写 入 消 息 。函数 xQueueGenericSend()在 queue.c 文件中有定义,具体的代码如下所示:

/*四个参数分别是 要写入的队列、要写入的消息、阻塞的时间、写入的位置*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );
    configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );

    /* 这里限制了只有在队列长度为 1 时,才能使用覆写,如果是覆写但队列长度不等于1则会报错*/
    configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    /*lint -save -e904 This function relaxes the coding standard somewhat to
     * allow return statements within the function itself.  This is done in the
     * interest of execution time efficiency. */
    for( ; ; )
    {
        taskENTER_CRITICAL();/*进入临界区*/
        {
            /*判断 非空闲队列项数目 是否小于 队列长度 ,或者入队方式是否为覆写*/
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
            {
                  /* 用于调试,不用理会 */
                traceQUEUE_SEND( pxQueue );
                   /*判断是否为队列集*/
                #if ( configUSE_QUEUE_SETS == 1 )
                    {
                        /* 获取队列中非空闲项目的数量 */
                        const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;
                        /* 将待写入消息按指定写入方式复制到队列中 */
                        xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
                        /* 判断队列是否在队列集中 */
                        if( pxQueue->pxQueueSetContainer != NULL )
                        {
                             /* 写入位置为覆写,且队列非空闲项目数量不为 0 */
                            if( ( xCopyPosition == queueOVERWRITE ) && ( uxPreviousMessagesWaiting != ( UBaseType_t ) 0 ) )
                            {
                                /* Do not notify the queue set as an existing item
                                 * was overwritten in the queue so the number of items
                                 * in the queue has not changed. */
                                mtCOVERAGE_TEST_MARKER();
                            }
                            /* 通知队列集 */
                            else if( prvNotifyQueueSetContainer( pxQueue ) != pdFALSE )
                            {
                                /* 根据需要进行任务切换 */
                                queueYIELD_IF_USING_PREEMPTION();
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        /* 队列不在队列集中 */
                        else
                        {
                            /* 队列的读取阻塞任务列表非空 */
                            if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                            {
                                 
                                /* 将队列读取阻塞任务从所在列表移除
                                  * 因为此时队列中已有可用消息*/

                                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                                {
                                    /* 根据需要进行任务切换 */
                                    queueYIELD_IF_USING_PREEMPTION();
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else if( xYieldRequired != pdFALSE )
                            {
                                /*在互斥信号量释放完且任务优先级恢复后需要进行任务切换 */

                                queueYIELD_IF_USING_PREEMPTION();
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                    }

                #else /*若为队列操作*/
                    {
                        /*将消息写入到队列存储区域的指定位置,三个参数:目标队列、传入的数据、
                           拷贝的位置 (尾部,头部或覆写)*/
                        xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                        /* 判断等待接收列表里面是否有任务,队列有阻塞的读取任务 * */
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {    /*将读取阻塞任务从队列读取任务阻塞列表中移除,
                               因为此时,队列中已经有非空闲的项目了*/
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                /*有任务解除阻塞后需要根据任务的优先级进行任务切换*/
                                queueYIELD_IF_USING_PREEMPTION();
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else if( xYieldRequired != pdFALSE )
                        {
                            /*在互斥信号量释放完且任务优先级恢复后,
                              需要进行任务切换*/
                            queueYIELD_IF_USING_PREEMPTION();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configUSE_QUEUE_SETS */

                taskEXIT_CRITICAL();/*退出临界区*/
                return pdPASS;/*写入队列成功*/
            }
            /*若此时队列已满并且不是覆写,此时将会进入阻塞*/
            else
            {
                 /*判断阻塞时间是否为0*/
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /* 退出临界区*/
                    taskEXIT_CRITICAL();

                    /* 用于调试,不用理会 */
                    traceQUEUE_SEND_FAILED( pxQueue );
                    return errQUEUE_FULL;/*返回队列已满*/
                }
                 /*若阻塞时间不等于0*/
                else if( xEntryTimeSet == pdFALSE )
                {
                    /*队列满,任务需要阻塞,记录下此时系统节拍
                     计数器的值和溢出次数用于下面对阻塞时间进行补偿 */
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
         /* 退出临界区
          * 退出临界区后系统时钟节拍会发生更新,
          * 因此任务如果需要阻塞的话,
          * 需要对阻塞时间进行补偿*/
        taskEXIT_CRITICAL();

        /* 挂起任务调度器 */
        vTaskSuspendAll();
        /* 队列上锁 */
         prvLockQueue( pxQueue );

        /*判断阻塞时间补偿后,是否还需要阻塞 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
             /* 阻塞时间补偿后,还需要进行阻塞 */
            if( prvIsQueueFull( pxQueue ) != pdFALSE )
            {
                 /* 用于调试,不用理会 */
                traceBLOCKING_ON_QUEUE_SEND( pxQueue );
                 /* 将任务添加到队列写入阻塞任务列表中进行阻塞 */
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );

                /*  解锁队列  */
                prvUnlockQueue( pxQueue );

                /* 恢复任务调度器 */
                if( xTaskResumeAll() == pdFALSE )
                {
                    /* 根据需要进行任务切换 */
                    portYIELD_WITHIN_API();
                }
            }
            else
            {
                /*  队列解锁 */
                prvUnlockQueue( pxQueue );
                /* 恢复任务调度器 */
                ( void ) xTaskResumeAll();
            }
        }
         /* 阻塞时间补偿后,已不需要阻塞 */
        else
        {
            /* 解锁队列  */
            prvUnlockQueue( pxQueue );
            /* 恢复任务调度器 */
            ( void ) xTaskResumeAll();

            /* 用于调试,不用理会 */
            traceQUEUE_SEND_FAILED( pxQueue );
            /* 返回队列满错误 */
            return errQUEUE_FULL;
        }
    } /*lint -restore */
}

 写入消息函数prvCopyDataToQueue()解析:

 

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
                                      const void * pvItemToQueue,
                                      const BaseType_t xPosition )
{
    BaseType_t xReturn = pdFALSE;
    UBaseType_t uxMessagesWaiting;

    /* 获取队列项非空闲的数目 */

    uxMessagesWaiting = pxQueue->uxMessagesWaiting;

    /*判断队列项大小是否等于0,等于0为信号量*/
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
    {
        #if ( configUSE_MUTEXES == 1 )
            {
                if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                {
                    /* The mutex is no longer being held. */
                    xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
                    pxQueue->u.xSemaphore.xMutexHolder = NULL;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        #endif /* configUSE_MUTEXES */
    }
    /*写入位置如果是尾部插入(往上走)*/
    else if( xPosition == queueSEND_TO_BACK )
    {
        ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*pcWriteTo表示下次写入位置,初始状态指向头,将数据pvItemToQueue拷贝到 
                         第一个队列项里面,大小为一个队列项大小*/
        /*更新下次写入位置*/
        pxQueue->pcWriteTo += pxQueue->uxItemSize;                                                    
             
          /*如果下次写入等于尾部地址 */
        if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail )                                             
        {
            /*又回到队列项头部地址*/
            pxQueue->pcWriteTo = pxQueue->pcHead;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    /*头部写入或覆写(往下走)*/
    else
    {
        ( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*pcReadFrom 指向最后一个队列项元素,赋值*/
         /*偏移pcReadFrom */
        pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;

        if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) /*如果下个地址小于头部地址*/
        {
            /*回到末尾元素地址*/
            pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
         /*如果写入方式为覆写*/
        if( xPosition == queueOVERWRITE )
        {

            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {
                /*若非空闲大于0,先让他减去1 */
                --uxMessagesWaiting;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    /*非空闲数目加1*/
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

    return xReturn;
}

 

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

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。 消息的读取是通过拷贝的形式传递的,具体拷贝数据的大小,为队列项目的大小。 该函数的函 数原型如下所示:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    /* Check the pointer is not NULL. */
    configASSERT( ( pxQueue ) );

    /* The buffer into which data is received can only be NULL if the data size
     * is zero (so no data is copied into the buffer). */
    configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );

    /* Cannot block if the scheduler is suspended. */
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    /*lint -save -e904  This function relaxes the coding standard somewhat to
     * allow return statements within the function itself.  This is done in the
     * interest of execution time efficiency. */
    for( ; ; )
    {
        taskENTER_CRITICAL();/*进入临界区*/
        {
            /*获取当前非空闲队列项数目*/
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

            /*判断当前队列是否有数据*/
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {
                /*拷贝数据到 pvBuffer 里面 */
                prvCopyDataFromQueue( pxQueue, pvBuffer );
                traceQUEUE_RECEIVE( pxQueue );

                /*非空闲数目减1*/
                pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;

                /* 如果等待发送列表有任务,则解除掉*/
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                /*退出临界区*/
                taskEXIT_CRITICAL();
                return pdPASS;
            }
            /*若当前队列里面并没有数据*/
            else
            {    
                /*判断阻塞时间是否为0*/
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /*退出临界区*/
                    taskEXIT_CRITICAL();
                    /**/
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    /*返回读取失败*/
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE )
                {
                    /* 记录系统节拍计数值以及溢出次数*/
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();

        /* Interrupts and other tasks can send to and receive from the queue
         * now the critical section has been exited. */

        vTaskSuspendAll();
        prvLockQueue( pxQueue );

        /*检查阻塞时间是否已超时,pdFALSE 未超时 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            /*判断队列是否为空 */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
                /*任务列表项挂载到等待接收列表里面*/
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );            
                  /*解锁队列*/
                prvUnlockQueue( pxQueue );

                /*如果需要任务切换*/
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                /* The queue contains data again.  Loop back to try and read the
                 * data. */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        /*阻塞时间已过*/
        else
        {
            /*解锁,恢复任务调度器 */
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();

            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    } /*lint -restore */
}

 拷贝数据函数prvCopyDataFromQueue()解析:

static void prvCopyDataFromQueue( Queue_t * const pxQueue,
                                  void * const pvBuffer )
{
    /*如果队列项大小不等于零*/
    if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
    {
        pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize; /* pcReadFrom 初始化指向最后 
                                                 一个元素,加一个列表项大小,指向队列末尾*/

        if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) /*如果指向末尾地址*/
        {
            pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;/*pcReadFrom 指向队列项头*/
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
           /*队列项1的数据拷贝到pvBuffer里面 */
        ( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值