鼠眼再看Linux調度器(2)

四、CFS

    CFS現在還是非常新的調度實現,並且本人水平也十分有限,有鑑於此,這裏很可能存在不當的地方甚至錯誤,權當拋磚引玉,不妥之處還請諸位有識之士不吝指正。

        在討論CFS之前,我們先回顧一下現有的調度器實現:這是一個巧妙的雙優先級數組方案。爲了儘量避免出現“過期數組”中的任務出現飢餓現象,內核使用了一些啓發式的方法判斷是否出現了飢餓。在絕大多數情況下,這個實現給了我們非常好的交互性體驗。不過,可惜在個別情況下仍會出現明顯的飢餓現象,更致命的是,這個問題是完全可以重現的。躲躲閃閃修修補補終究不是解決問題之道。用Ingo的話說,問題的根源在於調度器沒有一種機制可以跟蹤使用者的“眼球”--從而不能及時獲得哪些任務是最希望儘快處理的,哪些任務是可以暫緩處理的這種線索。現有的調度器試圖使用啓發式方法解決這個問題,但並不完美。這種情況下,儘量的公平可能就是一種可行的方案了,Con KolivasRDSLSD調度方案也從實踐上證明了這個方案是可行的。不過,CFS使用了與之完全不同的方法實現“公平”的概念。

        我們已經知道了CFS是“完全公平調度”的縮寫,那究竟什麼是“公平”呢?首先,“公平”絕不意味着“相等”,也就是說在分配處理器資源時,不能簡單地將系統內所有任務都一視同仁,而要區別對待。這是因爲系統內的任務本身就不是平等的,例如許多內核線程生來用來應付某種比較緊急的情況的,它們理應比普通的用戶空間任務更優越一些(事實上CFS的早期版本確實會引起某些內核線程的飢餓)。從另一方面上看,絕對公平也是不可能實現的,CFS所關注的是時間上相對長程(long-term)的公平(也可看成是總體上的,統計上的公平),在每個小的時間區間很可能看起來並不是公平的,引起這種現象的可能是需要對以往的不公平作補償、系統的負載發生變化、實現方法限制等諸多原因。此外,公平也應該是層次化的。不過,現在CFS並沒有支持這種性質的公平,所以這裏先按下不談。所以說,CFS中的“C”和“F”其實都不是絕對的。

        在實現公平的方法上,社區內也發生過激烈的討論。討論的導火索也是我們也試圖解決的老大難問題:如何處理X窗口系統的服務器。一派認爲X服務器是一個特殊的應用程序,它很大程度上影響了GUI的使用體驗,甚至於它還自己直接訪問硬件(主要是顯示卡),我們有充足的理由對其進行特殊處理,更具體地,就是讓使用者自己去提升X服務器的靜態優先級。甚至,CFS的有些版本曾經利用X服務器自行硬件訪問的行爲自動對X服務器做一些調度上的獎勵(通過截獲ioperm()系統調用);另一派試圖找到一種全能的公平調度方法,它們認爲X並沒有多少代表性,許多服務器類任務的運行時間都是因爲處理客戶端的要求而消耗的。“實踐是檢驗真理的唯一標準”,最終,新版本的CFS使得完美主義派佔得了上風。這次討論的成果遠不僅於此,yield的語義得到了改進,我們有了一個新的系統調用yield_to();在CS這種情景下,由IPC引起特殊的優先級反轉現象也做了一些討論,甚至還出現了一個原型補丁;最重要地,對分組化的調度做了初步的探討,這也是Srivatsa Vaddagiri所提交的分組化調度補丁的主要契機之一。分組化的調度(group scheduling,不要和gang scheduling混淆)是個非常有意思的方向,它一個可見的用武之地就是對虛擬化提供強有力的支持,對調度有興趣的讀者我衷心推薦跟蹤它的發展。

        那麼,如何達到“公平”呢?假設一個單處理器系統內有三個運行參數完全相同並且同等重要的任務,在理想的公平條件下,它們在應該同時開始同時完成。但恐怕只有在多維時間的條件下,這種處理器纔可能製造出來的~所以,必然會有任務的開始執行時間被延遲了,爲了做到公平,它在佔用處理器時也要將這部分時間找補回來。這樣,依據什麼指標選擇要佔用處理器的任務的問題就得到解決了:我們只需要選擇等待處理器時間最長的任務那個就行了,即,要佔用處理器時間最長的那個。那麼如何確定任務佔用時間的上限呢?在系統滿負荷時,最大隻能分配其指定配額。那麼,如何確定配額呢?簡單地按任務數量分割的“大鍋飯”顯然與上面剛剛介紹的關於公平和相等的討論相矛盾。某個處理器上各種任務的權重纔是更好的指標,它可以包納任務權值(重要程度)不同的情形。不過,暫且等等!只是“某個處理器上”嗎?那多處理器間的公平怎麼辦呢?答案,那是負載均衡部分的責任,稍後我們再簡單提一下CFS如何對負載均衡支持的。

        注意,上段有一句:“在系統滿負荷時,最大隻能分配其指定配額。”那麼,在系統不是滿負荷時呢?顯然我們不想因爲“配額”的原因就是讓處理器空閒着。例如:當處理器上有兩個任務就是純計算任務(不休眠的那種),兩者的權重相等,也就是公平的情況下,它們應該分別佔有50%的處理器時間。如果其中一個任務早於另一個提前結束了,我們可不希望剩下的一個還是隻能使用50%的處理器時間。那麼,有辦法可以達到這個要求嗎?

        當然有!實際上不僅僅在處理器調度上有這種要求,在分組交換網絡等領域上也有類似需求,這類調度有一個名字:work-conserving調度。Virtual Clock就是其中一種兼具實現容易和表現優異兩種優點的方法。它首先出現在應用於分組網絡的論文中,但當然我們會在處理器調度的場景下解釋它:

   
        在這種方法裏,除了實際的時鐘,每個處理器所對應的運行隊列還維護着一個虛擬時鐘。它的前進步伐與該處理器上的任務權重成反比。也就是說當系統內有多個任務時,虛擬時鐘的步調就按總權重大小成比例地慢下來,也即虛擬時間單元會按比例變長。例如:如果系統內有兩個就緒任務,一個權值爲1,另一個權值爲2,這種情況下虛擬時間單元就是3個實際時間單元。在每個虛擬時間單元內這兩個任務應該分別獲得1323的處理器時間。每個任務也都有自己的虛擬時鐘,它們的前進步伐與自己的權重成反比。套用上面的例子,第一個任務的虛擬時鐘單元與實際時間單元相同,爲1;第二個任務的爲12。請注意這樣一個事實:在每個虛擬時間單元結束時,如果任務得到正常的公平待遇了,它們的虛擬時鐘與運行隊列的虛擬時鐘是相同的。否則,如果任務的虛擬時鐘慢於運行隊列的,就說明該任務需要補償了,反而就應該遏制該任務了。在最簡單的情況下,我們通常選擇具有最小虛擬時鐘的任務就可以做到不錯的公平了。如果你沒有理解這段虛擬時鐘的介紹,下面使用這種最小虛擬時鐘調度的例子應該會有所幫助:(任務123的權重分別爲123

實際時鐘

運行隊列

虛擬時鐘

任務1

虛擬時鐘

任務2

虛擬時鐘

任務3

虛擬時鐘

0

0

0

0

0

1

0.17

1

0

0

2

0.34

1

0.5

0

3

0.5

1

0.5

0.33

4

0.67

1

0.5

0.67

5

0.83

1

1

0.67

6

1

1

1

1

        從上可知,運行隊列上的虛擬時鐘實際上就是把判斷公平與否的標尺。對,這就是CFS使用虛擬時鐘的方針!這個時鐘用rq結構的fair_clock成員表示,在函數update_curr()內維護着它的前進步伐。

    “單個處理器上的權重”,內核中用rq結構上的raw_weighted_load表示。它是該處理器上所有任務的load_weight成員之和。nice0(靜態優先級爲120)的任務的load_weight1024,以此爲基礎,nice每變化1個單位,load_weight就遞增20%,例如nice2的任務的load_weight就是6551024x80%x80%)。對應到處理器的佔用率上,相臨的nice值相差10%,也就是nice值減一,就可以得到10%的處理器佔用率的提升。實際上,內核裏還有一種根據該處理器的運行歷史情況計算的負載(rq->cpu_load[]),這種方法更準確一些,但波動也更大些,爲了抵消這種瞬時波動性所帶來的不良影響,內核對每一個處理器計算了多種粒度的負載結果,新版本的CFS沒有使用這種方法。

        前面說到的“等待處理器的時間”(代碼中爲task_struct->wait_runtime),是按任務進入就緒隊列之時開始計算的,這符合直覺,不過,新版本的CFS也對休眠於可中斷狀態(TASK_INTERRUPTIBLE)的任務給予補償,這樣做的原因是因爲IO操作而休眠的任務大多處於此狀態。任務狀態間的切換本來就是由內核維護的,記錄這兩種時間戳對於內核來說是非常容易的。

        無論是什麼調度方法,它要解決的無非就是挑選哪個任務、爲其分配多少CPU時間兩個基本問題,CFS的對應解決方法是:

     1、挑選那個等待處理器時間最長的任務佔用處理器。這樣,在挑選任務的過程中已經全然沒有了優先級的參與,只有時間因素參與其中,這樣,任務隊列就很自然地是某種按時間排序的數據結構了,CFS選擇了用紅黑樹表示運行隊列。紅黑樹,一種二叉平衡查找樹變體,它的左右子樹高差是有可能大於1的,所以,紅黑樹不是嚴格意義上的平衡二叉樹(也即AVL),但因爲對之進行平衡的代價較低,其平均統計性能要強於AVL。和大多數查找二叉樹一樣,紅黑樹中較小的鍵值也是在左子樹保存。紅黑樹鍵值的差不多就是插入時運行隊列虛擬時間與“等待處理器時間”之差,即rq->fair_clock-task_struct->wait_runtime。這樣,CFS選擇任務時,只需要選擇紅黑樹內最左小角的那個。在task_struct結構上用fair_key成員表示這個鍵值。

      2、按照任務權值所代表的負載(task_struct->load_weight)佔該處理器上總負載(rq->raw_weigthed_load)的比例計算要分配給任務的處理器時間。這樣,分配給任務的處理器時間就不是固定的了。系統內也沒有硬性規定佔用處理器的時間上限,具體運行多少時間是根據處理器負載及時變化的。我理解,這就是Ingo聲稱CFS內沒有“時間片”概念的最本質原因。那麼,它要如何及時變化才能體現出“公平”呢?呵呵,和我一起往下看吧。

      CFS提供了一系列微調其行爲的配置參數,在下面的討論中只針對默認配置,有興趣的讀者想要擴展開來,我認爲也是比較容易的。我在列舉代碼分支時去掉了非默認分支,甚至對分支條件的判斷。也許你已經迫不急待地想看看CFS的實現了,上面我們介紹過的“調度類”的幾個關鍵方法,CFS是這樣填補上去的:

struct sched_class fair_sched_class __read_mostly = {
    .enqueue_task        = enqueue_task_fair,
    .dequeue_task        = dequeue_task_fair,
    .pick_next_task        = pick_next_task_fair,
    .task_tick        = task_tick_fair,
    /* ...... */
};


    其中最簡單的就是pick_next_task_fair()了:


/* 功能:獲得要佔用處理器的下一個任務 */
static struct task_struct * pick_next_task_fair(struct rq *rq, u64 now)
{
1>    struct task_struct *p = __pick_next_task_fair(rq);

2>    update_stats_wait_end(rq, p, now);
3>    update_stats_curr_start(rq, p, now);

    return p;
}


  1. __pick_next_task_fair(),這是完成取“下一個要佔用處理器任務”功能的實際函數。如前所述,它取等待處理器時間最長(task_struct->fair_key最小)的那個任務。在實現上,就是取紅黑樹內左下角的那個結點,這裏還有一點點優化處理,類似於內核在處理VMA時所做的那樣。

  2. 不要被這個函數名稱中的“wait”所迷惑,它不是指任務狀態中的那個“休眠”,而是指停留於運行隊列的行爲。CFS內有幾個函數名稱是以“update_stat”作爲前綴。但不要因爲它們的名字而忽略它們,它們更新的不單單是統計信息,很多是CFS的關鍵參數。但在本場景下,這個函數沒做對調度行爲有任何影響的操作,只是計算了一個最長停留時間,它用在顯示任務的運行時統計信息時。

  3. 這個函數的行爲很簡單,它只是保存了任務p的開始運行時間(exec_start),它是update_curr()完成必要功能的重要輸入,而後者是CFS的重要實現環節。


/* 功能:時鐘中斷內的調度處理 */
static void task_tick_fair(struct rq *rq, struct task_struct *curr)
{
    struct task_struct *next;
1>    u64 now = __rq_clock(rq);

2>    dequeue_task_fair(rq, curr, 0, now);
    enqueue_task_fair(rq, curr, 0, now);

3>    next = __pick_next_task_fair(rq);
    if (next == curr)
        return;

4>    if ((curr == rq->idle) || (rt_prio(next->prio) &&
                    (next->prio < curr->prio)))
        resched_task(curr);
    else
5>        __check_preempt_curr_fair(rq, next, curr,
                     sysctl_sched_granularity);
}


  1. 在現有的調度實現中,任務隊列首先按動態優先級排序。一般的Linux任務有40種優先級別。而CFS則完全是以時間組織運行隊列,對比現有的調度實現CFS的“優先級”安排就相當於“無級變速”。但是深究起來,CFS的“變速器”也是有級的,其粒度取決於CFS所能感知的實際時間單元長度,它越精細CFS的“變速器”就更無級化,“公平”也更會理想化。這裏的__rq_lock()就是CFS用於感知時間的“傳感器”,獲取到的時間以納秒計,不過我們也要保持清醒的頭腦,它僅僅是爲納秒爲單位,並不是真正分辨到每個納秒。

  2. 恐怕所有的調度方法都不會放過時鐘中斷這個時機調整當前任務的運行時參數。調整過程之後,還要根據這些新參數調整它在運行隊列中的位置(先刪除再重新插入)。在現有調度實現中,我們是在“小運行隊列”之間切換(例子見rt_mutex_setprio()),但在CFS內,我們是在紅黑樹上玩“滑梯”遊戲。這兩個函數我們稍後就會介紹。

  3. 使用變量next保存現在運行隊列中最合適佔有處理器的任務。如果next與這個處理器上的當前任務相同,就說明當前任務還有資格使用處理器。後面的代碼也就沒有意義了,於是就直接返回了。

  4. 這條語句做兩個檢查:如果處理器現在無事可做(即正運行着空閒任務);或者,新任務是實時任務且比當前任務優先級更高,就直接使用resched_task(curr)做一個記號,使得當前任務稍後放棄處理器,讓賢。

  5. 因爲CFS是以很精細的時間作爲基礎決定選擇下一個調度任務的,並且它沒有固定時間片的概念。所以就很有可能發生任務頻繁切換的情況。這是我們不願見到的:它增大了調度過程對系統的負擔,也會使cache的利用率下降。__check_preempt_curr_fair()保證了當前任務佔用處理器的時間有一個最小下限。這個下限是受兩個因素影響:一個是運行時配置(通過sysctl),一個是當前任務與新任務的等待處理器時間之差的大小,這個函數的實現很直觀,我們不再跟蹤進去了。



/* 功能:將任務p加入到運行隊列rq內。 */
static void
enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup, u64 now)
{
1>    update_curr(rq, now);

2>    if (wakeup) {
        /* ...... */
    }
3>    update_stats_enqueue(rq, p, now);
4>    __enqueue_task_fair(rq, p);
}

1.將一個任務加入運行隊列的時候,處理器上的負載也發生了變化,所以此時也是調整當前任務的運行時參數的好時機,update_curr()就是來幹調整這個活計的,解釋見後。
2.上面說到,新版本的CFS對處於可中斷休眠狀態會做一些額外獎勵。這裏跳過的代碼就是該功能實現。有興趣的讀者可以自行閱讀。
3.在加入運行隊列之前,需要更新這個任務的“統計信息”。與之對應,還有一個在任務出隊之前調用的 update_stats_dequeue ()。這兩個函數和下面就要介紹的update_curr()是CFS核心邏輯的主要實現點。
4.這是將任務實際插入到紅黑樹運行隊列中的函數。

    dequeue_task_fair()的邏輯與這個函數非常相似,我們就略過不看了。

static inline void
update_stats_enqueue(struct rq *rq, struct task_struct *p, u64 now)
{
    s64 key;

1>    if (p != rq->curr)
        update_stats_wait_start(rq, p, now);
2>    key = rq->fair_clock;

3>    if (likely(p->load_weight == NICE_0_LOAD)) {
        key -= p->wait_runtime;
    } else {
4>        if (p->wait_runtime < 0)
            key -= div64_s(p->wait_runtime * NICE_0_LOAD, p->load_weight);
        else
            key -= div64_s(p->wait_runtime * p->load_weight, NICE_0_LOAD);
    }
5>    p->fair_key = key;
}


  1. 如果要加入運行隊列的任務不是當前任務,就說明它是一個新來者。那麼,這個任務在運行隊列中的等待就開始了。update_stats_wait_start()就是來辦理相關手續的,其實就是記錄時間戳。這樣我們就知道它在運行隊列裏究竟等待了多長時間了。如果由於某種原因,這個任務沒有得到處理器就被從運行隊列中除名了,這個時間戳就用來計算它在運行隊列中待了多長時間,將之補償在task_struct->wait_runtime中。

  2. rq->fair_lock,就是這個處理器上的虛擬時鐘;key是這個任務在紅黑樹運行隊列中的鍵值,接着向下看;

  3. 對於最常見的nice=0任務,計算key值:key -= p->wait_runtimewait_runtime就是該任務以前在運行隊列中等待時間,也就是在任務上次用完處理器後所剩的“資產”。key值越小,任務越富,越有可能早得到下次使用處理器的機會。

  4. 以下代碼是根據任務的“贏利情況”和系統內的負載,計算任務的“淨資產”。對於“超支”的任務,就加點稅:

  5. 對於“欠收”的任務,我們給一些“福利”:


     6. p->fair_key就是在紅黑樹內使用的鍵值了。

     update_stats_dequeue()的邏輯相對簡單,它也調用了update_curr(),我們就不再介紹了。經過這一番代碼的打擊,估計有些讀者已經被以上有些麻煩的調用路徑搞得些暈頭轉向了,看看下面的“地圖”應該會清晰些:




    OK,最後再看看這個update_curr()重要函數:


static inline void update_curr(struct rq *rq, u64 now)
{
    unsigned long load = rq->raw_weighted_load;
    u64 delta_exec, delta_fair, delta_mine;
    struct task_struct *curr = rq->curr;

1>    if (curr->sched_class != &fair_sched_class || curr == rq->idle)
        return;

2>    delta_exec = now - curr->exec_start;

3>    curr->exec_start = now;

1>    if (!load)
        return;

4>    if (unlikely(sysctl_sched_load_smoothing & 1))
        if (unlikely(test_tsk_thread_flag(curr, TIF_NEED_RESCHED)))
            return;

5>    delta_fair = delta_exec * NICE_0_LOAD;
    delta_fair += load >> 1;
    do_div(delta_fair, load);

6>    delta_mine = delta_exec * curr->load_weight;
    delta_mine += load >> 1;
    do_div(delta_mine, load);

7>    rq->fair_clock += delta_fair;

8>    add_wait_runtime(rq, curr, delta_mine-delta_exec);
}


  1. 在三種條件下更新當前任務的運行時信息是沒有意義的:當前任務不在CFS管轄範圍之內;當前處理器處於初始化或空閒狀態。此時,這個函數就直接返回了。

  2. delta_exec保存了自上次更新完exec_start之後,當前任務的實際執行時間。那麼什麼時候更新exec_start呢?上面說到的pick_next_task_fair()是一個地方,另一個地方就是update_curr()自己。再看看什麼地方調用update_curr() ,如果追蹤一下,就會發現簡直太多了,不過幸虧原始代碼在這裏有處註釋:):只要負載(可能)發生了變化,我們就修改exec_start。這也符合我們在enqueue_task_fair()中的分析。

  3. 將當前時間賦予exec_start,即啓動下一個“採樣週期”。

  4. 如果當前任務是已經被標記爲要讓出處理器就提前從函數返回。這時再按正常的邏輯走只是浪費處理器資源。

  5. 根據實際執行時間計算虛擬時鐘的增量delta_fair。其中加了rq->raw_weighted_load >> 1,是用來四捨五入的小技巧。這個結果稍後作爲虛擬時鐘的增量。

  6. delta_mine就是在delta_fair這個時段內,任務“應該”佔用的時間。這對應於任務自己虛擬時鐘的步伐長度。關於如何使用它的進一步解釋見8

  7. 運行隊列虛擬時鐘的前進增量delta_fair

  8. 調整wait_runtime,因爲delta_mine基本上總是小於delta_fair ,所以多數情況下是在減少wait_runime。這樣,對應的fair_key就會逐漸增大(見update_stats_enqueue() task_tick_fair()),任務的財富逐漸減少,最終導致這個任務失去其在紅黑樹運行隊列內最左下端的“寶座”,從而被迫讓出處理器。從概念上講,每個週期(兩次exec_start更新之間的時間間隔)之間,CFS只應該分配給這個任務(task_struct->load_weigth/rq->raw_weighted_load)%的處理器時間(也就是delta_mine)。但是下次更新exec_start的時間是不可能準確預言的,所以不可能直接實現這種分配方案,我們只能在更長的時間尺度上做到按比例分配。如果影響時間分配呢?我們只要按比例地將任務的fair_key在時間軸上向後移就能達到目的了,而wait_runtimefair_key是直接相關的,所以減少wait_runtime其實也就是向後移了fair_key,這便是這條語句的真正用意了。雖然因爲虛擬時鐘的緣故這裏做定量的分析有些麻煩,但是定性的分析還是很容易的:讀者可以試着推導一下如果task_struct->load_weight分別是較大或者較小的數值,上述幾個因素delta_minewait_runtimefair_key分別會向哪個方向變化。想必經過這一番思考讀者一定能體會此處是如何影響處理器時間分配的。

        那麼,我們能否歸納出一個數學公式清楚地表明wait_runtimenice是如何影響處理器佔用時間的呢?我試圖做過這樣的嘗試,但以失敗告終。在簡單的情況(UP,靜態負載)下,或者通過數學推導,或者通過實驗,我們能夠知道單單使用最小虛擬時鐘調度就可以做到按load_weight比例的公平。但按照最大wait_runtime調度則做不到按load_weight比例的公平:即相同的load_weight增量,得不到相同增量的處理器時間,但在相同增量的前提下,此時兩者的對應關係還是有保證的。現有CFSload_weight的增量幅度是20%,我嘗試過修改它爲10%,30%,40%:沒有一個可以達到類似於現有的10%處理器時間增量的效果的。看起來,這個20%的load_weight增量更像是一個經驗值。所以,我覺得不太可能存在一個簡單的線性關係公式。

        最後,有些讀者可能對在使用CFS時“常規動態優先級”是如何處理的有些好奇,我想下面的代碼足以解惑了:關於平均休眠時間(sleep_avg)的獎懲處理完全消失了,可見,CFS內根本沒有“動態優先級”的概念,在CFS內扮演與之對應的角色是fair_key(本質上是就是以時間爲計量)。


static inline int __normal_prio(struct task_struct *p)
{
    return p->static_prio;
}


        行文將近結束,大家應該已經大致瞭解了單處理器上CFS的工作原理了。但是在多處理器時呢?仔細觀察CFS的實現,尤其是update_curr()中,我們可以知道,虛擬時鐘只有在系統內有程序運行時纔會流逝,這只是處理器間虛擬時鐘不一致的其中一個原因。做負載均衡處理器間遷移任務時,就需要顯式地處理這種時間差,否則就會破壞目標處理器上的“平衡”,下面就是處理這種時間差的代碼,邏輯很清楚,我就不再地說詳細解釋它了:



void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
{
    int old_cpu = task_cpu(p);
    struct rq *old_rq = cpu_rq(old_cpu), *new_rq = cpu_rq(new_cpu);
    u64 clock_offset, fair_clock_offset;

    clock_offset = old_rq->clock - new_rq->clock;
    fair_clock_offset = old_rq->fair_clock - new_rq->fair_clock;

    if (p->wait_start)
        p->wait_start -= clock_offset;
    /* ...... */
    task_thread_info(p)->cpu = new_cpu;
}


五、參考材料:

        除了內核自帶的一些相關文檔外,以下另外一些有價值的資源:

        <<Real-time Systems>>作者Jane W.S. Liu

        第八章詳細討論了優先級反轉方面的內容。實際上,我覺得這本書是現在市面上能夠找到的最好的介紹實時系統調度的書了。更幸運的是,可以同時買到中文翻譯和影印版。這本書中關於EDF的介紹對我閱讀RTAI的代碼也起了很大的幫助作用。不過看這本書需要些耐心,數學的內容不少哦。強烈建議同時收藏中英文兩本。

        <<算法IIVC實現):數據結構、排序和搜索>>作者 Robert Sedgewick

        作者師從算法大師Knuth。在我能找到的若干本算法書裏,這本書對紅黑樹的概念介紹是最好的。如果你只死記硬背過一堆關於紅黑樹的定理,而沒有聽說過2-3-4樹的話,也許你真沒有理解好紅黑樹,應該看看這本書。不過這本書只能找到英文版的,似乎講C++語言實現的版本有中文版,但我沒有看過,無法評價。

     http://www.kerneltraps.org

       這個網站上收集了一些LKML上關於CFS的討論,還經過一些整理,雖然並不完整,但可讀性好多了。這裏值得看看,尤其是關於CFSEEVDF的討論,值得一讀。EEVDF論文的下載鏈接也可以在這找到。但這裏能找到的信息,在LKML上都可以得到。

    LKML

       網上有許多地方有LKMLarchieve,有些Site甚至支持查找功能,這使得我們不用訂閱也能讀到大俠們的墨跡。如果想詳細瞭解CFS成長過程的話,LKML是唯一的通天之路。除了LinusIngo,下面這三位大俠如果出手,可要留意一下他們的招式呀:Peter WilliamsWilliam Lee Irwin III Ting Yang
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章