linux公平排程cfs (completely fair schedule),基於linux版本2.6.34。
看原始碼所感:「而世之奇偉、瑰怪,非常之觀,常在於險遠,而人之所罕至焉,故非有志者不能至也。」
排程程式即(scheduler)決定了多個程式執行策略,排程程式的最大原則在於能夠最大限度的利用計算資源。
多工系統可以劃分為兩類:非搶占式多工(cooperative multitasking), 搶占式多工(preemptive mulittasking).
linux2.6.34核心中排程器的設計是模組化的,這樣做的好處是允許不同可以有針對性的選擇不同排程演算法,其中最基本的排程演算法為基於分時(time sharing)的技術。
整體架構如下,即排程策略是模組化設計的,排程器根據不同的程序依次遍歷不同的排程策略,找到程序對應的排程策略,排程的結果即為選出乙個可執行的程序指標,並將其加入到程序可執行佇列中。
fifo和rr排程演算法都為靜態優先順序。核心不為實時程序計算動態優先順序,保證了優先級別高的實時程序總能搶找優先順序比它低的程序。
主要針對cfs排程實現部分主要4個點:時間記賬,程序選擇,排程器入口,睡眠和喚醒
整體涉及的資料結構圖如下:
時間記賬
所有的排程器都必須對程序的執行時間做記賬。cfs不再有時間片的概念,維護了每個程序執行的時間記賬,因為每個程序只在公平分配給它的處理器時間內執行。關鍵資料結構如下:
vruntime: 虛擬執行時間是在所有可執行基礎的總數上計算出乙個程序應該執行多久,計算時相應的nice值在cfs被作為程序獲得處理器執行比的權重:越高的nice(越低優先順序)值,獲得更低的處理器權重,更低的nice值獲得更高的處理器使用權重。
個人理解:cfs的vruntime += 處理器執行時間 * nice對應的權重
// sched.h
struct sched_entity
程序選擇程序選擇是cfs排程演算法的最重要的模組,當cfs排程器選擇下乙個要進行排程的程序時,就會選擇具有最小vruntime的任務。涉及到獲取最小值,以及有序資料結構,在各種場景下都很適用的紅黑樹就發揮了其作用。即用紅黑樹維護以vruntime為排序條件,儲存著任務的運**況。
程序的維護都在紅黑樹上進行相關操作:
1. 選擇下乙個任務
執行__pick_next_entity函式即獲取了紅黑樹最左的節點(最小值)。
// kernel/sched_fair.c
static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)
2. 向紅黑樹中加入程序
這一步驟發生在程序變成可執行態,或者通過fork系統呼叫第一次建立程序時。
static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
update_stats_enqueue(cfs_rq, se);
check_spread(cfs_rq, se);
if (se != cfs_rq->curr)
// 將se的紅黑樹節點,插入到紅黑樹中,並維護和更新最左節點
__enqueue_entity(cfs_rq, se);}
static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) else }
/* * maintain a cache of leftmost tree entries (it is frequently
* used): 維護和更新最左子節點
*/if (leftmost)
cfs_rq->rb_leftmost = &se->run_node;
// 連線紅黑樹節點
rb_link_node(&se->run_node, parent, link);
// 對紅黑樹進行旋轉和著色
rb_insert_color(&se->run_node, &cfs_rq->tasks_timeline);
}
3. 從紅黑樹中刪除程序
這一步操作發生在程序阻塞,即程序變成不可執行狀態或者當程序終止時。
static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int sleep)
static void __dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
// 從紅黑樹中刪除節點
rb_erase(&se->run_node, &cfs_rq->tasks_timeline);
}
排程器入口程序排程器的入口函式為schedule(),總體流程即為選擇合適的排程策略選出下乙個需要被排程的程序任務,然後進行一次上下文切換,將程序置為執行態。
/*
* schedule() is the main scheduler function.
*/asmlinkage void __sched schedule(void)
pre_schedule(rq, prev);
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);
put_prev_task(rq, prev);
// pick_next_task 優先順序從高到低依次檢查每個排程策略類,並返回可執行程序的指標
next = pick_next_task(rq);
if (likely(prev != next)) else
raw_spin_unlock_irq(&rq->lock);
post_schedule(rq);
if (unlikely(reacquire_kernel_lock(current) < 0))
preempt_enable_no_resched();
if (need_resched())
// 需要重新排程,則返回重新排程標籤
goto need_resched;
}
睡眠和喚醒休眠(被阻塞)狀態的程序處於不可執行的狀態。程序休眠的原因有多種多樣,但通常來說都是等待某一事件的發生,例如等待i/o, 等待裝置輸入等等。
核心對於休眠和喚醒的操作如下:
休眠或者阻塞狀態有兩種:可中斷休眠(task_interruptible), 不可中斷休眠(task_uninterruptible). 通常程序的休眠,為可中斷休眠,即程序進入休眠,等待某一事件發生。一旦事件發生,或者滿足條件,核心將會把程序狀態置為執行,並將程序從等待佇列中移除。
程序進入等待休眠佇列如下:
void wait()
// 進行一次排程
schedule();
}finish_wait(q, &wait);
}
搶占和上下文切換上下文切換,處理器從即為從乙個可執行的程序切換到另乙個可執行的程序,其中包含了兩個關鍵的函式.
核心必須知道在什麼時候需要呼叫schedule()
來執行一次排程, 而不是靠使用者去執行schedule()
函式,為此核心提供了乙個need_resched
標誌位,表明是否需要重新進行一次排程。need_resched
標誌位為1時會觸發核心進行一次排程,有如下幾個情況:
核心態搶占(重新排程)
簡單的來說,cfs是動態計算程式優先順序的一種排程演算法,其內部演算法核心是選取vruntime
最小的程序進行排程執行,而維護最小的程序,使用了紅黑樹,而計算vruntime使用了所有程序數以及nice值的加權。
linux核心的排程程式cfs,盡可能的滿足了各個方面的需求,並找到了一種在排程週期和吞吐量之間的平衡。
學習linux核心程序排程,讓我窺見了程式的本質,資料結構是程式的基石,紅黑樹在各個地方的運用足以展現出其重要性。linux程序排程的設計,兼顧了可伸縮性,盡可能的提高了資源的利用率。
在學習和總結過程中,看了linux核心原始碼,並加入了一些自己的總結和理解,還需要在通往大牛的路上繼續努力。
非搶占式優先演算法例題 非搶占式高優先順序排程演算法
v1.0 可編輯可修改 非搶占式高優先順序排程演算法 優先數越大級別越高 演算法思想 在按程序達到時間由小到大的順序輸入程序資訊後,先對其優先數進行排列,將最先到達的程序的到達時間設為開始時間,計算結束時間,然後對後面到達的時間與該程序的結束時間進行比較,如若小於該程序的結束時 間,記錄程序的個數,...
非搶占式優先演算法例題 非搶占式優先順序
非搶占式優先順序 include define max 10 struct process char name 10 float at float st float rt float ft int priority 優先數int order 程序執行次序 int flag 標記程序狀態 p max ...
作業系統 搶占式優先排程 C語言設計
時間片長度 define time slice 50 任務的結構體 struct process 時間片資料 struct timeslice struct waitlist int next start for int i start i last i swap waitlist start wa...