四、CFS。 CFS現(xiàn)在還是非常新的調(diào)度實現(xiàn),并且本人水平也十分有限,有鑒于此,這里很可能存在不當(dāng)?shù)牡胤缴踔铃e誤,權(quán)當(dāng)拋磚引玉,不妥之處還請諸位有識之士不吝指正。 在討論CFS之前,我們先回顧一下現(xiàn)有的調(diào)度器實現(xiàn):這是一個巧妙的雙優(yōu)先級數(shù)組方案。為了盡量避免出現(xiàn)“過期數(shù)組”中的任務(wù)出現(xiàn)饑餓現(xiàn)象,內(nèi)核使用了一些啟發(fā)式的方法判斷是否出現(xiàn)了饑餓。在絕大多數(shù)情況下,這個實現(xiàn)給了我們非常好的交互性體驗。不過,可惜在個別情況下仍會出現(xiàn)明顯的饑餓現(xiàn)象,更致命的是,這個問題是完全可以重現(xiàn)的。躲躲閃閃修修補補終究不是解決問題之道。用Ingo的話說,問題的根源在于調(diào)度器沒有一種機制可以跟蹤使用者的“眼球”--從而不能及時獲得哪些任務(wù)是最希望盡快處理的,哪些任務(wù)是可以暫緩處理的這種線索?,F(xiàn)有的調(diào)度器試圖使用啟發(fā)式方法解決這個問題,但并不完美。這種情況下,盡量的公平可能就是一種可行的方案了,Con Kolivas的RDSL/SD調(diào)度方案也從實踐上證明了這個方案是可行的。不過,CFS使用了與之完全不同的方法實現(xiàn)“公平”的概念。 我們已經(jīng)知道了CFS是“完全公平調(diào)度”的縮寫,那究竟什么是“公平”呢?首先,“公平”絕不意味著“相等”,也就是說在分配處理器資源時,不能簡單地將系統(tǒng)內(nèi)所有任務(wù)都一視同仁,而要區(qū)別對待。這是因為系統(tǒng)內(nèi)的任務(wù)本身就不是平等的,例如許多內(nèi)核線程生來用來應(yīng)付某種比較緊急的情況的,它們理應(yīng)比普通的用戶空間任務(wù)更優(yōu)越一些(事實上CFS的早期版本確實會引起某些內(nèi)核線程的饑餓)。從另一方面上看,絕對公平也是不可能實現(xiàn)的,CFS所關(guān)注的是時間上相對長程(long-term)的公平(也可看成是總體上的,統(tǒng)計上的公平),在每個小的時間區(qū)間很可能看起來并不是公平的,引起這種現(xiàn)象的可能是需要對以往的不公平作補償、系統(tǒng)的負(fù)載發(fā)生變化、實現(xiàn)方法限制等諸多原因。此外,公平也應(yīng)該是層次化的。不過,現(xiàn)在CFS并沒有支持這種性質(zhì)的公平,所以這里先按下不談。所以說,CFS中的“C”和“F”其實都不是絕對的。 在實現(xiàn)公平的方法上,社區(qū)內(nèi)也發(fā)生過激烈的討論。討論的導(dǎo)火索也是我們也試圖解決的老大難問題:如何處理X窗口系統(tǒng)的服務(wù)器。一派認(rèn)為X服務(wù)器是一個特殊的應(yīng)用程序,它很大程度上影響了GUI的使用體驗,甚至于它還自己直接訪問硬件(主要是顯示卡),我們有充足的理由對其進(jìn)行特殊處理,更具體地,就是讓使用者自己去提升X服務(wù)器的靜態(tài)優(yōu)先級。甚至,CFS的有些版本曾經(jīng)利用X服務(wù)器自行硬件訪問的行為自動對X服務(wù)器做一些調(diào)度上的獎勵(通過截獲ioperm()系統(tǒng)調(diào)用);另一派試圖找到一種全能的公平調(diào)度方法,它們認(rèn)為X并沒有多少代表性,許多服務(wù)器類任務(wù)的運行時間都是因為處理客戶端的要求而消耗的?!皩嵺`是檢驗真理的唯一標(biāo)準(zhǔn)”,最終,新版本的CFS使得完美主義派占得了上風(fēng)。這次討論的成果遠(yuǎn)不僅于此,yield的語義得到了改進(jìn),我們有了一個新的系統(tǒng)調(diào)用yield_to();在C/S這種情景下,由IPC引起特殊的優(yōu)先級反轉(zhuǎn)現(xiàn)象也做了一些討論,甚至還出現(xiàn)了一個原型補?。蛔钪匾?,對分組化的調(diào)度做了初步的探討,這也是Srivatsa Vaddagiri所提交的分組化調(diào)度補丁的主要契機之一。分組化的調(diào)度(group scheduling,不要和gang scheduling混淆)是個非常有意思的方向,它一個可見的用武之地就是對虛擬化提供強有力的支持,對調(diào)度有興趣的讀者我衷心推薦跟蹤它的發(fā)展。 那么,如何達(dá)到“公平”呢?假設(shè)一個單處理器系統(tǒng)內(nèi)有三個運行參數(shù)完全相同并且同等重要的任務(wù),在理想的公平條件下,它們在應(yīng)該同時開始同時完成。但恐怕只有在多維時間的條件下,這種處理器才可能制造出來的~所以,必然會有任務(wù)的開始執(zhí)行時間被延遲了,為了做到公平,它在占用處理器時也要將這部分時間找補回來。這樣,依據(jù)什么指標(biāo)選擇要占用處理器的任務(wù)的問題就得到解決了:我們只需要選擇等待處理器時間最長的任務(wù)那個就行了,即,要占用處理器時間最長的那個。那么如何確定任務(wù)占用時間的上限呢?在系統(tǒng)滿負(fù)荷時,最大只能分配其指定配額。那么,如何確定配額呢?簡單地按任務(wù)數(shù)量分割的“大鍋飯”顯然與上面剛剛介紹的關(guān)于公平和相等的討論相矛盾。某個處理器上各種任務(wù)的權(quán)重才是更好的指標(biāo),它可以包納任務(wù)權(quán)值(重要程度)不同的情形。不過,暫且等等!只是“某個處理器上”嗎?那多處理器間的公平怎么辦呢?答案,那是負(fù)載均衡部分的責(zé)任,稍后我們再簡單提一下CFS如何對負(fù)載均衡支持的。 注意,上段有一句:“在系統(tǒng)滿負(fù)荷時,最大只能分配其指定配額?!蹦敲矗谙到y(tǒng)不是滿負(fù)荷時呢?顯然我們不想因為“配額”的原因就是讓處理器空閑著。例如:當(dāng)處理器上有兩個任務(wù)就是純計算任務(wù)(不休眠的那種),兩者的權(quán)重相等,也就是公平的情況下,它們應(yīng)該分別占有50%的處理器時間。如果其中一個任務(wù)早于另一個提前結(jié)束了,我們可不希望剩下的一個還是只能使用50%的處理器時間。那么,有辦法可以達(dá)到這個要求嗎? 當(dāng)然有!實際上不僅僅在處理器調(diào)度上有這種要求,在分組交換網(wǎng)絡(luò)等領(lǐng)域上也有類似需求,這類調(diào)度有一個名字:work-conserving調(diào)度。Virtual Clock就是其中一種兼具實現(xiàn)容易和表現(xiàn)優(yōu)異兩種優(yōu)點的方法。它首先出現(xiàn)在應(yīng)用于分組網(wǎng)絡(luò)的論文中,但當(dāng)然我們會在處理器調(diào)度的場景下解釋它: 在這種方法里,除了實際的時鐘,每個處理器所對應(yīng)的運行隊列還維護(hù)著一個虛擬時鐘。它的前進(jìn)步伐與該處理器上的任務(wù)權(quán)重成反比。也就是說當(dāng)系統(tǒng)內(nèi)有多個任務(wù)時,虛擬時鐘的步調(diào)就按總權(quán)重大小成比例地慢下來,也即虛擬時間單元會按比例變長。例如:如果系統(tǒng)內(nèi)有兩個就緒任務(wù),一個權(quán)值為1,另一個權(quán)值為2,這種情況下虛擬時間單元就是3個實際時間單元。在每個虛擬時間單元內(nèi)這兩個任務(wù)應(yīng)該分別獲得1/3和2/3的處理器時間。每個任務(wù)也都有自己的虛擬時鐘,它們的前進(jìn)步伐與自己的權(quán)重成反比。套用上面的例子,第一個任務(wù)的虛擬時鐘單元與實際時間單元相同,為1;第二個任務(wù)的為1/2。請注意這樣一個事實:在每個虛擬時間單元結(jié)束時,如果任務(wù)得到正常的公平待遇了,它們的虛擬時鐘與運行隊列的虛擬時鐘是相同的。否則,如果任務(wù)的虛擬時鐘慢于運行隊列的,就說明該任務(wù)需要補償了,反而就應(yīng)該遏制該任務(wù)了。在最簡單的情況下,我們通常選擇具有最小虛擬時鐘的任務(wù)就可以做到不錯的公平了。如果你沒有理解這段虛擬時鐘的介紹,下面使用這種最小虛擬時鐘調(diào)度的例子應(yīng)該會有所幫助:(任務(wù)1、2、3的權(quán)重分別為1、2、3)
從上可知,運行隊列上的虛擬時鐘實際上就是把判斷公平與否的標(biāo)尺。對,這就是CFS使用虛擬時鐘的方針!這個時鐘用rq結(jié)構(gòu)的fair_clock成員表示,在函數(shù)update_curr()內(nèi)維護(hù)著它的前進(jìn)步伐。 “單個處理器上的權(quán)重”,內(nèi)核中用rq結(jié)構(gòu)上的raw_weighted_load表示。它是該處理器上所有任務(wù)的load_weight成員之和。nice為0(靜態(tài)優(yōu)先級為120)的任務(wù)的load_weight為1024,以此為基礎(chǔ),nice每變化1個單位,load_weight就遞增20%,例如nice為2的任務(wù)的load_weight就是655(1024x80%x80%)。對應(yīng)到處理器的占用率上,相臨的nice值相差10%,也就是nice值減一,就可以得到10%的處理器占用率的提升。實際上,內(nèi)核里還有一種根據(jù)該處理器的運行歷史情況計算的負(fù)載(rq->cpu_load[]),這種方法更準(zhǔn)確一些,但波動也更大些,為了抵消這種瞬時波動性所帶來的不良影響,內(nèi)核對每一個處理器計算了多種粒度的負(fù)載結(jié)果,新版本的CFS沒有使用這種方法。 前面說到的“等待處理器的時間”(代碼中為task_struct->wait_runtime),是按任務(wù)進(jìn)入就緒隊列之時開始計算的,這符合直覺,不過,新版本的CFS也對休眠于可中斷狀態(tài)(TASK_INTERRUPTIBLE)的任務(wù)給予補償,這樣做的原因是因為I/O操作而休眠的任務(wù)大多處于此狀態(tài)。任務(wù)狀態(tài)間的切換本來就是由內(nèi)核維護(hù)的,記錄這兩種時間戳對于內(nèi)核來說是非常容易的。 無論是什么調(diào)度方法,它要解決的無非就是挑選哪個任務(wù)、為其分配多少CPU時間兩個基本問題,CFS的對應(yīng)解決方法是: 1、挑選那個等待處理器時間最長的任務(wù)占用處理器。這樣,在挑選任務(wù)的過程中已經(jīng)全然沒有了優(yōu)先級的參與,只有時間因素參與其中,這樣,任務(wù)隊列就很自然地是某種按時間排序的數(shù)據(jù)結(jié)構(gòu)了,CFS選擇了用紅黑樹表示運行隊列。紅黑樹,一種二叉平衡查找樹變體,它的左右子樹高差是有可能大于1的,所以,紅黑樹不是嚴(yán)格意義上的平衡二叉樹(也即AVL),但因為對之進(jìn)行平衡的代價較低,其平均統(tǒng)計性能要強于AVL。和大多數(shù)查找二叉樹一樣,紅黑樹中較小的鍵值也是在左子樹保存。紅黑樹鍵值的差不多就是插入時運行隊列虛擬時間與“等待處理器時間”之差,即rq->fair_clock-task_struct->wait_runtime。這樣,CFS選擇任務(wù)時,只需要選擇紅黑樹內(nèi)最左小角的那個。在task_struct結(jié)構(gòu)上用fair_key成員表示這個鍵值。 2、按照任務(wù)權(quán)值所代表的負(fù)載(task_struct->load_weight)占該處理器上總負(fù)載(rq->raw_weigthed_load)的比例計算要分配給任務(wù)的處理器時間。這樣,分配給任務(wù)的處理器時間就不是固定的了。系統(tǒng)內(nèi)也沒有硬性規(guī)定占用處理器的時間上限,具體運行多少時間是根據(jù)處理器負(fù)載及時變化的。我理解,這就是Ingo聲稱CFS內(nèi)沒有“時間片”概念的最本質(zhì)原因。那么,它要如何及時變化才能體現(xiàn)出“公平”呢?呵呵,和我一起往下看吧。 CFS提供了一系列微調(diào)其行為的配置參數(shù),在下面的討論中只針對默認(rèn)配置,有興趣的讀者想要擴展開來,我認(rèn)為也是比較容易的。我在列舉代碼分支時去掉了非默認(rèn)分支,甚至對分支條件的判斷。也許你已經(jīng)迫不急待地想看看CFS的實現(xiàn)了,上面我們介紹過的“調(diào)度類”的幾個關(guān)鍵方法,CFS是這樣填補上去的:
其中最簡單的就是pick_next_task_fair()了:
![]() 6. p->fair_key就是在紅黑樹內(nèi)使用的鍵值了。 update_stats_dequeue()的邏輯相對簡單,它也調(diào)用了update_curr(),我們就不再介紹了。經(jīng)過這一番代碼的打擊,估計有些讀者已經(jīng)被以上有些麻煩的調(diào)用路徑搞得些暈頭轉(zhuǎn)向了,看看下面的“地圖”應(yīng)該會清晰些: ![]() OK,最后再看看這個update_curr()重要函數(shù):
那么,我們能否歸納出一個數(shù)學(xué)公式清楚地表明wait_runtime和nice是如何影響處理器占用時間的呢?我試圖做過這樣的嘗試,但以失敗告終。在簡單的情況(UP,靜態(tài)負(fù)載)下,或者通過數(shù)學(xué)推導(dǎo),或者通過實驗,我們能夠知道單單使用最小虛擬時鐘調(diào)度就可以做到按load_weight比例的公平。但按照最大wait_runtime調(diào)度則做不到按load_weight比例的公平:即相同的load_weight增量,得不到相同增量的處理器時間,但在相同增量的前提下,此時兩者的對應(yīng)關(guān)系還是有保證的?,F(xiàn)有CFS內(nèi)load_weight的增量幅度是20%,我嘗試過修改它為10%,30%,40%:沒有一個可以達(dá)到類似于現(xiàn)有的10%處理器時間增量的效果的??雌饋?,這個20%的load_weight增量更像是一個經(jīng)驗值。所以,我覺得不太可能存在一個簡單的線性關(guān)系公式。 最后,有些讀者可能對在使用CFS時“常規(guī)動態(tài)優(yōu)先級”是如何處理的有些好奇,我想下面的代碼足以解惑了:關(guān)于平均休眠時間(sleep_avg)的獎懲處理完全消失了,可見,CFS內(nèi)根本沒有“動態(tài)優(yōu)先級”的概念,在CFS內(nèi)扮演與之對應(yīng)的角色是fair_key(本質(zhì)上是就是以時間為計量)。
行文將近結(jié)束,大家應(yīng)該已經(jīng)大致了解了單處理器上CFS的工作原理了。但是在多處理器時呢?仔細(xì)觀察CFS的實現(xiàn),尤其是update_curr()中,我們可以知道,虛擬時鐘只有在系統(tǒng)內(nèi)有程序運行時才會流逝,這只是處理器間虛擬時鐘不一致的其中一個原因。做負(fù)載均衡處理器間遷移任務(wù)時,就需要顯式地處理這種時間差,否則就會破壞目標(biāo)處理器上的“平衡”,下面就是處理這種時間差的代碼,邏輯很清楚,我就不再地說詳細(xì)解釋它了:
五、參考材料: 除了內(nèi)核自帶的一些相關(guān)文檔外,以下另外一些有價值的資源: <<Real-time Systems>>作者Jane W.S. Liu 第八章詳細(xì)討論了優(yōu)先級反轉(zhuǎn)方面的內(nèi)容。實際上,我覺得這本書是現(xiàn)在市面上能夠找到的最好的介紹實時系統(tǒng)調(diào)度的書了。更幸運的是,可以同時買到中文翻譯和影印版。這本書中關(guān)于EDF的介紹對我閱讀RTAI的代碼也起了很大的幫助作用。不過看這本書需要些耐心,數(shù)學(xué)的內(nèi)容不少哦。強烈建議同時收藏中英文兩本。 <<算法I-IV(C實現(xiàn)):數(shù)據(jù)結(jié)構(gòu)、排序和搜索>>作者 Robert Sedgewick 作者師從算法大師Knuth。在我能找到的若干本算法書里,這本書對紅黑樹的概念介紹是最好的。如果你只死記硬背過一堆關(guān)于紅黑樹的定理,而沒有聽說過2-3-4樹的話,也許你真沒有理解好紅黑樹,應(yīng)該看看這本書。不過這本書只能找到英文版的,似乎講C++語言實現(xiàn)的版本有中文版,但我沒有看過,無法評價。 http://www. 這個網(wǎng)站上收集了一些LKML上關(guān)于CFS的討論,還經(jīng)過一些整理,雖然并不完整,但可讀性好多了。這里值得看看,尤其是關(guān)于CFS和EEVDF的討論,值得一讀。EEVDF論文的下載鏈接也可以在這找到。但這里能找到的信息,在LKML上都可以得到。 LKML |
|