FreeRTOS消息队列详解第三讲(全网最全)——队列发送消息

一、队列发送消息函数简介

创建好队列以后就可以向队列发送消息了,FreeRTOS提供了8个向队列发送消息的API函数。如下表所示:
FreeRTOS消息队列详解第三讲(全网最全)——队列发送消息_第1张图片
1、函数xQueueSend()、xQueueSendToBack()和xQueueSendToFront()
这三个函数都是用于向队列中发送消息的,这三个函数本质都是宏,其中函数xQueueSend()和xQueueSendToBack()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToToFront()是前向入队,即将新消息插入到队列的前面。这三个函数最后都是调用的同一个函数xQueueGenericSend()。这三个函数只能用于任务函数中,不能用于中断服务函数,中断服务函数有专用的函数,它们以“FromISR”结尾,这三个函数的原型如下:

BaseType_t xQueueSend(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);

BaseType_txQueueSendToBack(QueueHandle_txQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait);

BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t XTicksToWait );
参数 描述
xQueue 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue 指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。如果为0的话当队列满的时候就立即返回;当为portMAX_DELAY的话就会一直等待,直到队列有空闲的队列项,也就是死等,但是宏INCLUDE_VTaskSuspend必须为1。
返回值 pdPASS:发送消息成功。其他:出错

2、函数xQueueOverwrite()
此函数也是用于向队列发送数据的,当队列满了以后会覆写掉旧的数据,不管这个旧数据有没有被其他任务或中断取走。这个函数常用于向那些长度为1的队列发送消息,此函数也是一个宏,最终调用的也是函数xQueueGenericSend(),函数原型如下:

BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void* pvItemToQueue);
参数 描述
xQueue 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
返回值 pdPASS:发送消息成功。

3、函数xQueueGenericSend()
此函数才是真正干活的,上面讲的所有的任务级入队函数最终都是调用的此函数,此函数也是重点要讲解的,先来看一下函数原型:

BaseType_txQueueGenericSend(QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition)
参数 描述
xQueue 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
xTicksToWait 阻塞时间。
xCopyPosition 入队方式,有三种入队方式:queueSEND_TO_BACK:后向入队queueSEND_TO_FRONT:前向入队queueOVERWRITE:覆写入队。
返回值 pdPASS:发送消息成功。errQUEUE_FULL:队列已经满了,消息发送失败。

4、函数xQueueSendFromISR()、
xQueueSendToBackFromISR()、
xQueueSendToFrontFromISR()

这三个函数也是向队列中发送消息的,这三个函数用于中断服务函数中。这三个函数本质也是宏,其中函数xQueueSendFromISR()和xQueueSendToBackFromISR()是一样的,都是后向入队,即将新的消息插入到队列的后面。函数xQueueSendToFrontFromISR()是前向入队,即将新消息插入到队列的前面。这三个函数同样调用同一个函数xQueueGenericSendFromISR0。这三个函数的原型如下:

Base Type_t xQueueSendFromISR(
QueueHandle_t xQueue,
const void pvItemToQueue,
BaseType_t* pxHigherPriorityTaskWoken);

BaseType_txQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void pvItemToQueue
BaseType_t* pxHigherPriorityTaskWoken);

BaseType_txQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void*pvItemToQueue,
BaseType_t*pxHigherPriorityTaskWoken );
参数 描述
xQueue 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvltemToQueue 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值 pdTRUE:向队列中发送消息成功!errQUEUE_FULL:队列已经满了,消息发送失败。

注意观察,可以看出这些函数都没有设置阻塞时间值。原因很简单,这些函数都是在中断服务函数中调用的,并不是在任务中,所以也就没有阻塞这一说了!
5、函数xQueueOverwriteFromISR()
此函数是xQueueOverwrite()的中断级版本,用在中断服务函数中,在队列满的时候自动覆写掉旧的数据,此函数也是一个宏,实际调用的也是函数xQueueGenericSendFromISR(),此函数原型如下:

BaseType_t xQueueOverwriteFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t*pxHigherPriorityTaskWoken);

参数和上面表格一样。
6、函数xQueueGenericSendFromISR()
上面说了4个中断级入队函数最终都是调用的函数xQueueGenericSendFromISR(),这是真正干活的主,也是我们下面会详细讲解的函数,先来看一下这个函数的原型,如下:

BaseType_txQueueGenericSendFromISR(
QueueHandle_t xQueue,
const void*pvItemToQueue,
BaseType_t*pxHigherPriorityTaskWoken,
BaseType_tXCopyPosition);
参数 描述
xQueue 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue 指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。
xCopyPosition 入队方式,有三种入队方式:queueSEND_TO_BACK:后向入队queueSEND_TO_FRONT:前向入队queueOVERWRITE:覆写入队。
返回值 pdPASS:发送消息成功。errQUEUE_FULL:队列已经满了,消息发送失败。

二、任务级通用入队函数详解

不管是后向入队、前向入队还是覆写入队,最终调用的都是通用入队函数xQueueGenericSend(),这个函数在文件queue.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;
	for( ;; )
	{
		taskENTER_CRITICAL();//进入临界区
		{
			/* 查询队列现在是否还有剩余存储空间,如果采用覆写方式入队的话那就不需要考虑队满 */
			if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )    (1)
			{
				traceQUEUE_SEND( pxQueue );
				xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );(2)
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )(3)
					{
			//检测是否有任务由于等待消息而进入阻塞态
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )(4)
						{
//解除阻塞态的任务优先级最高,因此要进行一次任务切换						queueYIELD_IF_USING_PREEMPTION();(5)
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else if( xYieldRequired != pdFALSE )
					{
						
					queueYIELD_IF_USING_PREEMPTION();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_QUEUE_SETS */

				taskEXIT_CRITICAL();
				return pdPASS;(6)
			}
			else
			{
				if( xTicksToWait == ( TickType_t ) 0 )(7)
				{
					taskEXIT_CRITICAL();
//队列是满的,并且没有设置阻塞时间就直接返回
					traceQUEUE_SEND_FAILED( pxQueue );
					return errQUEUE_FULL;(8)
				}
				else if( xEntryTimeSet == pdFALSE )(9)
				{
					/* 队列是满的并且指定了任务阻塞时间就初始化结构体*/
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					//确定时间结构体已经初始化
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();//退出临界区



		vTaskSuspendAll();(10)
		prvLockQueue( pxQueue );(11)

//更新时间状态,检查是否有超时产生
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )(12)
		{
			if( prvIsQueueFull( pxQueue ) != pdFALSE )(13)
			{
				traceBLOCKING_ON_QUEUE_SEND( pxQueue );
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(14)

				
				prvUnlockQueue( pxQueue );(15)

			
				if( xTaskResumeAll() == pdFALSE )(16)
				{
					portYIELD_WITHIN_API();
				}
			}
			else
			{
				//重试一次
				prvUnlockQueue( pxQueue );(17)
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			//超时产生
			prvUnlockQueue( pxQueue );(18)
			( void ) xTaskResumeAll();

			traceQUEUE_SEND_FAILED( pxQueue );
			return errQUEUE_FULL;(19)
		}
	}
}
  • (1)、要向队列发送数据,肯定要先检查一下队列是不是满的,如果是满的话肯定不能发送的。当队列未满或者是覆写入队的话就可以将消息入队了。
  • (2)、调用函数prvCopyDataToQueue()将消息拷贝到队列中。前面说了入队分为后向入队前向入队和覆写入队,他们的具体实现就是在函数prvCopyDataToQueue()中完成的。如果选择后向入队queueSEND_TO_BACK的话就将消息拷贝到队列结构体成员pcWriteTo所指向的队列项,拷贝成功以后pcWriteTo增加uxItemSize个字节,指向下一个队列项目。当选择前向入队queueSEND_TO_FRONT或者queueOVERWRITE的话就将消息拷贝到pcReadFrom所指向的队列项目,同样的需要调整pcReadFrom的位置。当向队列写入一个消息以后队列中统计当前消息数量的成员uxMessagesWaiting就会加一,但是选择覆写入队queueOVERWRITE的话还会将uxMessagesWaiting减一,这样一减一加相当于队列当前消息数量没有变。
  • (3)、检查是否有任务由于请求队列消息而阻塞,阻塞的任务会挂在队列的xTasksWaitingToReceive列表上。
  • (4)、有任务由于请求消息而阻塞,因为在(2)中已将向队列中发送了一条消息了,所以调用函数xTaskRemoveFromEventList()将阻塞的任务从列表xTasksWaitingToReceive上移除,并且把这个任务添加到就绪列表中,如果调度器上锁的话这些任务就会挂到列表xPendingReadyList上。如果取消阻塞的任务优先级比当前正在运行的任务优先级高还要标记需要进行任务切换。当函数xTaskRemoveFromEventList()返回值为pdTRUE的话就需要进行任务切换。
  • (5)、进行任务切换。
  • (6)、返回pdPASS,标记入队成功。
    注意:
    (2)到(6)都是非常理想的效果,即消息队列未满,入队没有任何障碍。但是队列满了以后呢?首先判断设置的阻塞时间是否为0,如果为0的话就说明没有阻塞时间。
  • (8)、由(7)得知阻塞时间为0,那就直接返回errQUEUE_FULL,标记队列已满就可以了。
  • (9)、如果阻塞时间不为0并且时间结构体还没有初始化的话就初始化一次超时结构体变量调用函数vTaskSetTimeOutState()完成超时结构体变量xTimeOut的初始化。其实就是记录当前的系统时钟节拍计数器的值xTickCount和溢出次数xNumOfOverflows。
  • (10)、任务调度器上锁,代码执行到这里说明当前的状况是队列已满了,而且设置了不为0的阻塞时间。那么接下来就要对任务采取相应的措施了,比如将任务加入到队列的
    XTasksWaitingToSend列表中。
  • (11)、调用函数prvLockQueue()给队列上锁,其实就是将队列中的成员变量cRxLock和cTxLock设置为queueLOCKED_UNMODIFIED。
  • (12)、调用函数xTaskCheckForTimeOut()更新超时结构体变量xTimeOut,并且检查阻塞时间是否到。
  • (13)、阻塞时间还没到,那就检查队列是否还是满的。
  • (14)、经过(12)和(13)得出阻塞时间没到,而且队列依旧是满的,那就调用函数VTaskPlaceOnEventList()将任务添加到队列的xTasksWaitingToSend列表中和延时列表中,并且将任务从就绪列表中移除。注意!如果阻塞时间是portMAX_DELAY并且宏INCLUDE_VTaskSuspend为1的话,函数vTaskPlaceOnEventList()会将任务添加到列表XSuspendedTaskList上。
  • (15)、操作完成,调用函数prvUnlockQueue()解锁队列。
  • (16)、调用函数xTaskResumeA110恢复任务调度器。
  • (17)、阻塞时间还没到,但是队列现在有空闲的队列项,那么就在重试一次。
  • (18)、相比于第(12)步,阻塞时间到了!那么任务就不用添加到那些列表中了,那就解锁队列,恢复任务调度器。
  • (19)、返回errQUEUE_FULL,表示队列满了。

三、中断级通用入队函数

讲完任务级入队函数再来看一下中断级入队函数xQueueGenericSendFromISR(),其他的中断级入队函数都是靠此函数来实现的。中断级入队函数和任务级入队函数大同小异,函数代码如下:

BaseType_t xQueueGenericSendFromISR( 
QueueHandle_t xQueue, 
const void * const pvItemToQueue, 
BaseType_t * const pxHigherPriorityTaskWoken, 
const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )(1)
		{
			const int8_t cTxLock = pxQueue->cTxLock;(2)

			traceQUEUE_SEND_FROM_ISR( pxQueue );
			( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );(3)
//队列上锁的时候不能操作事件队列,队列解锁的时候会补上这些操作
/*队列集相关代码开始*/
			if( cTxLock == queueUNLOCKED )
			{
				#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. */
							if( pxHigherPriorityTaskWoken != NULL )
							{
								*pxHigherPriorityTaskWoken = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )(5)
						{
							if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )(6)
							{
//刚刚从事件列表中移除的任务对应的任务优先级更高,所以需要标记进行任务切换
								if( pxHigherPriorityTaskWoken != NULL )
								{
									*pxHigherPriorityTaskWoken = pdTRUE;(7)
								}
								else
								{
									mtCOVERAGE_TEST_MARKER();
								}
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
				}
				#else /* configUSE_QUEUE_SETS */
				{
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
					{
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
						{
							if( pxHigherPriorityTaskWoken != NULL )
							{
								*pxHigherPriorityTaskWoken = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_QUEUE_SETS */
			}
			else
			{
	//cTxLock +1,z这样就知道队列在上锁期间向队列中发送了数据
				pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );(8)
			}

			xReturn = pdPASS;(9)
		}
		else
		{
			traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
			xReturn = errQUEUE_FULL;(10)
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

	return xReturn;
}
  • (1)、队列未满或者采用的覆写的入队方式,这是最理想的壮态。
  • (2)、读取队列的成员变量xTxLock,用于判断队列是否上锁。
  • (3)、将数据拷贝到队列中。
  • (4)、队列上锁了,比如任务级入队函数在操作队列中的列表的时候就会对队列上锁。
  • (5)、判断队列列表xTasksWaitingToReceive是否为空,如果不为空的话说明有任务在请求消息的时候被阻塞了。
  • (6)、将相应的任务从列表xTasksWaitingToReceive上移除。跟任务级入队函数处理过程一样。
  • (7)、如果刚刚从列表xTasksWaitingToReceive中移除的任务优先级比当前任务的优先级高,那么标记pxHigherPriorityTaskWoken为pdTRUE,表示要进行任务切换。如果要进行任务切换的话就需要在退出此函数以后,退出中断服务函数之前进行一次任务切换。
  • (8)、如果队列上锁的话那就将队列成员变量cTxLock加一,表示进行了一次入队操作,在队列解锁prvUnlockQueue()的时候会对其做相应的处理。
  • (9)、返回pdPASS,表示入队完成。
  • (10)、如果队列满的话就直接返回errQUEUE_FULL,表示队列满。

今天主要讲解了消息队列的入队相关的函数实现,内容比较多。

你可能感兴趣的:(FreeRTOS操作系统,1024程序员节,嵌入式,freertos,消息队列)