硬汉嵌入式论坛

 找回密码
 立即注册
查看: 291|回复: 3
收起左侧

[FreeRTOS] [FreeRTOS学习记录] 队列queue

[复制链接]

39

主题

196

回帖

323

积分

高级会员

积分
323
发表于 2024-3-26 10:02:41 | 显示全部楼层 |阅读模式
本帖最后由 会飞的猪_2020 于 2024-3-26 10:08 编辑

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

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

队列的动态创建方法如下:
[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的实现:
[C] 纯文本查看 复制代码
#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的结构体图示如下:
Untitled.png

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

然后
[C] 纯文本查看 复制代码
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );


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

prvInitialiseNewQueue初始化了queue。
[C] 纯文本查看 复制代码
/*-----------------------------------------------------------*/

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 代码如下:
[C] 纯文本查看 复制代码
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;
}





回复

使用道具 举报

39

主题

196

回帖

323

积分

高级会员

积分
323
 楼主| 发表于 2024-3-26 10:51:18 | 显示全部楼层
初始化完成之后,pcWriteTo 指针指向pcHead,而pcReadFrom指针指向pucQueueStorage的最后一个节点。


回复

使用道具 举报

39

主题

196

回帖

323

积分

高级会员

积分
323
 楼主| 发表于 2024-3-26 11:09:38 | 显示全部楼层
xQueueSend的代码比较长,我们可以看到xQueueSend的宏定义如下:

[C] 纯文本查看 复制代码
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )


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

[C] 纯文本查看 复制代码
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指针,
[C] 纯文本查看 复制代码
		( 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






回复

使用道具 举报

39

主题

196

回帖

323

积分

高级会员

积分
323
 楼主| 发表于 2024-3-26 15:24:02 | 显示全部楼层
写了那么多发现网络上有个博主的博客,有对freeRTOS源码的解析,写的比我详细多了。

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

大家可以参考一下这个博主的博文
https://www.cnblogs.com/lizhuming/p/16344076.html
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-1 15:23 , Processed in 0.289633 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表