1. 程式人生 > >linux內核調度算法(1)--快速找到最高優先級進程

linux內核調度算法(1)--快速找到最高優先級進程

保持 active fin long 開始 維護 運行 nice 好的

為什麽要了解內核的調度策略呢?呵呵,因為它值得我們學習,不算是廢話吧。內核調度程序很先進很強大,管理你的LINUX上跑的大量的亂七八糟的進程,同時還保持著對用戶操作的高靈敏響應,如果可能,為什麽不把這種思想放到自己的應用程序裏呢?或者,有沒有可能更好的實現自己的應用,使得操作系統能夠以自己的意誌來分配資源給自己的進程?



帶著這兩個問題來看看KERNEL。首先回顧上我們開發應用程序,基本上就兩種類型,1、IO消耗型:比如hadoop上的trunk服務,很明顯它的消耗主要在IO上,包括網絡IO磁盤IO等等。2、CPU消耗型,比如mapreduce或者其他的需要對大量數據進行計算處理的組件,就象對高清視頻壓縮成適合手機觀看分辨率的進程,他們的消耗主要在CPU上。當兩類進程都在一臺SERVER上運行時,操作系統會如何調度它們呢?現在的服務器都是SMP多核的,那麽一個進程在多CPU時會來回切換嗎?如果我有一個程序,既有IO消耗又有CPU消耗,怎麽讓多核更好的調度我的程序呢?


又多了幾個問題。來看看內核調度程序吧,我們先從它的優先隊列談起吧。調度程序代碼就在內核源碼的kernel/sched.c的schedule函數中。
首先看下面的優先級隊列,每一個runqueue都有。runqueue是什麽?下面會詳細說下,現在大家可以理解為,內核為每一顆CPU分配了一個runqueue,用於維護這顆CPU可以運行的進程。runqueue裏,有幾個成員是prio_array類型,這個東東就是優先隊列,先看看它的定義:




看看BITMAP_SIZE是怎麽算出來的:#define BITMAP_SIZE ((((MAX_PRIO+1+7)/8)+sizeof(long)-1)/sizeof(long))
那麽,LINUX默認配置(如果你用默認選項編譯內核的話)MAX_PRIO是140,就是說一共內核對進程一共定義了140種優先級。等待某個CPU來處理的進程中,可能包含許多種優先級的進程,但,LINUX是個搶占式調度算法的操作系統,就是說,需要調度時一定是找到最高優先級的進程執行。上面的BITMAP_SIZE值根據MAX_PRIO算出來為5,那麽bitmap實際是32*5=160位,這樣就包含了MAX_PRIO的140位。優先級隊列是怎麽使用的?看2649行代碼:idx=sched_find_first_bit(array->bitmap);這個方法就用來快速的找到優先級最高的隊列。看看它的實現可以方便我們理解這個優先級位的設計:



那麽__ffs是幹什麽的?



sched_find_first_bit返回值就是最高優先級所在隊列的序號,與queue是對應使用的哈,queue=array->queue + idx;這樣就取到了要處理的進程隊列。這個設計在查找優先級時是非常快的,非常值得我們學習。


好,優先級隊列搞明白了,現在來看看runqueue,每個runqueue包含三個優先級隊列。



LINUX是一個時間多路復用的系統,就是說,通過把CPU執行時間分成許多片,再分配給進程們使用,造成即使單CPU系統,也貌似允許多個任務在同時執行。那麽,時間片大小假設為100ms,過短過長,過長了有些不靈敏,過短了,連切換進程時可能都要消耗幾毫秒的時間。分給100個進程執行,在所有進程都用完自己的時間片後,需要重新給所有的進程重新分配時間片,怎麽分配呢?for循環遍歷所有的run狀態進程,重設時間片?這個性能無法容忍!太慢了,跟當前系統進程數相關。那麽2.6內核怎麽做的呢?它用了上面提到的兩個優先級隊列active和expired,顧名思義,active是還有時間片的進程隊列,而expired是時間片耗盡必須重新分配時間片的進程隊列。


這麽設計的好處就是不用再循環一遍所有進程重設時間片了,看看調度函數是怎麽玩的:




當所有運行進程的時間片都用完時,就把active和expired隊列互換指針,沒有遍歷哦,而時間片耗盡的進程在出acitve隊列入expired隊列時,已經單獨的重新分配好新時間片了。


再看一下schedule(void)調度函數,當某個進程休眠或者被搶占時,系統就開始調試schedule(void)決定接下來運行哪個進程。上面說過的東東都在這個函數裏有體現哈。



當然,在我們程序中,也可以通過執行以下系統調用來改變自己進程的優先級。nice系統調用可以改變某個進程的基本優先級,setpriority可以改變一組進程的優先級。

linux內核調度算法(1)--快速找到最高優先級進程