FreeRTOS queue (1)


1. Introduction to Queue

Queues are prepared for communication between tasks and between tasks and interrupts. Messages can be transmitted between tasks and between tasks and between tasks and interrupts. Limited data items of fixed size can be stored in the queue. The data to be exchanged between tasks and tasks, tasks and interrupts is stored in queues, called queue items. The maximum number of data items that a queue can hold is called the length of the queue. When creating a queue, the size of the data items and the length of the queue are specified. Because the queue is used to transmit messages, it is also called a message queue. The semaphore in FreeRTOS is also implemented according to the queue! So it is necessary to have a deep understanding of FreeRTOS queues.

1. Data storage

Usually the queue adopts the first-in-first-out (FIFO) storage buffer mechanism, that is, when sending data to the queue (also called entering the queue), it is always sent to the end of the queue, and when extracting data from the queue (also called going out of the queue) is extracted from the head of the queue. But you can also use LIFO's storage buffer, that is, last in, first out, and the queue in FreeRTOS also provides LIFO's storage buffer mechanism.

Sending data to the queue will cause data copying, that is, copying the data to be sent to the queue, which means that the original value of the data is stored in the queue, not the reference of the original data (that is, only the pointer to the data is passed) , this is also called passing by value. Students who have studied UCOS should know that the message queue of UCOS uses reference passing, and the message pointer is passed. If passing by reference is used, the content of the message must always remain visible, that is, the content of the message must be valid, so local variables, which may be deleted at any time, cannot be used to pass messages, but passing by reference will save time! Because there is no need to copy data. Although passing by value will result in data copying, it will waste a little time, but once the message is sent to the queue, the original data buffer can be deleted or overwritten, so that these buffers can be reused. If you use a queue to pass messages in FreeRTOS, although you use data copy, you can also use references to pass messages. I can just send the address pointer pointing to this message directly to the queue! In this way, when the message data I want to send is too large, I can directly send the address pointer of the message buffer. For example, in the network application environment, the data volume of the network is often very large, and it is unrealistic to use data copy.

2. Multitasking Access

The queue does not belong to a specific task, and any task can send messages to the queue or extract messages from the queue.

3. Dequeue blocking

When a task tries to read a message from a queue, a blocking time can be specified. This blocking time is the time when the task is blocked when the message read from the queue is invalid. Dequeue is to read messages from the queue, and dequeue blocking is for the task of reading messages from the queue. For example, task A is used to process the data received by the serial port. After the serial port receives the data, it will be put into the queue Q, and task A will read the data from the queue Q. But if the queue Q is empty at this time, it means that there is no data yet. If task A comes to read at this time, it will definitely not get anything, so what should we do? Task A now has three options, one: turn around and leave without further ado, two: I’ll wait and see later, maybe there will be data in a while, three: wait for death, and wait until death You have data! Which one to choose is determined by the blocking time, and the blocking time unit is the number of clock ticks. If the blocking time is 0, it will not block. If there is no data, it will immediately return to the task and continue to execute the next code, corresponding to the first option. If the blocking time is 0~portMAX_DELAY, when the task does not get a message from the queue, it will enter the blocking state. The blocking time specifies the time for the task to enter the blocking state. When the blocking time expires, if no data is received, it will exit the blocking state. The return task then runs the following code. If the data is received within the blocking time, it will return immediately and execute the following code in the task. This situation corresponds to the second option. When the blocking time is set to portMAX_DELAY, the task will enter the blocking state and wait until the data is received! This is the third option.

4. Enqueue blocking

Joining the queue means sending a message to the queue and adding the message to the queue. Like dequeue blocking, when a task sends a message to the queue, the blocking time can also be set. For example, if task B sends a message to message queue Q, but the queue Q is full at this time, it must fail to send. At this time, task B will encounter the same problem as task A above. The processing process of these two cases is similar, except that one is to send messages to queue Q, and the other is to read messages from queue Q.

5. Queue operation process diagram

The following pictures briefly demonstrate the process of entering and exiting the queue.
● Create a queue
insert image description here
In the figure above, task A wants to send a message to task B, and this message is the value of variable x. First create a queue, and specify the length of the queue and the length of each message. Here we create a queue with a length of 4, because the value of x is to be passed, and x is a variable of type int, so the length of each message is the length of type int, which is 4 bytes in STM32, that is, each Messages are 4 bytes.

● Send the first message to the queue
insert image description here
● Send the second message to the queue
insert image description here
In the figure above, task A sends another message to the queue, which is the new value of x, which is 20 here. At this time, the remaining length of the queue is 2.

● Read messages from the queue In
insert image description here
the figure above, task B reads messages from the queue and assigns the read message value to y, so that y is equal to 10. After task B reads the message from the queue, it can choose to clear the message or not. When you choose to clear this message, other tasks or interrupts will not be able to get this message, and the remaining size of the queue will be increased by one to become 3. If it is not cleared, other tasks or interrupts can also get this message, and the remaining size of the queue is still 2.

2. Queue structure

There is a structure used to describe the queue, called Queue_t, this structure is defined in the file queue.c as follows:

typedef struct QueueDefinition
{
    
    
	int8_t *pcHead; //指向队列存储区开始地址。
	int8_t *pcTail; //指向队列存储区最后一个字节。
	int8_t *pcWriteTo; //指向存储区中下一个空闲区域。
	
	union
	{
    
    
		int8_t *pcReadFrom; //当用作队列的时候指向最后一个出队的队列项首地址
		UBaseType_t uxRecursiveCallCount;//当用作递归互斥量的时候用来记录递归互斥量被
		//调用的次数。
	} u;
	List_t xTasksWaitingToSend; //等待发送任务列表,那些因为队列满导致入队失败而进
	//入阻塞态的任务就会挂到此列表上。
	List_t xTasksWaitingToReceive; //等待接收任务列表,那些因为队列空导致出队失败而进
	//入阻塞态的任务就会挂到此列表上。
	volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
	UBaseType_t uxLength; //创建队列时指定的队列长度,也就是队列中最大允许的
	//队列项(消息)数量
	UBaseType_t uxItemSize; //创建队列时指定的每个队列项(消息)最大长度,单位字节
	volatile int8_t cRxLock; //当队列上锁以后用来统计从队列中接收到的队列项数
	//量,也就是出队的队列项数量,当队列没有上锁的话此字
	//段为 queueUNLOCKED
	volatile int8_t cTxLock; //当队列上锁以后用来统计发送到队列中的队列项数量,
	//也就是入队的队列项数量,当队列没有上锁的话此字
	//段为 queueUNLOCKED
	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&\
	( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
	uint8_t ucStaticallyAllocated;//如果使用静态存储的话此字段设置为 pdTURE。
	#endif
	#if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
	struct QueueDefinition *pxQueueSetContainer;
	#endif
	#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
	UBaseType_t uxQueueNumber;
	uint8_t ucQueueType;
	#endif
} xQUEUE;
typedef xQUEUE Queue_t;

The queue in the old version of FreeRTOS may use the name xQUEUE, and the name of the queue in the new version of FreeRTOS uses Queue_t.

3. Queue creation

1. Function prototype

You must create a queue before using it. There are two ways to create a queue, one is static, using the function
xQueueCreateStatic(); the other is dynamic, using the function xQueueCreate(). These two functions are macros in essence. The functions that actually complete the queue creation are xQueueGenericCreate() and xQueueGenericCreateStatic(). These two functions are defined in the file queue.c. The prototypes of these four functions are as follows.

(1) Function xQueueCreate()

This function is essentially a macro, which is used to dynamically create a queue. This macro finally calls the function xQueueGenericCreate(). The function prototype is as follows:

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
						   UBaseType_t uxItemSize)

Parameters:
**uxQueueLength: **The queue length of the queue to be created, here is the number of items in the queue.
uxItemSize: the length of each item (message) in the queue, in bytes

Return value:
Other values: The queue handle returned after the queue creation is successful!
NULL: Queue creation failed.

(2) Function xQueueCreateStatic()

This function is also used to create a queue, but the static method is used to create the queue. The memory required by the queue is allocated by the user. This function is also a macro in essence. This macro finally calls the function xQueueGenericCreateStatic(). The function prototype is as follows:

QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
								 UBaseType_t uxItemSize,
							 	 uint8_t * pucQueueStorageBuffer,
							     StaticQueue_t * pxQueueBuffer)

Parameters:
uxQueueLength: The queue length of the queue to be created, here is the number of items in the queue.
uxItemSize: The length of each item (message) in the queue, in bytes.
pucQueueStorage: Points to the storage area of ​​the queue item, that is, the storage area of ​​the message. This storage area needs to be allocated by the user. This parameter must point to an array of type uint8_t. This storage area must be greater than or equal
to (uxQueueLength * uxItemsSize) bytes.
pxQueueBuffer: This parameter points to a variable of type StaticQueue_t, which is used to save the queue structure.

Return value:
Other values: The queue handle after the queue is created successfully!
NULL: Queue creation failed.

(3) Function xQueueGenericCreate()

The function xQueueGenericCreate() is used to dynamically create a queue. The memory required in the queue creation process is
allocated by the dynamic memory management function pvPortMalloc() in FreeRTOS. The function prototype is as follows:

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
								   const UBaseType_t uxItemSize,
								   const uint8_t ucQueueType )

Parameters:
**uxQueueLength: **The queue length of the queue to be created, here is the number of items in the queue.
uxItemSize: The length of each item (message) in the queue, in bytes.
ucQueueType: Queue type. Since the semaphore in FreeRTOS is also implemented through the queue, the function of creating the semaphore will eventually use this function. Therefore, it is necessary to specify the purpose of this queue when creating it, that is, the queue type. There are a total of Six types:
queueQUEUE_TYPE_BASE ordinary message queue
queueQUEUE_TYPE_SET queue set
queueQUEUE_TYPE_MUTEX mutex semaphore queueQUEUE_TYPE_COUNTING_SEMAPHORE counting semaphore
queueQUEUE_TYPE_BINARY_SEMAPHORE binary semaphore queueQUEUE_TYPE_RECURSIVE_MUT When
the EX
recursive mutex semaphore
function xQueueCreate() creates a queue, this parameter is selected by default as
queueQUEUE_TYPE_BASE.

Return value:
Other values: The queue handle after the queue is created successfully!
NULL: Queue creation failed.

(4) Function xQueueGenericCreateStatic()

This function is used to dynamically create a queue. The memory required for creating a queue needs to be allocated by the user. The function prototype is as follows:

QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, 
										 const UBaseType_t uxItemSize, 
										 uint8_t * pucQueueStorage, 
										 StaticQueue_t * pxStaticQueue, 
										 const uint8_t ucQueueType )

Parameters:
uxQueueLength: The queue length of the queue to be created, here is the number of items in the queue.
uxItemSize: The length of each item (message) in the queue, in bytes.
pucQueueStorage: Points to the storage area of ​​the queue item, that is, the storage area of ​​the message. This storage area needs to be allocated by the user. This parameter must point to an array of type uint8_t. This storage area must be greater than or equal to (uxQueueLength * uxItemsSize) bytes.
pxStaticQueue: This parameter points to a variable of type StaticQueue_t, which is used to save the queue structure.
ucQueueType: Queue type.

Return value:
Other values: Queue handle after queue creation is successful!
NULL: Queue creation failed.

2. Detailed description of the queue creation function

There are two functions that finally complete the queue creation, one is the static method xQueueGenericCreateStatic(), and the other is the dynamic method xQueueGenericCreate(). Let's analyze the dynamic creation function xQueueGenericCreate() in detail. The static methods are similar, and you can analyze it yourself. The function xQueueGenericCreate() has the following definition in the file queue.c:

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 )
	{
    
    
		//队列项大小为 0,那么就不需要存储区。
		xQueueSizeInBytes = ( size_t ) 0;
	}
	else
	{
    
    
		//分配足够的存储区,确保随时随地都可以保存所有的项目(消息),
		xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); (1)
	}
	
	pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); (2)
	//内存申请成功
	if( pxNewQueue != NULL )
	{
    
    
		pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); (3)
		#if( configSUPPORT_STATIC_ALLOCATION == 1 )
		{
    
    
			//队列是使用动态方法创建的,所以队列字段 ucStaticallyAllocated 标
			//记为 pdFALSE。
			pxNewQueue->ucStaticallyAllocated = pdFALSE;
		}
		#endif 
			
		prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, \ (4)
		ucQueueType, pxNewQueue );
	}
	return pxNewQueue;
}

(1) The queue is to store messages, so there must be a storage area for messages. The parameters uxQueueLength and uxItemSize of the function specify the maximum number of queue items (messages) in the queue and the length of each message. The multiplication of the two is the message The size of the storage area.

(2) Call the function pvPortMalloc() to allocate memory to the queue. Note that the memory size requested here is the total size of the queue structure and the message storage area in the queue.

(3) Calculate the first address of the message storage area. The memory requested in (2) is the total size of the queue structure and the memory area in the queue. Before the queue structure exists, the message storage area is immediately behind Memory.

(4) Call the function prvInitialiseNewQueue() to initialize the queue.
It can be seen that the important work of the function xQueueGenericCreate() is to allocate memory to the queue. When the memory allocation is successful, the function prvInitialiseNewQueue() is called to initialize the queue.

3. Queue initialization function

The queue initialization function prvInitialiseNewQueue() is used to initialize the queue. This function is defined in the file queue.c. The function code is as follows:

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, //队列长度
								   const UBaseType_t uxItemSize, //队列项目长度
								   uint8_t * pucQueueStorage, //队列项目存储区
								   const uint8_t ucQueueType, //队列类型
								   Queue_t * pxNewQueue ) //队列结构体
{
    
    
	//防止编译器报错
	( void ) ucQueueType;
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
    
    
		//队列项(消息)长度为 0,说明没有队列存储区,这里将 pcHead 指向队列开始地址
		pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
	}
	else
	{
    
    
		//设置 pcHead 指向队列项存储区首地址
		pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; (1)
	}
	//初始化队列结构体相关成员变量
	pxNewQueue->uxLength = uxQueueLength; (2)
	pxNewQueue->uxItemSize = uxItemSize;
	( void ) xQueueGenericReset( pxNewQueue, pdTRUE ); (3)
	#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 );
}

(1). The member variable pcHead in the queue structure points to the first address in the queue storage area.

(2) Initialize the member variables uxQueueLength and uxItemSize in the queue structure, these two member variables save the maximum queue item of the queue and the size of each queue item.

(3) Call the function xQueueGenericReset() to reset the queue. PS: Make a complaint, go around, the function is adjusted one after another.

4. Queue reset function

The queue initialization function prvInitialiseNewQueue() calls the function xQueueGenericReset() to reset the queue. The code of the function xQueueGenericReset() is as follows:

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->\ (1)
		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 ) (2)
		{
    
    
			//由于复位队列以后队列依旧是空的,所以对于那些由于出队(从队列中读取消
			//息)而阻塞的任务就依旧保持阻塞壮态。但是对于那些由于入队(向队列中发送
			//消息)而阻塞的任务就不同了,这些任务要解除阻塞壮态,从队列的相应列表中
			//移除。
			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 ) ); (3)
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}
	}
	taskEXIT_CRITICAL();
	return pdPASS;
}

(1) Initialize the relevant member variables in the queue.

(2) Determine whether the queue to be reset is a newly created queue according to the parameter xNewQueue, if not, other processing is required

(3) Initialize the lists xTasksWaitingToSend and xTasksWaitingToReceive in the queue.
So far, the queue is successfully created. For example, we create a queue TestQueue with 4 queue items, and each queue item has a length of 32 bytes. The successfully created queue is shown in the following figure:
insert image description here

Guess you like

Origin blog.csdn.net/Dustinthewine/article/details/130277162