亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費(fèi)注冊(cè) 查看新帖 |

Chinaunix

  平臺(tái) 論壇 博客 文庫
12下一頁
最近訪問板塊 發(fā)新帖
查看: 24354 | 回復(fù): 17
打印 上一主題 下一主題

完全公平調(diào)度(CFS) [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報(bào)告]
發(fā)表于 2009-03-02 14:52 |只看該作者 |倒序?yàn)g覽
總結(jié)了篇關(guān)于cfs調(diào)度器的文檔

喜歡pdf的可以查看檔案~


        CFS 調(diào)度器
                                                                                                                                             --wxc200
大家好哈,
兄弟最近在學(xué)習(xí)調(diào)度器,有點(diǎn)兒心得,更多得是迷惑,寫出心得來與大家分享,貼出迷惑來請(qǐng)大家解答。呵呵  
 linux自2.6.23后引入了一種新的調(diào)度器,叫'Completely Fair Scheduler'(wiki).是由Ingo Molnar在很短的時(shí)間內(nèi)寫的。他與的cfs代碼是對(duì)之前另一種調(diào)度器  "O(1) scheduler" 的替換.
先扯一下o(1)調(diào)度.
先看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講的進(jìn)程調(diào)度,7.2節(jié)提到"Scheduling Algorithm",說2.6 對(duì)進(jìn)程的選擇是constant time,兼顧了批處理和交互式進(jìn)程.進(jìn)程分類,實(shí)時(shí)進(jìn)程(又分為SCHED_FIFL AND SCHED_RR)和普通進(jìn)程.
最主要的問題是這些進(jìn)程怎么組織的,簡單看下結(jié)構(gòu):
沒辦法傳圖片,請(qǐng)參照附件"數(shù)組".

它有兩個(gè)數(shù) 組,active里面是has time-slice 的,expired數(shù)組是out-of-slice。簡單的說,一個(gè)普通的進(jìn)程被調(diào)度運(yùn)行后,放在active里,當(dāng)其時(shí)間片用光后,可能就要移到 expired數(shù)組里了。說“可能”是因?yàn)橛械倪M(jìn)程就不移走。比如交互式進(jìn)程。
說白了,就是把所有的process按照優(yōu)先級(jí)掛在鏈表上,從里面按照優(yōu)先級(jí)高低選擇第一個(gè)不為空的進(jìn)程運(yùn)行.
普通進(jìn)程的優(yōu)先級(jí)是100~139,實(shí)時(shí)進(jìn)程要更低,這符合調(diào)度的算法.
我有點(diǎn)等不及了,咱們直接奔cfs吧~
另外有點(diǎn)就是,引入hrtimer之后,進(jìn)程調(diào)度還是在tick中斷完成的.每個(gè)tick都會(huì)檢查進(jìn)程是否應(yīng)該調(diào)度,當(dāng)然,主動(dòng)讓cpu(即調(diào)用scheduler().)的就不算了吧...

hrtimer那部分東東等咱們聊完了cfs再說,那個(gè)主要是在原有的時(shí)間管理layer上新添加了“時(shí)間事件”,把時(shí)間中斷以事件的方式注冊(cè)。精確度由之前的hz提升到了ns(需要硬件支持)。。。
cfs
Documentation/scheduler/sched-design-CFS.txt 介紹了cfs相關(guān)東西,也可以在線看. 
我按照我的理解來“添油加醋”地翻譯下。
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.
"
模 擬了個(gè)理想的,多任務(wù)cpu.假如有n個(gè)進(jìn)程,那么每個(gè)進(jìn)程 的執(zhí)行速度是1/n,,即所有的任務(wù)都是并行執(zhí)行的。我認(rèn)為就算是有并行執(zhí)行的cpu,速度也不應(yīng)該完全平均,按照優(yōu)先級(jí)再分比較劃算。比如2個(gè)進(jìn) 程,a,b,a 的優(yōu)先級(jí)是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. ”
當(dāng)然沒有這種cpu,故引進(jìn)了個(gè)新概念"virtual runtime",這個(gè)概念真是折磨人好久。我曾經(jīng)在clf上發(fā)帖子問過,有個(gè)兄弟回復(fù)還是不錯(cuò)的。后面看過代碼后,我的理解是:1) 紅黑樹結(jié)點(diǎn)排列的值  2) 每次task run time轉(zhuǎn)換的一個(gè)值  .
先看上文顏色部分,我真是很難翻譯它~  照字面理解,是實(shí)際運(yùn)行時(shí)間與任務(wù)數(shù)量的一個(gè)比值? 我舉個(gè)例子來說明它是怎么計(jì)算 的吧。。
在 __update_curr()這個(gè)函數(shù)里,會(huì)更新當(dāng)前任務(wù)的運(yùn)行時(shí)間信息。假如一個(gè)任務(wù)a自上次調(diào)度到這次調(diào)度時(shí)間間隔為delta,那么它的 vrumtime的增量是按照這個(gè)公式:delta * NICE_0_LOAD / a->se.load。假如把NICE_0_LOAD / a->se.load作為一個(gè)比值p的話,我們可以這么描述p的變化:優(yōu)先級(jí)越高,p越小。這個(gè)load是與nice值 相關(guān)的,有一個(gè)靜態(tài)的數(shù)組,我后面在代碼里會(huì)介紹。
所以,一堆進(jìn)行都運(yùn)行了一段時(shí)間delta,那么它們的vrumtime就遵循上面的公式。很明顯,優(yōu)先級(jí)最大的進(jìn)程vrumtime增量最小。。。
2 操作細(xì)節(jié)
cfs就是通過追蹤這個(gè)vruntime來進(jìn)行任務(wù)調(diào)度的。它總是選 vruntime最小的進(jìn)程來運(yùn)行。
3 紅黑樹。
紅黑樹這個(gè)數(shù)據(jù)結(jié)構(gòu)在內(nèi)核里用得還不少嘛,不過俺不太懂。哪位兄弟給掃掃盲?  hrtimer里也用到了red-black-tree。這里把進(jìn)程的vruntime用rb-tree來存儲(chǔ)。
4  一些feature
cfs has no time-slice concept.o(1)有的,cfs沒有“明顯”得用,它偷偷摸摸地用。呵呵  翻譯完這幾段咱再說這個(gè)。文檔里面那個(gè)接口估計(jì)是用來調(diào)整“最小粒度”的。用于“桌面”和“服務(wù)器“兩 種不同的調(diào)度。后者可能不太希望頻繁的調(diào)度,浪費(fèi)時(shí)間。前者則希望面面俱到--”不患寡婦而患不均也“
5 調(diào)度policy
SCHED_FIFO/_RR are implemented in sched_rt.c and are as specified by POSIX.  實(shí)時(shí)進(jìn)程調(diào)度
實(shí)時(shí)調(diào)度和普通進(jìn)程調(diào)度。
6 調(diào)度的類。
調(diào)度哭可以模塊化注冊(cè),所以你可以選擇實(shí)時(shí)調(diào)度或者是cfs調(diào)度。不錯(cuò)!
sched_fair.c文件實(shí)現(xiàn)了cfs調(diào)度的所以classes
7 組調(diào)度。
不光是普通進(jìn)程,組調(diào)度也實(shí)現(xiàn)了。。這個(gè)是后來一個(gè)patch加的。
×××××××××××××××××××××××××××××××××××
上面 是對(duì)著內(nèi)核的一篇文檔,簡要地說了幾部分 。。。在正式進(jìn)行我們的hack之前,先嘮叨幾句,算是個(gè)總結(jié)和感性的認(rèn)識(shí)吧~吼吼

關(guān)于實(shí)時(shí)進(jìn)程的調(diào)度,這一次先不分析,它和o1差不多,還保持著優(yōu)先級(jí)數(shù)組的做法,那是”上流社會(huì)“玩兒的游戲,后面再折騰它。”普通大眾“們玩兒的就是cfs了。
cfs調(diào)度,我寫兩 部分,一部分是普通進(jìn)程的調(diào)度,沒有組的概念。一部分是組的調(diào)度。我個(gè)人覺得組得調(diào)度比較難理解~  這幾天一直在思考。。。今天下午好像豁然開朗了。。。畫了個(gè)草圖,到后面我做出這張圖來,大家看看對(duì)不對(duì)  
咱們先說普通進(jìn)程的調(diào)度
關(guān)于普通進(jìn)程的組織,應(yīng)該有這么一種印象。
有一個(gè)隊(duì)列,叫cfs_rq,里面維護(hù)了個(gè)紅黑樹。每一個(gè)task_struct has a se,稱為調(diào)度實(shí)體。它就代表了一個(gè)被調(diào)度的進(jìn)程,里面有此進(jìn)程的一些信息,比如前面提到的vruntime.
一個(gè)進(jìn)程創(chuàng)建的時(shí)候,就會(huì)被放置在這個(gè)紅黑樹里。它自己的位置是由它的vruntime值 來決定的。在每個(gè)tick中斷的時(shí)候,會(huì)更新進(jìn)程的信息,并檢查這個(gè)紅黑樹,判斷某些進(jìn)程是不是要被替換掉。
現(xiàn)在我們來想象下面一個(gè)例程。
進(jìn) 程a被創(chuàng)建了,sched_fork()會(huì)做一些新創(chuàng)建進(jìn)程調(diào)度方面的初始化。然后應(yīng)該嘗試把此進(jìn)程放到隊(duì)列里啊,讓它被調(diào)度。 set_task_cpu()做了這部分工作。然后這個(gè)進(jìn)程如果不是實(shí)時(shí)進(jìn)程,就讓它跟cfs的類綁定在一塊兒,這樣下面用到調(diào)度的一些方法,就會(huì)到 cfs相關(guān)的類去找嘍~ 這時(shí)候如果搶占打開了,會(huì)去調(diào)度一次,以讓新創(chuàng)建的進(jìn)程有機(jī)會(huì)被調(diào)度一次;蛘咴谙乱粋(gè)tick的時(shí)候,會(huì)檢查已經(jīng)紅黑樹,以確認(rèn)這個(gè)進(jìn)程是不是調(diào)度。(注:上面紅色這句有點(diǎn)胡扯的嫌疑,請(qǐng)明白人指點(diǎn))  
比如在每個(gè)tick中斷的時(shí)候,會(huì)去紅黑樹里面找vruntime最小的那個(gè)(紅黑樹最左邊的葉子)去調(diào)度。那么這個(gè)調(diào)度過程,所有用到的方法,就是上面提到的cfs的類給實(shí)現(xiàn)的。
最后,大家再對(duì)rb-tree里面的任務(wù)結(jié)點(diǎn)有個(gè)感性的認(rèn)識(shí)吧:
優(yōu)先級(jí)高的進(jìn)程,總是靠左,以有最大調(diào)度機(jī)會(huì)。比如說有n個(gè)進(jìn)程,那么在一段時(shí)間內(nèi),應(yīng)該把n個(gè)進(jìn)程都運(yùn)行一遍。這兒有兩三個(gè)問題,一段時(shí)間是多久?每個(gè)進(jìn)程有多少時(shí)間去運(yùn)行呢?每個(gè)進(jìn)程分到的運(yùn)行時(shí)間跟vruntime有什么關(guān)系呢?
一段時(shí)間,與運(yùn)行著的進(jìn)程數(shù)目有關(guān)。進(jìn)程越多,時(shí)間越長。每個(gè)進(jìn)程并不是平均分配這些時(shí)間的。按照我的理解,分到這個(gè)時(shí)間越多的進(jìn)程,后面vruntime增長得越慢。
上面 這幾句話,我可是暈了近一個(gè)星期才明白的。不知道跟大家的理解一致么?
本 來我錯(cuò)誤得理解是這樣的,完全公平調(diào)度么,當(dāng)然所有的進(jìn)程有同樣的運(yùn)行時(shí)間。如果是這樣,那么rb-tree就沒用了。因?yàn)槊總(gè)結(jié)點(diǎn)都一樣的值。所以,請(qǐng) 大家有這樣一種概念:兩 個(gè)進(jìn)程,a優(yōu)先級(jí)特別低,b特高。假如現(xiàn)在有n個(gè)正被調(diào)度的進(jìn)程,它們都被調(diào)度一遍的時(shí)間是delta,那么b會(huì)占很高的時(shí)間,a 很低的時(shí)間。運(yùn)行完成后,b還是在很靠左的地方呆著,而a一定往最靠右的地方。。。。哎,表述不清啊,,最好畫個(gè)圖。。。這樣就為了讓b得到更常的運(yùn)行時(shí) 間。。。而其vruntime仍然很。ǹ梢詤⒄丈戏忄]件里面那個(gè)計(jì)算vruntime的公式)
好了
我們開始真正的代碼旅行吧。


1   幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu)
1)task_struct : 大家都知道這個(gè)。

struct task_struct {
 ...

 int prio, static_prio, normal_prio; 進(jìn)程的優(yōu)先級(jí)
 unsigned int rt_priority;實(shí)時(shí)進(jìn)程的優(yōu)先級(jí)
 const struct sched_class *sched_class; 調(diào)度類,一堆函數(shù)指針,實(shí)現(xiàn)調(diào)度
 struct sched_entity se;調(diào)度實(shí)體 一個(gè)進(jìn)程對(duì)應(yīng)一個(gè)調(diào)度實(shí)體,,
 struct sched_rt_entity rt;
....
}
2) sched_class 調(diào)度相關(guān)的函數(shù)指針。
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);檢查當(dāng)前進(jìn)程可否被新進(jìn)程搶占

 struct task_struct * (*pick_next_task) (struct rq *rq); 選擇下一個(gè)進(jìn)程運(yùn)行
 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 請(qǐng)格外留意此宏。。。。跟組調(diào)度相關(guān)的,,暫時(shí)不管它,后面跟組相關(guān)的調(diào)度再回來看它。
 void (*moved_group) (struct task_struct *p);
#endif
};

3) 調(diào)度實(shí)體
struct sched_entity {
 struct load_weight load; /* for load-balancing */ nice對(duì)應(yīng)的load值 
 struct rb_node run_node; 紅黑樹結(jié)點(diǎn)
 struct list_head group_node;
 unsigned int on_rq; 這個(gè)是什么呢?不知道

 u64 exec_start; 上次開始調(diào)度時(shí)的運(yùn)行時(shí)間
 u64 sum_exec_runtime; 總運(yùn)行時(shí)間
 u64 vruntime;呵呵 都知道了
 u64 prev_sum_exec_runtime; 上次調(diào)度總運(yùn)行時(shí)間
...
#ifdef CONFIG_FAIR_GROUP_SCHED 如果是組調(diào)度的話,就多了些部分。
 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 運(yùn)行隊(duì)列
/* 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,運(yùn)行隊(duì)列
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)調(diào)度相關(guān)類
/*
 * 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  吃代碼
上面幾個(gè)結(jié)構(gòu)體的關(guān)系還是很好理解的,它們的關(guān)系我本來想畫個(gè)圖表示下的,覺得有點(diǎn)麻煩,何況也不難,我就不畫了。。直接進(jìn)代碼了哈~
在進(jìn)行之前呢,先介紹兩 篇文章,是一個(gè)網(wǎng)友寫的。之前和他聊cfs,后來我迷糊了,他沒迷糊,就寫了這篇文章。。。還不錯(cuò)。
Linux進(jìn)程管理之CFS調(diào)度器分析
Linux進(jìn)程管理之CFS組調(diào)度分析

大家的理解差不多,他寫得很條理,為了防止雷同,雷著大家了,我不會(huì)引用它里面的文字的。我就在邊讀代碼的過程中邊把自己的體驗(yàn)和疑惑貼出來,大家一同討論吧~   錯(cuò)誤,一定要指出我的錯(cuò)誤啊~呵呵

咱們先從新創(chuàng)建的一個(gè)進(jìn)程說起吧。
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)  這個(gè)函數(shù)做什么呢?
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);順序操作運(yùn)行隊(duì)列
    BUG_ON(p->state != TASK_RUNNING);
    update_rq_clock(rq);

    p->prio = effective_prio(p);   計(jì)算priority,,普通進(jìn)程的priority就是static priority

    if (!p->sched_class->task_new || !current->se.on_rq) {如果條件不滿足,直接入列,但請(qǐng)注意active_task()的最后參數(shù)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);調(diào)用完全公平類里面的task_new做些初始化的操作。
        inc_nr_running(rq);增加運(yùn)行隊(duì)列的運(yùn)行進(jìn)程數(shù)目
    }
    trace_sched_wakeup_new(rq, p);
    check_preempt_curr(rq, p, 0); 可不可以搶占當(dāng)前進(jìn)程?
#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()吧,它會(huì)掉到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);  放進(jìn)隊(duì)列里面
}
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);計(jì)算自上次調(diào)度此進(jìn)程到這次又調(diào)度,經(jīng)過的時(shí)間
  這個(gè)時(shí)間比較鬼異,是多久呢?假如在運(yùn)行隊(duì)列里的n個(gè)進(jìn)程之一的a,再一次被調(diào)度到時(shí),這個(gè)delta_exec是多大呢?  如果中途有進(jìn)程退出或睡眠,
那么運(yùn)行隊(duì)列會(huì)動(dòng)態(tài)更新的,所以這個(gè)delta_exec的變化曲線是什么樣子的難說。

    __update_curr(cfs_rq, curr, delta_exec);   把剛計(jì)算出來的運(yùn)行時(shí)間作為參數(shù)傳進(jìn)去,它做什么事情呢?
    curr->exec_start = now;呵呵,更新完了立刻打個(gè)時(shí)間戳。

    if (entity_is_task(curr)) {  這個(gè)條件比較有趣,我喜歡。跟過去發(fā)現(xiàn)是這樣定義的:
"/* An entity is a task if it doesn't "own" a runqueue */
#define entity_is_task(se)    (!se->my_q)   "  如果是組調(diào)度,調(diào)度實(shí)體可能代表一個(gè)組,不單單是一個(gè)task。就是看看有沒有my_q這個(gè)指針了,
即有沒有它c(diǎn)ontrol的cfs_rq
如果是task,條件滿足,


        struct task_struct *curtask = task_of(curr);

        cpuacct_charge(curtask, delta_exec);
        account_group_exec_runtime(curtask, delta_exec);  這兩 個(gè)不管了,看不懂
    }
}

cfs.pdf

712.74 KB, 下載次數(shù): 1227

評(píng)分

參與人數(shù) 1可用積分 +15 收起 理由
dreamice + 15 精品文章,感謝分享

查看全部評(píng)分

論壇徽章:
0
2 [報(bào)告]
發(fā)表于 2009-03-02 14:54 |只看該作者

回復(fù) #1 wxc200 的帖子

6)靜悄悄地過來,看看這個(gè)經(jīng)常調(diào)用的函數(shù),到底做了啥捏?
static inline void
__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
          unsigned long delta_exec)  傳進(jìn)來的執(zhí)行時(shí)間
{
    unsigned long delta_exec_weighted;

    schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));

    curr->sum_exec_runtime += delta_exec;  直接加到總運(yùn)行時(shí)間變量里去,
    schedstat_add(cfs_rq, exec_clock, delta_exec);
    delta_exec_weighted = calc_delta_fair(delta_exec, curr);  這個(gè)很重要。。。這個(gè)函數(shù)名字叫”公平計(jì)算delta",咋公平捏?第七步分析會(huì)看到
    curr->vruntime += delta_exec_weighted;  把上步計(jì)算出來的值加到了vruntime里
    update_min_vruntime(cfs_rq);
}

7)  看看怎么計(jì)算這個(gè)delta_exec_weighted的?
/*
 * delta /= w
 */
static inline unsigned long
calc_delta_fair(unsigned long delta, struct sched_entity *se)
{
    if (unlikely(se->load.weight != NICE_0_LOAD))  如果進(jìn)程實(shí)體有默認(rèn)的load值,直接返回delta
        delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);計(jì)算應(yīng)該修正的這個(gè)值
 
    return delta;
}
在往下走之前,先弄明白nice 和 se->load之間的關(guān)系吧。
nice 都知道,se->load是指調(diào)度實(shí)體的負(fù)載。同理一個(gè)cfs_rq也有負(fù)載。。一般是所有task的load之和。這個(gè)load是通過nice與 一個(gè)靜態(tài)數(shù)組轉(zhuǎn)換來的。一般普通進(jìn)程的nice值在-20~19之間,其對(duì)應(yīng)load.weight值是遞減的,具體可參見那個(gè)靜態(tài)數(shù)組。而 NICE_0_LOAD就是nice=0的對(duì)應(yīng)的load值。
好了,這相當(dāng)于是做一個(gè)修正嘍。假如現(xiàn)在傳進(jìn)來的運(yùn)行時(shí)間是10,那么,這個(gè)調(diào)度實(shí)體應(yīng)該返回的delta是多少呢?看這個(gè)函數(shù)的comment就明白了,delta * NICE_0_LOAD/se->load,是一個(gè)比重。。。之前這個(gè)地方說過了,呵呵。

那么,調(diào)度實(shí)體的優(yōu)先級(jí)越高,其得出來的值越小。
反回到6)中,可以知道vruntime是怎么update的了。那么,它是如何初始化的呢?后面有,再說,是通過vslice()計(jì)算的。
8)我們?cè)俜祷氐絫ask_new_fair,看update_curr()后,接下來做的事情   place_entity.

這個(gè)函數(shù)也比較好玩,做得是一個(gè)”獎(jiǎng)勵(lì)“工作:

static void
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
    u64 vruntime = cfs_rq->min_vruntime;

    /*
     * The 'current' period is already promised to the current tasks,
     * however the extra weight of the new task will slow them down a
     * little, place the new task so that it fits in the slot that
     * stays open at the end.
     */
    if (initial && sched_feat(START_DEBIT))
        vruntime += sched_vslice(cfs_rq, se);  稍微加一些,呵呵

    if (!initial) {    如果是睡了,喚醒的,應(yīng)該有些補(bǔ)償?shù)?#160;  具體怎么補(bǔ),多了怎么辦,少了怎么辦?
        /* sleeps upto a single latency don't count. */
        if (sched_feat(NEW_FAIR_SLEEPERS)) {
            unsigned long thresh = sysctl_sched_latency;

            /*
             * convert the sleeper threshold into virtual time
             */
            if (sched_feat(NORMALIZED_SLEEPER))
                thresh = calc_delta_fair(thresh, se);

            vruntime -= thresh;
        }

        /* ensure we never gain time by being placed backwards. */
        vruntime = max_vruntime(se->vruntime, vruntime);
    }

    se->vruntime = vruntime;
}

9)最后,task_new_fair做的事情就是讓task入列。enqueue_task_fair()
9) static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;

    for_each_sched_entity(se) {遍歷所有的se及它的父親。。。這個(gè)在非組調(diào)度時(shí),就是se,組調(diào)度時(shí),會(huì)將其dad一同入列
        if (se->on_rq)如果已經(jīng)在運(yùn)行隊(duì)列,就停止
            break;
        cfs_rq = cfs_rq_of(se);
        enqueue_entity(cfs_rq, se, wakeup);真正的入隊(duì)函數(shù),,,把se插入到rb-tree.
        wakeup = 1;
    }

    hrtick_update(rq);   hr部分的,后面再分析。
}

10)enqueue_entity() 會(huì)更新時(shí)間,最終調(diào)────enqueue_entity(),把schedule_entity往紅黑樹中存放。每個(gè)結(jié)點(diǎn)的值是通過 entity_key()來實(shí)現(xiàn)的,比較奇怪,是se->vruntime-cfs->min_vruntime....不知道這么做。

入列操作有一些跟喚醒判斷有關(guān)的,后面再查資料看看
11)再回到wake_up_new_task()來。
會(huì)調(diào)用check_preempt_curr(),判斷當(dāng)前進(jìn)程是否被新進(jìn)程搶占。這個(gè)函數(shù)調(diào)用sched_fair_class里的 check_preempt_wakeup,這個(gè)函數(shù)也很好玩兒,它會(huì)調(diào)其中一個(gè)函數(shù),wakeup_preempt_entity(),來判斷當(dāng)前的進(jìn) 程和新進(jìn)程之前滿足什么樣的關(guān)系才可以搶占。

這時(shí)新加了個(gè)區(qū)間:新進(jìn)程的vruntime比current小,但要滿足一定條件,才能完成搶占。
三種情況返回值為-1/0/1,代表不同的意思。

最后我們又返回到了wake_up_new_task.一個(gè)新進(jìn)程創(chuàng)建后被調(diào)度的過程大體就是上面的流程。

另一個(gè)比較有意思的就是tick了,明天看看tick中斷做的事情吧。

一些有意思的關(guān)于cfs調(diào)度的資料:
http://video.linuxfoundation.org/video/1029   視頻  cfs作者的演講
Some kernel hackers...
Thomas Gleixner
http://www.google.com/search?hl= ... ;btnG=Google+Search
http://kerneltrap.org/Thomas_Gleixner
http://de.wikipedia.org/wiki/Thomas_Gleixner

Schedulers: the plot thickens
http://lwn.net/Articles/230574/

[patch] Modular Scheduler Core and Completely Fair Scheduler [CFS]
http://lwn.net/Articles/230501/

http://www.ibm.com/developerworks/cn/linux/l-cfs/index.html 完全公平調(diào)度程序

鼠眼看Linux調(diào)度器
http://blog.chinaunix.net/u1/42957/showart.php?id=337604

http://www.ibm.com/developerwork ... cheduler/index.html
Linux 調(diào)度器發(fā)展簡述



至今不敢寫一篇關(guān)于cfs的文章收藏
http://blog.csdn.net/dog250/archive/2009/01/15/3793000.aspx

http://www.ibm.com/developerworks/cn/linux/l-cfs/index.html
使用完全公平調(diào)度程序(CFS)進(jìn)行多任務(wù)處理


3 關(guān)于tick中斷
為了保證調(diào)度,在每個(gè)tick都會(huì)對(duì)進(jìn)程時(shí)間信息等進(jìn)行更新。首先找一個(gè)從hrtimer上層到進(jìn)程調(diào)度之間的點(diǎn),暫時(shí)往進(jìn)程調(diào)度的方向說,后面談到hrtimer時(shí),再往hrtimer追根溯源吧。
這個(gè)點(diǎn)就是,update_process_times,它是在timer interrupt 中被調(diào) 的,它會(huì)調(diào)一個(gè)跟process直接相關(guān)的函數(shù),scheduler_tick().
/*
 * This function gets called by the timer code, with HZ frequency.
 * We call it with interrupts disabled.
 *
 * It also gets called by the fork code, when changing the parent's
 * timeslices.
 */
void scheduler_tick(void)
{
    int cpu = smp_processor_id();
    ...
    update_cpu_load(rq);
    curr->sched_class->task_tick(rq, curr, 0);   task_tick即公平調(diào)度類里的
    spin_unlock(&rq->lock);

#ifdef CONFIG_SMP
    rq->idle_at_tick = idle_cpu(cpu);
    trigger_load_balance(rq, cpu);
#endif
}

看看task_tick:
/*
 * scheduler tick hitting a task of our scheduling class:
 */
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &curr->se;

    for_each_sched_entity(se) {   老朋友了,組調(diào)度的時(shí)候會(huì)遍歷父親們,否則就是它自己
        cfs_rq = cfs_rq_of(se);
        entity_tick(cfs_rq, se, queued);  傳進(jìn)來的queued=0
    }
}

繼續(xù)看entity_tick;

static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
    /*
     * Update run-time statistics of the 'current'.
     */
    update_curr(cfs_rq);   更新當(dāng)前進(jìn)程current的信息,這個(gè)昨天已經(jīng)看過了

#ifdef CONFIG_SCHED_HRTICK
    /*
     * queued ticks are scheduled to match the slice, so don't bother
     * validating it and just reschedule.
     */
    if (queued) {
        resched_task(rq_of(cfs_rq)->curr);
        return;
    }
    /*
     * don't let the period tick interfere with the hrtick preemption
     */
    if (!sched_feat(DOUBLE_TICK) &&
            hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
        return;
#endif

    if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
        check_preempt_tick(cfs_rq, curr);  這個(gè)要與check_preempt_curr對(duì)比著看,我當(dāng)初就在這個(gè)地方暈了。。。注釋是這樣的:“ Preempt the current task with a newly woken task if needed:”

}

把兩 個(gè)函數(shù)都貼出來:

1) static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int sync)
{
    struct task_struct *curr = rq->curr;
    struct sched_entity *se = &curr->se, *pse = &p->se;
。。。
    if (wakeup_preempt_entity(se, pse) == 1) {  這個(gè)是根據(jù)當(dāng)前進(jìn)程curr和要調(diào)度的p之前的vruntime比較,有一個(gè)區(qū)間限度。
。。。
}
2)static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
    unsigned long ideal_runtime, delta_exec;

    ideal_runtime = sched_slice(cfs_rq, curr);算一下當(dāng)前進(jìn)程理想的運(yùn)行時(shí)間是多少?
    delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; 算算它已經(jīng)運(yùn)行了多久?
    if (delta_exec > ideal_runtime)
        resched_task(rq_of(cfs_rq)->curr);
}

有趣的是,上面兩 個(gè)函數(shù)的comment是一樣的  :-)

 第二個(gè)函數(shù)折磨了我好久~  既然是完全公平調(diào)度,每個(gè)進(jìn)程的運(yùn)行時(shí)間應(yīng)該完全公平呀? 實(shí)際上ideal_runtime 是與進(jìn)程的優(yōu)先級(jí)有關(guān)系的。
首先看ideal_runtime是怎么計(jì)算出來的。
3)
/*
 * We calculate the wall-time slice from the period by taking a part
 * proportional to the weight.
 *
 * s = p*P[w/rw]這是通過一個(gè)比例算出的
 */
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    unsigned long nr_running = cfs_rq->nr_running;運(yùn)行的進(jìn)程數(shù)目

    if (unlikely(!se->on_rq))
        nr_running++; 

    return calc_delta_weight(__sched_period(nr_running), se);   這個(gè)weight是跟進(jìn)程數(shù)目有關(guān)系的
}


4)看看cale_delata_weight第一個(gè)參數(shù)是怎么得到的。
/*
 * The idea is to set a period in which each task runs once.   設(shè)置一個(gè)period,讓每個(gè)進(jìn)程都跑一遍。
 *
 * When there are too many tasks (sysctl_sched_nr_latency) we have to stretch
 * this period because otherwise the slices get too small.  當(dāng)進(jìn)程數(shù)目大于默認(rèn)值(5)時(shí),要拉伸一下這個(gè)period 
 *
 * p = (nr <= nl) ? l : l*nr/nl
 */
static u64 __sched_period(unsigned long nr_running)
{
    u64 period = sysctl_sched_latency;
    unsigned long nr_latency = sched_nr_latency;

    if (unlikely(nr_running > nr_latency)) {
        period = sysctl_sched_min_granularity;  最小粒度   4ms
          period *= nr_running;  4ms * 進(jìn)程數(shù)目
    }

    return period;
}

這個(gè)比較好理解,小于5個(gè)進(jìn)程時(shí),這個(gè)period周期是20ms,大于5個(gè)running number 時(shí),就讓4*nr;后面就是線性的,

回到3,計(jì)算出來的這個(gè)period作為calc_delata_weight()的第一個(gè)參數(shù)往下傳,繼而算出當(dāng)前se的比重來。
5)
/*
 * delta *= P[w / rw]
 */
static inline unsigned long
calc_delta_weight(unsigned long delta, struct sched_entity *se)
{
    for_each_sched_entity(se) {
        delta = calc_delta_mine(delta,
                se->load.weight, &cfs_rq_of(se)->load);  這個(gè)函數(shù)不陌生,在計(jì)算vruntime的時(shí)候也有它,不過那個(gè)函數(shù)是calc_delta_fair,重點(diǎn)是中間的參數(shù),一個(gè)是 nice_0_load,一個(gè)是當(dāng)前進(jìn)程的load
    }

    return delta;
}
這個(gè)很簡單:   假如n 個(gè)進(jìn)程的period是4n,其中進(jìn)程i的 load.weight是m,所有進(jìn)程的load是M,那么進(jìn)程i應(yīng)該在這個(gè)period里面分到的蛋糕是:  4n * m/M

再返回check_preempt_tick,通過比較實(shí)際計(jì)算出來的值與理想值 比較,確定當(dāng)前進(jìn)程是否被搶占。。

這個(gè)地方有 個(gè)疑問:  check_preempt_tick是在tick中斷里調(diào)的,check_preempt_curr()昨晚分析過在進(jìn)程創(chuàng)建的時(shí)候有過調(diào)用,后都傳進(jìn) 去一個(gè)需要調(diào)度的進(jìn)程作為參數(shù),與當(dāng)前進(jìn)程比較,滿足一定的條件時(shí),才會(huì)把當(dāng)前進(jìn)程切換掉。前者是在tick中斷里掉,不知道下一個(gè)調(diào)的進(jìn)程是誰,這個(gè)應(yīng) 該由調(diào)度器后面去選一個(gè)。沒關(guān)系,它的工作就是看看當(dāng)前進(jìn)程把自己的時(shí)間片用完了沒?用完了的話,我就回去告訴調(diào)度器,把你調(diào)度走,,,具體怎么調(diào)度呢? 下面我們接著看吧。。。先猜想一下:1 ) 把當(dāng)前進(jìn)程出列,調(diào)整其在紅黑樹中的位置。2) 從紅黑樹中選擇下最左結(jié)點(diǎn)運(yùn)行

4 調(diào)度函數(shù)
resched_task()里設(shè)置某個(gè)進(jìn)程需要調(diào)度。其實(shí)就是設(shè)定一個(gè)flag而已。
static inline void set_tsk_need_resched(struct task_struct *tsk)
{
    set_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}


來看看大boss,
/*
 * schedule() is the main scheduler function.
 */
asmlinkage void __sched schedule(void)
{
...

    prev->sched_class->put_prev_task(rq, prev);
    next = pick_next_task(rq, prev);

....
}

中間這兩 個(gè)函數(shù),是我 們關(guān)心的。

1)
/*
 * Account for a descheduled task:
 */
static void put_prev_task_fair(struct rq *rq, struct task_struct *prev)
{
    struct sched_entity *se = &prev->se;
    struct cfs_rq *cfs_rq;

    for_each_sched_entity(se) {
        cfs_rq = cfs_rq_of(se);
        put_prev_entity(cfs_rq, se);
    }
}

這是對(duì)一個(gè)即將被調(diào)度出去的進(jìn)程的處理。 
2)    它會(huì)掉pub_prev_entity 這個(gè)例程看得有點(diǎn)兒迷糊。 如果進(jìn)程還在runqueue上,就把它重新放回紅黑樹,只是位置變了,并把當(dāng)前的curr指針清空。
static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
{
    /*
     * If still on the runqueue then deactivate_task()
     * was not called and update_curr() has to be done:
     */
    if (prev->on_rq)  這個(gè)條件還得研究研究,什么情況下用與不用
        update_curr(cfs_rq);

    check_spread(cfs_rq, prev);
    if (prev->on_rq) {
        update_stats_wait_start(cfs_rq, prev);
        /* Put 'current' back into the tree. */
        __enqueue_entity(cfs_rq, prev);   怎么又放回去呢?
    }
    cfs_rq->curr = NULL;
}

3) 回到scheduler函數(shù),
static struct task_struct *pick_next_task_fair(struct rq *rq)
{
    struct task_struct *p;
    struct cfs_rq *cfs_rq = &rq->cfs;
    struct sched_entity *se;

    if (unlikely(!cfs_rq->nr_running))
        return NULL;

    do {
        se = pick_next_entity(cfs_rq); 挑出下一個(gè)可以運(yùn)行的
        set_next_entity(cfs_rq, se);  設(shè)置它為cfs_rq的curr
        cfs_rq = group_cfs_rq(se);是group的 se么?
    } while (cfs_rq);   這個(gè)循環(huán)其實(shí)很有意思,它為group調(diào)度提供支持。如果cfs_rq這層里面的進(jìn)程被選擇完畢,它會(huì)接著選擇其父task_group里的去運(yùn)行。
   
    p = task_of(se);
    hrtick_start_fair(rq, p);

    return p;
}

4) pick_next_entity會(huì)調(diào)用__pick_next_entity(cfs_rq)去選擇紅黑樹最左側(cè)的結(jié)點(diǎn),然后有兩 個(gè)條件判斷,作為返回se的最終人選。
"
    if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, se) < 1)
        return cfs_rq->next;

    if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, se) < 1)
        return cfs_rq->last;
"
wakeup_preempt_entity()這個(gè)我在前面分析過了,這是最終決定兩 個(gè)進(jìn)程能否進(jìn)行切換的一個(gè)判斷。。。next 和 last估計(jì)是cfs里定時(shí)更新的親戚,好事當(dāng)然先輪著自己嘍。。所以這個(gè)地方調(diào)度會(huì)優(yōu)先選擇下,,,具體再討論吧。

5) 選出來了以后,會(huì)通過 set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)設(shè)置一些信息,把它賊給cfs_rq->curr,更新總運(yùn)行時(shí)間等。
6)這兒還有一個(gè)奇怪的函數(shù):
hrtick_start_fair(rq, p);
我認(rèn)為選出了可以運(yùn)行的進(jìn)程,下一步應(yīng)該真正讓它運(yùn)行吧,,這步操作在哪呢?
上面這個(gè)函數(shù)只是做了些判斷,是否當(dāng)前進(jìn)程真得需要運(yùn)行?然后更新了些與hrtick相關(guān)的rq信息。
也有可能是scheduler()函數(shù)下面做的吧,具體我還沒有打到。

這兒有個(gè)小猜想,希望得到驗(yàn)證:
1) a task call schedule(),it's se->on_rq = 1,so it need denqueue .call deactivate_task()

2) a task picked by schedule() need to get the cpu to run,it's se->on_rq = 0,so
it need enqueue.
3) a task's exec time is over,need change to rb-tree's right it's se->on_rq =  1,just change the tree

這個(gè)再想想。。。。

論壇徽章:
0
3 [報(bào)告]
發(fā)表于 2009-03-02 14:55 |只看該作者

回復(fù) #2 wxc200 的帖子

組調(diào)度

先說一下提綱

1 關(guān)于group schedule的文檔
2 關(guān)于組調(diào)度的認(rèn)識(shí)
3 一些與組調(diào)度相關(guān)的數(shù)組結(jié)構(gòu)
4 組調(diào)度結(jié)構(gòu)圖
5 具體的操作函數(shù)
6 后記


1   按照文檔的解釋,完全公平調(diào)度并不僅僅針對(duì)單一進(jìn)程,也應(yīng)該對(duì)組調(diào)度。例如有兩 個(gè)用戶wxc1,wxc2,以用戶userid來分,調(diào)度器分配給兩 個(gè)組的時(shí)間是公平的,wxc1 and wxc2各有50%的CPU,,組內(nèi)也實(shí)現(xiàn)公平調(diào)度。

幾個(gè)宏:
CONFIG_GROUP_SCHED :打開組調(diào)度

CONFIG_RT_GROUP_SCHED 實(shí)時(shí)進(jìn)程組調(diào)度

CONFIG_FAIR_GROUP_SCHED 普通進(jìn)程。
組的劃分:
1)Based on user id (CONFIG_USER_SCHED)
2)Based on "cgroup" pseudo filesystem (CONFIG_CGROUP_SCHED)
后面這一種還不太熟悉,參照eric xiao 網(wǎng)友的blog,在對(duì)cgroup文件系統(tǒng)進(jìn)程操作時(shí),會(huì)去調(diào)用一個(gè)接口,里面的函數(shù)完成對(duì)組調(diào)度的操作。
cgroup這部分后面再學(xué)習(xí),暫時(shí)還不了解。

2 關(guān)于組調(diào)度的認(rèn)識(shí)
1)組調(diào)度是分層次的。
2)一個(gè)組里面有數(shù)個(gè)進(jìn)程,這些進(jìn)程的調(diào)度作為一層。
3)所有的組都是連在一起的,他們之前有父子兄弟關(guān)系。為首的是root-task-group
4)另一層是組調(diào)度,即把一個(gè)組作為一個(gè)調(diào)度實(shí) 體,仍然用schedule_entity這個(gè)結(jié)構(gòu)體來表示。
5)一個(gè)task_group在每個(gè)cpu上都關(guān)聯(lián)著一個(gè)cfs_rq,一個(gè)schedule_entity,注意,這兩 個(gè)東東是與之前單個(gè)進(jìn)程調(diào)度里提到的cfs_rq & schedule_entity有分別的。。它們代表得是組。跟之前單個(gè)進(jìn)程不一樣。
6)每個(gè)cpu的run queue里有個(gè)鏈表,把所有在這個(gè)cpu上屬于某些組的cfs_rq鏈在了一起。
7) 組與組之間的父子關(guān)系,同時(shí)代表著組在某個(gè)cpu上的調(diào)度實(shí)體之間的父子關(guān)系。假如說wxc1是wxc2組的parent,那個(gè)wxc1 在cpu0上的調(diào)度實(shí)體schedu_entity 是wxc2在cpu0上調(diào)度實(shí)體的父親,這個(gè)一會(huì)在結(jié)構(gòu)圖中可以看到。
8) 每個(gè)調(diào)度實(shí)體都會(huì)指向一個(gè)它它所屬的cfs_rq,同時(shí)還有一個(gè)它所在的cfs_rq.對(duì)于代表組的調(diào)度實(shí)體來說,它的cfs_rq是其parent所在組的cfs_rq,而它自己的cfs_rq,用my_q指向,是它所在的組的cfs_rq

3 一些與組調(diào)度相關(guān)的數(shù)組結(jié)構(gòu)
1) 結(jié)構(gòu)體組:
/* task group related information */
struct task_group {
#ifdef CONFIG_CGROUP_SCHED
    struct cgroup_subsys_state css;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    /* schedulable entities of this group on each cpu */
    struct sched_entity **se;每個(gè)cpu上都有一個(gè)調(diào)度實(shí)體,代表這個(gè)組,不是單個(gè)task的調(diào)度實(shí)體
    /* runqueue "owned" by this group on each cpu */
    struct cfs_rq **cfs_rq;每個(gè)cpu上都有一個(gè)cfs_rq,屬于這個(gè)組的
    unsigned long shares;
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    struct sched_rt_entity **rt_se;
    struct rt_rq **rt_rq;

    struct rt_bandwidth rt_bandwidth;
#endif

    struct rcu_head rcu;
    struct list_head list;

    struct task_group *parent;  這三個(gè)代表著組與組之間的親戚
    struct list_head siblings;
    struct list_head children;
};


2) struct rq{
...
struct list_head leaf_cfs_rq_list; 用來鏈接在此cpu上所有組的cfs_rq
...
}
 3)
struct cfs_rq {

...
#ifdef CONFIG_FAIR_GROUP_SCHED
    //Note here: cfs has rq pointer when group schedule
    struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */   指向一個(gè)run queue

    /*
     * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
     * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
     * (like users, containers etc.)
     *
     * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
     * list is used during load balance.
     */
    struct list_head leaf_cfs_rq_list;             鏈表,把一個(gè)runqueue上的所有cfs_rq鏈接在一起
    struct task_group *tg;    /* group that "owns" this runqueue */    哪個(gè)group來的?
...
}
4) struct sched_entity {
。。。
#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity    *parent;    也有父親了~以前是野孩子   其父親一般是其所在group的父親的在同樣cpu上的調(diào)度實(shí)體
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq        *cfs_rq;  這兩 個(gè)cfs_rq挺繞的:cfs_rq這個(gè)指針指向的是parent->my_q
    /* rq "owned" by this entity/group: */
    struct cfs_rq        *my_q;自己的cfs_rq
#endif
};

4 組調(diào)度結(jié)構(gòu)圖
請(qǐng)參照附件,手工畫得,因?yàn)椴粫?huì)用做圖軟件   汗~

右上角部分:兩 個(gè)組之前是父子關(guān)系  每個(gè)組旁邊的三角形是代表組的調(diào)度實(shí)體

5 具體的操作函數(shù)

先看兩 個(gè)組:
1) /* Default task group.
 *    Every task in system belong to this group at bootup.
 */
struct task_group init_task_group;
2) /*
 * Root task group.
 *     Every UID task group (including init_task_group aka UID-0) will
 *     be a child to this group.
 */
init_task_group是系統(tǒng)初始化時(shí)進(jìn)程的所在組。
root_task_group 是所有g(shù)roup 的祖宗。
在sched_init()里面,有對(duì)它的初始化過程。
sched_init(){
.....
#ifdef CONFIG_FAIR_GROUP_SCHED
        init_task_group.se = (struct sched_entity **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);請(qǐng)留意這兒,  ptr加一個(gè)偏移,這段空間內(nèi)存放著在所有cpu上的代表組的調(diào)度實(shí)體的指針。

        init_task_group.cfs_rq = (struct cfs_rq **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);

#ifdef CONFIG_USER_SCHED
        root_task_group.se = (struct sched_entity **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);

        root_task_group.cfs_rq = (struct cfs_rq **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);
#endif /* CONFIG_USER_SCHED */
...
}
由于page_alloc還沒有建立,所以用alloc_bootmem(),上面的過程建立了一個(gè)task-group里面的**se,**cfs_rq部分。。。即兩 個(gè)指針數(shù)組。
留意這句:
    init_task_group.parent = &root_task_group;
可見init_task_group的父親也是root_task_group.

接著往下,有一個(gè)對(duì)每個(gè)cpu的注冊(cè)過程,這個(gè)過程和一個(gè)組創(chuàng)建并注冊(cè)的過程類似,簡單分析下。
這個(gè)過程只簡我感興趣的分析:
for_each_possible_cpu(i) {
        struct rq *rq;

        rq = cpu_rq(i);
        spin_lock_init(&rq->lock);
        rq->nr_running = 0;
        init_cfs_rq(&rq->cfs, rq);初始化運(yùn)行列隊(duì)自己的cfs_rq,不是組的哦~
        init_rt_rq(&rq->rt, rq);
#ifdef CONFIG_FAIR_GROUP_SCHED
        init_task_group.shares = init_task_group_load;
        INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
#ifdef CONFIG_CGROUP_SCHED
。。。

        init_tg_cfs_entry(&init_task_group, &rq->cfs, NULL, i, 1, NULL);   這個(gè)函數(shù)很重要。。。把initi_task_group,和 運(yùn)行列隊(duì)的cfs_rq綁定。
#elif defined CONFIG_USER_SCHED
        root_task_group.shares = NICE_0_LOAD;
        init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, 0, NULL);同理。
。。。


    init_tg_cfs_entry(&init_task_group,
                &per_cpu(init_cfs_rq, i),
                &per_cpu(init_sched_entity, i), i, 1,
                root_task_group.se);
。。

}

init_tg_cfs_entry()這個(gè)函數(shù)做了許多好事,提出表揚(yáng)。
急不可奈地先看看這廝到底做了啥捏:

#ifdef CONFIG_FAIR_GROUP_SCHED
static void init_tg_cfs_entry(struct task_group *tg, struct cfs_rq *cfs_rq,
                struct sched_entity *se, int cpu, int add,
                struct sched_entity *parent)   這幾個(gè)參數(shù)分別是:要初始化的組 + 要初始化的cfs_rq + 要初始化的se + CPU + 是否把cfs掛在rq上 + 代表組的父親的調(diào)度實(shí)體
{
    struct rq *rq = cpu_rq(cpu);
    tg->cfs_rq[cpu] = cfs_rq;先把指針真滿
    init_cfs_rq(cfs_rq, rq);把它和運(yùn)行隊(duì)列關(guān)鏈一下。 下面我們會(huì)分析這個(gè)初始化做什么事情
    cfs_rq->tg = tg;從這兩 句我感覺到,指針真是個(gè)好東西啊:隨意抽像出來一個(gè)玩意兒,就可以做為一個(gè)接口,把兩 個(gè)東西綁在一起,讓他們有關(guān)系。。。這里它就綁定了組和rq
    if (add)
        list_add(&cfs_rq->leaf_cfs_rq_list, &rq->leaf_cfs_rq_list);
    tg->se[cpu] = se;也把se放進(jìn)指針數(shù)組
    /* se could be NULL for init_task_group */
    if (!se)
        return;

    if (!parent)   這兒有意思:沒有父親的話,se指向的cfs_rq就是當(dāng)前運(yùn)行隊(duì)列的cfs_rq,這個(gè)條件在初始化“根組”和初”始化組“時(shí)成立。
        se->cfs_rq = &rq->cfs;
    else
        se->cfs_rq = parent->my_q;  否則呢,就指向父親所擁有的cfs_rq

    se->my_q = cfs_rq;然后把自己所在組的cfs_rq作為自己的隊(duì)列
    se->load.weight = tg->shares;   共享組的load值。
    se->load.inv_weight = 0;
    se->parent = parent;  親吻下自己的父母
}
#endif

看看上面那個(gè)如何把cfs_rq和運(yùn)行隊(duì)列關(guān)聯(lián)?
static void init_cfs_rq(struct cfs_rq *cfs_rq, struct rq *rq)
{
    cfs_rq->tasks_timeline = RB_ROOT;  初始化下自己的紅黑結(jié)點(diǎn)
    INIT_LIST_HEAD(&cfs_rq->tasks);
#ifdef CONFIG_FAIR_GROUP_SCHED
    cfs_rq->rq = rq;  我能找到你,你也能找到我,這樣才是穩(wěn)固的關(guān)系。。。。
#endif
    cfs_rq->min_vruntime = (u64)(-(1LL << 20));  默認(rèn)一個(gè)cfs_rq的最左結(jié)點(diǎn)的值
}

上面這些分析大體代表了“根組”和“初始化組”在系統(tǒng)初始化的過程中相關(guān)流程。

來看一個(gè)組怎么被創(chuàng)建的吧。
/* allocate runqueue etc for a new task group */
struct task_group *sched_create_group(struct task_group *parent)  parent這個(gè)參數(shù)一般在創(chuàng)建的時(shí)候會(huì)指定,比如是上一個(gè)進(jìn)程組,沒有的話可能是前面提到的那兩 個(gè)組
{
    struct task_group *tg;
    unsigned long flags;
    int i;

    tg = kzalloc(sizeof(*tg), GFP_KERNEL);   申請(qǐng)組結(jié)構(gòu)體的內(nèi)存空間
    if (!tg)
        return ERR_PTR(-ENOMEM);

    if (!alloc_fair_sched_group(tg, parent))   恩,這個(gè)比較好玩兒,申請(qǐng)那個(gè)指針數(shù)組,一會(huì)兒看,
        goto err;

    if (!alloc_rt_sched_group(tg, parent))
        goto err;

    spin_lock_irqsave(&task_group_lock, flags);
    for_each_possible_cpu(i) {  把申請(qǐng)到的組向每一個(gè)cpu注冊(cè)
        register_fair_sched_group(tg, i); 
        register_rt_sched_group(tg, i);
    }
    list_add_rcu(&tg->list, &task_groups);

    WARN_ON(!parent); /* root should already exist */

    tg->parent = parent;  再親吻一下自己的父母
    INIT_LIST_HEAD(&tg->children);
    list_add_rcu(&tg->siblings, &parent->children);
    spin_unlock_irqrestore(&task_group_lock, flags);

    return tg;

err:
    free_sched_group(tg);
    return ERR_PTR(-ENOMEM);
}

這個(gè)流程不審很清晰的。。。。申請(qǐng)空間-->申請(qǐng)指針數(shù)組-->向cpu的運(yùn)行隊(duì)列注冊(cè)

看看這兩 個(gè)函數(shù)做什么。
static
int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se, *parent_se;
    struct rq *rq;
    int i;

    tg->cfs_rq = kzalloc(sizeof(cfs_rq) * nr_cpu_ids, GFP_KERNEL);
    if (!tg->cfs_rq)
        goto err;
    tg->se = kzalloc(sizeof(se) * nr_cpu_ids, GFP_KERNEL);
    if (!tg->se)
        goto err;
上面兩 步不難,申請(qǐng)指向每個(gè)cpu的cfs_rq 和 se數(shù)組

    tg->shares = NICE_0_LOAD;

    for_each_possible_cpu(i) {  向每一個(gè)cpu注冊(cè)下申請(qǐng)的cfs_rq 和 se結(jié)構(gòu)體。
        rq = cpu_rq(i);

        cfs_rq = kmalloc_node(sizeof(struct cfs_rq),
                GFP_KERNEL|__GFP_ZERO, cpu_to_node(i));
        if (!cfs_rq)
            goto err;

        se = kmalloc_node(sizeof(struct sched_entity),
                GFP_KERNEL|__GFP_ZERO, cpu_to_node(i));
        if (!se)
            goto err;

        parent_se = parent ? parent->se : NULL;   判斷當(dāng)前組有父親么?有的話其父親在此cpu上的調(diào)度實(shí)體即為它的調(diào)度實(shí)體的父親。。真拗口
        init_tg_cfs_entry(tg, cfs_rq, se, i, 0, parent_se);這個(gè)是之前我們分析過的,注意其參數(shù)
    }

    return 1;

 err:
    return 0;
}
上面這個(gè)申請(qǐng)函數(shù)過程還是蠻順利的。
下面看看這個(gè)注冊(cè)更簡單,它就是把申請(qǐng)到的cfs_rq掛到運(yùn)行隊(duì)列的鏈表上。。。

這樣一個(gè)組的創(chuàng)建過程分析完了。


最后再分析一個(gè)進(jìn)程在不同組之間移動(dòng)的情況:

/* change task's runqueue when it moves between groups.
 *    The caller of this function should have put the task in its new group
 *    by now. This function just updates tsk->se.cfs_rq and tsk->se.parent to
 *    reflect its new group.
 */
void sched_move_task(struct task_struct *tsk)
{
    int on_rq, running;
    unsigned long flags;
    struct rq *rq;

    rq = task_rq_lock(tsk, &flags);   如果是單進(jìn)程調(diào)度,返回的是運(yùn)行隊(duì)列的cfs_rq結(jié)構(gòu)體,否則返回調(diào)度實(shí)體指向的cfs_rq

    update_rq_clock(rq);

    running = task_current(rq, tsk);
    on_rq = tsk->se.on_rq;

    if (on_rq)
        dequeue_task(rq, tsk, 0);  如果還在列上,出列吧,要移走了嘛
    if (unlikely(running))不希望它還在運(yùn)行,如果是的話,就讓它停止? 或者重新入列?
        tsk->sched_class->put_prev_task(rq, ts)

    set_task_rq(tsk, task_cpu(tsk));  設(shè)置新的運(yùn)行隊(duì)列  下面有分析

#ifdef CONFIG_FAIR_GROUP_SCHED
    if (tsk->sched_class->moved_group)
        tsk->sched_class->moved_group(tsk); 下面有分析這個(gè)函數(shù)
#endif

    if (unlikely(running))
        tsk->sched_class->set_curr_task(rq);  試著讓它運(yùn)行 。。。這個(gè)函數(shù)的邏輯也沒看懂
    if (on_rq)
        enqueue_task(rq, tsk, 0);  正式入列    下面分析它

    task_rq_unlock(rq, &flags);
}
#endif

這個(gè)流程的邏輯我是比較暈的,我搞不清楚為什么會(huì)這么做?
當(dāng)一個(gè)進(jìn)程從一個(gè)組移到另一個(gè)組的時(shí)候,這些操作具體的邏輯我不太懂,,需要再看下組調(diào)度理論性的東西。

set_task_rq將重新設(shè)置一個(gè)task的運(yùn)行列隊(duì):
/* Change a task's cfs_rq and parent entity if it moves across CPUs/groups */
static inline void set_task_rq(struct task_struct *p, unsigned int cpu)   這兒設(shè)置的可是一個(gè)進(jìn)程哦,一個(gè)進(jìn)程的調(diào)度實(shí)體。。。與代表組的調(diào)度實(shí)體要分清楚
{
#ifdef CONFIG_FAIR_GROUP_SCHED
    p->se.cfs_rq = task_group(p)->cfs_rq[cpu];
    p->se.parent = task_group(p)->se[cpu];
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    p->rt.rt_rq  = task_group(p)->rt_rq[cpu];
    p->rt.parent = task_group(p)->rt_se[cpu];
#endif
}

這樣我們大體有個(gè)概念了。。。在組高度中,一個(gè)組內(nèi)的進(jìn)程的父親都是代表這個(gè)組的調(diào)度實(shí)體。它們指向的cfs_rq就是當(dāng)前組所在這個(gè)cpu上擁有的cfs_rq

下面這個(gè)函數(shù)守成了進(jìn)程移到新的組的珠一些信息的更新
#ifdef CONFIG_FAIR_GROUP_SCHED
static void moved_group_fair(struct task_struct *p)
{
    struct cfs_rq *cfs_rq = task_cfs_rq(p);

    update_curr(cfs_rq);
    place_entity(cfs_rq, &p->se, 1);
}
#endif

最后我們看這個(gè)入列的函數(shù)吧:
static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;

    for_each_sched_entity(se) {這個(gè)for還是很有趣
        if (se->on_rq)
            break;
        cfs_rq = cfs_rq_of(se);  取得其cfs_rq  非組調(diào)度時(shí),返回當(dāng)前運(yùn)行隊(duì)列的cfs_rq,組調(diào)度時(shí),返回的是se將要調(diào)度到的cfs_rq
        enqueue_entity(cfs_rq, se, wakeup);  入列了  可能要初始化時(shí)間啊之類的
        wakeup = 1;
    }

    hrtick_update(rq);
}
看看for循環(huán):
#define for_each_sched_entity(se) \
        for (; se; se = se->parent)
這個(gè)在入列出列時(shí)經(jīng)常遇到。如果打開了組調(diào)度,就是上面這樣。
舉例,組A里的一個(gè)進(jìn)程a要加入到組B在運(yùn)行列隊(duì)cpu0上的cfs_rq。這時(shí)進(jìn)程的se->cfs_rq應(yīng)該指向的時(shí)在cpu0上的組B的cfs_rq。
首先執(zhí)行一些出隊(duì)入隊(duì)操作。當(dāng)這個(gè)環(huán)節(jié)完了后,還在對(duì)組A這個(gè)調(diào)度實(shí)體進(jìn)行更新,這樣往上遍歷,把所有組A的父親們遍歷一次,都執(zhí)行了入列操作。

為什么這么做呢?  考慮中~

后記
上面這些是我看組調(diào)度中想到的一部分。。。。后面看完cgroup調(diào)度,應(yīng)該會(huì)更有意思的。

                                     (end)


[ 本帖最后由 wxc200 于 2009-3-3 14:53 編輯 ]

論壇徽章:
0
4 [報(bào)告]
發(fā)表于 2009-03-02 15:05 |只看該作者
謝謝樓主,辛苦了. 下下來慢慢看!

論壇徽章:
0
5 [報(bào)告]
發(fā)表于 2009-03-02 15:42 |只看該作者

回復(fù) #4 scutan 的帖子

scutan,
不客氣  歡迎指教  

論壇徽章:
0
6 [報(bào)告]
發(fā)表于 2009-03-02 23:27 |只看該作者
太長了 ,先收藏了。

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
7 [報(bào)告]
發(fā)表于 2009-03-03 09:12 |只看該作者
總結(jié)的好,學(xué)習(xí)了

論壇徽章:
3
戌狗
日期:2014-09-10 17:07:162015年辭舊歲徽章
日期:2015-03-03 16:54:15wusuopu
日期:2016-06-17 17:43:45
8 [報(bào)告]
發(fā)表于 2009-03-03 09:51 |只看該作者
謝謝樓主

論壇徽章:
36
IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-10 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-16 06:20:0015-16賽季CBA聯(lián)賽之廣東
日期:2016-04-16 19:59:32IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-18 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-19 06:20:00每日論壇發(fā)貼之星
日期:2016-04-19 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-25 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-06 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-08 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-13 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-28 06:20:00每日論壇發(fā)貼之星
日期:2016-05-28 06:20:00
9 [報(bào)告]
發(fā)表于 2009-03-03 13:32 |只看該作者
建議LZ把第2樓的內(nèi)容,斜體部分編輯一下哈

論壇徽章:
0
10 [報(bào)告]
發(fā)表于 2009-03-03 14:54 |只看該作者

回復(fù) #9 Godbach 的帖子

okay  修改好啦   
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號(hào)-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號(hào):11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報(bào)專區(qū)
中國互聯(lián)網(wǎng)協(xié)會(huì)會(huì)員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請(qǐng)注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP