FreeRTOS源碼探析之——任務調度相關(freertos任務調度原理)

      網友投稿 1485 2022-05-30

      FreeRTOS可以運行多任務,在于其內核的任務調度功能,本篇介紹任務調度的基本思路與部分源碼分析。

      1 裸機編程與RTOS 的區別

      1.1 裸機程序基本框架

      /*主函數*/ int main() { init();//一些初始化 /*死循環*/ while(1) { do_something_1();//執行一些邏輯 do_something_2(); }//循環執行 } /*中斷服務函數*/ IRQ_Handler() { set_flag();//簡短的標記操作 }

      單片機裸機編程的思路比較簡單,就是一個死循環,程序依次執行while(1)中的各條語句,循環往復即可,需要處理某些緊急事件時,通過中斷服務函數來打斷while(1)的執行。

      裸機編程雖然簡單,但只能在一個循環中執行各種裸機,第一項功能執行完后才能執行第二項功能,就好比有多個人在輪流干活,CPU的利用率不高,不能處理并行邏輯。

      1.2 RTOS程序基本框架

      /*主函數*/ int main() { init();//一些初始化 xTaskCreate(); vTaskStartScheduler(); //啟動調度器 } /*子任務1(死循環)*/ void task1() { while(1) { do_something_1();//執行一些邏輯(如采集傳感器信息) vTaskDelay(); } } /*子任務2(死循環)*/ void task2() { while(1) { do_something_2();//執行一些邏輯(如執行電機運動) vTaskDelay(); } } /*中斷服務函數*/ IRQ_Handler() { set_event();//觸發事件、信號量等 }

      單片機引入RTOS,可以將各個功能模塊分別設計為單獨的任務,每個任務都是一個死循環,就好比有多個人在同時干活,這樣CPU的利用率就提高了,并且可以處理一些并行邏輯。

      FreeRTOS源碼探析之——任務調度相關(freertos任務調度原理)

      單片機只有一個CPU(核),那怎么讓多個人同時干活呢?其實每個子任務雖然都是死循環,但并不是每個子任務一直都在執行,每個子任務在執行期間,可能需要延時,也可能需要等另一個任務的數據到來,所有,在某個任務在等待的時候,CPU就可以停止此任務,然后切換到其它任務執行,這樣看起來就是多個人在同時干活了。

      2 RTOS任務間通信

      在裸機編程中,當設計了一個稍微復雜的功能是,會設計處許多子函數來實現一個整體功能,這之中通知會用到一些全局變量或全局數組等來實現各個子函數之間的聯系。

      在RTOS中,當然也可以使用全局變量,但RTOS更推薦我們使用系統自帶的任務間通信機制。原因有二:

      阻塞等待機制比輪詢等待更高效

      全局變量當用作某種事件標志時,獲取該標志的任務需要輪詢檢測標志是否變化,這樣會產生大量無效的判斷,而使用任務間通信中的阻塞等待機制,CPU可以轉而處理其它事情,當標志變化時,解除阻塞,又可以及時執行后續處理。

      全局變量會產生不可重入函數造成邏輯混亂

      RTOS運行時,CPU是在各個任務間跳來跳去的,若使用全局變量不恰當,會導致原本設計的邏輯產生混亂。比如某個低優先級任務正在訪問某個公共函數,并對該函數中的全局變量進行了修改,還未退出該函數時,更高優先級的任務搶占了CPU的使用權,并也對該函數中的全局變量進行了修改,此時,如果低優先級的任務若認為自己對變量修改成功,并因此而執行自己后續的邏輯,則會導致邏輯錯誤。

      FreeRTOS任務間通信方式

      信號量(Semaphore):用于任務間的同步,一個任務以阻塞方式等待另一個任務等待另一個任務釋放信號量。

      互斥量(Mutex):用于任務間共享資源的互斥訪問,使用前獲取鎖,使用后釋放鎖。

      事件標志組(EventGroup):也是用于任務間的同步,相比信號量,事件標志組可以等待多個事件發生。

      消息隊列(Queue):類比全局數據,它可以一次發送多個數據(一般將數據定義成結構體發送),每次數據的大小固定不變。

      流緩沖區(StreamBuffer):在隊列的基礎上,優化的一種更適合的數據結構,可以一次寫入任意數量的字節,并且可以一次讀取任意數量的字節。

      消息緩沖區(MessageBuffer):在流式緩沖區的基礎上實現的,其進一步針對“消息”進行設計改進,每一條消息的寫入增加了一個字節用來表示該條消息的長度,讀取時需要一次性讀出至少一條消息,否則會返回 0。

      任務通知(Notify):不同于上面的任務間通信方式(使用某種通信對象,通信對象是獨立于任務的實體,有單獨的存儲空間,可以實現數據傳遞和較復雜的同步、互斥功能),通知是發向一個指定的任務的,直接改變該任務TCB的某些變量。

      3 RTOS任務調度

      3.1 任務狀態

      1 創建任務→就緒態(Ready):任務創建完成后進入就緒態,表明任務已準備就緒,隨時可以運行,只等待調度器進行調度。

      2 就緒態→運行態(Running):發生任務切換時,就緒列表中最高優先級的任務被執行,從而進入運行態。

      3 運行態→就緒態:有更高優先級任務創建或者恢復后,會發生任務調度,此刻就緒列表中最高優先級任務變為運行態,那么原先運行的任務由運行態變為就緒態,依然在就緒列表中,等待最高優先級的任務運行完畢繼續運行原來的任務。

      4 運行態→阻塞態(Blocked):正在運行的任務發生阻塞(掛起、延時、讀信號量等待)時,該任務會從就緒列表中刪除,任務狀態由運行態變成阻塞態,然后發生任務切換,運行就緒列表中當前最高優先級任務。

      5 阻塞態→就緒態:阻塞的任務被恢復后(任務恢復、延時時間超時、讀信號量超時或讀到信號量等),此時被恢復的任務會被加入就緒列表,從而由阻塞態變成就緒態;如果此時被恢復任務的優先級高于正在運行任務的優先級,則會發生任務切換,將該任務將再次轉換任務狀態,由就緒態變成運行態。

      6、7、8 就緒態、阻塞態、運行態→掛起態(Suspended):任務可以通過調用vTaskSuspend() API 函數都可以將處于任何狀態的任務掛起,被掛起的任務得不到CPU的使用權,也不會參與調度,除非它從掛起態中解除。

      9 掛起態→就緒態:把 一 個 掛 起 狀態 的 任 務 恢復的 唯 一 途 徑 就 是 調 用 vTaskResume() 或vTaskResumeFromISR() API 函數,如果此時被恢復任務的優先級高于正在運行任務的優先級,則會發生任務切換,將該任務將再次轉換任務狀態,由就緒態變成運行態。

      以上是任務運行的各種狀態,看起來有點復雜,可以這樣理解:

      為簡單起見,先不考慮掛起態,在任一時刻,CPU只能處理某一個任務,則該任務就處于運行態,對于其它任 務,當是自己想要延時或等待時,則處于阻塞態,當自己想要執行但因為優先級低而不能執行時,則處于就緒態。

      然后,以上狀態如何被改變呢?

      1.運行態的自己想進入阻塞態,則就緒態的任務即可運行。

      2.阻塞態的解除阻塞進入就緒,若該任務的優先級更高,則可搶占當前處于運行的任務,使自己運行,使對方就緒。

      有沒有發現,阻塞態的任務要想運行,必須先進入就緒態,再進入運行態。

      3.2 調度器

      FreeRTOS中提供的任務調度器是基于優先級的搶占式調度:在系統中除了中斷處理函數、調度器上鎖部分的代碼和禁止中斷的代碼是不可搶占的之外,系統的其他部分都是可以搶占的。

      調度器就是使用相關的調度算法來決定當前需要執行的任務。所有的調度器有一些共同的特性:

      調度器可以區分就緒態任務和掛起態任務(由于延遲,信號量等待,事件組等待等原因而使得任務被掛起)。

      調度器可以選擇就緒態中的一個任務,然后激活它(通過執行這個任務)。 當前正在執行的任務是運行態的任務。

      FreeRTOS 主要有兩種調度方式

      搶占式調度:每個任務都有不同的優先級,任務會一直運行直到被高優先級任務搶占或者遇到阻塞式的 API 函數,如 vTaskDelay。

      時間片調度:每個任務都有相同的優先級,任務會運行固定的時間片個數或者遇到阻塞式的 API 函數,比如vTaskDelay,才會執行同優先級任務之間的任務切換。

      3.2.1 搶占式調度示例

      創建 3 個任務 Task1,Task2 和 Task3,優先級依次為1、2、3,即Task3的優先級最高。

      起初任務 Task1處于運行態(占用CPU),運行過程中由于 Task2 就緒,在搶占式調度器的作用下任務 Task2 搶占Task1 的執行。

      所以:Task2 由就緒態進入到運行態,Task1 由運行態進入到就緒態。

      任務 Task2 在運行中,由于 Task3 又處于了就緒態,在搶占式調度器的作用下任務 Task3 又搶占 Task2的執行。

      所以:Task3 由就緒態進入到運行態,Task2 由運行態進入到就緒態。

      任務 Task3 運行過程中調用了阻塞式 API 函數,比如 vTaskDelay,任務 Task3 被掛起,進入掛起態,在搶占式調度器的作用下查找到下一個要執行的最高優先級任務是 Task2,所以:任務 Task2 由就緒態又回到了運行態。

      任務 Task2 在運行中,由于 Task3 的阻塞時間結束, Task3 再次就緒,在搶占式調度器的作用下任務 Task3 再次搶占Task2 的執行。 Task3 進入到運行態,Task2 由運行態進入到就緒態。

      3.2.2 時間片調度示例

      創建 4 個同優先級任務 Task1,Task2,Task3 和 Task4。

      每個任務分配的時間片大小是 5 個系統時鐘節拍。

      先運行任務 Task1,運行夠 5 個系統時鐘節拍后,通過時間片調度切換到任務 Task2。

      任務 Task2 運行夠 5 個系統時鐘節拍后,通過時間片調度切換到任務 Task3。

      任務 Task3 在運行期間調用了阻塞式 API 函數,調用函數時,雖然 5 個系統時鐘節拍的時間片大小還沒有用完,此時依然會通過時間片調度切換到下一個任務 Task4。

      任務 Task4 運行夠 5 個系統時鐘節拍后,通過時間片調度切換到任務 Task1。

      注:以上以5個Tick的時間片舉例,而FreeRTOS的時間片只能是1個Tick。

      4 RTOS與TSOS

      RTOS

      英文為Real Time Operating System,即實時操作系統,實時是指當外界事件或數據產生時,能夠接收并以足夠快的速度予以處理,其處理的結果又能在規定的時間之內來控制生產過程或對處理系統作出快速響應,并控制所有實時任務協調一致運行的操作系統。

      RTOS一般用于相對低速的MCU,比如運動控制類、按鍵輸入等動作要求實時處理的系統,一般要求ms級,甚至us級響應。

      TSOS

      英文為Time Sharing Operating System,即分時操作系統,分時是指將系統處理機時間和內存空間按照一定的時間間隔(也就是我們所說的時間片)輪流地切換給各線程的程序使用。

      TSOS一般用于相對高速的CPU,如多用戶的桌面系統、服務器等系統(Windows、Linux)。

      主要區別

      RTOS具有高優先級任務搶占功能,以及同優先級間的時間片輪轉調度,因而可以對事件進行及時響應(即具有較好的實時性),而TSOS是固定的時間片輪轉調度,當有事件發送時,也只能等當前時間片執行完后,才能執行下一個時間片,因此可能不能及時響應某些緊急事件。

      5 FreeRTOS任務調度相關源碼

      5.1 任務控制塊TCB_t

      FreeRTOS對各個任務進行調度,首先需要一種方式來訪問和控制各個任務,任務控制塊就可以實現這種功能,它本質是一個結構體,記錄了任務的堆棧指針、任務當前狀態、任務優先級等。

      typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /*棧頂 */ ListItem_t xStateListItem; /*標記任務狀態的列表(Ready, Blocked, Suspended ) */ ListItem_t xEventListItem; /*任務的事件列表 */ UBaseType_t uxPriority; /*任務優先級 */ StackType_t *pxStack; /*任務棧起始地址 */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*創建時給任務的描述性名稱,便于調試 */ #if(...省略部分) ...省略部分 #endif } tskTCB; typedef tskTCB TCB_t;

      5.2 阻塞延時vTaskDelay

      當某個任務需要延時,調用vTaskDelay(),則該任務進入阻塞態,此時調度器會從就緒列表中找到優先級最高的就緒任務開始執行。

      那vTaskDelay()里面具體執行了哪些內容你呢?

      #if ( INCLUDE_vTaskDelay == 1 ) void vTaskDelay( const TickType_t xTicksToDelay ) { BaseType_t xAlreadyYielded = pdFALSE; /*delay時間為0則強制進行任務切換 */ if( xTicksToDelay > ( TickType_t ) 0U ) { configASSERT( uxSchedulerSuspended == 0 ); /*停止任務調度*/ vTaskSuspenvdAll(); { traceTASK_DELAY(); /* 在調度器被掛起時從事件列表中刪除的任務,在恢復調度器之前,不會被放置在就緒列表中或從阻塞列表中刪除 此任務不能出現在事件列表中,因為它是當前正在執行的任務。*/ prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); } xAlreadyYielded = xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } /* 如果xTaskResumeAll沒有進行任務切換,則強制進行任務切換 */ if( xAlreadyYielded == pdFALSE ) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* INCLUDE_vTaskDelay */

      vTaskDelay的延時參數是以tick為單位的,即vTaskDelay(1)延時1ms。

      當延時參數不為0時,即正常調用延時函數時,先停止任務調度,將當前任務添加至延時列表中,再恢復任務調度。

      當延時參數為0時,會強制進行任務切換(portYIELD_WITHIN_API)(疑問:如果當前任務的優先級是最高的,雖然強制切換,但由于該任務的優先級最高,所起其實沒有切換到其它任務?如果真的是強制切換到另一個任務,那上面時候這個最高優先級的任務再搶會CPU的使用權呢?)。

      5.2.1添加任務到延時列表

      static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely ) { TickType_t xTimeToWake; const TickType_t xConstTickCount = xTickCount; #if( INCLUDE_xTaskAbortDelay == 1 ) //...省略部分 #endif /* 在將任務添加到阻塞列表前先將其從就緒列表中移除*/ if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) { /* 當前任務必定在就緒列表中, 所以無需檢測, 并且port reset macro 可以被立即調用 */ portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); } else { mtCOVERAGE_TEST_MARKER(); } #if ( INCLUDE_vTaskSuspend == 1 ) { if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) ) { /* 為確保不是被定時器事件喚醒,添加任務到掛起列表而不是延時列表. 它將會無定期的阻塞*/ vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); } else { /* 如果事件沒有發生,計算任務應該被喚醒的時間。這可能會溢出,但這無關緊要,內核會正確地管理它*/ xTimeToWake = xConstTickCount + xTicksToWait; /* 列表項將按喚醒順序插入 */ listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake ); if( xTimeToWake < xConstTickCount ) { /* 喚醒時間已溢出,將該項放入溢出列表 */ vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); } else { /* 喚醒時間未溢出,所以當前的阻塞列表被使用. */ vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); /* 如果進入阻塞狀態的任務被放置在阻塞任務列表的頂部,那么xNextTaskUnblockTime也需要更新 */ if( xTimeToWake < xNextTaskUnblockTime ) { xNextTaskUnblockTime = xTimeToWake; } else { mtCOVERAGE_TEST_MARKER(); } } } } #else /* INCLUDE_vTaskSuspend */ //...省略部分 #endif /* INCLUDE_vTaskSuspend */ }

      5.2.2 任務切換

      #define portNVIC_PENDSVSET_BIT ( 1UL << 28UL ) //PendSV的懸起位(第28位) #ifndef portYIELD_WITHIN_API #define portYIELD_WITHIN_API portYIELD #endif /* Scheduler utilities. */ #define portYIELD() \ { \ /* Set a PendSV to request a context switch. */ \ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \ \ /* 觸發PendSV,產生上下文切換 */ \ __dsb( portSY_FULL_READ_WRITE ); \ __isb( portSY_FULL_READ_WRITE ); \ }

      portYIELD()任務切換函數,主要就是將PendSV的懸起位置1,在沒有其它中斷運行時執行PendSV中斷服務函數,在這個中斷函數中實現任務切換。

      5.2.3 PendSV中斷服務函數

      PendSV中斷服務函數是一段匯編代碼,可能不太容易看懂,該函數需要先了解如下:

      外部變量pxCurrentTCB是當前正在運行的任務的任務控制塊

      當進入PendSV中斷服務函數時,上一任務的運行環境為:xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參),這些CPU寄存器的值會自動保存到任務的棧中,剩下的R4~R11需要手動保存。

      總的來說,該函數實現3部分功能:上文保存,下文切換,中間調用了一個C函數vTaskSwitchContext,用于尋找要運行的任務。

      __asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 mrs r0, psp /* 保存當前任務PSP地址到R0中 */ isb ldr r3, =pxCurrentTCB /* 獲取pxCurrentTCBConst指針地址 */ ldr r2, [r3] /* R2被賦予當前TCB地址 */ /* Is the task using the FPU context? If so, push high vfp registers. */ tst r14, #0x10 it eq vstmdbeq r0!, {s16-s31} /* Save the core registers. */ stmdb r0!, {r4-r11, r14} /* Save the new top of stack into the first member of the TCB. */ str r0, [r2] stmdb sp!, {r3} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 /*屏蔽中斷*/ dsb isb bl vTaskSwitchContext /*調用vTaskSwitchContext*/ mov r0, #0 msr basepri, r0 /*打開中斷*/ ldmia sp!, {r3} /* The first item in pxCurrentTCB is the task top of stack. */ ldr r1, [r3] /* R3依然是pxCurrentTCBConst指針地址,R1變為新TCB地址 */ ldr r0, [r1] /* R0值成為新TCB的棧地址(該TCB發生上一次調度的PSP值) */ /* Pop the core registers. */ ldmia r0!, {r4-r11, r14} /* Is the task using the FPU context? If so, pop the high vfp registers too. */ tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31} msr psp, r0 isb #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */ #if WORKAROUND_PMU_CM001 == 1 push { r14 } pop { pc } nop #endif #endif bx r14 }

      5.2.4 尋找要運行的任務

      該函數實際是選擇一個最高優先級的任務運行

      void vTaskSwitchContext( void ) { if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ) { /* The scheduler is currently suspended - do not allow a context switch. */ xYieldPending = pdTRUE; } else { xYieldPending = pdFALSE; traceTASK_SWITCHED_OUT(); #if ( configGENERATE_RUN_TIME_STATS == 1 ) //...省略部分 #endif /* configGENERATE_RUN_TIME_STATS */ /* 檢查堆棧是否溢出 */ taskCHECK_FOR_STACK_OVERFLOW(); /* 選擇一個最高優先級的任務運行*/ taskSELECT_HIGHEST_PRIORITY_TASK(); traceTASK_SWITCHED_IN(); #if ( configUSE_NEWLIB_REENTRANT == 1 ) //...省略部分 #endif /* configUSE_NEWLIB_REENTRANT */ } }

      選擇一個最高優先級的任務的函數實現如下:

      獲取任務的本質是獲取任務的控制塊(TCB)

      define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority; \ \ /* 尋找就緒任務的最高休閑級 */ \ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \ /*獲取優先級最高的就緒任務的TCB,更新到pxCurrentTCB*/ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ } /* taskSELECT_HIGHEST_PRIORITY_TASK() */

      獲取優先級最高的就緒任務的TCB的函數實現如下:

      #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ { \ List_t * const pxConstList = ( pxList ); \ /* Increment the index to the next item and return the item, ensuring */ \ /* we don't return the marker used at the end of the list. */ \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \ { \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ } \ ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ }

      5.3 停止/恢復任務調度

      5.3.1 vTaskSuspenvdAll()

      掛起調度程序可以防止發生上下文切換,但可以使能中斷。如果在掛起調度程序時中斷請求上下文切換,則該請求將保持掛起狀態,并且僅在重新啟動調度程序(未掛起)時才執行該請求。

      該函數就是將調度器鎖定,每調用一次該函數,會對變量uxSchedulerSuspended進行自加,用于嵌套調用時記錄嵌套的深度。

      PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE; void vTaskSuspendAll( void ) { ++uxSchedulerSuspended; }

      5.3.2xTaskResumeAll()

      每調用一次vTaskSuspendAll()函數就會將uxSchedulerSuspended變量加一,那么調用對應的xTaskResumeAll()肯定就是將變量減一

      如果恢復調度程序導致上下文切換,則返回pdTRUE,否則返回pdFALSE

      BaseType_t xTaskResumeAll( void ) { TCB_t *pxTCB = NULL; BaseType_t xAlreadyYielded = pdFALSE; /* 如果uxSchedulerSuspended為0,則此函數與先前對vTaskSuspendAll()的調用不匹配 */ configASSERT( uxSchedulerSuspended ); /* 當調度器被掛起時,ISR可能導致任務從事件列表中刪除。如果是這種情況, 那么刪除的任務將被添加到xPendingReadyList中。一旦調度程序被恢復, 就可以安全地將所有掛起的就緒任務從這個列表移動到它們相應的就緒列表中 */ taskENTER_CRITICAL(); { --uxSchedulerSuspended; if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U ) { /* 將任何準備好的任務從待處理就緒列表移動到相應的就緒列表中 */ while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ) { pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ); ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); prvAddTaskToReadyList( pxTCB ); /* 如果移動的任務的優先級高于當前任務,需要進行一次任務的切換 */ if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xYieldPending = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } if( pxTCB != NULL ) { /*當調度程序被掛起時,一個任務被解除阻塞,這可能阻止了重新計算下一個解除阻塞時間, 在這種情況下,現在重新計算它。這對于低功耗無tickless實現非常重要, 它可以防止從低功耗狀態進行不必要的退出*/ prvResetNextTaskUnblockTime(); } /* 如果在調度器被掛起時發生任何滴答,那么現在應該處理它們。 這可以確保滴答計數不會滑動,并且任何延遲的任務都能在正確的時間恢復 */ { UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */ if( uxPendedCounts > ( UBaseType_t ) 0U ) { do { /*查找是否有待進行切換的任務,如果有則應該進行任務切換*/ if( xTaskIncrementTick() != pdFALSE ) { xYieldPending = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } --uxPendedCounts; } while( uxPendedCounts > ( UBaseType_t ) 0U ); uxPendedTicks = 0; } else { mtCOVERAGE_TEST_MARKER(); } } if( xYieldPending != pdFALSE ) { #if( configUSE_PREEMPTION != 0 ) { xAlreadyYielded = pdTRUE; } #endif /*如果需要任務切換,則調用taskYIELD_IF_USING_PREEMPTION()函數發起一次任務切換*/ taskYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } } else { mtCOVERAGE_TEST_MARKER(); } } taskEXIT_CRITICAL(); return xAlreadyYielded; }

      API C 語言 任務調度 單片機 嵌入式

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel表格復制為文字的方法教程(excel表格如何復制文字)
      下一篇:數據倉庫之維度建模介紹-- 未寫完,待更新(數據倉庫的維度建模)
      相關文章
      亚洲AV无码专区在线电影成人| 亚洲国产最大av| 亚洲av无码一区二区三区在线播放| 亚洲成人黄色在线| 久久久久亚洲av无码专区导航| 日韩亚洲人成在线综合日本| 中文字幕亚洲综合久久菠萝蜜| 国产亚洲漂亮白嫩美女在线| 亚洲色大成网站www永久男同| 亚洲日本国产综合高清| 亚洲一区二区三区高清视频| 亚洲国产精品人久久电影| 亚洲日产2021三区在线| 亚洲在成人网在线看| 亚洲电影在线播放| 亚洲国产精品成人综合色在线婷婷| 亚洲精品视频在线免费| 亚洲精品在线视频观看| 亚洲成AV人片久久| 亚洲一区二区三区四区视频| 国产成人亚洲综合网站不卡| 亚洲欧美第一成人网站7777| 亚洲国产精品网站在线播放| 亚洲av成人一区二区三区观看在线 | 亚洲精品国产精品乱码视色 | 亚洲中文字幕无码爆乳| 亚洲色精品三区二区一区| 亚洲人成网站免费播放| 亚洲经典千人经典日产| 一本久久综合亚洲鲁鲁五月天| 亚洲国产精品一区二区第一页免| 亚洲午夜福利精品久久| 亚洲无线码一区二区三区| 国产V亚洲V天堂A无码| 亚洲va在线va天堂va888www| 亚洲精品456在线播放| 国产成人精品日本亚洲专区6| 亚洲精品色播一区二区| 另类小说亚洲色图| 亚洲中文字幕在线观看| 无码乱人伦一区二区亚洲一 |