- 論壇徽章:
- 0
|
總結了篇關于cfs調度器的文檔
喜歡pdf的可以查看檔案~
CFS 調度器
--wxc200
大家好哈,
兄弟最近在學習調度器,有點兒心得,更多得是迷惑,寫出心得來與大家分享,貼出迷惑來請大家解答。呵呵
linux自2.6.23后引入了一種新的調度器,叫'Completely Fair Scheduler'(wiki).是由Ingo Molnar在很短的時間內寫的。他與的cfs代碼是對之前另一種調度器 "O(1) scheduler" 的替換.
先扯一下o(1)調度.
先看wiki的解釋:
"An O(1) scheduler is a kernel scheduling design that can schedule processes within a constant amount of time, regardless of how many processes are running on the operating system (OS)."
Understanding Linux Kernel(3th) 這本書chapter 7講的進程調度,7.2節(jié)提到"Scheduling Algorithm",說2.6 對進程的選擇是constant time,兼顧了批處理和交互式進程.進程分類,實時進程(又分為SCHED_FIFL AND SCHED_RR)和普通進程.
最主要的問題是這些進程怎么組織的,簡單看下結構:
沒辦法傳圖片,請參照附件"數組".
它有兩個數 組,active里面是has time-slice 的,expired數組是out-of-slice。簡單的說,一個普通的進程被調度運行后,放在active里,當其時間片用光后,可能就要移到 expired數組里了。說“可能”是因為有的進程就不移走。比如交互式進程。
說白了,就是把所有的process按照優(yōu)先級掛在鏈表上,從里面按照優(yōu)先級高低選擇第一個不為空的進程運行.
普通進程的優(yōu)先級是100~139,實時進程要更低,這符合調度的算法.
我有點等不及了,咱們直接奔cfs吧~
另外有點就是,引入hrtimer之后,進程調度還是在tick中斷完成的.每個tick都會檢查進程是否應該調度,當然,主動讓cpu(即調用scheduler().)的就不算了吧...
hrtimer那部分東東等咱們聊完了cfs再說,那個主要是在原有的時間管理layer上新添加了“時間事件”,把時間中斷以事件的方式注冊。精確度由之前的hz提升到了ns(需要硬件支持)。。。
cfs
Documentation/scheduler/sched-design-CFS.txt 介紹了cfs相關東西,也可以在線看.
我按照我的理解來“添油加醋”地翻譯下。
1 概括
“80% of CFS's design can be summed up in a single sentence: CFS basically models
an "ideal, precise multi-tasking CPU" on real hardware.”
""Ideal multi-tasking CPU" is a (non-existent ) CPU that has 100% physical
power and which can run each task at precise equal speed, in parallel, each at
1/nr_running speed. For example: if there are 2 tasks running, then it runs
each at 50% physical power --- i.e., actually in parallel.
"
模 擬了個理想的,多任務cpu.假如有n個進程,那么每個進程 的執(zhí)行速度是1/n,,即所有的任務都是并行執(zhí)行的。我認為就算是有并行執(zhí)行的cpu,速度也不應該完全平均,按照優(yōu)先級再分比較劃算。比如2個進 程,a,b,a 的優(yōu)先級是b的兩 倍,那么就照著速度比a:b = 2:1唄~
“On real hardware, we can run only a single task at once, so we have to
introduce the concept of "virtual runtime." The virtual runtime of a task
specifies when its next timeslice would start execution on the ideal
multi-tasking CPU described above. In practice, the virtual runtime of a task
is its actual runtime normalized to the total number of running tasks. ”
當然沒有這種cpu,故引進了個新概念"virtual runtime",這個概念真是折磨人好久。我曾經在clf上發(fā)帖子問過,有個兄弟回復還是不錯的。后面看過代碼后,我的理解是:1) 紅黑樹結點排列的值 2) 每次task run time轉換的一個值 .
先看上文顏色部分,我真是很難翻譯它~ 照字面理解,是實際運行時間與任務數量的一個比值? 我舉個例子來說明它是怎么計算 的吧。。
在 __update_curr()這個函數里,會更新當前任務的運行時間信息。假如一個任務a自上次調度到這次調度時間間隔為delta,那么它的 vrumtime的增量是按照這個公式:delta * NICE_0_LOAD / a->se.load。假如把NICE_0_LOAD / a->se.load作為一個比值p的話,我們可以這么描述p的變化:優(yōu)先級越高,p越小。這個load是與nice值 相關的,有一個靜態(tài)的數組,我后面在代碼里會介紹。
所以,一堆進行都運行了一段時間delta,那么它們的vrumtime就遵循上面的公式。很明顯,優(yōu)先級最大的進程vrumtime增量最小。。。
2 操作細節(jié)
cfs就是通過追蹤這個vruntime來進行任務調度的。它總是選 vruntime最小的進程來運行。
3 紅黑樹。
紅黑樹這個數據結構在內核里用得還不少嘛,不過俺不太懂。哪位兄弟給掃掃盲? hrtimer里也用到了red-black-tree。這里把進程的vruntime用rb-tree來存儲。
4 一些feature
cfs has no time-slice concept.o(1)有的,cfs沒有“明顯”得用,它偷偷摸摸地用。呵呵 翻譯完這幾段咱再說這個。文檔里面那個接口估計是用來調整“最小粒度”的。用于“桌面”和“服務器“兩 種不同的調度。后者可能不太希望頻繁的調度,浪費時間。前者則希望面面俱到--”不患寡婦而患不均也“
5 調度policy
SCHED_FIFO/_RR are implemented in sched_rt.c and are as specified by POSIX. 實時進程調度
實時調度和普通進程調度。
6 調度的類。
調度哭可以模塊化注冊,所以你可以選擇實時調度或者是cfs調度。不錯!
sched_fair.c文件實現了cfs調度的所以classes
7 組調度。
不光是普通進程,組調度也實現了。。這個是后來一個patch加的。
×××××××××××××××××××××××××××××××××××
上面 是對著內核的一篇文檔,簡要地說了幾部分 。。。在正式進行我們的hack之前,先嘮叨幾句,算是個總結和感性的認識吧~吼吼
關于實時進程的調度,這一次先不分析,它和o1差不多,還保持著優(yōu)先級數組的做法,那是”上流社會“玩兒的游戲,后面再折騰它!逼胀ù蟊姟皞兺鎯旱木褪莄fs了。
cfs調度,我寫兩 部分,一部分是普通進程的調度,沒有組的概念。一部分是組的調度。我個人覺得組得調度比較難理解~ 這幾天一直在思考。。。今天下午好像豁然開朗了。。。畫了個草圖,到后面我做出這張圖來,大家看看對不對 
咱們先說普通進程的調度
關于普通進程的組織,應該有這么一種印象。
有一個隊列,叫cfs_rq,里面維護了個紅黑樹。每一個task_struct has a se,稱為調度實體。它就代表了一個被調度的進程,里面有此進程的一些信息,比如前面提到的vruntime.
一個進程創(chuàng)建的時候,就會被放置在這個紅黑樹里。它自己的位置是由它的vruntime值 來決定的。在每個tick中斷的時候,會更新進程的信息,并檢查這個紅黑樹,判斷某些進程是不是要被替換掉。
現在我們來想象下面一個例程。
進 程a被創(chuàng)建了,sched_fork()會做一些新創(chuàng)建進程調度方面的初始化。然后應該嘗試把此進程放到隊列里啊,讓它被調度。 set_task_cpu()做了這部分工作。然后這個進程如果不是實時進程,就讓它跟cfs的類綁定在一塊兒,這樣下面用到調度的一些方法,就會到 cfs相關的類去找嘍~ 這時候如果搶占打開了,會去調度一次,以讓新創(chuàng)建的進程有機會被調度一次;蛘咴谙乱粋tick的時候,會檢查已經紅黑樹,以確認這個進程是不是調度。(注:上面紅色這句有點胡扯的嫌疑,請明白人指點)
比如在每個tick中斷的時候,會去紅黑樹里面找vruntime最小的那個(紅黑樹最左邊的葉子)去調度。那么這個調度過程,所有用到的方法,就是上面提到的cfs的類給實現的。
最后,大家再對rb-tree里面的任務結點有個感性的認識吧:
優(yōu)先級高的進程,總是靠左,以有最大調度機會。比如說有n個進程,那么在一段時間內,應該把n個進程都運行一遍。這兒有兩三個問題,一段時間是多久?每個進程有多少時間去運行呢?每個進程分到的運行時間跟vruntime有什么關系呢?
一段時間,與運行著的進程數目有關。進程越多,時間越長。每個進程并不是平均分配這些時間的。按照我的理解,分到這個時間越多的進程,后面vruntime增長得越慢。
上面 這幾句話,我可是暈了近一個星期才明白的。不知道跟大家的理解一致么?
本 來我錯誤得理解是這樣的,完全公平調度么,當然所有的進程有同樣的運行時間。如果是這樣,那么rb-tree就沒用了。因為每個結點都一樣的值。所以,請 大家有這樣一種概念:兩 個進程,a優(yōu)先級特別低,b特高。假如現在有n個正被調度的進程,它們都被調度一遍的時間是delta,那么b會占很高的時間,a 很低的時間。運行完成后,b還是在很靠左的地方呆著,而a一定往最靠右的地方。。。。哎,表述不清啊,,最好畫個圖。。。這樣就為了讓b得到更常的運行時 間。。。而其vruntime仍然很。ǹ梢詤⒄丈戏忄]件里面那個計算vruntime的公式)
好了
我們開始真正的代碼旅行吧。
1 幾個重要的數據結構
1)task_struct : 大家都知道這個。
struct task_struct {
...
int prio, static_prio, normal_prio; 進程的優(yōu)先級
unsigned int rt_priority;實時進程的優(yōu)先級
const struct sched_class *sched_class; 調度類,一堆函數指針,實現調度
struct sched_entity se;調度實體 一個進程對應一個調度實體,,
struct sched_rt_entity rt;
....
}
2) sched_class 調度相關的函數指針。
struct sched_class {
...
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup); 入列
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);出列
void (*yield_task) (struct rq *rq);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int sync);檢查當前進程可否被新進程搶占
struct task_struct * (*pick_next_task) (struct rq *rq); 選擇下一個進程運行
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
#ifdef CONFIG_SMP
int (*select_task_rq)(struct task_struct *p, int sync);
....
#ifdef CONFIG_FAIR_GROUP_SCHED 請格外留意此宏。。。。跟組調度相關的,,暫時不管它,后面跟組相關的調度再回來看它。
void (*moved_group) (struct task_struct *p);
#endif
};
3) 調度實體
struct sched_entity {
struct load_weight load; /* for load-balancing */ nice對應的load值
struct rb_node run_node; 紅黑樹結點
struct list_head group_node;
unsigned int on_rq; 這個是什么呢?不知道
u64 exec_start; 上次開始調度時的運行時間
u64 sum_exec_runtime; 總運行時間
u64 vruntime;呵呵 都知道了
u64 prev_sum_exec_runtime; 上次調度總運行時間
...
#ifdef CONFIG_FAIR_GROUP_SCHED 如果是組調度的話,就多了些部分。
struct sched_entity *parent;
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
#endif
}
4)cfs 運行隊列
/* CFS-related fields in a runqueue */
struct cfs_rq {
struct load_weight load;
unsigned long nr_running;
u64 exec_clock;
u64 min_vruntime;
5)大boss,運行隊列
struct rq {
,...
unsigned long nr_running;
#define CPU_LOAD_IDX_MAX 5
unsigned long cpu_load[CPU_LOAD_IDX_MAX];
...
struct cfs_rq cfs;
..
struct task_struct *curr, *idle;
}
6)調度相關類
/*
* All the scheduling class methods:
*/
static const struct sched_class fair_sched_class = {
.next = &idle_sched_class,
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.check_preempt_curr = check_preempt_wakeup,
.pick_next_task = pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_fair,
.load_balance = load_balance_fair,
.move_one_task = move_one_task_fair,
#endif
.set_curr_task = set_curr_task_fair,
.task_tick = task_tick_fair,
.task_new = task_new_fair,
.prio_changed = prio_changed_fair,
.switched_to = switched_to_fair,
#ifdef CONFIG_FAIR_GROUP_SCHED
.moved_group = moved_group_fair,
#endif
};
2 吃代碼
上面幾個結構體的關系還是很好理解的,它們的關系我本來想畫個圖表示下的,覺得有點麻煩,何況也不難,我就不畫了。。直接進代碼了哈~
在進行之前呢,先介紹兩 篇文章,是一個網友寫的。之前和他聊cfs,后來我迷糊了,他沒迷糊,就寫了這篇文章。。。還不錯。
Linux進程管理之CFS調度器分析
Linux進程管理之CFS組調度分析
大家的理解差不多,他寫得很條理,為了防止雷同,雷著大家了,我不會引用它里面的文字的。我就在邊讀代碼的過程中邊把自己的體驗和疑惑貼出來,大家一同討論吧~ 錯誤,一定要指出我的錯誤啊~呵呵
咱們先從新創(chuàng)建的一個進程說起吧。
1) kernel/fork.c里,fork routine,do_fork() will create a new process,if its state is running,it will
call wake_up_new_task() to wake up the newly created process FOR THE FIRST TIME.
do_fork(){
...
if (unlikely(clone_flags & CLONE_STOPPED)) {
...
} else {
wake_up_new_task(p, clone_flags);
}
...
}
2) 這個函數做什么呢?
void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
{
unsigned long flags;
struct rq *rq;
rq = task_rq_lock(p, &flags);順序操作運行隊列
BUG_ON(p->state != TASK_RUNNING);
update_rq_clock(rq);
p->prio = effective_prio(p); 計算priority,,普通進程的priority就是static priority
if (!p->sched_class->task_new || !current->se.on_rq) {如果條件不滿足,直接入列,但請注意active_task()的最后參數0,不喚醒
activate_task(rq, p, 0);
} else {
/*
* Let the scheduling class do new task startup
* management (if any):
*/
p->sched_class->task_new(rq, p);調用完全公平類里面的task_new做些初始化的操作。
inc_nr_running(rq);增加運行隊列的運行進程數目
}
trace_sched_wakeup_new(rq, p);
check_preempt_curr(rq, p, 0); 可不可以搶占當前進程?
#ifdef CONFIG_SMP
if (p->sched_class->task_wake_up)
p->sched_class->task_wake_up(rq, p);
#endif
task_rq_unlock(rq, &flags);
}
3) 先看task_new()吧,它會掉到fair_sched_class類里的task_new_fair.
static void task_new_fair(struct rq *rq, struct task_struct *p)
{
struct cfs_rq *cfs_rq = task_cfs_rq(p);
struct sched_entity *se = &p->se, *curr = cfs_rq->curr;
int this_cpu = smp_processor_id();
sched_info_queued(p);
update_curr(cfs_rq); 更新cfs的一些信息
place_entity(cfs_rq, se, 1);初始化se在cfs里的信息,包括vruntime
/* 'curr' will be NULL if the child belongs to a different group */
if (sysctl_sched_child_runs_first && this_cpu == task_cpu(p) &&
curr && curr->vruntime < se->vruntime) {
/*
* Upon rescheduling, sched_class::put_prev_task() will place
* 'current' within the tree based on its new key value.
*/
swap(curr->vruntime, se->vruntime);
resched_task(rq->curr);
}
enqueue_task_fair(rq, p, 0); 放進隊列里面
}
4) 我們先看update_curr()吧
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_of(cfs_rq)->clock;
unsigned long delta_exec;
if (unlikely(!curr))
return;
/*
* Get the amount of time the current task was running
* since the last time we changed load (this cannot
* overflow on 32 bits):
*/
delta_exec = (unsigned long)(now - curr->exec_start);計算自上次調度此進程到這次又調度,經過的時間
這個時間比較鬼異,是多久呢?假如在運行隊列里的n個進程之一的a,再一次被調度到時,這個delta_exec是多大呢? 如果中途有進程退出或睡眠,
那么運行隊列會動態(tài)更新的,所以這個delta_exec的變化曲線是什么樣子的難說。
__update_curr(cfs_rq, curr, delta_exec); 把剛計算出來的運行時間作為參數傳進去,它做什么事情呢?
curr->exec_start = now;呵呵,更新完了立刻打個時間戳。
if (entity_is_task(curr)) { 這個條件比較有趣,我喜歡。跟過去發(fā)現是這樣定義的:
"/* An entity is a task if it doesn't "own" a runqueue */
#define entity_is_task(se) (!se->my_q) " 如果是組調度,調度實體可能代表一個組,不單單是一個task。就是看看有沒有my_q這個指針了,
即有沒有它control的cfs_rq
如果是task,條件滿足,
struct task_struct *curtask = task_of(curr);
cpuacct_charge(curtask, delta_exec);
account_group_exec_runtime(curtask, delta_exec); 這兩 個不管了,看不懂
}
} |
-
-
cfs.pdf
2009-03-02 14:53 上傳
點擊文件名下載附件
712.74 KB, 下載次數: 1227
評分
-
查看全部評分
|