向隊列發送消息

BaseType_t xQueueSend ( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait 入隊阻塞時間);
BaseType_t xQueueSendToBack (QueueHandle_t xQueue, const void* pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
BaseType_t xQueueOverwrite (QueueHandle_t xQueue, const void * pvItemToQueue); 【沒有阻塞時間】
他們都用函數 xQueueGenericSend() 來實現。
類似的
xQueueSendFromISR()、
xQueueSendToBackFromISR()、
xQueueSendToFrontFromISR() 、
xQueueOverwriteFromISR()
都通過函數 xQueueGenericSendFromISR() 實現。
下面先分析xQueueGenericSend() :
/* xCopyPosition: 入隊方式,有三種入隊方式: queueSEND_TO_BACK: 后向入隊 queueSEND_TO_FRONT: 前向入隊 queueOVERWRITE: 覆寫入隊。 */
1 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) 2 { 3 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired; 4 TimeOut_t xTimeOut; 5 Queue_t * const pxQueue = ( Queue_t * ) xQueue; 6 7 configASSERT( pxQueue ); 8 configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) ); 9 configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) ); 10 #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ) 11 { 12 configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) ); 13 } 14 #endif 15 16 17 /* This function relaxes the coding standard somewhat to allow return 18 statements within the function itself. This is done in the interest 19 of execution time efficiency. 為了時間效率,允許函數本身的返回語句?*/ 20 for( ;; ) 21 { 22 taskENTER_CRITICAL(); 23 { 24 /* Is there room on the queue now? The running task must be the 25 highest priority task wanting to access the queue. If the head item 26 in the queue is to be overwritten then it does not matter if the 27 queue is full. 隊列是否有空間,覆蓋寫入就不用考慮空間問題了*/ 28 if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) 當前隊列里的消息個數 < 隊列長度(隊列有空間);或者是覆蓋寫入 29 { 30 traceQUEUE_SEND( pxQueue ); 沒有操作 31 xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); //<--拷貝數據 見后 ##1 32 33 #if ( configUSE_QUEUE_SETS == 1 ) 34 { 忽略隊列集相關的
81 } 82 #else /* configUSE_QUEUE_SETS */ 83 { 84 /* If there was a task waiting for data to arrive on the 85 queue then unblock it now. */ 86 if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) 如果有任務等待接受,激活這個任務 87 { 88 if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) 從事件列表刪除,加入就緒列表 或 Pending就緒列表 89 { 90 /* The unblocked task has a priority higher than 91 our own so yield immediately. Yes it is ok to do 92 this from within the critical section - the kernel 93 takes care of that. */ 94 queueYIELD_IF_USING_PREEMPTION(); //置位PendSV,切換任務 95 } 96 else 97 { 98 mtCOVERAGE_TEST_MARKER(); 99 } 100 } 101 else if( xYieldRequired != pdFALSE ) CopyDataToQueue后,由於互斥量的獲取 和 互斥量的優先級繼承問題,返回true 102 { 107 queueYIELD_IF_USING_PREEMPTION(); 請求PendSV 108 } 109 else 110 { 111 mtCOVERAGE_TEST_MARKER(); 112 } 113 } 114 #endif /* configUSE_QUEUE_SETS */ 115 116 taskEXIT_CRITICAL(); 117 return pdPASS; 118 } 119 else 【隊列沒有空余空間】 120 { 121 if( xTicksToWait == ( TickType_t ) 0 ) 122 { 123 /* The queue was full and no block time is specified (or 124 the block time has expired) so leave now. */ 125 taskEXIT_CRITICAL(); 126 127 /* Return to the original privilege level before exiting 128 the function. */ 129 traceQUEUE_SEND_FAILED( pxQueue ); 130 return errQUEUE_FULL; //返回 表示隊列滿了 131 } 132 else if( xEntryTimeSet == pdFALSE ) 133 { 134 /* The queue was full and a block time was specified so 135 configure the timeout structure. */ 136 vTaskSetTimeOutState( &xTimeOut ); //<--初始化時間結構體 137 xEntryTimeSet = pdTRUE; 138 } 139 else 140 { 141 /* Entry time was already set. */ 142 mtCOVERAGE_TEST_MARKER(); 143 } 144 } 145 } 146 taskEXIT_CRITICAL(); 147 148 /* Interrupts and other tasks can send to and receive from the queue 149 now the critical section has been exited. */ >>! 退出臨界區,至此,中斷 和 其它任務可以向這個隊列執行入隊(投遞)或出隊(讀取)操作.
>>! 如果阻塞時間不為0,則本任務會因為等待入隊而進入阻塞。
>>! 在將任務設置為阻塞的過程中,是不希望有其它任務和中斷操作這個隊列的事件列表的(WaitingToRcv列表和WaitingToSend列表),
>>! 因為操作隊列事件列表可能引起其它任務解除阻塞,這可能會發生優先級翻轉。
>>! 比如任務A的優先級低於本任務,但是在本任務進入阻塞的過程中,任務A卻因為其它原因解除阻塞了,這顯然是要絕對禁止的。
>>! 因此FreeRTOS使用掛起調度器來簡單粗暴的禁止其它任務操作隊列,
>>! 因為掛起調度器意味着任務不能切換並且不准調用可能引起任務切換的API函數。
>>! 但掛起調度器並不會禁止中斷,中斷服務函數仍然可以操作隊列事件列表,可能會解除任務阻塞、可能會進行上下文切換,這是不允許的。
>>! 於是,解決辦法是不但掛起調度器,還要給隊列上鎖!
操作事件列表的,可以是任務,可以是中斷。調度器掛起來禁止任務切換,隊列加鎖來禁止中斷對隊列的操作。 151 vTaskSuspendAll(); //調度器上鎖 152 prvLockQueue( pxQueue ); //隊列上鎖,隊列的cRxLock和cTxLock鎖定。 153 154 /* Update the timeout state to see if it has expired yet. */ 155 if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )【阻塞時間沒到】 156 { 157 if( prvIsQueueFull( pxQueue ) != pdFALSE )【隊列滿】 158 { 159 traceBLOCKING_ON_QUEUE_SEND( pxQueue );
/* 將任務添加到隊列的 xTasksWaitingToSend 列表中 和 延時列表中,並且將任務從就緒列表中移除。
注意!如果阻塞時間是 portMAX_DELAY 並且宏INCLUDE_vTaskSuspend 為 1 的話,
函數 vTaskPlaceOnEventList()會將任務添加到列表 xSuspendedTaskList 上。 */ 160 vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); 函數見后 ##2 161 162 /* Unlocking the queue means queue events can effect the 163 event list. It is possible that interrupts occurring now 164 remove this task from the event list again - but as the 165 scheduler is suspended the task will go onto the pending 166 ready last instead of the actual ready list. */ 167 prvUnlockQueue( pxQueue ); 解除隊列鎖,如果有任務要解除阻塞,只能移動到掛起就緒列表,因為調度器還沒開。 168 169 /* Resuming the scheduler will move tasks from the pending 170 ready list into the ready list - so it is feasible that this 171 task is already in a ready list before it yields - in which 172 case the yield will not cause a context switch unless there 173 is also a higher priority task in the pending ready list. */ 174 if( xTaskResumeAll() == pdFALSE ) 恢復調度器,將任務從掛起就緒列表,移動到就緒列表。 175 {
當任務成功阻塞在等待入隊操作后,當前任務就沒有必要再占用CPU了,所以接下來解除隊列鎖、恢復調度器、進行任務切換,下一個處於最高優先級的就緒任務就會被運行了。 176 portYIELD_WITHIN_API(); //portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 置位PendSV 177 } 178 } 179 else 【隊列有空閑了!】 180 { 181 /* Try again. */ 182 prvUnlockQueue( pxQueue ); 183 ( void ) xTaskResumeAll(); 184 } 185 } 186 else 【阻塞時間到了】 187 { 188 /* The timeout has expired. */ 189 prvUnlockQueue( pxQueue ); 解鎖的時候,會處理隊列上鎖期間的入隊操作。 190 ( void ) xTaskResumeAll(); 調度器解鎖 191 192 traceQUEUE_SEND_FAILED( pxQueue ); 193 return errQUEUE_FULL; // 返回,表示隊列滿了 194 } 195 }//end of for(;;) 196 }
for(大循環)
臨界區
>1 如果隊列有空間,或者是覆蓋寫入
拷貝數據到隊列,
@1 如果有任務等待接收信號量:
從事件列表踢掉這個任務,加入就緒列表 或 Pending就緒列表(函數TaskRemoveFromEventList)。並請求任務切換。
@2 如果沒有任務等待接收信號量:
拷貝的是互斥量話,那么由於優先級繼承問題,任務的優先級變了,所以要請求任務切換。
>2 隊列沒有空間
@1 如果沒有入隊阻塞時間,直接返回隊列已滿。
@2 設置了入隊阻塞時間,啟動一個定時器服務
臨界區
調度器上鎖
隊列上鎖
>1 阻塞時間沒到
@1 隊列還是滿的
執行雙加函數見##2,這個任務已經阻塞了,解鎖隊列、調度器,執行任務調度讓下個任務去運行吧!
@2 隊列有空了,解鎖隊列、調度器,返回到for大循環重來一套。
>2 阻塞時間到
解鎖隊列、調度器,返回隊列已滿錯誤
for(大循環結束)
##1 prvCopyDataToQueue 見信號量章節。
##2 加入到事件等待列表,加入到延時任務列表。雙加。
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 ); }
與這個函數操作相反的是,xTaskRemoveFromEventList
將當前任務從就緒列表中移除,並根據當前系統節拍計數器值計算喚醒時間,然后將任務加入延時列表。
函數 prvAddCurrentTaskToDelayedList 見時間管理。
xQueueGenericSendFromISR
1 BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition ) 2 { 3 BaseType_t xReturn; 4 UBaseType_t uxSavedInterruptStatus; 5 Queue_t * const pxQueue = ( Queue_t * ) xQueue; 6 7 configASSERT( pxQueue ); 8 configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) ); 9 configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) ); 10 11 /* RTOS ports that support interrupt nesting have the concept of a maximum 12 system call (or maximum API call) interrupt priority. Interrupts that are 13 above the maximum system call priority are kept permanently enabled, even 14 when the RTOS kernel is in a critical section, but cannot make any calls to 15 FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h 16 then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion 17 failure if a FreeRTOS API function is called from an interrupt that has been 18 assigned a priority above the configured maximum system call priority. 19 Only FreeRTOS functions that end in FromISR can be called from interrupts 20 that have been assigned a priority at or (logically) below the maximum 21 system call interrupt priority. FreeRTOS maintains a separate interrupt 22 safe API to ensure interrupt entry is as fast and as simple as possible. 23 More information (albeit Cortex-M specific) is provided on the following 24 link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */ 25 portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); 26 27 /* Similar to xQueueGenericSend, except without blocking if there is no room 28 in the queue. Also don't directly wake a task that was blocked on a queue 29 read, instead return a flag to say whether a context switch is required or 30 not (i.e. has a task with a higher priority than us been woken by this 31 post). */ 32 uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); //記錄BasePRI的值,並屏蔽所有系統可管理的中斷 33 { 34 if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ) 35 { 36 const int8_t cTxLock = pxQueue->cTxLock; //讀取cTxlock,用於判斷是否上鎖 37 38 traceQUEUE_SEND_FROM_ISR( pxQueue ); 39 40 /* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a 41 semaphore or mutex. That means prvCopyDataToQueue() cannot result 42 in a task disinheriting a priority and prvCopyDataToQueue() can be 43 called here even though the disinherit function does not check if 44 the scheduler is suspended before accessing the ready lists. */ 45 ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); 數據拷貝,見信號量章節。 46 47 /* The event list 事件列表 is not altered if the queue is locked. This will 48 be done when the queue is unlocked later. */ 49 if( cTxLock == queueUNLOCKED ) 【沒上鎖】 50 { 51 #if ( configUSE_QUEUE_SETS == 1 ) 52 {101 } 102 #else /* configUSE_QUEUE_SETS */ 103 { 104 if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) 【有任務在等待這個消息】 105 { 106 if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) 107 { 108 /* The task waiting has a higher priority so record that a 109 context switch is required. */ 110 if( pxHigherPriorityTaskWoken != NULL ) 111 { 112 *pxHigherPriorityTaskWoken = pdTRUE; 置位這個標記變量,進行任務切換 113 } 114 else 115 { 116 mtCOVERAGE_TEST_MARKER(); 117 } 118 } 119 else 120 { 121 mtCOVERAGE_TEST_MARKER(); 122 } 123 } 124 else 125 { 126 mtCOVERAGE_TEST_MARKER(); 127 } 128 } 129 #endif /* configUSE_QUEUE_SETS */ 130 } 131 else 【如果隊列上鎖的話,將隊列成員cTxLock+1,表示進行了一次入隊操作】 132 { 133 /* Increment the lock count so the task that unlocks the queue 134 knows that data was posted while it was locked. */ 135 pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 ); 136 } 137 138 xReturn = pdPASS; 【入隊完成】 139 } 140 else 141 { 142 traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue ); 143 xReturn = errQUEUE_FULL; 【隊列滿,直接返回】 144 } 145 } 146 portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); 147 148 return xReturn; 149 } 150 /*-----------------------------------------------------------*/
因為沒有阻塞的處理,所以代碼簡單了很多。
留白
