Linux scheduler 調度源碼初步閱讀

代碼版本

2.6.39.4

fork流程

在這裏插入圖片描述
爲什麼要先看fork的源代碼呢?
因爲在schedule函數裏面一開始我就有點看不懂:

cpu = smp_processor_id();

按理來說很好理解呀,不就是獲取當前的CPU的ID嗎?但是什麼叫做當前?在多處理器的機器上面,當前的定義是什麼?是在哪一塊CPU上面?我有點搞不懂,所以開始先看看fork都做了些什麼。

具體的流程可以看上面的圖,fork系統調用走到最後調用的是kernel/fork.c裏面的do_fork函數。
上圖中,我只是把我認爲關鍵的函數提了出來。

copy_process

從上面的圖可以看到do_fork有兩個主要的函數:

copy_process 和 wake_up_new_task

copy_process 主要是對父進程task struct 的複製,做一些新進程的初始化,然後調用sched_fork函數。

sched_fork {
    p->state = TASK_RUNNING;
    get_cpu();
    set_task_cpu();
}

主要就是這兩個函數,先獲取當前的CPU的ID,好了,又是當前CPU,但是現在這個 當前 比較好理解:因爲肯定是有一個線程來調用fork這個函數的,不管是直接還是間接。這個線程能運行那肯定是在某一塊CPU上面,那麼get_cpu()函數就是獲取當前運行這個線程的CPU。然後暫時把新的線程的可運行CPU設置跟調用fork的線程一樣。
注意這裏只是暫時地設置,而且新的線程並沒有真正地運行在任何一塊CPU上面。

wake_up_new_task

wake_up_new_task {
    int cpu __maybe_unused = get_cpu();
    p->state = TASK_WAKING;
    cpu = select_task_rq(rq, p, SD_BALANCE_FORK, 0);
    set_task_cpu(p, cpu);
    activate_task(rq, p, 0);
}

主要的函數就是上面的這些,還是先獲取當前的CPU ID,然後到了最重要的函數:select_task_rq,這個函數會根據新創建的這個線程所屬的調度類去執行不同的select_task_rq。

  1. 對於Real Time 任務
    看當前在這個CPU上面運行的任務【當前任務】是不是RealTime的,當前任務是否之能在這個CPU上面運行,新建的線程能否在其他的CPU上面運行。如果都滿足的話,就會調用find_lowest_rq來獲取可運行的CPU,否則直接返回當前的CPU。
  2. 對於Normal任務(CFS)
    先會根據cgroup,cpu_domain,task CPU親和性來選取適合的CPU,如果沒有這些要求的話,那麼會找到負載最低的CPU。

調用完這個函數之後,再調用set_task_cpu就設置相關的task struct 裏面的變量。最後再調用activate_task這個函數,activate_task又會調用enqueue_task這個函數,這個函數也是根據不同的調度類去調用不同的函數、

  1. 對於Real Time 任務
    直接放入rt_rq對應優先級的鏈表尾。
  2. 對於Normal任務(CFS)
    放入cfs_rq的紅黑樹裏面。

好了,到這裏整個fork流程就結束了,只是粗略地走了一遍,很多細節等到需要閱讀相關模塊的時候再深入。
看這個fork的流程的目的是知道smp_process_id()這個函數到底是獲得哪一塊CPU,現在已經清楚了,就是獲取當前進程運行的CPU,schedule函數也跟fork一樣,是被一些進程通過系統調用來調用的,所以能獲取當前進程的CPU。

schedule 流程

在這裏插入圖片描述
schedule比較清晰,主要的函數只有三個

schedule {
     pre_schedule();
     pick_next_task();
     context_switch();
}

pre_schedule

這個函數會根據不同的調度類來調度不同的函數,我暫時只看到了實時任務的調度類實現了這個函數。
會把其他CPU裏面的比當前CPU實時隊列的最高優先級低的任務拉到當前的CPU實時隊列裏面。

pick_next_task

這個就是主要的選取下一個可以運行的任務的函數。

    if (likely(rq->nr_running == rq->cfs.nr_running)) { //如果所有的任務都只是在cfs隊列裏面的話,直接調cfs的pick即可
        p = fair_sched_class.pick_next_task(rq);
        if (likely(p))
            return p;
    }

先是一個小優化,然後再遍歷每個調度類來調度不同的函數

  1. 對於Real Time 任務
    選取最高優先級隊列鏈表裏面的第一個任務。
  2. 對於Normal任務(CFS)
    選取vrun_time最小的那個任務,紅黑樹最左邊的那個節點。

context_switch

這個就是跟體系相關的上下文切換函數了,保存寄存器裏面的值等等。

可調度實體的vruntime、load計算

  1. vruntime:

    具體代碼在:kernel/sched_fair.c:__update_curr()

__update_curr()
update_delta_fair()
calc_delta_mine()

最主要的函數是最後一個,裏面的計算邏輯除開對溢出的處理外,最後歸成下面的式子:

delta = delta * weight / lw
對於CFS的計算,weight是nice值爲0的進程weight:1024
lw是可調度實體的curr->load.weight

  1. load weight:
static const int prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};
MAX_RT_PRIO
set_load_weight() {
    p->se.load.weight = prio_to_weight[p->static_prio - MAX_RT_PRIO];
    p->se.load.inv_weight = prio_to_wmult[p->static_prio - MAX_RT_PRIO];
}

通過上面可以看到,只有CFS的進程load weight有意義。同時這個load weight 在進程進入cpu的cfs rq的時候也會把這個值加到就緒隊列的load上面來反應這個cpu的負載程度。

Refer

《深入理解Linux內核》
《深入Linux內核架構》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章