歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> CFS中一些調度參數的實現原理

CFS中一些調度參數的實現原理

日期:2017/2/27 16:00:53   编辑:Linux教程
在cfs調度中有這麼幾個常用的參數:
  • sysctl_sched_latency:表示一段時間內,sched_entity肯定會被調度到一次,也就是一個sched_entity調度的最大的延時,2.6.35.13內核中默認是6ms。
  • sysctl_sched_min_granularity:表示調度的最小粒度,如果調度的時間間隔小於這個時間段,內核是不會挑選其他sched_entity進行調度,這個2.6.35.13內核中默認是2ms。
  • nr_latency:表示在上面的那段最大調度延遲中,最多處理的sched_entity。

上面的這些參數,書上都是這麼說的,說下個人的理解。
  • 對於sysctl_sched_latency參數是調度的最大延遲,在cfs中,調度根據sched_entity的vruntime來選擇對應的調度單位,對於不同的sched_entity的load並不相同,cfs_rq中記錄了running的sched_entity的總load,可以根據sched_entity的load占總cfs_rqload的比重來分配調度時間,內核中就是通過統計sched_entity的運行時間來確定是否運行的時間夠了,如果夠了,那麼要重新選擇vruntime最小的單位進行調度,這樣才能保證在sysctl_sched_latency讓所有的running的sched_entity運行一次。
  • sysctl_sched_min_granularity變量比較好理解,sched_tick中,應該判斷當前運行的sched_entity的運行時間是否超過了sysctl_sched_min_granularity,如果未超過,就表示當前sched_entity還需要繼續運行,如果超過了,可能會考慮讓wakeup的sched_entity搶占調度。
  • nr_latency是延遲時間段能最大處理任務的數量,在這段時間點有可能切換進程,這幾個時間點就是當一個sched_entity運行時間超過了sysctl_sched_min_granularity,那麼就可以考慮切換進程了,這樣如果每次都切換進程,那麼延遲時間段內最大處理任務的數量就是nr_latency

nr_latency=sysctl_sched_latency/sysctl_sched_min_granularity
sched_entity的理論運行時間是按照cfs_rq中的總的load和當前sched_entity的load來平分sysctl_sched_latency的。

    static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
            u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq);//獲得總的運行時間段,這個時間有可能比sysctl_sched_latency要大

            for_each_sched_entity(se) {
                    struct load_weight *load;
                    struct load_weight lw;

                    cfs_rq = cfs_rq_of(se);
                    load = &cfs_rq->load;//獲得cfs_rq總的load

                    if (unlikely(!se->on_rq)) { //如果se不再rq中,說明rq中的load不包括se的load,這個時候需要把se的load加到rq上
                            lw = cfs_rq->load;

                            update_load_add(&lw, se->load.weight);
                            load = &lw;
                    }
                    //根據slice是一輪調度總的時間,load是rq總的load,se->load.weight是se的load,這樣就可以算出load占總load的百分比,然後體現在平分slice上
                    slice = calc_delta_mine(slice, se->load.weight, load); 
            }
            return slice;
    }


__sched_period函數是計算調度一輪所有的sched_entity所需要的時間
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;
                period *= nr_running;
        }

        return period;
}

如果當前可以運行的sched_entity的個數超過了 nr_latency,那麼認為 在sysctl_sched_latency的一段時間內調度所有的sched_entity時間不夠,這時候需要按照nr_running的個樹,擴大 sysctl_sched_latency。時鐘周期中斷函數會調用scheduler_tick,最終調用了cfs中的check_preempt_tick,檢測當前調度的sched_entity是否需要被搶占,需要調度其他的sched_entity。
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);
   //獲得當前進程的理想運行時間,按照sched_entity的load和rq的總load平分 sysctl_sched_latency所得的時間
        delta_exec = curr->sum_exec_runtime – curr->prev_sum_exec_runtime;
//sum_exec_runtime是sched_entity自fork出來的運行時間,prev表示截止到上次切換進程的運行時間,所以這兩個時間之差就是切換到當前進程後,該進程所運行的時間
        if (delta_exec > ideal_runtime) {
  //可以知道如果當前進程運行時間超過了理論運行時間,那麼就應該讓其它的進程運行了。
                resched_task(rq_of(cfs_rq)->curr);
                /*
                 * The current task ran long enough, ensure it doesn't get
                 * re-elected due to buddy favours.
                 */
                clear_buddies(cfs_rq, curr);
//如果curr被放在了cfs_rq的next或者last位,就應該清除這個引用。
                return;
        }

        /*
         * Ensure that a task that missed wakeup preemption by a
         * narrow margin doesn't have to wait for a full slice.
         * This also mitigates buddy induced latencies under load.
         */
        if (!sched_feat(WAKEUP_PREEMPT))
                return;

        if (delta_exec < sysctl_sched_min_granularity)
  //如果進程運行的時間小於最小的進程切換的時間,那麼是不需要切換進程的,切換的越頻繁,成本開銷也就越大
                return;

        if (cfs_rq->nr_running > 1) {
    //給剛wakeup的進程一個機會,避免使他等太久的時間未得到調度(由於當前進程的理想運行時間過長),其實在wakeup一個進程 的時候已經有機會讓wakeup的進程先調度,可能由於vruntime和curr的vruntime相距不大,沒有得到運行的機會,這裡再給新wakeup的進程一次被調度的機會。
                struct sched_entity *se = __pick_next_entity(cfs_rq);
                s64 delta = curr->vruntime - se->vruntime;

                if (delta > ideal_runtime)
                        resched_task(rq_of(cfs_rq)->curr);
        }
}
在切換sched_entity的時候會記錄當前sched_entity的 prev_sum_exec_runtime,表示當前的 prev_sum_exec_runtime=sum_exec_runtime,至於最後pick出next sched_entity後為什麼要判斷和當前vruntime之差大於理想運行時間,才能重新調度新的進程,猜測一下,對於一個剛剛wakeup的進程,sleep了足夠長的時間了,一個sysctl_sched_latency就可以讓curr進程的vruntime增加ideal_runtime(nice=0,優先級低的增加的會更快,優先級高的增加的會相對慢一些,需要多個sysctl_sched_latency的時間),那麼可以認為curr的vruntime和wakeup的vruntime差距超過了ideal_runtime,curr相對於sleep進程已經執行了足夠長的時間了,這時候即使curr在這一輪調度沒有達到ideal_runtime的時間,那麼也該需要新喚醒的進程輪到調度了,所以這個時候需要resched
Copyright © Linux教程網 All Rights Reserved