会飞的猪_2020 发表于 2024-3-26 10:02:41

[FreeRTOS学习记录] 队列queue

本帖最后由 会飞的猪_2020 于 2024-3-26 10:08 编辑

看到freertos里面的消息通知方式有,notify,eventgroup,queue,semaphore。不清楚它们的区别是什么啊。

所以昨天开始去读信号量的源代码,发现他这个怎么没有.c文件,里面貌似是用queue实现的,然后就从最开始的queue.c这个源文件看起。
个人的理解如下,可能有错误。

队列的动态创建方法如下:
struct AMessage
{
    char ucMessageID;
    char ucData[ 20 ];
};

void vATask( void *pvParameters )
{
QueueHandle_t xQueue1, xQueue2;

    /* Create a queue capable of containing 10 unsigned long values. */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    if( xQueue1 == NULL )
    {
      /* Queue was not created and must not be used. */
    }

    /* Create a queue capable of containing 10 pointers to AMessage
    structures.These are to be queued by pointers as they are
    relatively large structures. */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    if( xQueue2 == NULL )
    {
      /* Queue was not created and must not be used. */
    }

    /* ... Rest of task code. */
}

我们看xQueueCreate的实现:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 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;

                configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

                if( uxItemSize == ( UBaseType_t ) 0 )
                {
                        /* There is not going to be a queue storage area. */
                        xQueueSizeInBytes = ( size_t ) 0;
                }
                else
                {
                        /* Allocate enough space to hold the maximum number of items that
                        can be in the queue at any time. */
                        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                }

                pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

                if( pxNewQueue != NULL )
                {
                        /* Jump past the queue structure to find the location of the queue
                        storage area. */
                        pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + 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 );
                }

                return pxNewQueue;
      }

#endif /* configSUPPORT_STATIC_ALLOCATION */

结合网络上一张图,这个queue的结构体图示如下:


左边的是XQUEUE,右边的是pucQueueStorage
如果是动态申请的,这两片内存是连在一起的,只是pcHead去指向了pucQueueStorage:
从下面这行代码就可以看出,malloc的时候申请了"queue_t + xQueueSizeInBytes"的大小
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
然后
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

所以申请的的那个pxNewQueue,前面是XQUEUE,后面是pucQueueStorage

prvInitialiseNewQueue初始化了queue。
/*-----------------------------------------------------------*/

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 )
      {
                /* No RAM was allocated for the queue storage area, but PC head cannot
                be set to NULL because NULL is used as a key to say the queue is used as
                a mutex.Therefore just set pcHead to point to the queue as a benign
                value that is known to be within the memory map. */
                pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
      }
      else
      {
                /* Set the head to the start of the queue storage area. */
                pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
      }

      /* Initialise the queue members as described where the queue type is
      defined. */
      pxNewQueue->uxLength = uxQueueLength;
      pxNewQueue->uxItemSize = uxItemSize;
      ( 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 )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

        configASSERT( pxQueue );

        taskENTER_CRITICAL();
        {
                pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
                pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
                pxQueue->pcWriteTo = pxQueue->pcHead;
                pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
                pxQueue->cRxLock = queueUNLOCKED;
                pxQueue->cTxLock = queueUNLOCKED;

                if( xNewQueue == pdFALSE )
                {
                        /* If there are tasks blocked waiting to read from the queue, then
                        the tasks will remain blocked as after this function exits the queue
                        will still be empty.If there are tasks blocked waiting to write to
                        the queue, then one should be unblocked as after this function exits
                        it will be possible to write to it. */
                        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
                {
                        /* Ensure the event queues start in the correct state. */
                        vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
                        vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
                }
        }
        taskEXIT_CRITICAL();

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




会飞的猪_2020 发表于 2024-3-26 10:51:18

初始化完成之后,pcWriteTo 指针指向pcHead,而pcReadFrom指针指向pucQueueStorage的最后一个节点。


会飞的猪_2020 发表于 2024-3-26 11:09:38

xQueueSend的代码比较长,我们可以看到xQueueSend的宏定义如下:

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

xQueueGenericSend前两个参数,第一个是队列,第二个要发动到队列的Item。第三个是一个内部参数queueSEND_TO_BACK 。我还没把所有代码看完,不确定它这样设计的作用是什么。
目前如果是xQueueSend的话,这里的宏定义默认是queueSEND_TO_BACK 。

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;

        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


        /* 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();
                {
                        /* Is there room on the queue now?The running task must be the
                        highest priority task wanting to access the queue.If the head item
                        in the queue is to be overwritten then it does not matter if the
                        queue is full. */
                        if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
                        {
                                traceQUEUE_SEND( pxQueue );
                                xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

                                #if ( configUSE_QUEUE_SETS == 1 )
                                {
                                        if( pxQueue->pxQueueSetContainer != NULL )
                                        {
                                                if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) != pdFALSE )
                                                {
                                                        /* The queue is a member of a queue set, and posting
                                                        to the queue set caused a higher priority task to
                                                        unblock. A context switch is required. */
                                                        queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
                                                        mtCOVERAGE_TEST_MARKER();
                                                }
                                        }
                                        else
                                        {
                                                /* 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 )
                                                        {
                                                                /* The unblocked task has a priority higher than
                                                                our own so yield immediately.Yes it is ok to
                                                                do this from within the critical section - the
                                                                kernel takes care of that. */
                                                                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();
                                                }
                                        }
                                }
                                #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 )
                                                {
                                                        /* The unblocked task has a priority higher than
                                                        our own so yield immediately.Yes it is ok to do
                                                        this from within the critical section - the kernel
                                                        takes care of that. */
                                                        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 )
                                {
                                        /* 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 )
                        {
                                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;
                }
        }
}
/*-----------------------------------------------------------*/

前面的assert是判断参数的合法性,taskENTER_CRITICAL这个是判断是否在中断内调用,如果在中断内的话,要用FromISR后缀的。

在下面的代码里,它先用pxQueue->uxLength和pxQueue->uxMessagesWaiting比较,判断是否有空闲的位置。
如果有空闲的位置,或者函数入参的第三个参数为queueOVERWRITE的时候(我估计这个就是允许覆盖的意思),就去调用prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

在prvCopyDataToQueue里面,把pvItemToQueue复制到了pxQueue->pcWriteTo下,并且增加了pxQueue->pcWriteTode指针,
                ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. */
                pxQueue->pcWriteTo += pxQueue->uxItemSize;
                if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
                {
                        pxQueue->pcWriteTo = pxQueue->pcHead;
                }
                else
                {
                        mtCOVERAGE_TEST_MARKER();
                }

从这里也可以看出,FreeRTOS的队列用了复制,数据是复制到空间的,所以可以用局部变量之类的。
它文档里自己说这样做有很多好处,并且如果数据量比较大的话,也可以把指针复制到队列里传送,它的FreeRTOS-Plus UDP里面就是那样做的。

然后继续往下看代码,它这里用了一个pxQueueSetContainer这个变量,它只有configUSE_QUEUE_SETS == 1的时候才会被用到。

去看了一下文档,这个configUSE_QUEUE_SETS 是一个叫队列集的功能:https://www.freertos.org/zh-cn-cmn-s/Pend-on-multiple-rtos-objects.html






会飞的猪_2020 发表于 2024-3-26 15:24:02

写了那么多发现网络上有个博主的博客,有对freeRTOS源码的解析,写的比我详细多了。

我很多细节,因为才开始看,没弄明白。

大家可以参考一下这个博主的博文
https://www.cnblogs.com/lizhuming/p/16344076.html
页: [1]
查看完整版本: [FreeRTOS学习记录] 队列queue