當核心從系統呼叫返回,或者從中斷處理程式返回,核心都會檢查當前程序是否設定了tif_need_resched標誌;或者程序主動放棄cpu時(sched_yield,sleep或者收到sigstop,sigttop訊號)都會進入主排程器。同樣的我們先看一下主排程的框架部分,該部分就是sched.c:schedule(void):
關閉核心搶占
如果程序之前是不可執行並且被核心搶占了,那麼如果它現在有非阻塞訊號,則將它的狀態改為task_running而且不移出就緒佇列,否則該程序(不可執行)從就緒佇列中取出
deactivate_task
判斷是否要進行負載均衡(當前執行隊列為空)
通知排程器類將當前(活動)程序將被其它程序替換掉(
put_prev_task)
選擇下乙個將要被執行的程序(
pick_next_task),並清除前乙個程序的
tif_need_resched
進行上下文切換(context_switch)
重新計算新程序所在的cpu及rq,即為當前cpu(因為新的程序之前可能在不同的cpu上執行了,同樣老的程序喚醒時也是所這裡開始)
如果新的程序也被設定了tif_need_resched,則再次排程
大體過程如下圖:
圖 schedule與cfs的互動
deactivate_task
:該函式最終呼叫cfs的dequeue_task_fair,並且將程序的p->se.on_rq置0,表示該程序不在執行佇列裡。
dequeue_task_fair
對於非組排程的話就是呼叫dequeue_entity更新執行程序的資訊update_curr,把該se從buddies中去掉(clear_buddies,見後面的分析),如果這個se不是正在執行的程序則把該se從執行佇列的紅黑樹上刪除掉(執行的程序已經不存在紅黑樹裡),置se->on_rq = 0,並且減少執行列隊的相應load(update_cfs_load:這裡更新的是統計值的load,account_entity_dequeue這個才是真正更新跟程序排程相關的cfs_rq->load)及se的weight(update_cfs_shares)其它統計資訊(注意:當se出隊時如果它不是dequeue_sleep必須把vruntime標準化se->vruntime -= cfs_rq->min_vruntime,否則就不需要標準化,
這裡不是很明白?
)。對於組排程,它從當前程序開始dequeue_entity,如果它的父group load為0,那麼說明這個父group也應該被
dequeue_entity
,直到不為0(該group有其它程序就緒)的祖先group為止,到這裡就把從葉子(當前程序)到該程序向上遞迴load為空的父group都出隊了;然後再更新從這個非空的父group到根的其餘group se的load(這裡只是更新統計的load update_cfs_load,而cfs_rq的load因為只記錄它本層的se的load之和不遞迴,所以不需要再更新該load),shares及h_nr_running統計,因為它們下層的se已經被出佇列了。另外,所有被dequeue的se的on_rq被置為0
put_prev_task_fair
不可執行
的程序從執行佇列中刪除掉,而
put_prev_task_fair
主要是通知cfs當前程序將會被排程出去了,如果當前程序已經不是可執行程序(on_rq=0),
那麼這個函式只會把當前
cfs_rq->curr
置為null
,表示當前cfs_rq沒有程序正在執行,否則如果當前程序還是可執行的那麼還需要對它的狀態進行更新:update_curr更新它的實質物理執行時間,虛擬時間及它從現在開始就是進入等待的時間,並且再次將該程序重新入佇列(__enqueue_entity當前程序還是可執行狀態)。對於組排程同樣的需要更新從該程序到它的根group的所有se,包括每個se的執行時間(這裡的執行時間並不是代表它在cpu的執行時間,而是由它的下級執行時間的乙個反映),至於每個層次的se都把它的cfs_rq->curr置為null是因為:在乙個cpu上某一時刻只有乙個程序在執行,當當前執行要被排程出去的時候,也代表了它的所有上層group在這個cpu上將被排程出去(對於group這只是乙個理論概論,它並不會真正在cpu上執行,只是為了與真正task統一起來才有這個標誌,表示當它的葉子task在cpu上執行;同樣的,當某group的葉子被排程[執行]時,它的所有上層group在它所在的執行佇列裡也被表示為執行的)。
pick_next_task
:挑選乙個最需要執行的程序來執行。如果當前佇列的等待執行的程序總數等於cfs等待的數目,那麼就直接從cfs中挑選乙個,否則從高優先策略的排程類中挑選乙個程序來執行。這裡我們直接看cfs的pick_next_task_fair(這裡從根的cgroup開始一層一層往左邊找):通過
pick_next_entity
從當前層的cfs_rq判斷哪個se將被取出,它採用這樣的優先順序(從高到低)——已經被要求執行的se(cfs_rq->next,即next要求搶占),上乙個執行的se(cfs_rq->last),不是被skip的se,而且這三個優先順序都還需要滿足——它們比起cfs_rq最左邊的se更需要先被執行(wakeup_preempt_entity,它們的虛擬執行時間小於最左邊的虛擬執行時間,或者比最左邊再執行最小執行時間後的新的虛擬時間還小,減少不必要的切換);這樣就能選出乙個合適的se;然後呼叫set_next_entity將該se設定為當前cfs_rq上正在執行的程序:如果這個se還在執行佇列裡則更新它的等待結束時間,及出佇列(執行程序不應該放在就緒佇列裡,注這裡呼叫的是__dequeue_entity而不是
dequeue_entity
,後者是把這可執行的se出佇列並需要更新nr_running--,on_rq=0等,而前者是不需要的,即執行的程序雖然不在紅黑樹裡,但是se->nr_rq還是等於1,cfs_rq->nr_running還是包括這個執行的程序);更新開始執行的時鐘,將cfs_rq->curr置為該se。對於非組排程這樣就能把該se的task返回;而對於組排程其實也很簡單,如果pick_next_entity取得的是乙個group的話,那麼再從它的執行佇列裡se->my_q裡選出乙個合適的se出來,直到該se是非group,而且這些group的se所在的cfs_rq也會把curr置為當前遞迴的group se(這也是我們上面說的put_prev_entity的反操作)。
總之,schedule是為了完成從prev程序切換到next程序的過程,如果prev是不可執行的並且沒收到訊號那麼應該先把它從執行佇列裡去掉(deactivate_task),注意此時還是它占用的cpu所以還需要更新它的執行時間(update_curr);然後告訴cfs該prev將要被排程出去了,此時也是需要考慮它是否是可執行的狀態,還是不可執行狀態,如果是不可執行狀態,那麼上面它已經被從執行佇列中去掉,並且on_rq的標誌也被清0,所以只需要把cfs_rq->curr置為null就可以了,否則就是它是可執行的,那麼首先也是先更新它的執行時間update_curr,然後把它重新放到執行佇列裡(當前執行的程序是不在執行佇列裡的),最後同樣把cfs_rq->curr置為null;接著從cfs裡挑選乙個合適的程序來執行,一些比較優先考慮的程序被儲存在buddies(next,last),所以它先從這些裡及最左篩選,篩選後把該se從執行佇列中出隊,相應的最後需要把cfs_rq->curr置為當前被篩選出來的se,表示該se是當前cfs_rq上執行的se。
這樣我們就把排程器兩個主要部件介紹完了,下面介紹到task建立時的排程器對新任務的初始化過程。我們估且把該過程稱為程序排程初始化,下面我們就來分析該過程。
linux排程器(九) 排程器的配置引數
排程器的配置引數 proc sys kernel sched min granularity ns 4000000ns sysctl sched min granularity 表示程序最少執行時間,防止頻繁的切換,對於互動系統 如桌面 該值可以設定得較小,這樣可以保證互動得到更快的響應 見週期排程...
程序排程與排程器及演算法
核心v2.6.23之後 程序的優先順序 總結linux核心的三種 排程策略 sched other 分時排程策略,預設的 sched fifo實時排程策略,先到先服務 sched rr實時排程策略,時間片輪轉 linux 程序排程有乙個有趣歷史。在 2.5 版本之前,linux 核心採用傳統 uni...
linux程序排程 週期性排程器
週期性排程器是在scheduler tick中實現。如果系統正在活動中,核心會按照頻率hz自動呼叫該函式。如果沒有程序在等待排程,那麼在計算機電力 不足的情況下,也可以關閉該排程器以減少電能消耗。3469 3470 this function gets called by the timer cod...