(代碼主要參考5.10)
1. __schedule的參數preempt
static void __sched notrace __schedule(bool preempt)
preempt
是一個bool的類型的值。
在
__schedule
中有這樣的一段代碼,(有删減):
switch_count = &prev->nivcsw;
prev_state = prev->state;
if (!preempt && prev_state) {
if (signal_pending_state(prev_state, prev)) {
prev_state = TASK_RUNNING;
} else {
......;
deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);
......;
}
switch_count = &prev->nvcsw;
}
......;
if (likely(prev != next)) {
......;
++switch_context;
}
preempt
代表是否自願上下文切換。如果是自願(非搶占進行排程),則為false;如果是非自願(搶占進行排程),則為true。
struct task_struct
有兩個成員
nvcsw
和
nivcsw
。
nvcsw | nivcsw |
---|---|
Number of Voluntary Context Switches(自願上下文切換的計數) | Number of InVoluntary Context Switches(非自願上下文切換計數) |
當一個程序非自願上下文切換的時候,即被搶占的時候,會少判斷一些内容;
而當一個程序自願上下文切換的時候,即主動放棄CPU的時候,要進行一些判斷,會決定prev的狀态,是否出隊,以及負載均衡的一些操作,這裡就不較長的描述了。
至于哪些函數,會觸發排程
__schedule
,它們分别是搶占還是非搶占呢?5.10中如下所示:
function | preempt |
---|---|
do_task_dead | false |
schedule | false |
schedule_idle | false |
preempt_schedule_comm | true |
preempt_schedule_notrace | true |
preempt_schedule_irq | true |
這些函數留給後續分析吧。
2. pick_next_task的兩條路徑
pick_next_task
函數在
__schedule
中調用,挑選下一個要執行的程序。
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
......;
if (likely(prev->sched_class <= &fair_sched_class &&
rq->nr_running == rq->cfs.h_nr_running)) {---短路徑
p = pick_next_task_fair(rq, prev, rf);
......;
}
restart:
......;
for_each_class(class) {---長路徑
p = class->pick_next_task(rq);
......;
}
}
是走長路徑、還是短路徑呢?判斷條件為:目前程序的排程類是否為cfs或者idle以及運作隊列的程序數量是否與cfs運作隊列的程序數量相等。
cfs_rq
中除了
h_nr_running
外,還有一個
nr_running
,以及
rq
中也存在一個
nr_running
,它們分别代表什麼?
成員 | 解釋 |
---|---|
rq的nr_running | 代表運作隊列的程序個數 |
cfs_rq的nr_running | 開啟組排程的話,代表組排程最上層的group個數 |
cfs_rq的h_nr_running | 代表cfs_rq中的程序個數 |
3. context_switch的四種情況
挑選出下一個要執行的程序next後,要使用
context_switch
進行位址空間的切換。
static __always_inline stuct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
......;
if (!next->mm) {
enter_lazy_tlb(prev->active);
next->active_mm = prev->active_mm;
if (prev->mm)
mmgrab(prev->active_mm);
else
prev->active_mm = NULL;
} else {
membarrier_switch_mm(rq, prev->active_mm, next->mm);
switch_mm_irqs_off(prev->active_mm, next->mm, next);
if (!prev->mm) {
rq->prev_mm = prev->active_mm;
prev->active_mm = NULL;
}
}
......;
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev);
}
所謂的四種情況,其實就是prev和next分别是user線程還是kernel線程的組合情況。
prev | next | 操作 |
---|---|---|
kernel | kernel | tlb lazy模式,next借用prev的active_mm,prev的active_mm清空 |
user | kernel | tlb lazy模式,next借用prev的active_mm,prev的mm_count增加計數 |
kernel | user | 位址空間切換,rq記錄prev_mm,将prev->active_mm清空 |
user | user | 程序位址空間切換 |
Q1:什麼是tlb lazy模式?
tlb是什麼?是一個虛拟位址轉換成實體位址的快速轉換表,常用于cache尋址中。
通常CPU都是程序切換一次,進行一次flush(後面有其他不用全部flush的方法,不較長的描述了)。
而核心空間是所有程序通用的,故可以不用flush tlb,這就是tlb lazy模式。
Q2:
mm
與
active_mm
差別?
mm
的存在與否用于判定該程序是屬于user還是kernel;
active_mm
則為實際使用的位址空間,kernel線程總是借用user線程的位址空間。
可以看到,每次kernel線程被切換出去後,它的
active_mm
就會被清空,因為是借用的;而每次user線程切換kernel的時候,還會增加一個計數值,用于表示該user線程的位址空間被借用了。
4. switch_to的三個參數
switch_to
的工作主要是切換核心棧,它的具體實作就不在這裡分析。
不同的體系架構下也不一樣,例如,X86的實作主要使用将目前寄存器的一些值壓到prev的核心棧,将核心棧頂指針儲存到每個程序相關聯的
thread_info
,然後切換到next的核心棧,并出棧,将其棧中内容填充到寄存器,以恢複現場。
switch_to
三個參數,其中兩個prev的考慮:
設想場景如下:a切換到b,b切換到c,c切換到a。
a壓棧時内容:prev為a,next為b;
b壓棧時内容:prev為b,next為c;
c壓棧時内容:prev為c,next為a。
c切換a,出棧之後呢?
prev為a,next為b
可以看到完全沒有c的事了。我們必須得留下c存在過的痕迹。
故這裡使用三個參數,其中兩個prev,用來留下最新的prev的痕迹。
5. finish_task_switch與context_switch的關聯
為什麼一定要留下最新的prev的痕迹呢?你有沒有想過?
finish_task_switch
的參數就是prev,就是因為它使用到了prev,是以才得留下它。
finsih_task_switch
其實涉及到的東西蠻多,計算vtime,perf追蹤點等等。
但就在程序排程過程中,還有一個細節沒有處理,還記得是什麼嗎,參見
finish_task_switch
的部分代碼:
struct mm_struct *mm = rq->prev_mm;
......;
if (mm) {
membarrier_mm_sync_core_before_usermode(mm);
mmdrop(mm);
}
如果從user到kernel,那麼得給借用的
mm
增加一個計數;
但是什麼時候減去呢?
一旦從kernel到user,
rq
就記錄下prev使用的
active_mm
,在
finish_task_switch
中減去這個計數。
有道詞典
static void __s ...
詳細X
靜态孔隙__sched notrace __schedule (bool搶占)