FreeRTOS队列原理

1. 队列

队列是为了任务与任务、任务与中断之间通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项。创建队列的时候需要指定数据项目的大小和队列的长度。

1.1 数据存储

●队列采用的是先进先出(FIFO)的缓存机制

往队列发送数据(入队):永远发送到队列的尾部

从队列提取数据(出队):永远从队列头部提取

FreeRTOS也提供了后进后出(LIFO)数据缓存机制

●队列将要发送的数据复制到队列中(值传递)

队列中存储的是数据的原始值,而非数据的指针(引用),当然也可以使用指针传递。

1.2 多任务访问

队列不属于某个特别指定的任务,任何任务都可以向队列中发送消息或者从队列中提取消息

1.3 出队阻塞

出队就是指的是从队列中读取消息的任务,阻塞是指当任务尝试从一个队列中读取消息时,可以指定一个阻塞时间。比如任务A从队列Q中读取数据,如果此时队列Q为空,那么任务A有三种选择:

1.直接返回:阻塞时间设置为0

2.等待一会儿:等待时间为设置的阻塞时间

3.一直等待,直到有数据才会返回:阻塞时间被设置为了portMAX_DELAY

值得一提的是,当多个任务读取空队列时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?

●优先级最高的任务

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

1.4 入队阻塞

入队阻塞和出队阻塞基本上一样,就是入队时如果队列已满,其处理方法和出队一样。

2. 队列结构体

typedef struct QueuePointers
{
    int8_t * pcTail;     /*< 用作队列时指向最后一个出队的队列项的末尾地址*/
    int8_t * pcReadFrom; /*< 用作队列时指向最后一个出队的队列项首地址 */
} QueuePointers_t;

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;                 /*<  队列项的大小,int、char、short或其他*/

    volatile int8_t cRxLock;/*< 当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量 */
    volatile int8_t cTxLock;/*<当队列上锁以后用来统计发送到队列中队列项的数量,也就是入队的队列项数量  */

    #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; /*< 如果使用静态存储,此字段设置为pdTRUE */
    #endif

    #if ( configUSE_QUEUE_SETS == 1 )/*< 队列集相关宏 */
        struct QueueDefinition * pxQueueSetContainer;
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif
} xQUEUE;
typedef xQUEUE Queue_t;

3. 队列创建

队列的创建有两种方法:动态分配内存、静态分配内存

●动态分配内存:xQueueCreate,队列的内存在函数内部动态分配,实际调用:xQueueGenericCreate()

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
返回值 非0:成功,返回句柄,以后使用句柄来操作队列NULL:失败,因为内存不足
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
ucQueueType 队列类型。由于FreeRTOS中的信号量等也是通过队列来实现的,创建信号量的函数最终也是使用此函数的,因此,在创建的时候需要指定此队列的用途,也就是队列类型,一共有6种类型
返回值 非0:成功,返回句柄,以后使用句柄来操作队列NULL:失败,因为内存不足
参数 说明
queueQUEUE_TYPE_BASE 普通的队列消息
queueQUEUE_TYPE_SET 队列集
queueQUEUE_TYPE_MUTEX 互斥信号量
queueQUEUE_TYPE_COUNTING_SEMAPHORE 计数型信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX 递归信号量
  •  静态分配内存:xQueueCreateStatic,队列的内存要事先分配好,实际调用:xQueueGenericCreateStatic()
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

QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength,
                                             const UBaseType_t uxItemSize,
                                             uint8_t * pucQueueStorage,
                                             StaticQueue_t * pxStaticQueue,
                                             const uint8_t ucQueueType )

  •  队列创建函数分析(动态创建)
//队列动态创建函数
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue;
        size_t xQueueSizeInBytes;
        uint8_t * pucQueueStorage;

        configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

       //分配足够的存储区,确保随时随地都可以保存所有的消息,队列长度*队列项大小
        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); 

        /* Check for multiplication overflow. */
        configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );

        /* Check for addition overflow. */
        configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) >  xQueueSizeInBytes );

        //申请内存,申请的是队列结构体和队列中消息存储区的总大小
        pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); 

        if( pxNewQueue != NULL )
        {
            //计算出消息存储区的首地址,申请到的内存是队列结构体和队列中消息存储区的总大小,队列结构体内存在前,紧跟在					//后面的就是消息存储区内存。
            pucQueueStorage = ( uint8_t * ) pxNewQueue;
            //指针偏移,计算消息存储区的首地址
            pucQueueStorage += sizeof( Queue_t );

            #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();
        }

        return pxNewQueue;
    }

//队列初始化函数
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;

    if( uxItemSize == ( UBaseType_t ) 0 )
    {
        /*  队列项长度为0,说明没有队列存储区,这里将pcHead指向队列开始地址*/
        pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
    }
    else
    {
        /* 队列项长度不为0,设置pcHead指向队列存储区的首地址*/
        pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
    }

    /*初始化队列结构体相关成员变量 */
    pxNewQueue->uxLength = uxQueueLength;
    pxNewQueue->uxItemSize = uxItemSize;
    ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

    #if ( configUSE_TRACE_FACILITY == 1 )
        {
            pxNewQueue->ucQueueType = ucQueueType;
        }
    #endif /* 跟踪调式相关字段初始化 */

    #if ( configUSE_QUEUE_SETS == 1 )
        {
            pxNewQueue->pxQueueSetContainer = NULL;
        }
    #endif /* 队列集相关初始化 */

    traceQUEUE_CREATE( pxNewQueue );
}

//队列复位函数
BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
                               BaseType_t xNewQueue )
{
    Queue_t * const pxQueue = xQueue;

    configASSERT( pxQueue );

    taskENTER_CRITICAL();
    {
        pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
        pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
        pxQueue->pcWriteTo = pxQueue->pcHead;
        pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); 
        pxQueue->cRxLock = queueUNLOCKED;
        pxQueue->cTxLock = queueUNLOCKED;
		//根据xNewQueue字段确定该队列是否是新创建的队列,如果不是需要进行下面的处理
        if( xNewQueue == pdFALSE )
        {
            
            /* 由于复位队列以后队列依旧是空的,所以对于那些由于出队(从队列中读取消息)
             * 而阻塞的任务就依旧保持阻塞状态。但是对于那些由于入队(向队列中发送消息)
             * 而阻塞的任务就不同了,这些任务要解除阻塞状态,从队列的相应列表中移除*/
            if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
            {
                //如果将阻塞任务从xTasksWaitingToSend列表中移除之后触发了任务切换,则进行任务切换
                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();

    /* A value is returned for calling semantic consistency with previous
     * versions. */
    return pdPASS;
}

 队列初始化完成之后的结构如下所示:

4. 向队列发送消息

 可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

/* 等同于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
                                   );
/* 覆盖队列,队列长度必须为1
 * xQueue: 写哪个队列
 * pvItemToQueue: 数据地址
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueOverwrite(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue
                      );

BaseType_t xQueueOverwriteFromISR(
                           QueueHandle_t xQueue,
                           const void * pvItemToQueue,
                           BaseType_t *pxHigherPriorityTaskWoken
                      );

 这些函数的参数都类似,这里统一说明一下:

yH5BAAAAAAALAAAAAAOAA4AAAIMhI+py+0Po5y02qsKADs=wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

下面讲解一下任务级入队函数xQueueGenericSend()函数(通用入队函数,上面的非中断入队函数最终都是调用的这个): 

//入队函数
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,//队列句柄
                              const void * const pvItemToQueue,//指向要发送的消息,发送过程中会将消息复制到队列中
                              TickType_t xTicksToWait,//阻塞时间
                              const BaseType_t xCopyPosition )//入队方式:1.后向入队 2.前向入队 3.覆写入队
{
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    for( ; ; )
    {
        taskENTER_CRITICAL();//进入临界区
        {
            /* 查询队列是否还有剩余的空间,如果采用覆写的方式入队则不用在乎队列是不是满的*/
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
            {
                traceQUEUE_SEND( pxQueue );

                #if ( configUSE_QUEUE_SETS == 1 )//队列集相关
                    ........省略相关代码.......
                    }
                #else /* configUSE_QUEUE_SETS */
                    {
                        //将消息复制到队列中,重点说明一下
                        //如果选择后向入队:将消息复制到队列结构成员pcWriteTo指向的队列项;复制成功后pcWriteTo增加 							//uxItemSize个字节,指向下一个队列项目
                        //如果选择前向入队或者是覆写时:将消息复制到u.pcReadFrom所指向的队列项目;同样复制成功后其向前
                        //移动uxItemSize个字节
                        //最后当消息写入成功之后,会将uxMessagesWaiting加一
                        xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                        /* 检查是否有任务由于等待消息而进入阻塞状态 */
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            //因为前面已经向队列发送了一条消息,所以接下来将阻塞任务从xTasksWaitingToReceive上移除并
                            //将其添加到就绪列表中;如果调度器已经暂停,则这些任务就会挂到列表xPendingReadyList上
                            //如果取消阻塞的任务优先级比当前正在运行的任务的优先级高,则标记需要任务切换,即返回值为pdTRUE
                           if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {//解除阻塞态任务优先级最高,因此要进行一次任务切换
                                queueYIELD_IF_USING_PREEMPTION();
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else if( xYieldRequired != pdFALSE )
                        {
                            /* This path is a special case that will only get
                             * executed if the task was holding multiple mutexes and
                             * the mutexes were given back in an order that is
                             * different to that in which they were taken. */
                            queueYIELD_IF_USING_PREEMPTION();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configUSE_QUEUE_SETS */

                taskEXIT_CRITICAL();
                return pdPASS;
            }
            else//队列满的情况并且不采用覆写的方式
            {
                if( xTicksToWait == ( TickType_t ) 0 )
                {//阻塞时间为0,队列已满,之间返回
                   
                    taskEXIT_CRITICAL();
                    traceQUEUE_SEND_FAILED( pxQueue );
                    return errQUEUE_FULL;
                }
                else if( xEntryTimeSet == pdFALSE )
                {//阻塞时间不为0,初始化时间结构体,记录当前系统时钟节拍计数器的值xTickCount和溢出次数xNumOfOverflows
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();

        /* 任务执行到这里说明当前的状况是队列已满,而且设置了不为0的超时时间 */
		//调度器暂停,先处理当前任务,队列上锁
        vTaskSuspendAll();
        prvLockQueue( pxQueue );

        /* 更新时间状态,检查是否有超时产生 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            //超时时间未到,队列已满
            if( prvIsQueueFull( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_SEND( pxQueue );
                //将当前任务添加到等待发送队列和延时列表中,并且将任务从就绪列表中移除
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );

                /* Unlocking the queue means queue events can effect the
                 * event list.  It is possible that interrupts occurring now
                 * remove this task from the event list again - but as the
                 * scheduler is suspended the task will go onto the pending
                 * ready last instead of the actual ready list. */
                prvUnlockQueue( pxQueue );//解锁队列

                /* Resuming the scheduler will move tasks from the pending
                 * ready list into the ready list - so it is feasible that this
                 * task is already in a ready list before it yields - in which
                 * case the yield will not cause a context switch unless there
                 * is also a higher priority task in the pending ready list. */
                if( xTaskResumeAll() == pdFALSE )//恢复任务调度器
                {
                    portYIELD_WITHIN_API();
                }
            }
            else
            {//队列存在空闲队列项
                /* Try again. */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else
        {//超时产生,解锁队列,恢复调度
            /* The timeout has expired. */
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();

            traceQUEUE_SEND_FAILED( pxQueue );
            return errQUEUE_FULL;
        }
    } /*lint -restore */
}

中断级通用入队函数:xQueueGenericSendFromISR().

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
                                     const void * const pvItemToQueue,
                                     BaseType_t * const pxHigherPriorityTaskWoken,//标记退出此函数后是否进行任务切换
                                     const BaseType_t xCopyPosition )
{
    BaseType_t xReturn;
    UBaseType_t uxSavedInterruptStatus;
    Queue_t * const pxQueue = xQueue;

    
    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
        {//队列未满或者是采用覆写入队的方式
            const int8_t cTxLock = pxQueue->cTxLock;//判断队列是否上锁
            const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;

            traceQUEUE_SEND_FROM_ISR( pxQueue );

            
            ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

            /* 队列上锁的时候不能操作时间列表,队列解锁的时候会补上这些操作 */
            if( cTxLock == queueUNLOCKED )//队列未上锁
            {
                #if ( configUSE_QUEUE_SETS == 1 )
                   
                ......省略队列集..............
                #else /* configUSE_QUEUE_SETS */
                    {
                    	//判断接收列表是否为空,不为空则说明有任务在请求消息的时候被阻塞了
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                /* The task waiting has a higher priority so record that a
                                 * context switch is required. */
                                if( pxHigherPriorityTaskWoken != NULL )
                                {//标记表示要进行任务切换
                                    *pxHigherPriorityTaskWoken = pdTRUE;
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        /* Not used in this path. */
                        ( void ) uxPreviousMessagesWaiting;
                    }
                #endif /* configUSE_QUEUE_SETS */
            }
            else
            {
                /* cTxLock加一,这样就知道在队列上锁期间向队列发送了数据 */
                configASSERT( cTxLock != queueINT8_MAX );

                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }

            xReturn = pdPASS;
        }
        else
        {//队列已满,入队失败,直接返回
            traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
            xReturn = errQUEUE_FULL;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

5. 队列上锁和解锁

  • 队列上锁函数:prvLockQueue().
#define prvLockQueue( pxQueue )                            \
    taskENTER_CRITICAL();                                  \
    {                                                      \
        if( ( pxQueue )->cRxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
        if( ( pxQueue )->cTxLock == queueUNLOCKED )        \
        {                                                  \
            ( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
        }                                                  \
    }                                                      \
    taskEXIT_CRITICAL()
//上锁函数只是一个宏,本质上就是将cRxLock,cTxLock初始化为queueLOCKED_UNMODIFIED
  •  队列解锁函数:
static void prvUnlockQueue( Queue_t * const pxQueue )
{
      //上锁计数器(cTxLock 和 cRxLock)记录了在队列上锁期间入队或出队的数量,当队列
        //上锁以后,队列项是可以加入或者移除队列的,但是相应的列表不会更新*/
    taskENTER_CRITICAL();
    {
        int8_t cTxLock = pxQueue->cTxLock;

        /* See if data was added to the queue while it was locked. */
        while( cTxLock > queueLOCKED_UNMODIFIED )
        {
            /* Data was posted while the queue was locked.  Are any tasks
             * blocked waiting for data to become available? */
            #if ( configUSE_QUEUE_SETS == 1 )

            #else /* configUSE_QUEUE_SETS */
                {
                    /* Tasks that are removed from the event list will get added to
                     * the pending ready list as the scheduler is still suspended. */
                    if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                    {
                        if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                        {
                            /*如果刚刚从列表 xTasksWaitingToReceive 移除的任务优先级比当前任务的优 先 级 高,那 么 就 要 标 记需 要 进行任 务 切换。这 里 调 用 函 数vTaskMissedYield()来完成此任务,函数 vTaskMissedYield()只是简单地将全局变量xYieldPending 设置为 pdTRUE,那么真正的任务切换是在哪里完成的呢? 在时钟节拍处理函数 xTaskIncrementTick()中,此函数会判断 xYieldPending 的值,从而决定是否进行任务切换
 */
                            vTaskMissedYield();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            #endif /* configUSE_QUEUE_SETS */
            //处理完一条就减一,直到处理完所有
            --cTxLock;
        }

        pxQueue->cTxLock = queueUNLOCKED;
    }
    taskEXIT_CRITICAL();

    /* Do the same for the Rx lock. */
    taskENTER_CRITICAL();
    {//处理cRxLock
        int8_t cRxLock = pxQueue->cRxLock;

        while( cRxLock > queueLOCKED_UNMODIFIED )
        {
            if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
            {
                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                {
                    vTaskMissedYield();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                --cRxLock;
            }
            else
            {
                break;
            }
        }

        pxQueue->cRxLock = queueUNLOCKED;
    }
    taskEXIT_CRITICAL();
}

 6. 从队列读取消息

/* 从队列中读取队列项,完成以后删除掉队列项
 * xQueue: 读取哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );
/* 从队列中读取队列项,完成以后删除掉队列项,中断中使用
 * xQueue: 读取哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );
/* 偷看队列,完成以后不会删除掉队列项
 * xQueue: 偷看哪个队列
 * pvItemToQueue: 数据地址, 用来保存复制出来的数据
 * xTicksToWait: 没有数据的话阻塞一会
 * 返回值: pdTRUE表示成功, pdFALSE表示失败
 */
BaseType_t xQueuePeek(
                          QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait
                      );

BaseType_t xQueuePeekFromISR(
                                 QueueHandle_t xQueue,
                                 void *pvBuffer,
                             );

猜你喜欢

转载自blog.csdn.net/qq_40648827/article/details/127656119