FreeRTOS 原理 --- 队列

发布时间 2023-10-02 17:04:32作者: 流水灯

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

队列的优点:

  • 不同任务之间的读写队列操作是互斥的(通过关中断实现)
  • 读写队列由阻塞唤醒机制,阻塞的任务不抢占CPU资源(比如读队列,发现队列空,阻塞当前任务,除非其他任务有写队列,否则当前任务不再占用CPU资源)

队列的核心:

  • 关中断实现互斥
  • 唤醒缓冲区保存数据
  • 链表实现阻塞唤醒

队列相关的API

xQueueCreate()  // 创建队列
xQueueSend() // 写队列
xQueueReceive() // 读队列 
...

 

代码分析

xQueueCreate()

创建一个队列描述符和buffer,并初始化,buffer占用的内存紧跟描述符后面

队列描述符如下:

typedef struct QueueDefinition
{
    int8_t *pcHead;                    // 指向队列头地址
    int8_t *pcTail;                    /*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
    int8_t *pcWriteTo;                // 指向下一个要写入的消息内存地址,初始化为队列头地址

    union                            /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
    {
        int8_t *pcReadFrom;            // 指向上一个读的消息内存地址,初始化为队列尾地址
        UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
    } u;

    List_t xTasksWaitingToSend;        // 链表,记录哪些任务等待消息写入队列
    List_t xTasksWaitingToReceive;    // 链表,记录哪些任务等待队列空,从而把消息写入队列

    volatile UBaseType_t uxMessagesWaiting;// 当前队列包含多少个消息
    UBaseType_t uxLength;            // 队列容量,能包含多少个消息
    UBaseType_t uxItemSize;            // 每个消息所占字节数

    volatile int8_t cRxLock;        /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */
    volatile int8_t cTxLock;        /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated;    /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if ( configUSE_QUEUE_SETS == 1 )
        struct QueueDefinition *pxQueueSetContainer;
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif

} xQUEUE;

 

xQueueSend()

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 = ( Queue_t * ) xQueue;

    /* 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 ) ) // 队列非满
            {
                xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); // 消息拷贝到队列

                #if ( configUSE_QUEUE_SETS == 1 )
                {
                }
                #else /* configUSE_QUEUE_SETS */
                {
                    /* If there was a task waiting for data to arrive on the
                    queue then unblock it now. */
                    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 // 队列满,无法写入消息
            {
                if( xTicksToWait == ( TickType_t ) 0 ) // 不阻塞,直接返回
                {
                    /* The queue was full and no block time is specified (or
                    the block time has expired) so leave now. */
                    taskEXIT_CRITICAL();

                    /* Return to the original privilege level before exiting
                    the function. */
                    traceQUEUE_SEND_FAILED( pxQueue );
                    return errQUEUE_FULL;
                }
                else if( xEntryTimeSet == pdFALSE ) // 阻塞,记录下时间
                {
                    /* The queue was full and a block time was specified so
                    configure the timeout structure. */
                    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 );

        /* Update the timeout state to see if it has expired yet. */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) // 设置的等待时间未结束
        {
            if( prvIsQueueFull( pxQueue ) != pdFALSE )
            {
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); // 当前任务的消息没写入队列,需要把当前任务记录到链表中,等队列非满再唤醒,同时把当前任务从就绪链表移到阻塞链表

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