調度器2—cat /proc/ /sched內容分析


一、文件內容和統計

1. /proc/<pid>/sched 文件內容

# cat /proc/1040/sched
surfaceflinger (1040, #threads: 35)
-------------------------------------------------------------------
se.exec_start                                :      13494669.924940
se.vruntime                                  :       3727092.232665
se.sum_exec_runtime                          :         66530.390986
se.nr_migrations                             :                24462
se.statistics.sum_sleep_runtime              :      12630670.892047
se.statistics.wait_start                     :             0.000000
se.statistics.sleep_start                    :      13618468.809058
se.statistics.block_start                    :             0.000000
se.statistics.sleep_max                      :       6094680.389694
se.statistics.block_max                      :           399.362397
se.statistics.exec_max                       :             4.024010
se.statistics.slice_max                      :            18.451818
se.statistics.wait_max                       :           236.678333
se.statistics.wait_sum                       :          1173.521716
se.statistics.wait_count                     :                 1413
se.statistics.iowait_sum                     :           531.031575
se.statistics.iowait_count                   :                  338
se.statistics.nr_migrations_cold             :                    0
se.statistics.nr_failed_migrations_affine    :                    0
se.statistics.nr_failed_migrations_running   :                  207
se.statistics.nr_failed_migrations_hot       :                    0
se.statistics.nr_forced_migrations           :                   31
se.statistics.nr_wakeups                     :                88930
se.statistics.nr_wakeups_sync                :                51540
se.statistics.nr_wakeups_migrate             :                23234
se.statistics.nr_wakeups_local               :                  787
se.statistics.nr_wakeups_remote              :                88143
se.statistics.nr_wakeups_affine              :                    0
se.statistics.nr_wakeups_affine_attempts     :                    0
se.statistics.nr_wakeups_passive             :                    0
se.statistics.nr_wakeups_idle                :                    0
avg_atom                                     :             0.739399
avg_per_cpu                                  :             2.719744
nr_switches                                  :                89979
nr_voluntary_switches                        :                88921
nr_involuntary_switches                      :                 1058
se.load.weight                               :              6246400
se.runnable_weight                           :              6246400
se.avg.load_sum                              :                  425
se.avg.runnable_load_sum                     :                  425
se.avg.util_sum                              :               435200
se.avg.load_avg                              :                  154
se.avg.runnable_load_avg                     :                  154
se.avg.util_avg                              :                   25
se.avg.last_update_time                      :       13440015644236
se.avg.util_est.ewma                         :                   76
se.avg.util_est.enqueued                     :                   69
policy                                       :                    0
prio                                         :                  112
clock-delta                                  :                  104

 

2. 文件導出函數

//fs/proc/base.c
static const struct pid_entry tgid_base_stuff[] = {
...
#ifdef CONFIG_SCHED_DEBUG
    REG("sched", S_IRUGO|S_IWUSR, proc_pid_sched_operations),
#endif
...
};

static const struct file_operations proc_pid_sched_operations = {
    .open        = sched_open,
    .read        = seq_read,
    .write        = sched_write,
    .llseek        = seq_lseek,
    .release    = single_release,
};

有寫權限,sched_write 中 p->se.statistics 清0,寫之后,再cat會發現se.statistics.X成員全部是0了,這樣就可以實現觀測感興趣的線程一段時間內的 se.statistics 的統計情況了。sched_show()中可知 打印出來的 se.statistics.X 的單位都是ms

#define P_SCHEDSTAT(F)    SEQ_printf(m, "  .%-30s: %lld\n",    #F, (long long)schedstat_val(F)) //直接顯示的是值

//這個pid_namespace只是為了獲取一個pid
void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, struct seq_file *m)
{
    unsigned long nr_switches;

    SEQ_printf(m, "%s (%d, #threads: %d)\n", p->comm, task_pid_nr_ns(p, ns), get_nr_threads(p));//return task->signal->nr_threads;
    SEQ_printf(m, "------------------------------------------------------------------\n");
//宏在編譯預處理時起作用,作用域不會被限制,放在函數外是一樣的
#define __P(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)F)
#define P(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)p->F)
#define P_SCHEDSTAT(F) SEQ_printf(m, "%-45s:%21Ld\n", #F, (long long)schedstat_val(p->F))
#define __PN(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)F))
#define PN(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)p->F)) //SPLIT_NS搞過后單位是ms
#define PN_SCHEDSTAT(F) SEQ_printf(m, "%-45s:%14Ld.%06ld\n", #F, SPLIT_NS((long long)schedstat_val(p->F)))

    PN(se.exec_start);
    PN(se.vruntime);
    PN(se.sum_exec_runtime);

    nr_switches = p->nvcsw + p->nivcsw;

    P(se.nr_migrations); //單位是次數

    if (schedstat_enabled()) {
        u64 avg_atom, avg_per_cpu;

        PN_SCHEDSTAT(se.statistics.sum_sleep_runtime);
        PN_SCHEDSTAT(se.statistics.wait_start);
        PN_SCHEDSTAT(se.statistics.sleep_start);
        PN_SCHEDSTAT(se.statistics.block_start);
        PN_SCHEDSTAT(se.statistics.sleep_max);
        PN_SCHEDSTAT(se.statistics.block_max);
        PN_SCHEDSTAT(se.statistics.exec_max);
        PN_SCHEDSTAT(se.statistics.slice_max);
        PN_SCHEDSTAT(se.statistics.wait_max);
        PN_SCHEDSTAT(se.statistics.wait_sum);
        P_SCHEDSTAT(se.statistics.wait_count);
        PN_SCHEDSTAT(se.statistics.iowait_sum);
        P_SCHEDSTAT(se.statistics.iowait_count);
        P_SCHEDSTAT(se.statistics.nr_migrations_cold);
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_affine);
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_running);
        P_SCHEDSTAT(se.statistics.nr_failed_migrations_hot);
        P_SCHEDSTAT(se.statistics.nr_forced_migrations);
        P_SCHEDSTAT(se.statistics.nr_wakeups);
        P_SCHEDSTAT(se.statistics.nr_wakeups_sync);
        P_SCHEDSTAT(se.statistics.nr_wakeups_migrate);
        P_SCHEDSTAT(se.statistics.nr_wakeups_local);
        P_SCHEDSTAT(se.statistics.nr_wakeups_remote);
        P_SCHEDSTAT(se.statistics.nr_wakeups_affine);
        P_SCHEDSTAT(se.statistics.nr_wakeups_affine_attempts);
        P_SCHEDSTAT(se.statistics.nr_wakeups_passive);
        P_SCHEDSTAT(se.statistics.nr_wakeups_idle);

        avg_atom = p->se.sum_exec_runtime;
        if (nr_switches)
            avg_atom = div64_ul(avg_atom, nr_switches); //除的是切換的次數
        else
            avg_atom = -1LL;

        avg_per_cpu = p->se.sum_exec_runtime;
        if (p->se.nr_migrations) {
            avg_per_cpu = div64_u64(avg_per_cpu, p->se.nr_migrations); //除的是遷移的次數
        } else {
            avg_per_cpu = -1LL;
        }

        __PN(avg_atom); //單位也是ms
        __PN(avg_per_cpu);
    }

    __P(nr_switches);
    SEQ_printf(m, "%-45s:%21Ld\n", "nr_voluntary_switches", (long long)p->nvcsw);
    SEQ_printf(m, "%-45s:%21Ld\n", "nr_involuntary_switches", (long long)p->nivcsw);

    P(se.load.weight);
    P(se.runnable_weight);
#ifdef CONFIG_SMP
    P(se.avg.load_sum);
    P(se.avg.runnable_load_sum);
    P(se.avg.util_sum);
    P(se.avg.load_avg);
    P(se.avg.runnable_load_avg);
    P(se.avg.util_avg);
    P(se.avg.last_update_time);
    P(se.avg.util_est.ewma);
    P(se.avg.util_est.enqueued);
#endif
    P(policy);
    P(prio);
    if (task_has_dl_policy(p)) {
        P(dl.runtime);
        P(dl.deadline);
    }
#undef PN_SCHEDSTAT //使上面定義的宏失效
#undef PN
#undef __PN
#undef P_SCHEDSTAT
#undef P
#undef __P

    {
        unsigned int this_cpu = raw_smp_processor_id();
        u64 t0, t1;

        t0 = cpu_clock(this_cpu);
        t1 = cpu_clock(this_cpu);
        SEQ_printf(m, "%-45s:%21Ld\n", "clock-delta", (long long)(t1-t0)); //獲取cpu時間的代碼的執行時長,但是ns
    }

    sched_show_numa(p, m); //沒使能CONFIG_NUMA_BALANCING,不執行
}

 

二、每個成員解析

1. se.exec_start

(1) CFS
在 update_curr() 中給他賦值為 rq->clock_task

update_curr 的調用路徑:

pick_next_task_fair
set_next_task_fair
    set_next_entity
        update_stats_curr_start //里面只做了更新se.exec_start的操作
fair_sched_class.task_fork
    task_fork_fair //對current的cfs_rq調用
fair_sched_class.yield_task
    yield_task_fair //對rq->curr的cfs_rq調用
fair_sched_class.pick_next_task
    pick_next_task_fair //若cfs_rq->curr->on_rq調用
fair_sched_class.check_preempt_curr
    check_preempt_wakeup //對rq->curr的cfs_rq調用
task_tick_fair
    entity_tick //若參數queued為真就對參數cfs_rq調用
put_prev_task_fair
pick_next_task_fair //在pick_next_entity得到的不是prev的時候調用
    put_prev_entity //若prev->on_rq才調用
dequeue_task_fair
throttle_cfs_rq
    dequeue_entity //入口處無條件調用
unthrottle_cfs_rq
enqueue_task_fair
    enqueue_entity //入口處無條件調用
update_cfs_group
reweight_task //更改prio時調用
    reweight_entity //若要被reweight正在執行,就調用
fair_sched_class.update_curr
    update_curr_fair
        update_curr
            se.exec_start = rq->clock_task;

注意,雖然每個事件都更新 se.exec_start,但是並不是每個se的都使用,對於curr這個se可以用於做差求上個事件距現在運行的時長。

(2) DL

pick_next_task_dl
dl_sched_class.set_next_task
    set_next_task_dl
dequeue_task_dl
yield_task_dl
put_prev_task_dl
task_tick_dl
dl_sched_class.update_curr
    update_curr_dl
        curr->se.exec_start = rq->clock_task;

(3) STOP

stop_sched_class.put_prev_task
    put_prev_task_stop //stop_task.c
        curr->se.exec_start = rq->clock_task;
stop_sched_class.set_next_task
pick_next_task_stop
    set_next_task_stop //stop_task.c
        stop->se.exec_start = rq->clock_task;

(4) RT

pick_next_task_rt
rt_sched_class.set_next_task
    set_next_task_rt
dequeue_task_rt
put_prev_task_rt
task_tick_rt
rt_sched_class.update_curr
    update_curr_rt
        curr->se.exec_start = rq->clock_task;

(5) 其它

init_idle //core.c
    idle->se.exec_start = sched_clock();
normalize_rt_tasks
    p->se.exec_start = 0;

使用到的 rq->clock_task 的更新路徑:

hrtick //core.c
enqueue_task //core.c 若參數flags中無ENQUEUE_NOCLOCK才執行
dequeue_task //core.c 若參數flags中無ENQUEUE_NOCLOCK才執行
move_queued_task //core.c 若參數rq->clock_update_flags中無RQCF_UPDATED才執行
__migrate_task //core.c
__set_cpus_allowed_ptr //core.c
ttwu_remote //core.c
sched_ttwu_pending //core.c
ttwu_queue //core.c
wake_up_new_task //core.c
task_sched_runtime //core.c
scheduler_tick //core.c
sched_tick_remote //core.c
__schedule //core.c
rt_mutex_setprio //core.c
set_user_nice //core.c
__sched_setscheduler //core.c
migrate_tasks //core.c 在遷移任務的for循環執行前調用一次,然后在遷移一個任務之前和之后,只要rq->clock_update_flags中無RQCF_UPDATED都調用一次
sched_move_task
cpu_cgroup_fork
    update_rq_clock //里面會更新rq->clock
        update_rq_clock_task //core.c 參數delta是當前時間減去rq->clock的差值,然后將當前時間賦值為rq->clock
            rq->clock_task += delta; //加上的是參數delta中除去中斷時間后的delta

看來,幾乎在所有事件執行前都要對 rq->clock_task 進行賦值,它累積的是除去中斷時間后的時間差值。

兩者維護的主體不同,rq->clock_task 是 core.c 維護的 rq 的時間線信息, 是個時間點,se.exec_start 是各個調度類維護的時間線信息,兩者都頻繁更新,cat sched文件中的這個字段沒啥參考意義了。

 

2. se.vruntime

(1) 更新位置1

static void update_curr(struct cfs_rq *cfs_rq)
{
    struct sched_entity *curr = cfs_rq->curr;
    u64 now = rq_clock_task(rq_of(cfs_rq));
    delta_exec = now - curr->exec_start; //這個delta是不包括中斷時間的
    curr->vruntime += calc_delta_fair(delta_exec, curr); //return delta_exec * NICE_0_LOAD / cfs_rq->curr->load.weight;
}

update_curr更上層的調用路徑見對 se.exec_start 字段的說明。幾乎在所有事件下都會更新當前任務cfs_rq->curr的虛擬時間。

(2) 更為位置2

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

    //這個feature是對新線程進程懲罰的,懲罰的虛擬時間其權重在cfs_rq上分得的時間大小
    if (initial && sched_feat(START_DEBIT)) //默認使能
        vruntime += sched_vslice(cfs_rq, se);

    /*對非新fork()的線程, 再進行一點補償,這里主要是sleep喚醒的線程*/
    if (!initial) {
        unsigned long thresh = sysctl_sched_latency;

        if (sched_feat(GENTLE_FAIR_SLEEPERS)) //默認使能,使能后補償時間減少一半
            thresh >>= 1;
        vruntime -= thresh;

#ifdef CONFIG_SCHED_WALT
        if (entity_is_task(se)) {
            /*若是寫/proc/<pid>/sched_boost正在進行boost的線程,或是標記的low_latency的線程並且負載低於指定的閾值,
            或是rtg組里面的線程並且優先級高於指定的閾值,就進行虛擬時間補償。
            */
            if ((per_task_boost(task_of(se)) == TASK_BOOST_STRICT_MAX) ||
                    walt_low_latency_task(task_of(se)) ||
                    task_rtg_high_prio(task_of(se))) {
                vruntime -= sysctl_sched_latency;
                vruntime -= thresh;
                se->vruntime = vruntime;
                return;
            }
        }
#endif
    }

    /*為了保證虛擬時間不會反向減少,取個最大值*/
    se->vruntime = max_vruntime(se->vruntime, vruntime);
}

place_entity的調用路徑:

enqueue_entity //若參數 flags 包含 ENQUEUE_WAKEUP(喚醒任務后進行的enqueue) 時調用,傳參(cfs_rq, se, 0)
task_fork_fair //直接調用,傳參(cfs_rq, se, 1)
detach_task_cfs_rq //任務遷移出去時,若p不在cfs隊列上且不在遷移中且不是新fork的任務,就調用,傳參(cfs_rq, se, 0)
    place_entity

place_entity 是主要的調度實體虛擬時間更新函數,在更新時會對其進行補償。比如對於長時間休眠的線程喚醒后,在 cfs_rq->min_vruntime 的基礎上再補償一些一時間,有助於其及時被調度到。而對比剛 fork 出來的新任務,對其懲罰一定時間,可以避免其剛 fork 出來就立即被調度。

(3) 更新位置3

static void detach_task_cfs_rq(struct task_struct *p)
{
    struct sched_entity *se = &p->se;
    struct cfs_rq *cfs_rq = cfs_rq_of(se);

    /*若p不在cfs隊列上且不在遷移中且不是新fork的任務,就執行,應該就是對應sleep的線程*/
    if (!vruntime_normalized(p)) {
        place_entity(cfs_rq, se, 0);
        se->vruntime -= cfs_rq->min_vruntime;
    }
    detach_entity_cfs_rq(se);
}

static void attach_task_cfs_rq(struct task_struct *p)
{
    struct sched_entity *se = &p->se;
    struct cfs_rq *cfs_rq = cfs_rq_of(se);

    attach_entity_cfs_rq(se);

    if (!vruntime_normalized(p))
        se->vruntime += cfs_rq->min_vruntime;
}

在CFS任務遷移的時候,虛擬時間保存的是差值,對於遷移的sleep狀態的線程,先對其虛擬時間有補償,然后求差值。

(4) 更新位置4

static void dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    ...
    if (!(flags & DEQUEUE_SLEEP)) //被搶占的或被遷移的
        se->vruntime -= cfs_rq->min_vruntime; //計算差值,enqueue時再加上min_vruntime,注意這里保存的是相對值
    ...
}

static void enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    /* 此次enqueue不是喚醒 或 是遷移導致的enqueue*/
    bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_MIGRATED);
    bool curr = cfs_rq->curr == se; //等於

    if (renorm && curr)
        se->vruntime += cfs_rq->min_vruntime;

    update_curr(cfs_rq); //里面有重新計算虛擬時間

    if (renorm && !curr)
        se->vruntime += cfs_rq->min_vruntime;
    ...
}

renorm的判斷條件和上面 detach_task_cfs_rq/attach_task_cfs_rq 的互補,應該在 detach/attach 時不在cfs隊列上且不在遷移中且不是新fork的任務就已經對 se->vruntime 做了差值和在新rq上恢復。也就是說正在運行或在cfs隊列上或正在遷移的任務的虛擬時間的做差和恢復是在 dequeue_entity/enqueue_entity 中完成了。

(5) 更新位置5

move_queued_task //core.c 將一個已經queue的task移動到另一個隊列中
__migrate_swap_task //core.c
detach_task //fair.c 將p->wake_cpu = dest_cpu;
try_to_wake_up //task之前的cpu不等於新選出的cpu的時候執行,這里面唯一執行p->state = TASK_WAKING;
    set_task_cpu //task之前運行的cpu和新選出的cpu不是同一個cpu的時候執行
        fair_sched_class.migrate_task_rq
            migrate_task_rq_fair
                if (p->state == TASK_WAKING) {
                    se->vruntime -= min_vruntime; //這里執行還是有個條件的
                }

根據 if (p->state == TASK_WAKING) 判斷后才賦值,這個是針對喚醒過程中發現選出的cpu不是task之前運行的cpu的時候執行,也就是說正在喚醒過程中遷移的任務的虛擬時間做差值是在 migrate_task_rq_fair 函數中執行的,應該也是在 enqueue_entity 加回來的。

(6) 更新位置6

fork_idle //fork.c
_do_fork //fork.c
    copy_process //fork.c
        sched_fork //core.c
            fair_sched_class.task_fork
                task_fork_fair //fair.c
                    se->vruntime -= cfs_rq->min_vruntime;

在 wake_up_new_task-->activate_task(rq, p, ENQUEUE_NOCLOCK) 傳參 flag=ENQUEUE_NOCLOCK,對應在 enqueue_entity 中將 se->vruntime 差值加回來的。

 

3. se.sum_exec_runtime

update_curr //fair.c
    curr->sum_exec_runtime += delta_exec; //delta_exec = now - curr->exec_start;

update_curr_dl //deadline.c
    curr->se.sum_exec_runtime += delta_exec;

put_prev_task_stop //stop_task.c
    curr->se.sum_exec_runtime += delta_exec;

__sched_fork //core.c
    p->se.sum_exec_runtime = 0;
    p->se.prev_sum_exec_runtime = 0;

update_curr 的執行路徑見對 1. se.exec_start 的說明,它通常和 se->prev_sum_exec_runtime 配合起來使用,如下:

set_next_task_fair
pick_next_task_fair
    set_next_entity //se->prev_sum_exec_runtime的唯一賦值位置

static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    ...
    se->prev_sum_exec_runtime = se->sum_exec_runtime;
}

當 se dequeue出去運行的時候,prev_sum_exec_runtime 記錄 sum_exec_runtime 的值。而 sum_exec_runtime 在運行途中不停累加,此時任何時刻下做差值就可以得到任務運行的時間。

static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) //fair.c
{
    ideal_runtime = sched_slice(cfs_rq, curr); //curr根據其權重分配的虛擬時間
    delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
    if (delta_exec > ideal_runtime) { //時間片用完了就觸發搶占
        resched_curr(rq_of(cfs_rq)); //只是設置 TIF_NEED_RESCHED 標志,在搶占點到來時搶占
        clear_buddies(cfs_rq, curr);
        return;
    }
}

調用路徑:

scheduler_tick
    task_tick_fair
        entity_tick //這里執行了 update_curr(cfs_rq),對 curr->sum_exec_runtime 進行了更新
            check_preempt_tick

在 hrtick_start_fair 中也有使用:

static void hrtick_start_fair(struct rq *rq, struct task_struct *p)
{
    struct sched_entity *se = &p->se;
    struct cfs_rq *cfs_rq = cfs_rq_of(se);

    if (rq->cfs.h_nr_running > 1) {
        u64 slice = sched_slice(cfs_rq, se);
        u64 ran = se->sum_exec_runtime - se->prev_sum_exec_runtime;
        s64 delta = slice - ran;

        if (delta < 0) {
            if (rq->curr == p)
                resched_curr(rq); //時間片使用完了,要被切走
            return;
        }
        hrtick_start(rq, delta);
    }
}

此時 se->sum_exec_runtime - se->prev_sum_exec_runtime 差值就是任務從上次開始運行,到這次tick時所此次調度到運行的時間。單純的 se->sum_exec_runtime 表示任務在測試時間段的運行的總時間。

 

4. se.nr_migrations

賦值位置:

void set_task_cpu(struct task_struct *p, unsigned int new_cpu)
{
    if (task_cpu(p) != new_cpu) {
        if (p->sched_class->migrate_task_rq)
            //CFS的:做一些負載的加減和調頻,然后將被遷移的進程設置為新cpu上的新進程。
            p->sched_class->migrate_task_rq(p, new_cpu); //migrate_task_rq_fair
        p->se.nr_migrations++;
    }
}

調用路徑:

move_queued_task
__migrate_swap_task
try_to_wake_up //新cpu和原cpu不同時執行
dl_task_offline_migration
push_dl_task
pull_dl_task
detach_task
push_rt_task
pull_rt_task
    set_task_cpu

se.nr_migrations 記錄任務在不同CPU間遷移的次數。

 

5. se.statistics.sum_sleep_runtime

賦值位置:

static inline void update_stats_enqueue_sleeper(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    struct task_struct *tsk = NULL;
    u64 sleep_start, block_start;

    if (!schedstat_enabled())
        return;

    /*一個是sleep狀態,一個是D狀態*/
    sleep_start = schedstat_val(se->statistics.sleep_start);
    block_start = schedstat_val(se->statistics.block_start);

    if (entity_is_task(se))
        tsk = task_of(se);

    /*上次是由於任務是 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 而進行的dequeue時賦值的 */ 
    if (sleep_start) {
        u64 delta = rq_clock(rq_of(cfs_rq)) - sleep_start;
        if ((s64)delta < 0)
            delta = 0;

        if (unlikely(delta > schedstat_val(se->statistics.sleep_max)))
            __schedstat_set(se->statistics.sleep_max, delta); //sleep_max是單次sleep的最大值

        __schedstat_set(se->statistics.sleep_start, 0); //這里又將sleep_start設置為0了,確保 sleep_start 不為0的時候是 TASK_INTERRUPTIBLE 的睡眠狀態進來的
        __schedstat_add(se->statistics.sum_sleep_runtime, delta); //無論是block的還是sleep的,都加到這里了
        ...
    }

    if (block_start) {
        u64 delta = rq_clock(rq_of(cfs_rq)) - block_start;
        if ((s64)delta < 0)
            delta = 0;

        if (unlikely(delta > schedstat_val(se->statistics.block_max)))
            __schedstat_set(se->statistics.block_max, delta); //block_max是單次sleep的最大值

        __schedstat_set(se->statistics.block_start, 0); //這里又將block_start設置為0了
        __schedstat_add(se->statistics.sum_sleep_runtime, delta); //無論是block的還是sleep的,都加到這里了

        if (tsk) {
            if (tsk->in_iowait) {
                __schedstat_add(se->statistics.iowait_sum, delta); //記錄iowait的總時間
                __schedstat_inc(se->statistics.iowait_count); //記錄iowait的次數
                ...
            }

            /*"comm=%s pid=%d delay=%Lu [ns]" 可以trace blcok的時間*/
            trace_sched_stat_blocked(tsk, delta);
            /*"pid=%d iowait=%d caller=%pS" 能監控D狀態,那么也能寫在sleep狀態那里*/
            trace_sched_blocked_reason(tsk);
        }
    }
}

調用路徑:

enqueue_entity
    update_stats_enqueue //只有if (flags & ENQUEUE_WAKEUP) 成立才執行,也就是喚醒導致的enqueue才執行
        update_stats_enqueue_sleeper //fair.c 記錄任務 sleep 和 D狀態 的時間

(1) sleep_start 和 block_start 的更新位置

static inline void update_stats_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    if (se != cfs_rq->curr) //出隊,遷移,設置優先級,調度策略,都會走這里
        update_stats_wait_end(cfs_rq, se);

    //被throttle的時候會傳flags=DEQUEUE_SLEEP, 還有:
    if ((flags & DEQUEUE_SLEEP) && entity_is_task(se)) {
        struct task_struct *tsk = task_of(se);

        if (tsk->state & TASK_INTERRUPTIBLE)
            __schedstat_set(se->statistics.sleep_start, rq_clock(rq_of(cfs_rq)));
        if (tsk->state & TASK_UNINTERRUPTIBLE)
            __schedstat_set(se->statistics.block_start, rq_clock(rq_of(cfs_rq)));
    }
}

schedule()中非搶占是由於 sleep/block 而休眠切走的任務 flags 中包含 DEQUEUE_SLEEP,若是此時任務是 INTERRUPTIBLE 的就記錄在 sleep_start 上,若是 UNINTERRUPTIBLE 的,就記錄 block_start 上。

調用路徑:

update_stats_dequeue 
dequeue_entity
    update_stats_dequeue

說明:開始記錄的時間點是任務任務休眠,結束記錄的時間點是任務被喚醒入隊列時,也就是說 se->statistics.sum_sleep_runtime 統計的是任務測試時間段內 sleep 和 block 兩種休眠狀態的的時長之和。

(2) tsk->in_iowait 的標記位置

int io_schedule_prepare(void)
{
    current->in_iowait = 1;
}
void io_schedule_finish(int token)
{
    current->in_iowait = token;
}

調用路徑:

blkcg_maybe_throttle_blkg //blk-cgroup.c 先調用prepare,處理完后調用finish設置回原來的狀態
io_schedule_timeout //core.c 先調用prepare,處理完后調用finish設置回原來的狀態
io_schedule //core.c 先調用prepare,然后將任務切走,處理完后調用finish設置回原來的狀態
mutex_lock_io_nested //mutex.c 還有一個mutex!
mutex_lock_io
    io_schedule_prepare
    ...
    io_schedule_finish

從 TASK_UNINTERRUPTIBLE 狀態喚醒 enqueue 時(iowait是在D狀態里面),若發現 tsk->in_iowait 是被設置的,se->statistics.iowait_sum 記錄的是 iowait 導致休眠的時間之和,記錄 iowait次數的 se->statistics.iowait_count 也加1.

 

6. se.statistics.wait_start

賦值位置:

static inline void update_stats_wait_start(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
{
    u64 wait_start, prev_wait_start;

    if (!schedstat_enabled())
        return;

    wait_start = rq_clock(rq_of(cfs_rq));
    prev_wait_start = schedstat_val(se->statistics.wait_start);

    //若是在遷移中,se->statistics.wait_start 保存的也是差值
    if (entity_is_task(se) && task_on_rq_migrating(task_of(se)) && likely(wait_start > prev_wait_start))
        wait_start -= prev_wait_start; //減法

    __schedstat_set(se->statistics.wait_start, wait_start);
}

static inline void update_stats_wait_end(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
{
    struct task_struct *p;
    u64 delta;

    delta = rq_clock(rq_of(cfs_rq)) - schedstat_val(se->statistics.wait_start); //return rq->clock;

    if (entity_is_task(se)) {
        p = task_of(se);
        if (task_on_rq_migrating(p)) { //判斷:p->on_rq == TASK_ON_RQ_MIGRATING;
            __schedstat_set(se->statistics.wait_start, delta); //若是遷移更新的是start,然后return了。
            return;
        }
        trace_sched_stat_wait(p, delta);
    }

    __schedstat_set(se->statistics.wait_max, max(schedstat_val(se->statistics.wait_max), delta));
    __schedstat_inc(se->statistics.wait_count);
    __schedstat_add(se->statistics.wait_sum, delta);
    __schedstat_set(se->statistics.wait_start, 0); //這里做了將 wait_start 清0
}

void normalize_rt_tasks(void) //core.c
{
    ...
    p->se.exec_start = 0;
    schedstat_set(p->se.statistics.wait_start, 0);
    schedstat_set(p->se.statistics.sleep_start, 0);
    schedstat_set(p->se.statistics.block_start, 0);
    ...
}

調用路徑:

pick_next_task_fair
put_prev_task_fair
    put_prev_entity //se->on_rq才執行
enqueue_entity
    update_stats_enqueue //se != cfs_rq->curr 才執行
        update_stats_wait_start

pick_next_task_fair
set_next_task_fair
    set_next_entity //se->on_rq才執行
dequeue_entity
    update_stats_dequeue //se != cfs_rq->curr 才執行
        update_stats_wait_end

    sysrq_unrt_op.handler //sysrq_key_table[]中的成員
        sysrq_handle_unrt
            normalize_rt_tasks //core.c 這個函數將所有CFS、RT、DL 任務都設置為優先級為120的CFS任務。

可以看出來,start 的記錄時間是 enqueue 時,end 的記錄時間是 dequeue 時。而 sysrq_handle_unrt 的作用是可以一鍵讓系統沒有RT任務,全部變成優先級為120的CFS任務,這里不重點關注。因此可以看出 se->statistics.wait_start 記錄的是 enqueue 到 cfs隊列上的時間點,其次,若此任務是 dequeue 狀態,其 wait_start 就是0,enqueue等待狀態,其 wait_start 就不為0。se->statistics.wait_max 記錄的是在 cfs_rq上等待的最大一次時間。se->statistics.wait_count 記錄的是 enqueue 到 cfs 隊列上進行等待的次數,se->statistics.wait_sum 記錄的是任務在cfs隊列上等待的總時間。

 

7. se.statistics.sleep_start

標記由於sleep而dequeue的時間點,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

8. se.statistics.block_start

標記由於block而dequeue的時間點,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

9. se.statistics.sleep_max

標記單次sleep的最大時間間隔,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

10. se.statistics.block_max

標記單次block的最大時間間隔,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

11. se.statistics.exec_max

賦值位置:

static void update_curr(struct cfs_rq *cfs_rq) //fair.c
{
    delta_exec = now - curr->exec_start;
    curr->exec_start = now; //更新exec_start時間
    schedstat_set(curr->statistics.exec_max, max(delta_exec, curr->statistics.exec_max));
}

exec_max 表示兩次事件之間任務執行的最大時長,update_curr 的調用路徑分析見“1. se.exec_start”,由於 entity_tick 中調用了,因此 exec_max 頂破天最大也只能是一個tick的大小,250Hz下就是4ms.

 

12. se.statistics.slice_max

賦值位置:

static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c 只有CFS線程這個域有意義
{
    if (schedstat_enabled() && rq_of(cfs_rq)->cfs.load.weight >= 2*se->load.weight) { //奇怪,為啥權重高就不更新統計了?
        schedstat_set(se->statistics.slice_max, max((u64)schedstat_val(se->statistics.slice_max), se->sum_exec_runtime - se->prev_sum_exec_runtime));
    }
    se->prev_sum_exec_runtime = se->sum_exec_runtime; //要運行時保存一下運行前的統計結果
}

調用路徑:

pick_next_task_fair
set_next_task_fair
    set_next_entity

slice_max 表示任務單次運行的最大物理時長,單位ms,這個倒不受 tick 的影響,可以是一個很大的值。

 

13. se.statistics.wait_max

記錄的是在cfs_rq上等待的最大一次時間,見“6. se.statistics.wait_start”

 

14. se.statistics.wait_sum

記錄的是任務在cfs隊列上等待的總時間,見“6. se.statistics.wait_start”

 

15. se.statistics.wait_count

記錄的是enqueue到cfs隊列上進行等待的次數,見“6. se.statistics.wait_start”

 

16. se.statistics.iowait_sum

記錄由於 iowait 而導致任務休眠時間總和,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

17. se.statistics.iowait_count

記錄由於 iowait 而導致任務休眠的次數,見“5. se.statistics.sum_sleep_runtime”中的分析。

 

18. se.statistics.nr_migrations_cold

Qcom 5.4內核中沒有使用到這個成員

 

19. se.statistics.nr_failed_migrations_affine

賦值位置:

static int can_migrate_task(struct task_struct *p, struct lb_env *env) //fair.c
{
    ...
    if (!cpumask_test_cpu(env->dst_cpu, p->cpus_ptr)) {
        schedstat_inc(p->se.statistics.nr_failed_migrations_affine);
    }
    ...
    /*不對正在運行的任務進行遷移*/
    if (task_running(env->src_rq, p)) { //return p->on_cpu;
        schedstat_inc(p->se.statistics.nr_failed_migrations_running);
        return 0;
    }
    ...
    /*
     * Aggressive migration if:侵略性遷徙
     * 1) IDLE or NEWLY_IDLE balance.
     * 2) destination numa is preferred
     * 3) task is cache cold, or
     * 4) too many balance attempts have failed.
     */
    //p在原cpu上即將被調度到(已經設置為next或last buddy)或運行時間小於遷移閾值0.5ms,就是task_hot
    tsk_cache_hot = task_hot(p, env);
    if (env->idle != CPU_NOT_IDLE || tsk_cache_hot <= 0 || env->sd->nr_balance_failed > env->sd->cache_nice_tries) {
        if (tsk_cache_hot == 1) {
            schedstat_inc(p->se.statistics.nr_forced_migrations);
        }
        return 1;
    }

    schedstat_inc(p->se.statistics.nr_failed_migrations_hot);
}

調用路徑:

load_balance //busiest->nr_running > 1 才執行
    detach_tasks
active_load_balance_cpu_stop
    detach_one_task 
        can_migrate_task //只有此函數判斷可以遷移時才會遷移

nr_failed_migrations_affine 表示在遷移時,由於此任務的cpu親和性設置導致的中止遷移的次數。若執行”taskset -p 01 <pid>“ 將一個死循環綁定在小核上,將觀察的非常清楚。若是沒有設置親和性的話,此域一般是0.


20. se.statistics.nr_failed_migrations_running

遷移此任務時,若發現此任務正在運行而中止遷移的計數,也就是不對正在運行的任務進行遷移,設置位置和調用路徑見”19. se.statistics.nr_failed_migrations_affine“。

 

21. se.statistics.nr_failed_migrations_hot

遷移此任務時,若發現此任務在原cpu上即將被調度到(已經設置為next或last buddy)或運行時間小於遷移閾值0.5ms,就是task_hot。若遷移失敗次數比較多的話也會遷移

cache_hot的任務。這里是記錄由於cache_hot而放棄遷移此任務的計數。設置位置和調用路徑見”19. se.statistics.nr_failed_migrations_affine“。

 

22. se.statistics.nr_forced_migrations

遷移此任務時,雖然此任務是 tsk_cache_hot 的,但是仍然要對其進行遷移的次數。設置位置和調用路徑見”19. se.statistics.nr_failed_migrations_affine“。

 

23. se.statistics.nr_wakeups

賦值位置:

static void ttwu_stat(struct task_struct *p, int cpu, int wake_flags) //core.c
{
    struct rq *rq = this_rq();

#ifdef CONFIG_SMP
    if (cpu == rq->cpu) {
        __schedstat_inc(rq->ttwu_local); //此成員沒有在debug.c中進行打印
        __schedstat_inc(p->se.statistics.nr_wakeups_local);
    } else {
        struct sched_domain *sd;
        __schedstat_inc(p->se.statistics.nr_wakeups_remote);
        rcu_read_lock();
        for_each_domain(rq->cpu, sd) {
            if (cpumask_test_cpu(cpu, sched_domain_span(sd))) {
                __schedstat_inc(sd->ttwu_wake_remote); //此成員沒有在debug.c中進行打印
                break;
            }
        }
        rcu_read_unlock();
    }

    if (wake_flags & WF_MIGRATED) //WF_MIGRATED: Internal use, task got migrated
        __schedstat_inc(p->se.statistics.nr_wakeups_migrate);
#endif /* CONFIG_SMP */

    __schedstat_inc(rq->ttwu_count); //此成員沒有在debug.c中進行打印
    __schedstat_inc(p->se.statistics.nr_wakeups);

    if (wake_flags & WF_SYNC) //WF_SYNC: Waker goes to sleep after wakeup
        __schedstat_inc(p->se.statistics.nr_wakeups_sync);
}

調用路徑:

wake_up_q //傳參(task, TASK_NORMAL, 0, head->count) wake_flags=0,各種鎖、進程間通信機制的喚醒
wake_up_process //傳參(p, state, 0, 1) wake_flags=0,各種驅動中使用,只有一個參數task_truct使用簡單方便
wake_up_state //傳參(p, state, 0, 1) wake_flags=0,參數p和state,kernel部分子系統核心實現代碼使用
default_wake_function //傳參(curr->private, mode, wake_flags, 1) 叫default的原因是wait機制使用就是這個函數來喚醒的,見 __WAITQUEUE_INITIALIZER 和 init_waitqueue_entry
    try_to_wake_up
        ttwu_stat

 

24. se.statistics.nr_wakeups_sync

喚醒任務時,若任務喚醒函數傳參 wake_flags 中包含 WF_SYNC(=1,表示喚醒者喚醒被喚醒者后睡眠)標志就加1,只有wait機制的默認實現使用的喚醒函數可能傳這個標志。cat shced節點看 nr_wakeups_sync 的計數值很大,但是內核中卻沒找到哪里使用了這個flag,比較奇怪, 設置位置和調用路徑見”23. se.statistics.nr_wakeups“。

 

25. se.statistics.nr_wakeups_migrate

喚醒任務時,若任務喚醒函數傳參 wake_flags 中包含 WF_MIGRATED 就加1,表示喚醒一個遷移過來的任務。設置位置和調用路徑見”23. se.statistics.nr_wakeups“。

 

26. se.statistics.nr_wakeups_local

喚醒任務時,若任務喚醒在當前CPU上就加1。設置位置和調用路徑見”23. se.statistics.nr_wakeups“。

 

27. se.statistics.nr_wakeups_remote

喚醒任務時,若任務不是喚醒在當前CPU上就加1。設置位置和調用路徑見”23. se.statistics.nr_wakeups“。


28. se.statistics.nr_wakeups_affine

設置位置:

static int wake_affine(struct sched_domain *sd, struct task_struct *p, int this_cpu, int prev_cpu, int sync)
{
    int target = nr_cpumask_bits;

    if (sched_feat(WA_IDLE))
        target = wake_affine_idle(this_cpu, prev_cpu, sync);

    if (sched_feat(WA_WEIGHT) && target == nr_cpumask_bits)
        target = wake_affine_weight(sd, p, this_cpu, prev_cpu, sync);

    schedstat_inc(p->se.statistics.nr_wakeups_affine_attempts); //無條件加1
    if (target == nr_cpumask_bits)
        return prev_cpu;

    schedstat_inc(sd->ttwu_move_affine);
    schedstat_inc(p->se.statistics.nr_wakeups_affine); //任務有親和性的喚醒才加1
    return target;
}

調用路徑:

select_task_rq_fair
    for_each_domain(cpu, tmp) {
        if (want_affine && (tmp->flags & SD_WAKE_AFFINE) && cpumask_test_cpu(prev_cpu, sched_domain_span(tmp))) {
        if (cpu != prev_cpu)
            new_cpu = wake_affine(tmp, p, cpu, prev_cpu, sync);
    }

調用條件苛刻,基本上這個域沒有計數值,參考 WAKE_AFFINE 機制

 

29. se.statistics.nr_wakeups_affine_attempts

調用條件苛刻,基本上這個域沒有計數值。設置位置和調用路徑見”28. se.statistics.nr_wakeups_affine“。


30. se.statistics.nr_wakeups_passive

Qcom Linux5.4沒有使用此成員

 

31. se.statistics.nr_wakeups_idle

Qcom Linux5.4沒有使用此成員


32. avg_atom

此成員是上面 proc_sched_show_task()打印函數中直接設置的,取值為 avg_atom = div64_ul(p->se.sum_exec_runtime, nr_switches),兩個參數也都是打印出來的成員。表示任務平均每次運行的實際時間。使用cat /proc/pid/sched的出來的 se.sum_exec_runtime / nr_switches 剛好等於 avg_atom

 

33. avg_per_cpu

此成員是上面 proc_sched_show_task()打印函數中直接設置的,取值為 avg_per_cpu = div64_u64(p->se.sum_exec_runtime, p->se.nr_migrations),兩個參數也都是打印出來的成員。注意除的是遷移次數,而不是cpu個數。表示平均每次遷移在一個cpu上運行的平均時間。

 

34. nr_switches

此成員是上面 proc_sched_show_task()打印函數中計算出來的,取值為 nr_switches = p->nvcsw + p->nivcsw;

(1) 設置位置1

static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
    ...
    tsk->nvcsw = tsk->nivcsw = 0;
#ifdef CONFIG_DETECT_HUNG_TASK //默認不使能
    tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
    tsk->last_switch_time = 0;
#endif
    ...
}

調用路徑:

_do_fork
    copy_process
        copy_mm //fork.c

(2) 設置位置2

static void __sched notrace __schedule(bool preempt)
{
    ...
    switch_count = &prev->nivcsw; /*首先指向nivcsw*/

    if (!preempt && prev->state) {
        ...
        switch_count = &prev->nvcsw; /*非搶占式的切換改為指向nvcsw*/
    }
    ...
    if (likely(prev != next)) {
        ++*switch_count; /*只有選中的 prev != next 時才計數*/
    }
}

在任務切換時,若 prev 任務是主動休眠導致的任務切換,prev->nvcsw 計數加1,若 prev 是被搶占而發生的任務切換,prev->nivcsw 計數加1。nr_switches 表示發生任務切換的次數,nvcsw 表示非搶占任務被切走的次數,nivcsw 表示發生搶占任務被切走的次數,這里說的任務切換不包括切換后還是自己的情況。

 

35. nr_voluntary_switches

取值為 p->nvcsw,表示非搶占任務被切走的次數。見“34. nr_switches”

 

36. nr_involuntary_switches

取值為 p->nivcsw,表示被搶占而導致的任務被切走的次數。見“34. nr_switches”

 

37. se.load.weight

(1)設置位置1:

static inline void update_load_add(struct load_weight *lw, unsigned long inc) //fair.c
{
    lw->weight += inc;
    lw->inv_weight = 0;
}

調用路徑:

sched_fork
set_user_nice //設置任務的優先級時會更改其權重,進而應該cfs_rq的權重
__setscheduler_params
    set_load_weight
        reweight_task
        update_cfs_group //fair group sched 中與 shares 對比
            reweight_entity //if (se->on_rq) 才需要dequeue后設置后再enqueue,這種情況下才需要設置cfs_rq的weight
        unthrottle_cfs_rq //ENQUEUE_WAKEUP
        enqueue_task_fair
            enqueue_entity //enqueue時將se->load的權重加到cfs_rq->load上
                account_entity_enqueue //無條件執行
scheduler_tick
    task_tick_fair
        entity_tick
            check_preempt_tick //在每個tick的流程中都會計算任務的理想運行時間(但沒改變任務的虛擬時間),運行超出了會觸發任務切換
            hrtick_start_fair
        sched_rr_get_interval //系統調用
            get_rr_interval_fair
                sched_slice //if (!se->on_rq) 才設置,因為不在cfs_rq上的任務其權重不包含在cfs_rq->load內
                    update_load_add

check_preempt_tick 中調用 sched_slice 計算的只是一個理想的運行時間,但是並沒有對任何成員賦值。正在運行的任務每次被tick命中都會判斷其運行時間是否超過了理想的分配時間,若超過了,則觸發重新調度。注意,由於curr每次被tick命中時cfs_rq上的任務的數量和優先級不同,每次計算出的理想時間也不同,但是只要curr一直在運行,其單次運行時間 delta_exec 就是一直增加的,因此運行時間越長的任務越容易在tick中被觸發搶占。

(2)設置位置2:

static inline void update_load_sub(struct load_weight *lw, unsigned long dec) //fair.c
{
    lw->weight -= dec;
    lw->inv_weight = 0;
}

調用路徑:

reweight_entity //設置優先級導致reweight,若是在隊列上,dequeue --> set --> enqueue,這里是sub cfs_rq的weight
dequeue_entity
    account_entity_dequeue
        update_load_sub

(3)設置位置3:

static inline void update_load_set(struct load_weight *lw, unsigned long w) //fair.c
{
    lw->weight = w;
    lw->inv_weight = 0;
}

調用路徑:

sched_create_group
    alloc_fair_sched_group //cfs組調度,對每一個cpu都執行
    sched_init //會創建一個root_task_group,傳參rq->cfs_rq
        init_tg_cfs_entry
        reweight_entity //調用路徑上面有
            update_load_set

se.load.weight 表示CFS任務的權重,和其優先級掛鈎,優先級變化了也會設置。同事cfs_rq也有自己的load和權重,為其上queue的任務的權重之和,若是要設置一個已經enqueue到隊列上的任務,需要先dequeue下來,設置后再enqueue回去。

 

38. se.runnable_weight

(1)設置位置1

void init_entity_runnable_average(struct sched_entity *se) //fair.c
{
    /*等效:*/
    sa->runnable_load_avg = sa->load_avg = scale_load_down(se->load.weight); //se->load.weight >> 10
    se->runnable_weight = se->load.weight;
}

調用路徑:

sched_create_group
    alloc_fair_sched_group //fair group sched使用
        init_entity_runnable_average

static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, unsigned long weight, unsigned long runnable) //fair.c
{
    ...
    se->runnable_weight = runnable;
    ...
}
update_cfs_group //fair group sched使用
reweight_task //傳參 runnable 就是參數 weight,為 scale_load(sched_prio_to_weight[prio]),調用路徑見“37. se.load.weight”
    reweight_entity

(2)設置位置2

static void set_load_weight(struct task_struct *p, bool update_load) //core.c
{
    /*SCHED_IDLE tasks get minimal weight:*/
    if (task_has_idle_policy(p)) {
        load->weight = scale_load(WEIGHT_IDLEPRIO); //3<<10,3比139優先級的15還小,weight也需要scale!
        load->inv_weight = WMULT_IDLEPRIO; //0x55555555 = 2^32 / 3 是最大的,比139的inv還大
        p->se.runnable_weight = load->weight;
        return;
    }
    /*非idle等效:*/
    p->se.runnable_weight = load->weight;
}

調用路徑:

sched_fork
set_user_nice //設置任務的優先級時會更改其權重,進而應該cfs_rq的權重
__setscheduler_params
    set_load_weight

此外,還有cfs_rq.runnable_weight

(1) 賦值位置1:

static inline void dequeue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
{
    cfs_rq->runnable_weight -= se->runnable_weight;
    ...
}

調用路徑:

reweight_entity //設置優先級時先dequeue--> set --> enqueue
dequeue_entity
    dequeue_runnable_load_avg

(2) 賦值位置2

static inline void enqueue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
{
    cfs_rq->runnable_weight += se->runnable_weight;
    ...
}

調用路徑:

reweight_entity //設置優先級時先dequeue--> set --> enqueue
enqueue_entity
    enqueue_runnable_load_avg

若是沒有使能 CONFIG_FAIR_GROUP_SCHED,se.runnable_weight 就等於 se.load.weight。

 

39. se.avg.load_sum

(1) 設置位置1:

//使能 CONFIG_FAIR_GROUP_SCHED 才存在
static inline void update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq) //fair.c
{
    ...
    se->avg.load_sum = runnable_sum;
    se->avg.load_avg = load_avg;
    ...
}

調用路徑:

enqueue_entity
dequeue_entity
set_next_entity
put_prev_entity
entity_tick
enqueue_task_fair
dequeue_task_fair
__update_blocked_fair
propagate_entity_cfs_rq
detach_entity_cfs_rq
attach_entity_cfs_rq
sched_group_set_shares
    update_load_avg //若沒有使能 CONFIG_FAIR_GROUP_SCHED 就是只是去調頻
        propagate_entity_load_avg
            update_tg_cfs_runnable

(2) 設置位置2

static void attach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
    u32 divider = LOAD_AVG_MAX - 1024 + cfs_rq->avg.period_contrib;
    ...
    se->avg.load_sum = divider;
    if (se_weight(se)) {
        se->avg.load_sum = div_u64(se->avg.load_avg * se->avg.load_sum, se_weight(se));
    }
    se->avg.runnable_load_sum = se->avg.load_sum;
    ...
}

調用路徑:

update_load_avg
attach_entity_cfs_rq
    attach_entity_load_avg

(3) 此外,還有cfs_rq的:

static inline void enqueue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    cfs_rq->avg.load_avg += se->avg.load_avg;
    cfs_rq->avg.load_sum += se_weight(se) * se->avg.load_sum;
}

static inline void dequeue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
    sub_positive(&cfs_rq->avg.load_avg, se->avg.load_avg);
    sub_positive(&cfs_rq->avg.load_sum, se_weight(se) * se->avg.load_sum);
}

PELT算法中在 load_sum 就是運行時間乘以權重,衰減累加和,PELT在 accumulate_sum() 中計算負載,見 https://www.cnblogs.com/hellokitty2/p/15335189.html

 

40. se.avg.runnable_load_sum

對於調度實體,runnable_load_sum 等於 load_sum,對於 group se 兩者有區別。

 

41. se.avg.util_sum

PELT算法下,為scale_up后的運行時間的衰減累加值,與優先級無關,只與delta時間有關,其最終會趨向一個定值:1024 * 47742 = 48887808。


42. se.avg.load_avg

平均負載,若任務一直跑,就會接近其優先級對應的權重值。這里的跑的時間為runnable+running.

 

43. se.avg.runnable_load_avg

runnable狀態的平均負載,對於調度實體和 load_avg 是一致的


44. se.avg.util_avg

PELT算法下,為 running% * 1024,是一個比值,只包含running的時間,通常使用它作為負載來觸發調頻。


45. se.avg.last_update_time

PELT算法下,負載統計基於的時間點,通常 delta = now - last_update_time,然后拿 delta 去更新負載。


46. se.avg.util_est.ewma

struct util_est 結構體定義位置對此成員的解釋:任務的指數加權移動平均 (EWMA) 利用率, 支持數據結構以跟蹤 FAIR 任務利用率的指數加權移動平均值 (EWMA)。每次任務完成喚醒時,都會將新樣本添加到移動平均值中。選擇樣本的權重以使 EWMA 對任務工作負載的瞬態變化相對不敏感。


47. se.avg.util_est.enqueued

struct util_est 結構體定義位置對此成員的解釋:
enqueued 屬性對於 tasks 和 cpus 的含義略有不同:
- task:上次任務出隊時任務的 util_avg
- cfs_rq:該 CPU 上每個 RUNNABLE 任務的 util_est.enqueued 總和。因此,任務(非cfs_rq)的 util_est.enqueued 表示該任務當前排隊的 CPU 估計利用率的貢獻。僅對於我們跟蹤過去瞬時估計利用率的移動平均值的任務。這允許吸收其他周期性任務的利用率的零星下降。

 

48. policy

來自 task_truct 的 policy 成員,表示進程的調度策略,取值如下

#define SCHED_NORMAL    0 //CFS
#define SCHED_FIFO        1 //RT
#define SCHED_RR        2 //RT
#define SCHED_BATCH        3 //CFS
#define SCHED_IDLE        5 //CFS
#define SCHED_DEADLINE    6 //DL

 

49. prio

來自 task_truct 的 prio 成員,表示進程的優先級,RT: 0-99,CFS: 100-139,數值越小優先級越高。

 

50. clock-delta

這個值是執行 cpu_clock() 的耗時,是記錄一次讀取CPU時間需要的時長,涉及到讀取硬件,測試發現和 CPU 頻點高低無線性關系。

 

51. 5.10內核中會打印uclamp的值

# cat /proc/1/sched
uclamp.min                                   :                    0
uclamp.max                                   :                 1024
effective uclamp.min                         :                    0
effective uclamp.max                         :                 1024

對應代碼:

//kernel/sched/debug.c
void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, struct seq_file *m)
{
    ...
    #ifdef CONFIG_UCLAMP_TASK
    __PS("uclamp.min", p->uclamp_req[UCLAMP_MIN].value);
    __PS("uclamp.max", p->uclamp_req[UCLAMP_MAX].value);
    __PS("effective uclamp.min", uclamp_eff_value(p, UCLAMP_MIN));
    __PS("effective uclamp.max", uclamp_eff_value(p, UCLAMP_MAX));
    #endif
    ...
}

unsigned long uclamp_eff_value(struct task_struct *p, enum uclamp_id clamp_id)
{
    struct uclamp_se uc_eff;

    /* Task currently refcounted: use back-annotated (effective) value */
    /*se 當前在 rq 的存儲桶中被引用計數的話,active就為1 */
    if (p->uclamp[clamp_id].active)
        return (unsigned long)p->uclamp[clamp_id].value;

    /*
     * 默認 uclamp_eff_get() 是返回 uc_req(p->uclamp_req[clamp_id]與task_group(p)->uclamp[clamp_id]
     * 二者之間value較小的那個) 和 uc_max(uclamp_default[clamp_id]) 再比一次,二者之間較小的那個。
     */
    uc_eff = uclamp_eff_get(p, clamp_id);

    return (unsigned long)uc_eff.value;
}

uclamp.min 和 uclamp.max 直接來自 p->uclamp_req[UCLAMP_MIN].value 和 p->uclamp_req[UCLAMP_MAX].value。effective uclamp.min 和 effective uclamp.max 應該表示正在被引用的值。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM