文章目錄
代碼版本
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。
- 對於Real Time 任務
看當前在這個CPU上面運行的任務【當前任務】是不是RealTime的,當前任務是否之能在這個CPU上面運行,新建的線程能否在其他的CPU上面運行。如果都滿足的話,就會調用find_lowest_rq來獲取可運行的CPU,否則直接返回當前的CPU。 - 對於Normal任務(CFS)
先會根據cgroup,cpu_domain,task CPU親和性來選取適合的CPU,如果沒有這些要求的話,那麼會找到負載最低的CPU。
調用完這個函數之後,再調用set_task_cpu就設置相關的task struct 裏面的變量。最後再調用activate_task這個函數,activate_task又會調用enqueue_task這個函數,這個函數也是根據不同的調度類去調用不同的函數、
- 對於Real Time 任務
直接放入rt_rq對應優先級的鏈表尾。 - 對於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;
}
先是一個小優化,然後再遍歷每個調度類來調度不同的函數
- 對於Real Time 任務
選取最高優先級隊列鏈表裏面的第一個任務。 - 對於Normal任務(CFS)
選取vrun_time最小的那個任務,紅黑樹最左邊的那個節點。
context_switch
這個就是跟體系相關的上下文切換函數了,保存寄存器裏面的值等等。
可調度實體的vruntime、load計算
-
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
- 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內核架構》