FreeRTOS--队列集

发布时间 2023-12-02 18:42:01作者: 流翎

示例源码基于FreeRTOS V9.0.0

队列集

1. 概述

队列集的本质也是队列,只不过里面存放的是“队列句柄”。

当任务需要及时读取多个队列时,可以使用队列集。它类似于posix的多路复用思想。可以将想要监听消息的队列放入队列集中,当其中有队列有数据达到时,队列集的接口会返回可读的队列句柄,用户获得句柄后,就可以从队列中读取数据。

2. 接口API

2.1 创建队列集

#if( ( configUSE_QUEUE_SETS == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

    QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )
    {
    QueueSetHandle_t pxQueue;

        pxQueue = xQueueGenericCreate( uxEventQueueLength, sizeof( Queue_t * ), queueQUEUE_TYPE_SET );

        return pxQueue;
    }

#endif /* configUSE_QUEUE_SETS */

创建队列集使用xQueueCreateSet接口,仅需传入队列深度参数。使用队列集,需要开启宏configUSE_QUEUE_SETS和configSUPPORT_DYNAMIC_ALLOCATION;

队列集创建实际调用的是xQueueGenericCreate接口,它本质也是个队列,其数据项是队列句柄。

2.2 添加队列到队列集

#if ( configUSE_QUEUE_SETS == 1 )

    BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet )
    {
    BaseType_t xReturn;

        taskENTER_CRITICAL();
        {
            if( ( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer != NULL )
            {
                /* Cannot add a queue/semaphore to more than one queue set. */
                xReturn = pdFAIL;
            }
            else if( ( ( Queue_t * ) xQueueOrSemaphore )->uxMessagesWaiting != ( UBaseType_t ) 0 )
            {
                /* Cannot add a queue/semaphore to a queue set if there are already
                items in the queue/semaphore. */
                xReturn = pdFAIL;
            }
            else
            {
                ( ( Queue_t * ) xQueueOrSemaphore )->pxQueueSetContainer = xQueueSet;
                xReturn = pdPASS;
            }
        }
        taskEXIT_CRITICAL();

        return xReturn;
    }

#endif /* configUSE_QUEUE_SETS */

添加队列到队列集,需要在临界区内操作。同时,待添加的队列,其原先必须不属于任何队列集(pxQueueSetContainer需指向空),且队列需为空。

添加队列到队列集,本质操作是把队列的pxQueueSetContainer指针指向队列集即可。

2.3 删除队列集的队列

#if ( configUSE_QUEUE_SETS == 1 )

    BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet )
    {
    BaseType_t xReturn;
    Queue_t * const pxQueueOrSemaphore = ( Queue_t * ) xQueueOrSemaphore;

        if( pxQueueOrSemaphore->pxQueueSetContainer != xQueueSet )
        {
            /* The queue was not a member of the set. */
            xReturn = pdFAIL;
        }
        else if( pxQueueOrSemaphore->uxMessagesWaiting != ( UBaseType_t ) 0 )
        {
            /* It is dangerous to remove a queue from a set when the queue is
            not empty because the queue set will still hold pending events for
            the queue. */
            xReturn = pdFAIL;
        }
        else
        {
            taskENTER_CRITICAL();
            {
                /* The queue is no longer contained in the set. */
                pxQueueOrSemaphore->pxQueueSetContainer = NULL;
            }
            taskEXIT_CRITICAL();
            xReturn = pdPASS;
        }

        return xReturn;
    } /*lint !e818 xQueueSet could not be declared as pointing to const as it is a typedef. */

#endif /* configUSE_QUEUE_SETS */

待删除的队列,其队列集需存在,且只有空队列才允许从队列集中删除。

删除队列集的队列,需要在临界区内操作。删除的本质操作是把队列的pxQueueSetContainer指针置空。

2.4 等待队列可读

#if ( configUSE_QUEUE_SETS == 1 )

    QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait )
    {
    QueueSetMemberHandle_t xReturn = NULL;

        ( void ) xQueueGenericReceive( ( QueueHandle_t ) xQueueSet, &xReturn, xTicksToWait, pdFALSE ); /*lint !e961 Casting from one typedef to another is not redundant. */
        return xReturn;
    }

#endif /* configUSE_QUEUE_SETS */
/*-----------------------------------------------------------*/

#if ( configUSE_QUEUE_SETS == 1 )

    QueueSetMemberHandle_t xQueueSelectFromSetFromISR( QueueSetHandle_t xQueueSet )
    {
    QueueSetMemberHandle_t xReturn = NULL;

        ( void ) xQueueReceiveFromISR( ( QueueHandle_t ) xQueueSet, &xReturn, NULL ); /*lint !e961 Casting from one typedef to another is not redundant. */
        return xReturn;
    }

#endif /* configUSE_QUEUE_SETS */

FreeRTOS提供了类似posix select操作的xQueueSelectFromSet接口,它等待队列集可读,并返回从队列集读取的数据(可读队列的句柄)。

队列集内的队列句柄,均是可读的队列。当有任务或中断往队列写数据时,队列非空可读,如果开启了configUSE_QUEUE_SETS,会同时把队列句柄置入队列集中(见xQueueGenericSend)。

xQueueSelectFromSet对应的中断版本是xQueueSelectFromSetFromISR。

关于xQueueGenericReceive和xQueueReceiveFromISR以及xQueueGenericSend、xQueueGenericSendFromISR,见 队列

2.5 通知队列可读

#if ( configUSE_QUEUE_SETS == 1 )

    static BaseType_t prvNotifyQueueSetContainer( const Queue_t * const pxQueue, const BaseType_t xCopyPosition )
    {
    Queue_t *pxQueueSetContainer = pxQueue->pxQueueSetContainer;
    BaseType_t xReturn = pdFALSE;

        /* This function must be called form a critical section. */

        configASSERT( pxQueueSetContainer );
        configASSERT( pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength );

        if( pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength )
        {
            const int8_t cTxLock = pxQueueSetContainer->cTxLock;

            traceQUEUE_SEND( pxQueueSetContainer );

            /* The data copied is the handle of the queue that contains data. */
            xReturn = prvCopyDataToQueue( pxQueueSetContainer, &pxQueue, xCopyPosition );

            if( cTxLock == queueUNLOCKED )
            {
                if( listLIST_IS_EMPTY( &( pxQueueSetContainer->xTasksWaitingToReceive ) ) == pdFALSE )
                {
                    if( xTaskRemoveFromEventList( &( pxQueueSetContainer->xTasksWaitingToReceive ) ) != pdFALSE )
                    {
                        /* The task waiting has a higher priority. */
                        xReturn = pdTRUE;
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                pxQueueSetContainer->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        return xReturn;
    }

#endif /* configUSE_QUEUE_SETS */

prvNotifyQueueSetContainer是静态接口,定义在queue.c文件,仅对内使用,不对外开放;

prvNotifyQueueSetContainer在xQueueGenericSend、xQueueGenericSendFromISR、xQueueGiveFromISR、prvUnlockQueue内调用,见 队列

prvNotifyQueueSetContainer支持任务和中断内使用。

通知队列可读的本质操作是将可读队列自身的句柄拷贝入队列集中。

当队列集满则接口返回失败;

当队列cTxLock非锁定时,判断是否有任务阻塞等待从队列集接收数据(等待可读队列),若有则将最高优先级任务从阻塞列表移除,移入就绪等待列表,如果任务的优先级高于当前任务,返回pdTRUE表示需要进行任务切换;

当队列cTxLock锁定时,累加cTxLock计数,待调用prvUnlockQueue时再操作阻塞列表和任务切换;

因为prvNotifyQueueSetContainer可能在中断内使用,所以函数内部不直接唤醒切换任务,而是根据返回值标记,返回pdTRUE表示需要任务切换;