队列属于数据结构的知识。是非常重要的。我在前边的blog中写过一篇静态队列的文章。需要的点击静态队列浏览。
FreeRTOS也提供了队列,主要用于任务与任务,任务与中断之间传递消息,FreeRTOS的队列已经帮我们做好了阻塞超时机制,怎么用怎么爽。在裸机代码中我们使用全局变量传递消息,在OS中全局变量传递消息是有风险的。废话不多说我们来看代码。
认识一下FreeRTOS的队列结构体
typedef struct QueueDefinition
{
//指向第一个队列项的地址
int8_t *pcHead; /*< Points to the beginning of the queue storage area. */
//指向最后一个队列项的下一个地址
int8_t *pcTail; /*< Points to the byte at the end of the queue storage area. Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
//指向队列中第一个空闲的列表项
int8_t *pcWriteTo; /*< Points to the free next place in the storage area. */
union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
{
//最后一个出队的队列项的地址
int8_t *pcReadFrom; /*< Points to the last place that a queued item was read from when the structure is used as a queue. */
UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
} u;
//入队阻塞列表
List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
//出队阻塞列表
List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */
//队列中有效的队列项数
volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */
//队列的长度
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
//队列项的大小
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
//队列上锁以后 队列接收到的队列项数
volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
//队列上锁以后 发送到队列的队列项数
volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
//如果队列是静态方式创建的 那么该成员变量会设置为pdTRUE
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
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
咋咋呼呼的一个队列结构体搞了这么多成员,其实核心成员就那么几个,其它成员都是辅助成员,可以说是不重要或是可有可无的。
队列创建函数
队列创建和任务创建一样,也分为静态创建和动态创建。
如果使用静态方式创建队列,队列结构体变量和队列缓存区的内存需要用户提供。静态创建函数如下:
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
#define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )
#endif /* configSUPPORT_STATIC_ALLOCATION */
如果使用动态方式创建队列,队列结构体变量和队列缓存区的内存是有FreeRTOS的内存管理分配。函数如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
我们接下来分析动态方式创建队列函数。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
//ucQueueType参数表示队列的类型,信号量也是通过队列来实现
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 );
}
return pxNewQueue;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
/*-----------------------------------------------------------*/
1、通过队列项的大小和队列项的数目计算出队列缓存区的大小。
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize )
申请队列所需要的内存,包括队列结构体的内存和队列缓存区的内存。
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
计算出队列缓存区的起始地址。
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
2、初始化一个新的队列。
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 );
}
该函数是比较简单的,只是初始化了一部分队列结构体变量,初始化了pcHead,uxLength,uxItemSize等成员变量,最后调用了xQueueGenericReset()函数初始化队列。
我们还的看一下xQueueGenericReset()函数。
//xNewQueue参数表示复位的队列是否是新创建的队列
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;//队列中当前队列项数量归0
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;
}
复位队列函数首先是初始化了队列结构体的成员变量。然后分两种情况讨论,第一种情况是复位新的队列,初始化入队阻塞列表和出队阻塞列表。第二种情况是复位已经存在的队列,我们知道队列复位以后就是空队列了。这里处理了入队阻塞列表,把入队阻塞列表中优先级最高的任务解除阻塞。为什么不处理出队阻塞列表呢?出队阻塞是因队列为空进入阻塞,而复位队列就是让队列变为空,那当然不用处理出队阻塞列表了。
队列通用入队函数
看完队列的创建我们再来看入队,入队就是把数据存入队列。FreeRTOS提供了好多个入队函数,比如从前向后入队,从后向前入队,覆写方式入队等。他们最终都是通过调用通用入队函数实现入队。这些函数都有对应的中断级和任务级。
通用入队函数(任务级)
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 );
//xCopyPosition拷贝方式
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();//因为队列满而且阻塞时间为0 那么就退出临界区代码,立即返回
/* Return to the original privilege level before exiting
the function. */
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL; //队列满,阻塞时间为0立即返回
}
else if( xEntryTimeSet == pdFALSE )//队列满而且延时时间不为0
{
/* The queue was full and a block time was specified so
configure the timeout structure. */
//其实该函数内部就是记录下当前时刻的系统时钟节拍值,和当前系统时钟溢出的次数
vTaskSetTimeOutState( &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 )//未到延时时间
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )//队列满
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
//队列满而且延时时间不为0
//下边函数做两件事
//1.将当前任务控制块的事件列表项添加到入队阻塞列表中
//2.将当前任务添加到延时列表中
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;
}
}
}
该函数比较复杂,我们先来梳理一下大概的流程。
判断队列未满或是使用了覆写方式。
如果队列未满或是使用了覆写方式那么一定会入队成功。这是一种情况
如果队列满是另一种情况。在队列满的情况下,又通过判断了阻塞时间分为两种情况,如果阻塞时间是0,那么立即返回,如果阻塞时间不为0则做其它处理(这种情况就蛋疼了)。这里的描述有点乱,应该用思维导图画一下。
接下来我们也是分情况讨论:
一、队列未满或是使用了覆写方式,
1、调用拷贝函数将数据拷贝到队列缓存区。
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 )//用于信号量的
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )//如果是互斥信号量
{
/* The mutex is no longer being held. */
//处理优先级继承问题
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );//参数是正在占有互斥信号量的任务的TCB
pxQueue->pxMutexHolder = NULL;//互斥信号量释放以后互斥信号量的占有者指针不再指向TCB,而是指向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 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();
}
}
else//从后向前入队
{
( void ) memcpy( ( void * ) pxQueue->u.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
pxQueue->u.pcReadFrom -= pxQueue->uxItemSize;//指针偏移
if( pxQueue->u.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
{
pxQueue->u.pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xPosition == queueOVERWRITE )//如果是覆写方式的话
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* An item is not being added but overwritten, so subtract
one from the recorded number of items in the queue so when
one is added again below the number of recorded items remains
correct. */
--uxMessagesWaiting;//队列中的数据先减一下,保证队列项数不变
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;//拷贝以后队列中的队列项数加1
return xReturn;
}
1).如果队列项的大小是0,那么处理的是信号量,信号量的知识我们后续分析。
2).FreeRTOS的队列支持两种入队方式,从前向后入队和从后向前入队(这里的前指的是队列缓存区的起始地址)。
从前向后入队方式:
void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );//拷贝内存
pxQueue->pcWriteTo += pxQueue->uxItemSize;//指针偏移到下一个队列项
if( pxQueue->pcWriteTo >= pxQueue->pcTail )
{
pxQueue->pcWriteTo = pxQueue->pcHead;//超过队尾回到队头
}
我们可以看到存到队尾以后又从头开始存,这在物理地址上是线性的,在逻辑处理上是环形的。
从后向前入队也是同样的道理,这里就不啰嗦了。
3).pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
//拷贝以后队列中的队列项数加1
2、判断是否有任务因为队列为空而进入了阻塞。因为队列为空而进入阻塞的任务会挂载到xTasksWaitingToReceive列表上
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
调用函数xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) )
将xTasksWaitingToReceive
列表中第一个任务删除,并添加到就绪列表中。这里处理的是列表中第一个列表项所属的任务。由此看出如果多个任务都因为队列为空而进入阻塞态,那么优先级最高的任务会解除阻塞态进入就绪态
3、队列未满或是使用了覆写方式的话一定会返回pdTRUE
二、入队时队列满的情况。
入队时队列满分为两种情况处理。一种情况是阻塞时间为0,这就很简单了,直接返回errQUEUE_FULL
。另一种情况是阻塞时间不为0,这就很蛋疼了,我们来讨论这种情况。
1).vTaskSetTimeOutState( &xTimeOut ); //初始化超时结构体
目的是记录下当前时刻的系统时钟节拍值,和当前系统时钟溢出的次数
2). 调度器上锁和队列上锁。
vTaskSuspendAll();//调度器上锁
prvLockQueue( pxQueue );//队列上锁
3).if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
判断阻塞时间是否到,函数内部维护了这两个参数。
如果阻塞时间到就返回errQUEUE_FULL
,
如果阻塞时间未到调用vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
该函数内部做了两件事
将当前任务控制块的事件列表项添加到入队阻塞列表中
将当前任务添加到延时列表中
4).任务切换。
队列中断入队函数
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;
configASSERT( pxQueue );
configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );
/* RTOS ports that support interrupt nesting have the concept of a maximum
system call (or maximum API call) interrupt priority. Interrupts that are
above the maximum system call priority are kept permanently enabled, even
when the RTOS kernel is in a critical section, but cannot make any calls to
FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h
then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has been
assigned a priority above the configured maximum system call priority.
Only FreeRTOS functions that end in FromISR can be called from interrupts
that have been assigned a priority at or (logically) below the maximum
system call interrupt priority. FreeRTOS maintains a separate interrupt
safe API to ensure interrupt entry is as fast and as simple as possible.
More information (albeit Cortex-M specific) is provided on the following
link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
/* Similar to xQueueGenericSend, except without blocking if there is no room
in the queue. Also don't directly wake a task that was blocked on a queue
read, instead return a flag to say whether a context switch is required or
not (i.e. has a task with a higher priority than us been woken by this
post). */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
const int8_t cTxLock = pxQueue->cTxLock;//记录队列发送锁
traceQUEUE_SEND_FROM_ISR( pxQueue );
/* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a
semaphore or mutex. That means prvCopyDataToQueue() cannot result
in a task disinheriting a priority and prvCopyDataToQueue() can be
called here even though the disinherit function does not check if
the scheduler is suspended before accessing the ready lists. */
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* The event list is not altered if the queue is locked. This will
be done when the queue is unlocked later. */
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 )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so
record that a context switch is required. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
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 )
{
/* The task waiting has a higher priority so record that a
context switch is required. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
}
else //队列是上锁的情况
{
/* Increment the lock count so the task that unlocks the queue
knows that data was posted while it was locked. */
//变量加1 队列消息已经复制到队列中了,这里加1 表示在上锁期间有多少个消息入队了
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
}
xReturn = pdPASS;
}
else
{
//如果队列满,中断级函数直接返回队列满
traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
xReturn = errQUEUE_FULL;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
在ISR里向队列发送数据就简单多了,因为中断不是任务,谈不上阻塞。入队函数中去掉阻塞就很清晰。
1、if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
判断队列是否未满或是覆写方式,如果满足条件,那就是可以向队列中拷贝数据,如果不满足条件就返回队列满错误码。
2、( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
拷贝数据
3、if( cTxLock == queueUNLOCKED )
拷贝完数据以后判断队列是否上锁,如果队列没有上锁就处理xTasksWaitingToReceive
列表,如果队列是上锁的,那就执行pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
cTxLock
成员表示了在队列上锁期间入队多少条消息,cTxLock的处理会在队列解锁的时候处理。
我们再来看一下队列解锁函数
static void prvUnlockQueue( Queue_t * const pxQueue )
{
/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. */
/* The lock counts contains the number of extra data items placed or
removed from the queue while the queue was locked. When a queue is
locked items can be added or removed, but the event lists cannot be
updated. */
taskENTER_CRITICAL();
{
int8_t cTxLock = pxQueue->cTxLock;//获取队列在上锁器件入队的个数
/* See if data was added to the queue while it was locked. */
while( cTxLock > queueLOCKED_UNMODIFIED )//循环处理
{
/* Data was posted while the queue was locked. Are any tasks
blocked waiting for data to become available? */
#if ( configUSE_QUEUE_SETS == 1 )
{
if( pxQueue->pxQueueSetContainer != NULL )
{
if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != 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. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Tasks that are removed from the event list will get
added to the pending ready list as the scheduler is still
suspended. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so record that a
context switch is required. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
}
#else /* configUSE_QUEUE_SETS */
{
/* Tasks that are removed from the event list will get added to
the pending ready list as the scheduler is still suspended. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority so record that
a context switch is required. */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
break;
}
}
#endif /* configUSE_QUEUE_SETS */
--cTxLock;
}
pxQueue->cTxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
/* Do the same for the Rx lock. */
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
while( cRxLock > queueLOCKED_UNMODIFIED )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--cRxLock;
}
else
{
break;
}
}
pxQueue->cRxLock = queueUNLOCKED;
}
taskEXIT_CRITICAL();
}
队列解锁相对简单了。
假设如下一种情况(以入队方式讲解,出队类似),
队列是上锁的,并且中断方式入队,每入队一条消息,cTxLock成员变量加加一次。(在中断中加加了)
那么什么时候减减呢?就是在队列解锁的时候,通过while循环一个一个处理。至于处理的内容还是处理xTasksWaitingToReceive
列表。检查是否有任务因为队列满而阻塞。