FreeRTOS 原理 --- 队列
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(固定长度消息传递)。
队列的优点:
- 不同任务之间的读写队列操作是互斥的(通过关中断实现)
- 读写队列有阻塞唤醒机制,阻塞的任务不抢占CPU资源(比如读队列,发现队列空,阻塞当前任务,除非其他任务有写队列,否则当前任务不再占用CPU资源)
队列的核心:
- 关中断实现互斥
- 环形缓冲区保存数据
- 链表实现阻塞和唤醒
- 向队列写一个消息,唤醒等待任务中优先级最高的(阻塞时按优先级插入)
队列相关的API
xQueueCreate() // 创建队列 xQueueSend() // 写队列 xQueueReceive() // 读队列
...
代码分析
xQueueCreate()
创建一个队列描述符和buffer,并初始化,buffer占用的内存紧跟描述符后面
队列描述符如下:
typedef struct QueueDefinition { int8_t *pcHead; // 指向队列头地址 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; // 指向下一个要写入的消息内存地址,初始化为队列头地址 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; // 指向上一个读的消息内存地址,初始化为队列尾地址 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_t xTasksWaitingToReceive; // 链表,记录哪些任务等待队列空,从而把消息写入队列 volatile UBaseType_t uxMessagesWaiting;// 当前队列包含多少个消息 UBaseType_t uxLength; // 队列容量,能包含多少个消息 UBaseType_t uxItemSize; // 每个消息所占字节数 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 ) ) 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;
xQueueSend()
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; /* 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(); { if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) // 队列非满 { xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); // 消息拷贝到队列 #if ( configUSE_QUEUE_SETS == 1 ) { } #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 ) // 取出一个优先级最高的,放入就绪队列,如果此任务优先级比当前任务高,触发任务切换 { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else if( xYieldRequired != pdFALSE ) { 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 ) { vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); // 当前任务的消息没写入队列,需要把当前任务记录到链表中,等队列非满再唤醒,同时把当前任务从就绪链表移到阻塞链表 prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); // 触发 PendSV 中断 } } else { /* Try again. */ prvUnlockQueue( pxQueue ); // 队列非满,第一次循环,再尝试写队列 ( void ) xTaskResumeAll(); } } else // 设置的等待时间已经到了,写入队列失败,函数返回 { /* The timeout has expired. */ prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); traceQUEUE_SEND_FAILED( pxQueue ); return errQUEUE_FULL; } } }