【FreeRTOS】之队列
队列是为任务与任务、任务与中断之间的通信而实现的,也称消息队列。队列中可以存储有限的、大小固定的数据项目。队列是FreeRTOS的重点,后面的信号量,互斥锁其本质都是队列。
文章目录
QueueSend()
{
// 关中断,任务的中断都是在定时器中断函数里完成切换的,关掉时钟中断就没有办法切换任务,这样就避免访问冲突的情况发生。而且关闭的是部分中断(某优先级以下的中断),近似于全部中断但不是关闭所有中断。注意:关闭中断不是关闭定时器
// 写数据
// 开中断
}
队列
- 创建队列,队列的存储空间从heap中分配,API如下:
/** * uxQueueLength:指定队列的长度,即最多可以存放的元素个数 * uxItemSize:队列中存储的元素的大小(占用的字节数) */ QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
- 元素入队时,将数据插入队尾,使用API如下:
/** * xQueue:目标队列的句柄。 * pvItemToQueue:入队元素的指针。队列将存储此指针指向的数据的备份。 * xTicksToWait:指定等待队列有空间可以容纳新元素入队的最长等待(阻塞)时间,这种情况发生在队列已经满了的时候。如果需要等待,则任务会因为调用这个函数而进入阻塞状态,直到队列非满而能让这个任务写入数据或者指定的阻塞时间过期,才会转变为就绪态。如果此参数使用0,则当队列已经满了的时候,此函数立即返回而不阻塞。 */ #define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
- 元素出队时,从队首取出数据,使用API如下:
/** xQueue:目标队列的句柄。 * pvBuffer:用于存放读取到的队列元素的缓冲区,队列将把出队的元素拷贝到此缓冲区中。 * xTicksToWait:参见xQueueSend()函数的解释。 */ BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
使用队列的好处
- 数据互斥
- 休眠唤醒,提高CPU的使用率
队列的核心:关中断—环形缓冲区—任务链表
- 关中断: 使用
taskENTER_CRITICAL()
关闭中断,防止写/读操作被打断,从而实现数据互斥 - 环形缓冲区: 数据保存
- 任务链表: 用两个任务链表实现休眠与唤醒
关调度与关中断
- 关闭调度器一般是防止任务切换,但仍然可以响应中断,使用
void vTaskSuspendAll( void )
;而关中断一般是将某优先级以下的中断关闭,防止中断对当前操作进行干扰,使用taskENTER_CRITICAL()
。
环形缓冲区
- 实质上就是有读和写两个指针
- 假设申请的ringbuf大小为LEN,那么w=w+1/LEN,r=r+1/LEN。这样能使指针指向的范围保持在[ringbuf头地址,头地址+LEN]之间
Queue list:两个任务链表
List_t xTaskWaitingToSend;
// 存放写此队列不成功而挂起的任务List_t xTaskWaitingToReceive;
// 存放读此队列不成功而挂起的任务
队列如何知道唤醒哪个任务?
- Queue.list[],此队列结构体中有需要唤醒的任务。
- 以读队列为例,如果发现队列中没有数据,此时不仅需要将task自身从ReadyList[]移到pxDelayedTaskList[],还需要将自己插入Queue.list[]。
- 经过上一步,发送任务在写数据之后就可以根据Queue.list唤醒接收任务。
队列结构体以及读写队列
队列结构体
/*
* Definition of the queue used by the scheduler.
* Items are queued by copy, not reference. See the following link for the
* rationale: https://www.FreeRTOS.org/Embedded-RTOS-Queues.html
*/
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /*< 指向队列存储区的开头. */
int8_t * pcWriteTo; /*< 指向存储区域中空闲的下一个位置. */
union
{
QueuePointers_t xQueue; /*< 将此结构用作队列时专门需要的数据,这里就是ringbuf了. */
SemaphoreData_t xSemaphore; /*< 仅当此结构用作信号量时才需要的数据,作为信号量时用此值,信号量与队列共用结构体. */
} 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; /*< 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 ) )
uint8_t ucStaticallyAllocated; /*< 如果队列使用的内存是静态分配的,则设置为 pdTRUE,以确保不会尝试释放内存。 */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
Queue创建好之后的总大小是 Queue结构体大小 + Ringbuf大小(Queue uxLength * uxItemSize)。ringbuf是紧跟在Queue之后的。
在xQueueGenericCreate()
中:
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
读队列
流程
- 关中断
- 判断有无数据
- 有数据:直接进行第三步
- 无数据:等有数据被唤醒后进行第三步
- 返回ERR
- 休眠:
(a). 在Queue.xTasksWaitingToReceive记录自己
(b). 将自己所在任务从ReadyList[]放入DelayedList[]
- 终于有数据
(a). Copy数据
(b). 唤醒Queue.xTaskWaitingToSend[]中的第一个任务,并从此列表中移除;并将前面移除的任务从pxDelayedTaskList[]移到ReadyList[]
代码:xQueueReceive
点击展开代码
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
/* Check the pointer is not NULL. */
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 ); // 从队列复制数据
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 ) // 从xTasksWaitingToSend移除第一个等待任务
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else // 没有数据
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was empty and no block time is specified (or
* the block time has expired) so leave now. */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* The queue was empty 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 ) // 没有数据愿意等待
{
/* 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 ); // 把当前任务放入xTasksWaitingToReceive,并将任务从ReadyList[]移到pxDelayedTaskList[]
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
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
{
/* Timed out. If there is no data in the queue exit, otherwise loop
* back and attempt to read the data. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
} /*lint -restore */
}
读队列阻塞
- 如果队列为空时读取队列,并且参数xTicksToWait不为0,则将当前任务移至移至DelayedList中阻塞。直到队列中有消息到来或者超时,才会唤醒DelayedList中优先级最高的或者最先超时的任务。
写队列
流程
- 一言不合关中断
- 判断有无空间,若有空间跳过此步骤
- 有空间:直接进行第三步
- 无空间:
- 返回ERR
- 休眠:
(a): 将当前任务放入Queue.xTasksWaitingToSend
(b): 将当前任务从ReadyList[]移至DelayedList[]
- 终于有空间了:
(a): write Data
(b): 唤醒接收任务:从Queue.xTasksWaitingToReceive中移除第一个任务, 并将此任务从DelayedList[]移至ReadyList[]
被阻塞的发送任务如何被唤醒?
有两种情况:
- 超时(此情况在tick中断中处理)
- 任务写队列不成功时,它会被挂起:从ready list移到delayed list中
- 在delayed list中,按照"超时时间"排序
- 系统Tick中断不断发生,在Tick中断里判断delayed list中的任务时间到没?时间到后就唤醒它,具体代码参照task.c中的
BaseType_t xTaskIncrementTick( void )
- 其他任务读取队列导致队列有空间(此情况在其他任务的xQueueGenericSend中处理)
- prvCopyDataFromQueue(pxQueue, pvBuffer)有队列在读完数据之后会留出空间
- 这时会检测xTaskWaitingToSend有无任务在等待发送数据,若有则唤醒。具体代码请参考下方代码(queue.c中的xQueueGenericSend)
pxDelayedTaskList[]
- 按照被唤醒的时间存放阻塞任务的链表。
Tick中断都做了什么?(xTaskIncrementTick)
- 增加计数值, const TickType_t xConstTickCount = xTickCount + ( TickType_t)1
- 判断是否有阻塞中的任务超时,若有则把DelayedList中超时的任务移动到ReadyList[]
BaseType_t xTaskIncrementTick( void )
:BaseType_t xTaskIncrementTick( void ) { TCB_t * pxTCB; TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; traceTASK_INCREMENT_TICK( xTickCount ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1; // 计数 xTickCount = xConstTickCount; if( xConstTickCount == ( TickType_t ) 0U ) { taskSWITCH_DELAYED_LISTS(); } else { mtCOVERAGE_TEST_MARKER(); } if( xConstTickCount >= xNextTaskUnblockTime ) { for( ; ; ) { if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) { xNextTaskUnblockTime = portMAX_DELAY; break; } else { // 延时链表不为空说明有任务在等待 pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); // 获取第一个延时任务超时时间 if( xConstTickCount < xItemValue ) { // 判断是否超时,没超时则退出循环 xNextTaskUnblockTime = xItemValue; break; } else { mtCOVERAGE_TEST_MARKER(); } listREMOVE_ITEM( &( pxTCB->xStateListItem ) ); if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) { listREMOVE_ITEM( &( pxTCB->xEventListItem ) ); } else { mtCOVERAGE_TEST_MARKER(); } prvAddTaskToReadyList( pxTCB ); // 将超时任务移至ReadyList #if ( configUSE_PREEMPTION == 1 ) { if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xSwitchRequired = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_PREEMPTION */ } } } ······ // 此处省略若干代码,具体参考https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/356fff8028734dc50a46972b2e315e2871b0a12e/tasks.c#L2734 } else { ++xPendedTicks; #if ( configUSE_TICK_HOOK == 1 ) { vApplicationTickHook(); } #endif } return xSwitchRequired; }
代码:xQueueGenericSend
点击展开代码
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;
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
/*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(); // 一言不合关中断
{
/* 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 );
#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 ); //把数据拷贝到队列中
/* 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 ); // 愿意等待就将当前任务放到 xTasksWaitingToSend 链表上, 并在vTaskPlaceOnEventList内部会将函数从ReadyList[]放到pxDelayedTaskList[]
/* 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 list 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 the 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;
}
} /*lint -restore */
}
写队列阻塞
- 如果在队列满时写队列,并且xTicksToWait不为0,这时会将当前发送数据到队列的任务移入DelayedList中阻塞,直到队列有空间或发送超时才被唤醒。