FreeRTOS学习 消息队列

消息队列

FreeRTOS学习仓库地址:https://gitee.com/killerp/free-rtos_-study

消息队列是RTOS的基础数据结构,用于任务之间、任务与中断之间进行数据传递。

没有使用消息队列时,若想要在两个任务之间进行数据传递,那么必须通过全局变量来传递,而在多任务系统中,访问全局变量往往需要用户对资源进行保护,这样就使得编程变得麻烦。

消息队列封装了对共享数据的访问保护,同时还加入了阻塞等待机制。使用户编程时不用去考虑复杂的并发访问。

一、队列的结构

消息队列结构体的定义如下:

  • 消息队列可理解为一个环形的队列,通过pcHead和pcTail将队列的首位连接起来。

  • pcWriteTo和pcReadFrom分别是队列的首部和尾部(若采用默认的FIFO)

  • 队列中每个消息的大小是固定的,一个队列可容纳若干个消息,由消息大小和数量决定队列所占内存的大小

  • 队列有两个链表,分别用于存放因发送/接收消息而进入阻塞的任务。

  • 队列支持锁定,当队列锁定时,中断函数不能修改以上链表,实现对数据的保护。

/*
 * 消息队列 入队使用的是内存复制
 */
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    int8_t * pcHead;           /*< 指向队列内存起始地址. */
    int8_t * pcWriteTo;        /*< 指向队列下一个空闲地址. */

    union
    {
        QueuePointers_t xQueue;    //TODO 当结构体作为队列时  
        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;                //队列上锁时,任务向队列发送的消息数量

    uint8_t ucStaticallyAllocated;      //标记队列的内存分配方式


} xQUEUE;

消息队列可简单的抽象成如下图片:

在同一时间内,只能有一个任务 or 中断在修改队列的链表。是的,队列锁并不能阻止中断向队列复制数据。

在这里插入图片描述

FreeRTOS学习 消息队列_第1张图片

二、创建队列

创建队列的过程与创建任务类似,需要为队列结构体和队列存储区分配内存,并初始化队列结构体的成员变量。

以动态内存分配为例:首先分配内存

    QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue;   //指向新的队列结构体
        size_t xQueueSizeInBytes;   //队列总大小(字节)
        uint8_t * pucQueueStorage;  //指向队列存储区域起始地址

        //队列的长度至少为1
        configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

        //计算队列使用的内存大小
        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
        /* 检查乘法溢出 */
        configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );

        /* 检查溢出. */
        configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) >  xQueueSizeInBytes );

        //todo 申请内存, pvPortMalloc字节对齐问题
        pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); 

        if( pxNewQueue != NULL )
        {
            //队列结构体与队列实际内存区域在同一连续的内存中 所以pucQueueStorage跳过Queue_t
            pucQueueStorage = ( uint8_t * ) pxNewQueue;
            pucQueueStorage += sizeof( Queue_t ); 

            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    pxNewQueue->ucStaticallyAllocated = pdFALSE;
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
			//初始化队列结构体
            prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
        }
        else
        {
            traceQUEUE_CREATE_FAILED( ucQueueType );
            mtCOVERAGE_TEST_MARKER();
        }

        return pxNewQueue;
    }

初始化队列结构体的成员变量,初始化后的内存大概为:

FreeRTOS学习 消息队列_第2张图片

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,
                                   const UBaseType_t uxItemSize,
                                   uint8_t * pucQueueStorage,
                                   const uint8_t ucQueueType,
                                   Queue_t * pxNewQueue )
{
    //移除编译器的警告
    ( void ) ucQueueType;

    //设置队列结构体的成员

    //当队列作为信号量时,uxItemSize为0
    if( uxItemSize == ( UBaseType_t ) 0 )
    {

        //pcHead不能为null,因为pcHead为null表示互斥信号量 所以设为一个确定的值
        pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
    }
    else
    {
        //作为队列,pcHead指向存储区
        pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
    }
    //设置队列长度
    pxNewQueue->uxLength = uxQueueLength;
    pxNewQueue->uxItemSize = uxItemSize;

    //重置队列 
    ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

}

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;

        
        if( xNewQueue == pdFALSE )
        {
            //不是新的队列
            /* xTasksWaitingToSend的任务可以发送消息,而 xTasksWaitingToReceive则继续等待 */
            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();

    return pdPASS;
}

三、发送消息到队列

RTOS中发送消息的函数五花八门,但万变不离其宗,只要掌握了以下这个函数,其他的发送函数都是这个函数的变体。

任务中发送消息

任务调用发送消息函数、在该函数中任务会进入死循环,若任务能成功发送消息,则退出循环。否则,任务就需要进入阻塞,等待队列中出现空闲位置。

同时通过进入临界区来保证当前任务对队列的独占性访问。具体代码逻辑如下:

BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition )
{
    //xEntryTimeSet标记任务是否已经设置过阻塞时间
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut; //阻塞时间结构体
    Queue_t * const pxQueue = xQueue;

    //检查一些错误的用法
    configASSERT( pxQueue );
    configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
    configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    //循环
    for( ; ; )
    {
        //进入临界区 要操作链表
        taskENTER_CRITICAL();
        {

            //队列未满或是覆盖写入时,消息可以写入队列
            if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
            {
                traceQUEUE_SEND( pxQueue );
                
                    {
                        //将队列项复制到队列指定位置
                        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();
                        }
                    }

                taskEXIT_CRITICAL();    //消息发送完成 退出临界区
                return pdPASS;
            }
            else    //队列满不能入队 需要延时 or 退出
            {
                //任务不要求延时 直接退出
                if( xTicksToWait == ( TickType_t ) 0 )
                {

                    taskEXIT_CRITICAL();

                    traceQUEUE_SEND_FAILED( pxQueue );
                    return errQUEUE_FULL;
                }
                else if( xEntryTimeSet == pdFALSE ) //未初始化阻塞
                {
                    
                    //初始化阻塞结构体,用于辅助计算阻塞时间
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                 
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        //到此、任务未能成功发送消息 需要阻塞等待

        //退出临界区后,其他任务或中断可能抢占当前任务(然后向队列获取消息,那么当前任务就有可能发送消息)
        taskEXIT_CRITICAL();
        
        //挂起调度器、防止其他任务访问队列的xTasksWaitingToSend链表
        vTaskSuspendAll();
        //锁住队列 防止中断修改xTasksWaitingToSend链表
        prvLockQueue( pxQueue );

        //检查当前任务的阻塞时间是否到达
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            //阻塞时间未到,且队列中为满 则将当前任务插入队列的WaitingToSend
            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 */
}

FreeRTOS的消息是通过内存复制实现的。过程如图:先从write指针写入消息,再将write指针移动到下一个空闲位置。

FreeRTOS学习 消息队列_第3张图片

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;
    //作为信号量的情况
    if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
    {
        
            {
                //若是互斥信号量的话,说明信号量被释放、需要解除优先级反转
                if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                {
                    
                    xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
                    pxQueue->u.xSemaphore.xMutexHolder = NULL;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
       
    }
    //作为消息队列 发送到队列尾部(writeto)
    else if( xPosition == queueSEND_TO_BACK )
    {
        //内存复制
        ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
        //pcWriteTo增加
        pxQueue->pcWriteTo += pxQueue->uxItemSize;                                                       
        //若pcWriteTo到达内存末尾,则回到内存首部
        if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail )                                            
        {
            pxQueue->pcWriteTo = pxQueue->pcHead;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else  
    {
        //复制到队首(pcReadFrom)
        ( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); 
        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();
        }
        //若是覆盖写入,则有一个消息被覆盖,uxMessagesWaiting-1
        if( xPosition == queueOVERWRITE )
        {
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {

                --uxMessagesWaiting;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    //入队成功
    pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

    return xReturn;
}

中断中发送消息

由于在中断中不能进入阻塞,所以需要有一个函数来另外实现在中断发送消息。与在任务中的区别在于,临界区保护以及当不满足发送条件时,直接退出函数,不会进入延时等待。

同时,在中断中若队列上锁,则不能修改链表。这是因为中断触发时,任务可能正在访问队列链表。

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;

    //检查队列
    configASSERT( pxQueue );
    configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
    configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );

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

            /* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a
             *  semaphore or mutex.  That means prvCopyDataToQueue() cannot result
             *  in a task disinheriting a priority and prvCopyDataToQueue() can be
             *  called here even though the disinherit function does not check if
             *  the scheduler is suspended before accessing the ready lists. */
            //复制数据到队列
            ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

            //若队列没有被锁
            if( cTxLock == queueUNLOCKED )
            {
                    {
                        //如果有任务正在等待数据
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            //将任务从等待链表中移除
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                //标记有更高优先级的任务解锁 退出中断后需要任务调度
                                if( pxHigherPriorityTaskWoken != NULL )
                                {
                                    *pxHigherPriorityTaskWoken = pdTRUE;
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        ( void ) uxPreviousMessagesWaiting;
                    }

            }
            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;
}

四、接收消息

任务中接收消息

接收消息的实现与发送消息类似,同样需要在循环中,成功获取队列的消息则退出,否则进入阻塞,等待消息到来。

代码注释如下:

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

    BaseType_t xEntryTimeSet = pdFALSE;     //标记是否初始化的阻塞时间
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    configASSERT( ( pxQueue ) );

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


    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif


    //循环
    for( ; ; )
    {
        taskENTER_CRITICAL();
        {
            //获取队列中的消息数量
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

            //若队列中有数据
            if( uxMessagesWaiting > ( UBaseType_t ) 0 )
            {
                //复制数据到buffer,消息数量-1
                prvCopyDataFromQueue( pxQueue, pvBuffer );
                traceQUEUE_RECEIVE( pxQueue );
                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    //队列为空
            {
                //不作等待立刻退出
                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();
        //退出临界区后,其他任务或中断可能抢占当前任务(然后向队列发送消息,那么当前任务就能成功获取消息)

        //挂起调度器和锁住队列,防止任务、中断修改队列的链表
        vTaskSuspendAll();
        prvLockQueue( pxQueue );


        //任务阻塞时间未到
        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
            {
                //队列有消息了,重新进入当前循环
                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 */
}

向队列复制消息。在这里需要注意当队列的消息大小为0时,这个队列是信号量,不需要内存的复制,这个部分在下一章中讲信号量说明。

读取消息时,是先将read指针移动,再读取。

FreeRTOS学习 消息队列_第4张图片

static void prvCopyDataFromQueue( Queue_t * const pxQueue,
                                  void * const pvBuffer )
{
    if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )
    {
        //pcReadFrom向前移动一个单元
        pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize;          

        //是否溢出、需要回到队列头
        if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) 
        {
            pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        //内存复制
        ( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize ); 
    }
}


中断中接收消息

同理,在中断中读取消息,当队列中没有消息时,不能进入等待,必须马上退出。

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void * const pvBuffer,
                                 BaseType_t * const pxHigherPriorityTaskWoken )
{
    BaseType_t xReturn;
    UBaseType_t uxSavedInterruptStatus;
    Queue_t * const pxQueue = xQueue;

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

    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    //进入临界区 保存中断状态
    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        //获取消息数量
        const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

        //检查是否有消息
        if( uxMessagesWaiting > ( UBaseType_t ) 0 )
        {
            const int8_t cRxLock = pxQueue->cRxLock;    //获取接收锁

            traceQUEUE_RECEIVE_FROM_ISR( pxQueue );
            //数据有效,复制数据到buffer
            prvCopyDataFromQueue( pxQueue, pvBuffer );
            pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;

            //队列未锁,可以修改队列链表
            if( cRxLock == queueUNLOCKED )
            {
                //消息出队后,队列有空闲的位置,若有任务在等待发送消息,则需要恢复该任务
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    //将等待发送的任务移出
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {

                        //移出的任务优先级更高,需要退出中断后启动任务调度
                        if( pxHigherPriorityTaskWoken != NULL )
                        {
                            //设置返回参数
                            *pxHigherPriorityTaskWoken = pdTRUE;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    else    
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else    //队列上锁,不能操作链表
            {

                configASSERT( cRxLock != queueINT8_MAX );
                //设置cRxLock-1 使队列知道在其锁定的时候有消息被读取
                pxQueue->cRxLock = ( int8_t ) ( cRxLock + 1 );
            }
            //成功获取消息
            xReturn = pdPASS;
        }
        else    //队列为空 立刻返回
        {
            xReturn = pdFAIL;
            traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue );
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

你可能感兴趣的:(FreeRTOS,消息队列,队列,FreeRTOS,实时操作系统,源码)