线程调度
線程調度
? ?內核調度器是基于優先級實現的,他可以使應用程序線程共享CPU
相關概念
在每一個時間點調度器都會決定執行那個線程,這個被執行的線程有個統稱叫“當前線程” 無論什么時候,只要調度器改變了當前線程的ID或者當前線程被中斷服務函數打斷,內核都會先保存當前線程的寄存器值,這些寄存器的值在線程被重新執行時恢復線程狀態
一個已經是就緒狀態的線程是不會被阻止其運行的,并有可能被調度器選擇為當前線程 一個線程有一個或多個因素阻止一個未就緒的線程運行,未就緒的線程是不可以被選擇誒當前線程的 下面幾個因素可使一個線程為未就緒態: ①線程沒有啟動 ②線程等待一個內核對象完成一個指定操作(比如線程在等待一個還不可用的信號量) ③線程在等待超時出現 ④現場被掛起 ⑤線程被同步終止或異步中止線程優先級
線程優先級是一個整數值,可以是一個負整數或者正整數。數值小的優先級比數值大的優先級要高。比如,優先級是4的線程A要比優先級7的線程B優先級高,優先級為-2的線程C要比線程A和線程B的優先級都高 調度器通過線程優先級來區分這兩類線程 ①協作式線程優先級是一個負數。如果協作式線程變成了當前線程,它會一直占用CPU直到這個線程被某個操作指定為未就緒狀態 ②搶占式線程優先級是一個正數。如果搶占式線程變成了當前線程,任何時候他都會被就緒的協作式線程替代,或者是一個優先級大于等于它并且處于就緒態的搶占式線程替代 線程的優先級在線程啟動后可以被動態上調和下調。因此一個搶占式線程通過修改它的優先級可能會變成一個協作式線程 內核支持一個幾乎無線數量的優先級級別,配置項CONFIG_NUM_COOP_PRIORITIES和CONFIG_NUM_PREEMPT_PRIORITIES分別為兩類線程指定優先級級別個數,最后的設置優先級范圍情況如下 協作式線程:(-CONFIG_NUM_COOP_PRIORITIES)到 -1 搶占式線程:0 到 (CONFIG_NUM_PREEMPT_PRIORITIES - 1) 例如,設置CONFIG_NUM_COOP_PRIORITIES = 5 和?CONFIG_NUM_PREEMPT_PRIORITIES = 0時,協作式線程優先級范圍是 -5 到 -1,搶占式線程的優先級范圍是 0 到 9調度算法
內核調度器會在就緒態線程中選擇優先級最高的線程作為當前線程。當同時有多個相同優先級的線程處于就緒態時,調度器會選擇等待時間最長的那個線程作為當前線程 note:中斷服務函數的優先級要高于線程的優先級,所以如果中斷沒有被關閉的話,線程在任何時候都有可能被中斷服務函數打斷。無論是協作式線程還是搶占式線程都是如此協作時間片
當一個協作式線程變成了當前線程,他會一直占用CPU到這個協作式線程變成未就緒狀態。因此,如果協作式線程需要執行很長時間,其他線程有可能會因此延時一段不可接受的時間,包括那些優先級大于等于它的線程 為了解決這個問題,協作式線程會主動讓出CPU一段時間來讓其他線程執行。一個線程可以通過兩種方式讓出CPU ①通過調用k_yield()把當前線程放到調度器就緒線程優先表之后,然后調用調度器。所有優先級大于等于yield線程的就緒線程在yield線程重新調用之前都有可能被執行。如果沒有優先級符合的線程存在,調度器會立即重新調度yield線程,不進行上下文切換 ②通過調用k_sleep()使線程睡眠一段指定的時間,所有的就緒線程都可以被執行。然而,這樣也不會保證在sleeping線程變成就緒態之前那些優先級小的線程會被調度搶占時間片
? ?當一個搶占式線程變成當前線程時,在一個優先級高的線程變成就緒態之前會一直保持為當前線程,或者線程執行一個動作使他變成未就緒狀態。因此,如果搶占式線程需要執行很長時間,其他線程有可能會因此延時一段不可接收的時間,包括那些優先級相等的線程 ? ?為了解決這個問題,搶占式線程可以執行一個協作時間片(如上面描述那樣),或者通過調度器時間片讓具有相同優先級的線程被調度 ? ?調度器把時間分成一系列時間片,這個時間片是以系統時鐘為基準的。時間片長短是可配置的,而且這個時間大小在程序運行時也是可變的 ? ?在每個時間片的結尾,調度器都會查看當前線程是否是可搶占的,如果是可搶占線程,將會自動調用k_yield()。這樣在當前線程被重新調度之前其他具有相同優先級的就緒線程有可能會被調度執行。如果沒有相同優先級的就緒線程,那么調度器就不會做線程調度切換 ? ?如果一個線程優先級比設置的優先級范圍高的話,那么這個線程就不會有搶占時間片,所以就永遠不會被相同優先級的就緒線程搶占。這樣應用程序就可以在處理對時間不敏感的低優先級線程時利用搶占時間片來做一些操作(也就是說把節省下來的搶占時間片用來處理其他操作) ? ?note:內核時間片算法不能保證一些具有相同優先級的線程獲得相等的CPU執行時間,因為內核不會測量每個線程實際執行了多長時間。比如,一個線程可能在時間片尾端變成當前線程然后立即讓出CPU。然而,調度算法也不會確保線程執行時間因為沒讓出CPU而超過一個時間片調度器上鎖
一個搶占式線程如果在處理一個臨界操作時不想被搶占,可以通過調用k_sched_unlock()接口實現,這樣調度器會把搶占式線程當做協作式線程而不被搶占。這樣線程在在執行臨界操作時就不會被其他線程干涉了 當臨界操作執行完畢后線程必須調用k_sched_unlock()恢復到正常的搶占式線程狀態 如果一個線程在執行k_sched_lock()后又執行了一個操作是自己變成未就緒狀態,調度器會切換出上鎖線程并且調度其他線程運行。當上鎖線程重新變成當前線程,會保持原有的不可搶占狀態線程睡眠
線程可以通過調用k_sleep()把當前操作延時一個指定時間段。在這段時間內會讓出CPU讓其他線程運行。當指定的延時時間到達時線程會變成就緒態并會被重新調度 一個處于睡眠態的線程可被其他線程通過調用k_wakeup()喚醒。這種機制可在某些事件發生時通過二次線程來通知一個睡眠線程,而不是通過線程定義一個內核異步對象實現(比如信號量)。如果一個線程沒在睡眠狀態也是可以被喚醒的,只不過不會產生任何效果忙等待
一個線程可以通過調用k_busy_wait()執行一次忙等待、延時一個指定的時間段,在延時期間線程不會讓出CPU 當一個線程延時時間太小時就會調用線程忙等待(busy wait)而不是調用線程睡眠(sleeping),因為這個延時的時間已經比調度器從當前線程切換到其他線程再切換回來需要花費的時間還要短推薦用法
使用協作式線程處理設備驅動程序和其他有性能要求的工作 使用協作式線程實現具有互斥關系且不需要內核對象的功能,這個內核對象指的是互斥鎖等 通過搶占式線程,可以給一個時間敏感的操作高優先級而給一個時間不敏感的操作一個低優先級配置選項
相關的配置選項如下:- CONFIG_NUM_COOP_PRIORITIES
- CONFIG_NUM_PREEMPT_PRIORITIES
- CONFIG_TIMESLICING
- CONFIG_TIMESLICE_SIZE
- CONFIG_TIMESLICE_PRIORITY
APIs
下面是在kernel.h中定義的和線程調度相關的APIs- k_current_get()
- k_sched_lock()
- k_sched_unlock()
- k_yield()
- k_sleep()
- k_wakeup()
- k_busy_wait()
- k_sched_time_slice_set()
總結