FreeRTOS队列
FreeRTOS队列
在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。
需要队列的原因如上图所示:当我们同时操作一个变量时候,比如进行a++这个操作,其实内部是分为这三个步骤:读数据、修改数据、写数据。假如同时进行任务1和任务2,a的值是否为2呢。但其实a的值是未知的。有可能任务1执行完第二步修改数据,但是还没能及时将数据写入a.任务2就紧接着执行。所以我们需要需要队列来传递信息。
接下来来了解下队列的特点:
1、数据存储
通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓冲机制。数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。但是在面对一些很大的数据的时候,采用值传递的方式会导致效率很低,所以可以采用指针的方式去存储。并不是只能采用值传递。
队列具有多任务访问的特点:队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。
2:出队堵塞
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是针对从队列中读取消息的任务而言的。
3:入队堵塞
入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。
接下来了解下队列的操作过程:
创建队列的时候要知道两个已知参数,指定队列的长度和每条消息的长度。在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度 。因为要传递的是x 值,而 x 是个 int 类型的变量,所以每条消息的长度就是 int 类型的长度,在 STM32 中就是 4字节,即每条消息是 4 个字节的
任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。
下面让我们去详细看下队列结构体:
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /*pcHead是一个指针,指向队列存储区域的开始。这个存储区域用于实际存放队列中的元素。 */
int8_t * pcWriteTo; /*pcWriteTo是一个指针,指向存储区域中下一个空闲位置。这是队列中下一个元素将要被写入的位置。 */
//这是一个匿名联合体,它允许这个结构体存储额外的、特定于用途的数据。如果这个结构体被用作队列,那么会使用xQueue成员;如果被用作信号量,那么会使用xSemaphore成员。
union
{
QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */
SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
} u;
List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
List_t xTasksWaitingToReceive;
/*这两个成员是任务列表,分别用于存储等待向队列发送数据和等待从队列接收数据的任务。这些列表按任务优先级顺序存储。*/
volatile UBaseType_t uxMessagesWaiting; /*< uxMessagesWaiting是当前在队列中的元素数量。 */
UBaseType_t uxLength; /*< 定义了队列的长度,即它可以容纳的元素数量,而不是字节数。*/
UBaseType_t uxItemSize; /*< uxItemSize是队列中每个元素的大小(以字节为单位) */
volatile int8_t cRxLock;
//当队列上锁以后用来统计从队列中接收到的队列项数
//量,也就是出队的队列项数量,当队列没有上锁的话此字
//段为 queueUNLOCKED
volatile int8_t cTxLock;
//当队列上锁以后用来统计发送到队列中的队列项数量,
//也就是入队的队列项数量,当队列没有上锁的话此字
//段为 queueUNLOCKED
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
//如果启用了队列集功能,pxQueueSetContainer指向这个队列所属的队列集容器。
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
具体上面的参数可以参考下图:
创建队列
接下来我们来看队列的创建函数:
这里发现源码中使用宏定义的方法进行替换。
会发现不同的宏定义对应不同的类型,由于我们使用的是队列。所以这里采用是queueQUEUE_TYPE_BASE:
我 们 来 详 细 的 分 析 一 下 动 态 创 建 函 数xQueueGenericCreate()
,静态方法大同小异,大家可以自行分析一下。函数 xQueueGenericCreate()
在文件 queue.c
中有如下定义:
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
{
Queue_t * pxNewQueue = NULL;
//定义一个指向队列结构体Queue_t的指针pxNewQueue,并初始化为NULL。这个指针将用来指向新创建的队列。
size_t xQueueSizeInBytes;
//定义一个size_t类型的变量xQueueSizeInBytes,用来存储队列需要的总字节数。
uint8_t * pucQueueStorage;
//定义一个指向uint8_t的指针pucQueueStorage,它将用来指向队列的存储区域。
//if语句检查三个条件以确保参数的有效性和内存分配的可能性:队列长度大于0、乘法没有溢出、加法没有溢出。
//SIZE_MAX是size_t类型能表示的最大值。这行代码的逻辑是:
//首先计算SIZE_MAX除以队列长度uxQueueLength的结果,然后检查这个结果是
//否大于或等于每个队列项的大小uxItemSize。如果是,那么uxQueueLength * uxItemSize的结果
//不会超过size_t能表示的最大值,从而避免了乘法溢出
//这行代码先计算SIZE_MAX减去Queue_t结构体大小的结果,然后检查这个结果是否大于等于
//队列存储区域所需的总字节数(uxQueueLength * uxItemSize)。这样做是为了确保在为
//Queue_t结构体和队列存储区域分配内存时不会发生加法溢出。
if( ( uxQueueLength > ( UBaseType_t ) 0 ) &&
/* Check for multiplication overflow. */
( ( SIZE_MAX / uxQueueLength ) >= uxItemSize ) &&
/* Check for addition overflow. */
( ( SIZE_MAX - sizeof( Queue_t ) ) >= ( uxQueueLength * uxItemSize ) ) )
{
/* Allocate enough space to hold the maximum number of items that
* can be in the queue at any time. It is valid for uxItemSize to be
* zero in the case the queue is used as a semaphore. */
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
/*计算队列存储区域所需的字节数,并将结果赋给xQueueSizeInBytes。 */
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
/*使用pvPortMalloc分配足够的内存来存储Queue_t结构体和队列存储区域。返回的指针赋给pxNewQueue */
if( pxNewQueue != NULL )
{
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t );
/*计算队列存储区域的起始地址。首先将pxNewQueue的地址赋给pucQueueStorage,
然后通过指针运算跳过Queue_t结构体的大小,使pucQueueStorage指向队列存储区域的开始。*/
#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 );
//调用prvInitialiseNewQueue函数来初始化新创建的队列,包括设置队列长度、项目大小、存储区域和队列类型等。
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
}
else
{
configASSERT( pxNewQueue );
mtCOVERAGE_TEST_MARKER();
}
return pxNewQueue;
}
队列初始化函数 prvInitialiseNewQueue()
用于队列的初始化,此函数在文件 queue.c
中有定义,函数代码如下:
这是prvInitialiseNewQueue
函数的定义。它是一个静态函数,意味着它只能在定义它的文件中被调用。它接收五个参数:队列长度uxQueueLength
、每个队列项的大小uxItemSize
、指向队列存储区域的指针pucQueueStorage
、队列类型ucQueueType
和指向新队列的指针pxNewQueue
。
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 )
{
/* 如果每个队列项的大小为0,这意味着队列被用作信号量。
在这种情况下,队列不需要存储区域,所以pcHead(指向队列存储区域起始的指针)
被设置为指向队列自身的指针,作为一个已知且安全的值。 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
/* 如果每个队列项的大小不为0,pcHead被设置为指向实际队列存储区域的指针。 */
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
}
/* 初始化队列的长度和每个队列项的大小。 */
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );//调用xQueueGenericReset函数来重置队列,将其设置为初始状态
#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
函数的定义。它接收两个参数:xQueue是要重置的队列的句柄,xNewQueue是一个标志,指示这是否是新创建的队列(pdTRUE)或是需要重置的现有队列(pdFALSE)。
BaseType_t xQueueGenericReset( QueueHandle_t xQueue,
BaseType_t xNewQueue )
{
BaseType_t xReturn = pdPASS;
//定义并初始化返回值xReturn为pdPASS,表示操作默认成功。
Queue_t * const pxQueue = xQueue;
//将传入的队列句柄xQueue转换为Queue_t类型的指针pxQueue,以便于访问队列的内部结构。
configASSERT( pxQueue );
//使用configASSERT宏来检查pxQueue指针是否有效。如果pxQueue是NULL,这会导致断言失败,通常表示程序中存在错误。
if( ( pxQueue != NULL ) &&
( pxQueue->uxLength >= 1U ) &&
/* Check for multiplication overflow. */
( ( SIZE_MAX / pxQueue->uxLength ) >= pxQueue->uxItemSize ) )
//检查pxQueue是否非NULL、队列长度是否至少为1、以及是否没有发生乘法溢出。这些检查确保了队列的配置是合法的。
{
taskENTER_CRITICAL();//进入临界区,这是为了防止在重置队列时被中断
{
pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
/*设置队列尾指针pcTail。由于队列是循环的,这里通过计算来确定队列尾部的位置。 */
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;//将等待在队列中的消息数量重置为0。
pxQueue->pcWriteTo = pxQueue->pcHead;//将写指针pcWriteTo设置为队列头部,表示下一个写入操作将从队列的开始位置进行。
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 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
{
/* 如果这是新创建的队列,则初始化等待发送和接收的任务列表。 */
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();
}
else
{
xReturn = pdFAIL;
}
configASSERT( xReturn != pdFAIL );
/* A value is returned for calling semantic consistency with previous
* versions. */
return xReturn;
}
向队列发送消息
这一部分具体如何使用可以在正点原子FreeRTOS手册里面去查看。
不 管 是 后 向 入 队 、 前 向 入 队 还 是 覆 写 入 队 , 最 终 调 用 的 都 是 通 用 入 队 函 数xQueueGenericSend()
,这个函数在文件 queue.c
文件中由定义,
xQueue:
要操作的队列的句柄。
pvItemToQueue:
指向要发送到队列的数据的指针。
xTicksToWait:
如果队列满,调用任务愿意等待的时间(以tick为单位)。
xCopyPosition:
发送数据的模式(队列尾、队列头或覆盖)。
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 = xQueue;
//xEntryTimeSet用于跟踪是否已经设置了超时。
//xYieldRequired用于指示是否需要进行任务切换。
//pxQueue是一个指向队列结构的指针。
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
/*函数的主体是一个无限循环,只有在成功发送数据或遇到超时时才会退出。 */
for( ; ; )
{
taskENTER_CRITICAL();
{
/* 如果队列未满或者调用是为了覆盖队列头部的数据,则尝试发送*/
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
traceQUEUE_SEND( pxQueue );
#if ( configUSE_QUEUE_SETS == 1 )
{
const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
if( pxQueue->pxQueueSetContainer != NULL )
{
if( ( xCopyPosition == queueOVERWRITE ) && ( uxPreviousMessagesWaiting != ( UBaseType_t ) 0 ) )
{
/* Do not notify the queue set as an existing item
* was overwritten in the queue so the number of items
* in the queue has not changed. */
mtCOVERAGE_TEST_MARKER();
}
else if( prvNotifyQueueSetContainer( pxQueue ) != 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 */
{
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/*这一行调用prvCopyDataToQueue函数尝试将数据项复制到队列中。
pvItemToQueue是指向要发送的数据的指针,xCopyPosition决定了数据
应该被添加到队列的哪个位置(例如,队尾、队首或覆盖当前头部数据)。
函数返回一个标志xYieldRequired,指示是否需要进行任务切换(如果因为
这次操作使得一个更高优先级的任务变为就绪状态)*/
/* 这里检查队列的等待接收列表是否为空。如果不为空,说明有任务正在等待队列中的数据变得可用。 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* 如果有任务因为当前操作而从等待接收列表中被移除(即因为新数据的到来而变为就绪状态),
并且这个任务的优先级高于当前任务,那么xTaskRemoveFromEventList会返回pdTRUE,此时
设置xYieldRequired为pdTRUE,表示当前任务应该让出CPU以便更高优先级的任务执行。 */
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 )
{
/* 这里检查调用函数时指定的阻塞时间(xTicksToWait)是否为0。
阻塞时间为0表示如果队列满,调用者不希望等待,而是立即返回。 */
taskEXIT_CRITICAL();
/* 如果阻塞时间为0,首先退出临界区。这是因为
在检查队列状态和修改队列状态时需要保护这些操
作不被其他任务或中断打断,以避免数据不一致的问题。
一旦确定了操作流程(在这里是因为队列满而无法发送数据)
,就可以安全地退出临界区。 */
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
//接下来,记录一个队列发送失败的跟踪事件(如果启用了跟踪功能),
//然后返回errQUEUE_FULL,表示因为队列满而发送失败。
}
else if( xEntryTimeSet == pdFALSE )
{
/*如果指定了阻塞时间(即希望在队列满时等待一段时间),
且尚未设置入队时间(用于开始计时阻塞时间),则执行以下操作:
这里调用vTaskInternalSetTimeOutState函数来初始化超时控制结构xTimeOut,
并标记入队时间已设置(xEntryTimeSet设为pdTRUE)。这是为了开始计算阻塞时间,
如果在阻塞时间内队列变为非满状态,则任务可以再次尝试发送数据。 */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/*在队列满且任务选择等待的情况下。它展示了当任务因为队列满而不能立即发送数据时,如何处理等待和超时逻辑 */
vTaskSuspendAll();
//这行代码暂停了任务调度器,防止在操作队列的过程中发生任务切换。这是为了保证队列状态的一致性和操作的原子性。
prvLockQueue( pxQueue );
//锁定队列以进行操作,确保在操作队列时不会被中断打断,保护队列的完整性。
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
//通过调用xTaskCheckForTimeOut函数检查是否已经超时。如果返回pdFALSE,表示还没有超时,任务可以继续等待。
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
/* 再次检查队列是否满。如果队列仍然满,执行以下操作:
traceBLOCKING_ON_QUEUE_SEND( pxQueue ):记录一个事件,表示任务因为队列满而阻塞。
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ):
将任务放在等待队列发送的事件列表上,并设置等待时间。
prvUnlockQueue( pxQueue ):解锁队列,允许其他任务或中断操作队列。
*/
prvUnlockQueue( pxQueue );
/* 恢复任务调度器。如果在暂停调度器期间有更高优先级的任务变为就绪状态,
则通过portYIELD_WITHIN_API发起一次任务切换。 */
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else
{
/* Try again. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* 如果xTaskCheckForTimeOut返回pdTRUE,表示等待时间已过,任务无法发送数据到队列:
解锁队列,恢复任务调度器。
记录队列发送失败的事件。
返回errQUEUE_FULL,表示因为队列满而发送失败。 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
} /*lint -restore */
}
接下来来详细看这个函数prvCopyDataToQueue
:
pxQueue:指向队列结构的指针。
pvItemToQueue:指向要复制到队列中的数据项的指针。
xPosition:指示数据项应该被复制到队列的哪个位置。这个参数可以是queueSEND_TO_BACK(发送到队尾)、queueSEND_TO_FRONT(发送到队头)或queueOVERWRITE(覆盖队列中的当前项)。
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/* This function is called from a critical section. */
uxMessagesWaiting = pxQueue->uxMessagesWaiting;//获取队列中当前等待的消息数量。
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
//这行代码首先检查队列项的大小(uxItemSize)是否为0。
//在FreeRTOS中,互斥锁和信号量等同步机制是通过队列实现的,
//但它们不用于存储数据项,因此uxItemSize会被设置为0。这是区分互斥锁或信号量与普通队列的一个关键特征。
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
else if( xPosition == queueSEND_TO_BACK )
{
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 !e9087 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. Cast to void required by function signature and safe as no alignment requirement and copy length specified in bytes. */
pxQueue->pcWriteTo += pxQueue->uxItemSize; /*lint !e9016 Pointer arithmetic on char types ok, especially in this use case where it is the clearest way of conveying intent. */
if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
{
pxQueue->pcWriteTo = pxQueue->pcHead;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else//包含处理这两种情况:queueSEND_TO_FRONT(发送到队头)或queueOVERWRITE(覆盖队列中的当前项)。
{
( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
/*这行代码使用memcpy函数将数据从pvItemToQueue复制到队列的读取位置pcReadFrom。
memcpy需要目标地址、源地址和复制的字节数作为参数。这里的目标地址是队列的读取位置,
源地址是传入的数据项地址,需要复制的字节数是队列项的大小uxItemSize。 */
pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;
//复制数据后,更新队列的读取位置pcReadFrom,将其向前移动uxItemSize字节。这意味着下一次读取操作将从新插入的数据项开始。
if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) /*这段代码检查更新后的读取位置pcReadFrom是否超出了队列的头部pcHead。如果是,说明需要将读取位置循环移动到队列的尾部,具体到最后一个数据项的位置。这里的计算确保了队列的循环使用。 */
{
pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xPosition == queueOVERWRITE )//这行代码检查当前的操作是否是覆写操作(queueOVERWRITE)。如果是,执行下面的逻辑:
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* 如果队列中已经有等待的消息(uxMessagesWaiting > 0),
那么在覆写一条新消息之前,先将等待消息的数量减1。这是因为覆
写操作实际上没有增加队列中的消息数量,只是替换了队列中的现有消息。 */
--uxMessagesWaiting;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
//无论是插入还是覆写操作,最后都将队列中等待处理的消息数量uxMessagesWaiting增加1,以反映新的数据项已被添加到队列中。
return xReturn;
}
从队列读取消息:
下面来分析这几个函数具体实现过程:
函数定义: xQueueReceive函数用于从队列中接收数据。它接受三个参数:队列句柄xQueue,接收数据的缓冲区pvBuffer,以及在队列为空时等待的最大时长xTicksToWait。
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
/* 变量初始化: 初始化一个标志xEntryTimeSet为pdFALSE,
表示超时计时尚未开始。声明一个TimeOut_t类型的变量xTimeOut
用于处理超时逻辑。将传入的队列句柄xQueue转换为队列类型的指针pxQueue。 */
configASSERT( ( pxQueue ) );
/* The buffer into which data is received can only be NULL if the data size
* is zero (so no data is copied into the buffer). */
configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* Cannot block if the scheduler is suspended. */
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/*lint -save -e904 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();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
//获取队列中消息数量: 读取队列中当前等待的消息数量。
/* Is there data in the queue now? To be running the calling task
* must be the highest priority task wanting to access the queue. */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* Data available, remove one item. */
prvCopyDataFromQueue( pxQueue, pvBuffer );
//从队列复制数据: 调用prvCopyDataFromQueue函数,从队列中复制一项数据到pvBuffer。
traceQUEUE_RECEIVE( pxQueue );
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
/* There is now space in the queue, were any tasks waiting to
* post to the queue? If so, unblock the highest priority waiting
* task. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
//检查是否有任务等待发送数据: 如果有任务在等待向队列发送数据。
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
//唤醒等待发送的任务: 尝试从等待发送列表中移除一个任务,并将其唤醒。
{
queueYIELD_IF_USING_PREEMPTION();
//如果使用抢占式调度,触发调度器:
//如果系统配置为抢占式调度,这一步会触发调度器,可能会立即切换到刚被唤醒的任务执行。
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else//处理队列为空的情况
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* 检查是否设置了等待时间: 如果队列为空且没有设置等待时间(xTicksToWait为0),则不阻塞等待。 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )//设置超时状态: 如果设置了等待时间且超时计时尚未开始,则开始超时计时。
{
/* 初始化超时状态: 通过vTaskInternalSetTimeOutState函数初始化超时状态。*/
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;//标记超时计时已开始: 将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 )
//检查是否超时: 使用xTaskCheckForTimeOut检查是否已经达到超时时间。
{
/* The timeout has not expired. If the queue is still empty place
* the task on the list of tasks waiting to receive from the queue. */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
//检查队列是否仍然为空: 如果超时尚未发生且队列仍然为空,则准备将任务阻塞。
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
//任务放入等待列表: 将当前任务放入队列的等待接收列表中,并设置阻塞时间。
prvUnlockQueue( pxQueue );
//解锁队列: 完成操作后解锁队列。
if( xTaskResumeAll() == pdFALSE )
//恢复所有任务: 恢复任务调度。如果在此期间有更高优先级的任务被唤醒,xTaskResumeAll会返回pdFALSE。
{
portYIELD_WITHIN_API();
//触发上下文切换: 如果有更高优先级的任务需要运行,通过portYIELD_WITHIN_API触发上下文切换。
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* The queue contains data again. Loop back to try and read the
* data. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* 处理超时: 如果检查到超时且队列仍然为空,解锁队列,恢复任务调度,
并返回errQUEUE_EMPTY,表示超时期间没有数据到达队列。*/
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore */
}
具体过程如下图总结的过程一样。
本文来自博客园,作者:Bathwind_W,转载请注明原文链接:https://www.cnblogs.com/bathwind/p/18147990