linux2.4到linux2.6核心調度(11)__linux

來源:互聯網
上載者:User

當前 runqueue 中沒有就緒進程了,則啟動Server Load Balancer從別的 cpu 上轉移進程,再進行挑選(詳見"調度器相關的Server Load Balancer"); 
如果仍然沒有就緒進程,則將本 cpu 的 IDLE 進程設為候選。 
在挑選出 next 之後,如果發現 next 是從 TASK_INTERRUPTIBLE 休眠中醒來後第一次被調度到(activated>0),調度器將根據 next 在就緒

隊列上等待的時間長度重新調整進程的優先順序(並存入就緒隊列中新的位置,詳見"進程平均等待時間 sleep_avg")。
除了 sleep_avg 和 prio 的更新外,next 的 timestamp 也更新為目前時間,用於下一次被切換下來時計算運行時間長度。
4) 外環境
這裡說的外環境指的是調度器對除參與調度的進程以及所在就緒隊列以外的環境的影響,主要包括切換計數處理和 cpu 狀態的更新(qsctr)


9. 調度器對核心搶佔啟動並執行支援
在2.4 系統中,在核心態啟動並執行任何進程,只有當它調用 schedule() 主動放棄控制時,調度器才有機會選擇其他進程運行,因此我們說

Linux 2.4 的核心是不可搶佔啟動並執行。缺乏這一支援,核心就無法保證即時任務的及時響應,因此也就滿足不了即時系統(即使是軟即時)的

要求。
2.6 核心實現了搶佔運行,沒有鎖保護的任何程式碼片段都有可能被中斷,它的實現,對於調度技術來說,主要就是增加了調度器啟動並執行時機。我

們知道,在 2.4 核心裡,調度器有兩種啟動方式:主動式和被動式,其中被動方式啟動調度器只能是在控制從核心態返回使用者態的時候,因此

才有核心不可搶佔的特點。2.6 中,調度器的啟動方式仍然可分為主動式和被動式兩種,所不同的是被動啟動調度器的條件放寬了很多。它的

修改主要在 entry.S 中:
……
ret_from_exception:   #從異常中返回的入口
preempt_stop     #解釋為 cli,關中斷,即從異常中返回過程中不允許搶佔
ret_from_intr:     #從中斷返回的入口
GET_THREAD_INFO(%ebp) #取task_struct的thread_info資訊
movl EFLAGS(%esp), %eax
movb CS(%esp), %al
testl $(VM_MASK | 3), %eax
jz resume_kernel    #"返回使用者態"和"在核心態中返回"的分路口
ENTRY(resume_userspace)
   cli
movl TI_FLAGS(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx #
(_TIF_NOTIFY_RESUME | _TIF_SIGPENDING            
#  | _TIF_NEED_RESCHED)
jne work_pending
jmp restore_all
ENTRY(resume_kernel)
cmpl $0,TI_PRE_COUNT(%ebp)
jnz restore_all    
#如果preempt_count非0,則不允許搶佔
need_resched:
movl TI_FLAGS(%ebp), %ecx
testb $_TIF_NEED_RESCHED, %cl
jz restore_all    
#如果沒有置NEED_RESCHED位,則不需要調度
testl $IF_MASK,EFLAGS(%esp)
jz restore_all     #如果關中斷了,則不允許調度
movl $PREEMPT_ACTIVE,TI_PRE_COUNT(%ebp)  
#preempt_count 設為 PREEMPT_ACTIVE,
通知調度器目前這次調度正處在一次搶
                                #占調度中
sti       
call schedule
movl $0,TI_PRE_COUNT(%ebp) #preemmpt_count清0
cli
jmp need_resched
……
work_pending:      #這也是從系統調用中返回時的resched入口
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig   
#不需要調度,那麼肯定是因為有訊號需要處理才進入work_pending的
work_resched:
call schedule
cli    
movl TI_FLAGS(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx 
jz restore_all     #沒有work要做了,也不需要resched
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched     #或者是需要調度,或者是有訊號要處理
work_notifysig:
……

現在,無論是返回使用者態還是返回核心態,都有可能檢查 NEED_RESCHED 的狀態;返回核心態時,只要 preempt_count 為 0,即當前進程目前

允許搶佔,就會根據 NEED_RESCHED 狀態選擇調用 schedule()。在核心中,因為至少時鐘中斷是不斷髮生的,因此,只要有進程設定了當前進

程的 NEED_RESCHED 標誌,當前進程馬上就有可能被搶佔,而無論它是否願意放棄 cpu,即使是核心進程也是如此。
調度器的工作時機 
除核心應用主動調用調度器之外,核心還在應用不完全感知的情況下在以下三種時機中啟動調度器工作: 
從中斷或系統調用中返回; 
進程重新允許搶佔(preempt_enable()調用preempt_schedule()); 
主動進入休眠(例如wait_event_interruptible()介面)


10. 調度器相關的Server Load Balancer
在 2.4 核心中,進程p被切換下來之後,如果還有 cpu 空閑,或者該 cpu 上啟動並執行進程優先順序比自己低,那麼 p 就會被調度到那個 cpu 上

運行,核心正是用這種辦法來實現負載的平衡。
簡單是這種Server Load Balancer方式最大的優點,但它的缺點也比較明顯:進程遷移比較頻繁,互動式進程(或高優先順序的進程)可能還會在 cpu 之間不

斷"跳躍"。即使是在 SMP 的環境中,進程遷移也是有代價的,2.4 系統的使用經驗表明,這種Server Load Balancer方式弊大於利,解決這一"SMP親和"的

問題是 2.6 系統設計的目標之一。
2.6 調度系統採用相對集中的Server Load Balancer方案,分為"推"和"拉"兩類操作:
1) "拉" 
當某個 cpu 負載過輕而另一個 cpu 負載較重時,系統會從重載 cpu 上"拉"進程過來,這個"拉"的Server Load Balancer操作實現在 load_balance() 函數

中。
load_balance() 有兩種調用方式,分別用於當前 cpu 不空閑和空閑兩種狀態,我們稱之為"忙平衡"和"空閑平衡":
a) 忙平衡 
無論當前 cpu 是否繁忙或空閑,時鐘中斷(rebalance_tick()函數中)每隔一段時間(BUSY_REBALANCE_TICK)都會啟動一次 load_balance()

平衡負載,這種平衡稱為"忙平衡"。
Linux 2.6 傾向於儘可能不做Server Load Balancer,因此在判斷是否應該"拉"的時候做了很多限制:
系統最繁忙的 cpu 的負載超過當前 cpu 負載的 25% 時才進行Server Load Balancer; 
當前 cpu 的負載取當前真實負載和上一次執行Server Load Balancer時的負載的較大值,平滑負載凹值; 
各 cpu 的負載情況取當前真實負載和上一次執行Server Load Balancer時的負載的較小值,平滑負載峰值; 
對源、目的兩個就緒隊列加鎖之後,再確認一次源就緒隊列負載沒有減小,否則取消Server Load Balancer動作; 
源就緒隊列中以下三類進程參與負載情況計算,但不做實際遷移: 
正在啟動並執行進程 
不允許遷移到本 cpu 的進程(根據 cpu_allowed 屬性) 
進程所在 cpu 上一次調度事件發生的時間(runqueue::timestamp_last_tick,在時鐘中斷中取值)與進程被切換下來的時間

(task_struct::timestamp)之差小於某個閥值(cache_decay_ticks的nanosecond值),--該進程還比較活躍,cache 中的資訊還不夠涼。 
負載的曆史資訊 為了避免競爭,調度器將全系統各個 CPU 進行Server Load Balancer時的負載情況(就緒進程個數)儲存在本 cpu 就緒隊列的

prev_cpu_load 數組的對應元素中,在計算當前負載時會參考這一曆史資訊。 
找到最繁忙的 cpu(源 cpu)之後,確定需要遷移的進程數為源 cpu 負載與本 cpu 負載之差的一半(都經過了上述曆史資訊平滑),然後按

照從 expired 隊列到 active 隊列、從低優先順序進程到高優先順序進程的順序進行遷移。但實際上真正執行遷移的進程往往少於計劃遷移的進程

,因為上述三類"不做實際遷移"的情況的進程不參與遷移。
b) 空閑平衡 
空閑狀態下的Server Load Balancer有兩個調用時機:
在調度器中,本 cpu 的就緒隊列為空白; 
在時鐘中斷中,本 cpu 的就緒隊列為空白,且當前絕對時間(jiffies 值)是 IDLE_REBALANCE_TICK 的倍數(也就是說每隔

IDLE_REBALANCE_TICK 執行一次)。 
此時 load_balance() 的動作比較簡單:尋找當前真實負載最大的 cpu(runqueue::nr_running 最大),將其中"最適合"(見下)的一個就緒

進程遷移到當前 cpu 上來。
"空閑平衡"的候選進程的標準和"忙平衡"類似,但因為空白閑平衡僅"拉"一個進程過來,動作要小得多,且執行頻率相對較高

(IDLE_REBALANCE_TICK 是BUSY_REBALANCE_TICK 的 200 倍),所以沒有考慮負載的曆史情況和負載差,候選的遷移進程也沒有考慮 Cache

活躍程度。
計算最繁忙 cpu 演算法中的問題 
實際上有可能成為平衡源的 cpu 的負載至少應該比當前 cpu 的負載要大,因此 find_busiest_queue() 函數中 max_load 的初值如果是

nr_running,且同時保證 load 最少為 1,那麼計算會稍少一點。 
c) pull_task() 
"拉"進程的具體動作在這個函數中實現。進程從源就緒隊列遷移到目的就緒隊列之後,pull_task() 更新了進程的 timestamp 屬性,使其能繼

續說明進程相對於本 cpu 的被切換下來的時間。如果被拉來的進程的優先順序比本 cpu 上正在啟動並執行進程優先順序要高,就置當前進程的

NEED_RESCHED 位等待調度。
2) "推"
a) migration_thread() 
與"拉"相對應,2.6 的Server Load Balancer系統還有一個"推"的過程,執行"推"的主體是一個名為 migration_thread() 的核心進程。該進程在系統啟動

時自動載入(每個 cpu 一個),並將自己設為 SCHED_FIFO 的即時進程,然後檢查 runqueue::migration_queue 中是否有請求等待處理,如

果沒有,就在 TASK_INTERRUPTIBLE 中休眠,直至被喚醒後再次檢查。
migration_queue 僅在 set_cpu_allowed() 中添加,當進程(比如通過 APM 關閉某 CPU 時)調用 set_cpu_allowed() 改變當前可用 cpu,

從而使某進程不適於繼續在當前 cpu 上運行時,就會構造一個遷移請求資料結構 migration_req_t,將其植入進程所在 cpu 就緒隊列的

migration_queue 中,然後喚醒該就緒隊列的遷移 daemon(記錄在 runqueue::migration_thread 屬性中),將該進程遷移到合適的cpu上去

(參見"新的資料結構 runqueue")。
在目前的實現中,目的 cpu 的選擇和負載無關,而是"any_online_cpu(req->task->cpus_allowed)",也就是按 CPU 編號順序的第一個

allowed 的CPU。所以,和 load_balance() 與調度器、Server Load Balancer策略密切相關不同,migration_thread() 應該說僅僅是一個 CPU 綁定以及

CPU 電源管理等功能的一個介面。
b) move_task_away() 
實際遷移的動作在 move_task_away() 函數中實現,進程進入目的就緒隊列之後,它的 timestamp 被更新為目的 cpu 就緒隊列的

timestamp_last_tick,說明本進程是剛開始(在目的 cpu 上)等待。因為"推"的操作是在本地讀遠地寫(與 pull_task() 正相反),因此,

在啟動遠地 cpu 的調度時需要與遠地的操作同步,還可能要通過 IPI(Inter-Processor Interrupt)通知目的 cpu,所有這些操作實現在

resched_task()函數中。
兩個 runqueue 的鎖同步 
在遷移進程時會牽涉到兩個 cpu 上的就緒隊列,通常在操作之前需要對兩個就緒隊列都加鎖,為了避免死結,核心提供了一套保證加鎖順序的

double_rq_lock()/double_rq_unlock() 函數。這套函數並沒有操作 IRQ,因此開關中斷的動作需要使用者自己做。 
這套函數在 move_task_away() 中採用了,而 pull_task() 中使用的是 double_lock_balance(),但原理與 double_rq_lock

()/double_rq_unlock() 相同。


11. NUMA結構下的調度
在 Linux 調度器看來,NUMA 與 SMP 之間主要的不同在於 NUMA 下的 cpu 被組織到一個個節點中了。不同的體繫結構,每個節點所包含的

cpu 數是不同的,例如 2.6 的 i386 平台下,NUMAQ 結構每個節點上可配置 16 個 cpu,SUMMIT 結構可配置 32 個 cpu。 NUMA 結構正式體

現在 Linux 核心中是從 2.6 開始的,在此之前,Linux 利用已有的"不連續記憶體"(Discontiguous memory,CONFIG_DISCONTIGMEM)體繫結構

來支援 NUMA。除了記憶體配置上的特殊處理以外,以往的核心在調度系統中是等同於 SMP 看待的。2.6 的調度器除了單個 cpu 的負載,還考慮

了 NUMA 下各個節點的負載情況。
NUMA 結構在新核心中有兩處特殊處理,一處是在做Server Load Balancer時對各NUMA節點進行均衡,另一處是在系統執行新程式(do_execve())時從負載

最輕的節點中選擇執行cpu:
1) balance_node()
節點間的平衡作為 rebalance_tick() 函數中的一部分在 load_balance() 之前啟動(此時的 load_balance() 的工作集是節點內的 cpu,也

就是說,NUMA下不是單純平衡全系統的 cpu 負載,而是先平衡節點間負載,重新平衡節點內負載),同樣分為"忙平衡"和"空閑平衡"兩步,執行

間隔分別為IDLE_NODE_REBALANCE_TICK(當前實現中是 IDLE_REBALANCE_TICK 的 5 倍)和 BUSY_NODE_REBALANCE_TICK(實現為

BUSY_NODE_REBALANCE_TICK 的 2 倍)。
balance_node() 先調用 find_busiest_node() 找到系統中最繁忙的節點,然後在該節點和本 cpu 組成的 cpu 集合中進行 load_balance()。

尋找最繁忙節點的演算法涉及到幾個資料結構:
node_nr_running[MAX_NUMNODES],以節點號為索引記錄了每個節點上的就緒進程個數,也就是那個節點上的即時負載。這個數組是一

個全域資料結構,需要通過 atomic 系列函數訪問。 
runqueue::prev_node_load[MAX_NUMNODES],就緒隊列資料結構中記錄的系統各個節點上一次Server Load Balancer操作時的負載情況,它按照以

下公式修正: 
當前負載=上一次的負載/2 + 10*當前即時負載/節點cpu數 
採用這種計算方式可以平滑負載峰值,也可以考慮到節點cpu數不一致的情況。 
NODE_THRESHOLD,負載的權值,定義為 125,被選中的最繁忙的節點的負載必須超過當前節點負載的 125/100,也就是負載差超過

25%。 
2) sched_balance_exec()
當 execve() 系統調用載入另一個程式投入運行時,核心將在全系統中尋找負載最輕的一個節點中負載最輕的一個 cpu(sched_best_cpu())

,然後調用sched_migrate_task() 將這個進程遷移到選定的 cpu 上去。這一操作通過 do_execve() 調用 sched_balance_exec() 來實現。
sched_best_cpu() 的選擇標準如下:
如果當前cpu就緒進程個數不超過2,則不做遷移; 
計算節點負載時,使用(10*當前即時負載/節點cpu數)的演算法,不考慮負載的曆史情況; 
計算節點內cpu的負載時,使用就緒進程的實際個數作為負載指標,不考慮負載的曆史情況。 
和"忙平衡"與"空閑平衡"採用不同負載評價標準一樣,sched_balance_exec() 採用了與 balance_node() 不一樣的(更簡單的)評價標準。
sched_migrate_task() 借用了 migration_thread 服務進程來完成遷移,實際操作時將進程的 cpu_allowed 暫設為僅能在目的 cpu 上運行,

喚醒migration_thread 將進程遷移到目的 cpu 之後再恢複 cpu_allowed 屬性。


12. 調度器的即時效能
1) 2.6 對於即時應用的加強
2.6 核心調度系統有兩點新特性對即時應用至關重要:核心搶佔和 O(1) 調度,這兩點都保證即時進程能在可預計的時間內得到響應。這種"限

時響應"的特點符合軟即時(soft realtime)的要求,離"立即響應"的硬即時(hard realtime)還有一定距離。並且,2.6 調度系統仍然沒有

提供除 cpu 以外的其他資源的剝奪運行,因此,它的即時性並沒有得到根本改觀。
2) 即時進程的優先順序
2.4 系統中,即時進程的優先順序通過 rt_priority 屬性工作表示,與非即時進程不同。2.6 在靜態優先順序之外引入了動態優先順序屬性,並用它同時

表示即時進程和非即時進程的優先順序。
從上面的分析我們看到,進程的靜態優先順序是計算進程初始時間片的基礎,動態優先順序則決定了進程的實際調度優先順序。無論是即時進程還

是非即時進程,靜態優先順序都通過 set_user_nice() 來設定和改變,預設值都是 120(MAX_PRIO-20),也就是說,即時進程的時間片和非實

時進程在一個配量內。
可區分即時進程和非即時進程的地方有兩處:調度策略 policy(SCHED_RR或SCHED_FIFO)和動態優先順序 prio(小於 MAX_USER_RT_PRIO),實

際使用上後者作為檢驗標準。即時進程的動態優先順序在 setscheduler() 中設定(相當於 rt_priority),並且不隨進程的運行而改變,所以

即時進程總是嚴格按照設定的優先順序進行排序,這一點和非即時進程動態優先順序含義不同。可以認為,即時進程的靜態優先順序僅用於計算時間

片,而動態優先順序則相當於靜態優先順序。
3) 即時調度
2.4中SCHED_RR和SCHED_FIFO兩種即時調度策略在2.6中未作改變,兩類即時進程都會保持在active就緒隊列中運行,只是因為2.6核心是可搶佔

的,即時進程(特別是核心級的即時進程)能更迅速地對環境的變化(比如出現更高優先順序進程)做出反應。

13. 後記:從調度器看 Linux 發展
近年來,Linux 對於案頭系統、低端伺服器、高端伺服器以及嵌入式系統都表現出越來越強的興趣和競爭力,對於一個仍然處於"集市式"開放

開發模式的作業系統來說,能做到這一點簡直就是一個奇蹟。
但從調度系統的實現上我感覺,Linux 的長項仍然在案頭系統上,它仍然保持著早年開發時"利己主義"的特點,即自由軟體的開發人員的開發動

力,很大程度上來自於改變現有系統對自己"不好用"的現狀。儘管出於種種動機和動力,Linux 表現出與 Windows 等商用作業系統競爭的強勢

,但從開發人員角度來看,這種願望與自由軟體的開發特點是有矛盾的。
Ingo Monar 在接受採訪時說,他設計的 O(1) 調度演算法,基本上來自於個人的創意,沒有參考市面上以及研究領域中已有的調度演算法。從調度

器設計上可以看出,2.6 調度系統考慮了很多細節,但總體上並沒有清晰的主線,且無法(或者也無意於)在理論上對 O(1) 模型進行效能分

析。從 2.6 的開發過程中我們也能看到,各種調度相關的權值在不同的版本中一直在微調,可以認為,2.6 調度系統的效能最佳化主要是實測得

來的。
這就是典型的 Linux 開發模式--充滿激情、缺乏規劃。
對於 Linux 的市場來說,最緊迫、最活躍的需要在於嵌入式系統,但至少從調度系統來看,2.6 並沒有在這方面下很大功夫,也許開發人員本人

對此並無多大感受和興趣。可以肯定,雖然 Linux 在市場上很火,但它的開發仍然不是市場驅動的。這或許會影響 Linux 的競爭力,但或許

也因此能保持 Linux 開發的活力。
就在今年(2004年)3月1日,著名的開源網路安全項目 FreeS/WAN 宣布停止開發,其原因主要是開發人員的意圖和使用者的需求不吻合。對於網路

安全系統,使用者更多考慮的是系統功能的完整、強大,而不是它可預知的先進性,因此,FreeS/WAN 新版本中主打推出的 Opportunistic

Encryption (OE) 沒有吸引到足夠數量的使用者測試。鑒於此,投資者停止了對 FreeS/WAN 項目的資助,這一為開源系統提供了強大的網路安全

支援的系統也許會再次轉入地下。
至今為止,還沒有聽到 Linux 的開發依賴於某種商業基金的報道,因此相對而言,Linux 的開發更具自由和隨意性,推廣 Linux 的人與開發

Linux 的人基本上獨立運作著,Linux 的受益者和 Linux 的開發人員也沒有緊密結合。這對於 Linux 或許是福而不是禍。

參考資料 
[1][Linus Torvalds,2004] 
Linux 核心源碼 v2.6.2,www.kernel.org
[2][pubb@163.net,2004] 
Linux 2.4調度系統分析 ,IBM Developerworks 
[3][Ingo Molnar,2002] 
Goals, Design and Implementation of the new ultra-scalable O(1) scheduler, Linux Documentation,sched-design.txt 
[4][Anand K Santhanam (),2003] 
走向 Linux 2.6 ,IBM Developerworks 
[5][Robert Love,2003] 
Linux Kernel Development,SAMS 
[6][ ,2003] 
2.5.62 SMP筆記,www.linux-forum.net核心技術版 
[7][ Vinayak Hegde,2003] 
The Linux Kernel,Linux Gazette 2003年4月號第89篇 
[8][ Rick Fujiyama,2003] 
Analyzing The Linux Scheduler's Tunables,kerneltrap.org

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.