前言
本篇文章主要来为大家分析队列的内部机制和源码实现。
一、队列结构体分析
在FreeRTOS中队列会使用一个结构体来表示:
1.int8_t * pcHead 和 int8_t * pcWriteTo:这些指针指向队列存储区的头部和下一个可写入的位置。队列存储区是一个用于存储队列中数据项的缓冲区。
2.union:这个联合体 u 可以是两种不同类型之一:QueuePointers_t 或 SemaphoreData_t,这允许队列结构用于不同的用途,例如队列或信号量。
3.在QueuePointers_t 结构体中包含pcReadFrom指向当前读位置。
4.List_t xTasksWaitingToSend 和 List_t xTasksWaitingToReceive:这些是用于存储等待发送或接收数据的任务的链表。这些链表按任务的优先级排序,以确保高优先级的任务有更高的访问权。
5.volatile UBaseType_t uxMessagesWaiting:这是队列中当前待处理的数据项数量。
6.UBaseType_t uxLength:这是队列的长度,即它可以容纳的数据项数量,不是字节数。
7.UBaseType_t uxItemSize:这是队列中每个数据项的大小。
8.volatile int8_t cRxLock 和 volatile int8_t cTxLock:这些变量用于跟踪队列的锁状态。它们记录队列是否被锁定,并在锁定时记录发送到队列的数据项数量。
9.uint8_t ucStaticallyAllocated:这个变量用于标志队列内存的分配方式。如果设置为 pdTRUE,表示队列的内存是静态分配的,不应尝试释放内存。
10.struct QueueDefinition * pxQueueSetContainer:这个成员仅在配置中启用了队列集(configUSE_QUEUE_SETS为1)时有效。它指向包含此队列的队列集。
二、创建队列
队列由队列头和队列中的每一个元素组成:
故分配内存时需要包含队列头部和全部元素的大小
三、读写队列源码分析
读写队列主要涉及到 环形缓冲区、链表,共享资源的访问。
1.读队列源码分析
在写队列时首先需要先禁止任务调度,防止一个任务在写队列时,其他任务也来写队列导致出现错误。
taskENTER_CRITICAL();//禁止任务调度
判断当前队列中消息数量是否小于队列的长度
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
如果当前队列中消息数量小于队列的长度,那么就证明还可以写入数据。
调用prvCopyDataToQueue函数往队列中写入数据。
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
判断是否有任务在等待数据,如果有任务在等待数据,那么将这个任务从等待接收数据链表中移除,并且唤醒这个任务。
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(); } }
如果队列已满并且没有设置超时时间或者超时时间到了,那么就会返回错误,并且恢复中断,让任务可以重新调度。
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; }
如果设置了超时时间,那么会调用vTaskPlaceOnEventList函数将当前任务挂入xTasksWaitingToSend 链表
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) { if( prvIsQueueFull( pxQueue ) != pdFALSE ) { traceBLOCKING_ON_QUEUE_SEND( pxQueue ); vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
vTaskPlaceOnEventList函数
void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait ) { configASSERT( pxEventList ); /* THIS FUNCTION MUST BE CALLED WITH EITHER INTERRUPTS DISABLED OR THE * SCHEDULER SUSPENDED AND THE QUEUE BEING ACCESSED LOCKED. */ /* Place the event list item of the TCB in the appropriate event list. * This is placed in the list in priority order so the highest priority task * is the first to be woken by the event. The queue that contains the event * list is locked, preventing simultaneous access from interrupts. */ vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) ); prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); }
2.写队列源码分析
读取队列时也是需要禁止任务调度的,防止多个任务一起读取数据
taskENTER_CRITICAL();
首先判断队列中的数据量是否大于0,如果大于0就将队列中的数据读取出来,并且将uxMessagesWaiting减1。
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;
读取完数据后判断是否有任务在等待写数据,如果有任务在等待写数据那么就将这个任务从等待写数据链表中移除
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); }
如果没有设置超时时间,或者是时间到了那么就返回错误并且退出
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; }
如果设置了超时时间那么就将当前读取数据任务挂入链表当中
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 ); prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } }
总结
本篇文章就讲解到这里,大家可以自己对FreeRTOS的源代码进行分析,分析源代码对学习FreeRTOS有重要的意义。