linux内核分析之调度算法,Linux内核分析之调度算法

/*每个处理器都会配置一个rq*/

structrq {

/* runqueue lock: */

spinlock_t lock;

/*

* nr_running and cpu_load should be in the same cacheline because

* remote CPUs use both these fields when doing load calculation.

*/

/*用以记录目前处理器rq中执行task的数量*/

unsignedlongnr_running;

#define CPU_LOAD_IDX_MAX 5

/*用以表示处理器的负载,在每个处理器的rq中

都会有对应到该处理器的cpu_load参数配置,在每次

处理器触发scheduler tick时,都会呼叫函数

update_cpu_load_active,进行cpu_load的更新。在系统初始化的时候

会呼叫函数sched_init把rq的cpu_load array初始化为0.

了解他的更新方式最好的方式是通过函数update_cpu_load,公式如下澹?

cpu_load[0]会直接等待rq中load.weight的值。

cpu_load[1]=(cpu_load[1]*(2-1)+cpu_load[0])/2

cpu_load[2]=(cpu_load[2]*(4-1)+cpu_load[0])/4

cpu_load[3]=(cpu_load[3]*(8-1)+cpu_load[0])/8

cpu_load[4]=(cpu_load[4]*(16-1)+cpu_load[0]/16

呼叫函数this_cpu_load时,所返回的cpu load值是cpu_load[0]

而在进行cpu blance或migration时,就会呼叫函数

source_load target_load取得对该处理器cpu_load index值,

来进行计算*/

unsignedlongcpu_load[CPU_LOAD_IDX_MAX];

#ifdef CONFIG_NO_HZ

unsignedlonglast_tick_seen;

unsignedcharin_nohz_recently;

#endif

/* capture load from *all* tasks on this cpu: */

/*load->weight值,会是目前所执行的schedule entity的

load->weight的总和,也就是说rq的load->weight越高,

也表示所负责的排程单元load->weight总和越高

表示处理器所负荷的执行单元也越重*/

structload_weight load;

/*在每次scheduler tick中呼叫update_cpu_load时,

这个值就增加一,可以用来反馈目前cpu

load更新的次数*/

unsignedlongnr_load_updates;

/*用来累加处理器进行context switch的次数,会在

函数schedule呼叫时进行累加,并可以通过函数

nr_context_switches统计目前所有处理器总共的context switch

次数,或是可以透过查看档案/proc/stat中的ctxt位得知目前

整个系统触发context switch的次数*/

u64 nr_switches;

u64 nr_migrations_in;

/*为cfs fair scheduling class 的rq*/

structcfs_rq cfs;

/*为real-time scheduling class 的rq*/

structrt_rq rt;

/*用以支援可以group cfs tasks的机制*/

#ifdef CONFIG_FAIR_GROUP_SCHED

/* list of leaf cfs_rq on this cpu: */

/*在有设置fair group scheduling 的环境下,

会基于原本cfs rq中包含有若干task的group

所成的排程集合,也就是说当有一个group a

就会有自己的cfs rq用来排程自己所属的tasks,

而属于这group a的tasks所使用到的处理器时间

就会以这group a总共所分的的时间为上限。

基于cgroup的fair group scheduling 架构,可以创造出

有阶层性的task组织,根据不同task的功能群组化

在配置给该群主对应的处理器资源,让属于

该群主下的task可以透过rq机制排程。使用属于

该群主下的资源。

这个变数主要是管理CFS RQ list,操作上可以透过函数

list_add_leaf_cfs_rq把一个group cfs rq加入到list中,或透过

函数list_del_leaf_cfs_rq把一个group cfs rq移除,并可以

透过for_each_leaf_cfs_rq把一个rq上得所有leaf cfs_rq走一遍

*/

structlist_head leaf_cfs_rq_list;

#endif

/*用以支援可以group real-time tasks的机制*/

#ifdef CONFIG_RT_GROUP_SCHED

/*类似leaf_cfs_rq_list所扮演的角色,只是这里

是针对属于real-time的task,在实际操作上可以

透过函数list_add_leaf_rt_rq,list_del_leaf_rt_rq或

巨集for_each_leaf_rt_rq*/

structlist_head leaf_rt_rq_list;

#endif

/*

* This is part of a global counter where only the total sum

* over all CPUs matters. A task can increase this counter on

* one CPU and if it got migrated afterwards it may decrease

* it on another CPU. Always updated under the runqueue lock:

*/

/*一般来说,linux kernel 的task状态可以为TASK_RUNNING

TASK_INTERRUPTIBLE(sleep),

TASK_UNINTERRUPTIBLE(Deactivate Task,此时Task会从rq中

移除)或TASK_STOPPED.

透过这个变数会统计目前rq中有多少task属于

TASK_UNINTERRUPTIBLE的状态。当呼叫函数

active_task时,会把nr_uninterruptible值减一,并透过 该函数

enqueue_task把对应的task依据所在的scheduling class

放在 对应的rq中,并把目前rq中nr_running值加一*/

unsignedlongnr_uninterruptible;

/*curr:指向目前处理器正在执行的task;

idle:指向属于idle-task scheduling class 的idle task;

stop:指向目前最高等级属于stop-task scheduling class

的task;*/

structtask_struct *curr, *idle;

/*基于处理器的jiffies值,用以记录下次进行处理器

balancing 的时间点*/

unsignedlongnext_balance;

/*用以存储context-switch发生时,前一个task的memory management

结构并可用在函数finish_task_switch中,透过函数mmdrop释放前一个

task的记忆体资源*/

structmm_struct *prev_mm;

/*用以记录目前rq的clock值,基本上该值会等于透过sched_clock_cpu

(cpu_of(rq))的回传值,并会在每次呼叫scheduler_tick时透过

函数update_rq_clock更新目前rq clock值。

在实作部分,函数sched_clock_cpu会透过sched_clock_local或

ched_clock_remote取得对应的sched_clock_data,而处理的sched_clock_data

值,会透过函数sched_clock_tick在每次呼叫scheduler_tick时进行更新;

*/

u64 clock;

/*用以记录目前rq中有多少task处于等待i/o的sleep状态

在实际的使用上,例如当driver接受来自task的调用,但处于等待i/o

回复的阶段时,为了充分利用处理器的执行资源,这时

就可以在driver中呼叫函数io_schedule,此时

就会把目前rq中的nr_iowait加一,并设定目前task的io_wait为1

然后触发scheduling 让其他task有机会可以得到处理器执行时间*/

atomic_t nr_iowait;

#ifdef CONFIG_SMP

/*root domain是基于多核心架构下的机制,

会由rq结构记住目前采用的root domain,其中包括了

目前的cpu mask(包括span,online rt overload), reference count 跟cpupri

当root domain有被rq参考到时,refcount 就加一,反之就减一。而cpu

mask span表示rq可挂上的cpu mask,noline为rq目前已经排程的

cpu mask cpu上执行real-time task.可以参考函数pull_rt_task,当一个rq中属于

real-time的task已经执行完毕,就会透过函数pull_rt_task从该

rq中属于rto_mask cpu mask 可以执行的处理器上,找出是否有一个处理器

有大于一个以上的real-time task,若有就会转到目前这个执行完成

real-time task 的处理器上

而cpupri不同于Task本身有区分140個(0-139)

Task Priority (0-99為RT Priority 而 100-139為Nice值 -20-19).

CPU Priority本身有102個Priority (包括,-1 為Invalid,

0為Idle,1為Normal,2-101對應到Real-Time Priority 0-99).

參考函式convert_prio, Task Priority如果是 140就會對應到

CPU Idle,如果是大於等於100就會對應到CPU Normal,

若是Task Priority介於0-99之間,就會對應到CPU Real-Time Priority 101-2之間.)

在實際的操作上,例如可以透過函式cpupri_find

帶入一個要插入的Real-Time Task,此時就會依據cpupri中

pri_to_cpu選擇一個目前執行Real-Time Task且該Task

的優先級比目前要插入的Task更低的處理器,

並透過CPU Mask(lowest_mask)返回目前可以選擇的處理器Mask.

實作的部份可以參考檔案kernel/sched_cpupri.c.

在初始化的過程中,會透過函式sched_init呼叫函式init_defrootdomain,

對Root Domain與 CPU Priority機制進行初始化.

*/

structroot_domain *rd;

/*Schedule Domain是基於多核心架構下的機制.

每個處理器都會有一個基礎的Scheduling Domain,

Scheduling Domain可以有階層性的架構,透過parent

可以找到上一層的Domain,或是透過child找到

下一層的 Domain (NULL表示結尾.).並可透過span

栏位,表示這個Domain所能涵蓋的處理器範圍.

通常Base Domain會涵蓋系統中所有處理器的個數,

而Child Domain所能涵蓋的處理器個數不超過它的

Parent Domain. 而當在進行Scheduling Domain 中的Task Balance

時,就會以該Domain所能涵蓋的處理器為最大範圍.

同時,每個Schedule Domain都會包括一個或一個以上的

CPU Groups (結構為struct sched_group),並透過next變數把

CPU Groups串連在一起(成為一個單向的Circular linked list),

每個CPU Group都會有變數cpumask來定义這個CPU Group

所涵蓋的處理器範圍.並且CPU Group所包括的處理器

範圍,必需涵蓋在所屬的Schedule Domain處理器範圍中.

當進行Scheduling Domain的Balancing時,會以其下的CPU Groups

為單位,根據cpu_power (會是該Group所涵蓋的處理器

Tasks Loading的總和)來比較不同的CPU Groups的負荷,

以進行Tasks的移動,達到Balancing的目的.

在有支援SMP的架構下,會在函式sched_init中,呼叫open_softirq,

註冊 SCHED_SOFTIRQ Software IRQ与其对应的 Callback函式

run_rebalance_domains. 並會在每次呼叫函式scheduler_tick時,

透過函式trigger_load_balance确认是否目前的jiffies值已經

大於RunQueue下一次要觸發Load Balance的next_balance時間值,

並透過函式raise_softirq觸發SCHED_SOFTIRQ Software IRQ.

在Software IRQ觸發後,就會呼叫函式run_rebalance_domains,

並在函式rebalance_domains中,進行后续處理器上的

Scheduling Domain Load Balance動作.

有關Scheduling Domain進一步的內容,也可以參考

Linux Kernel文件 Documentation/scheduler/sched-domains.txt.

*/

structsched_domain *sd;

/*這值會等於函式idle_cpu的返回值,如果為1表示

目前CPU RunQueue中執行的為Idle Task. 反之為0,

則表示處理器執行的不是Idle Task (也就是說

處理器正在忙碌中.).*/

unsignedcharidle_at_tick;

/* For active balancing */

/*若這值不為0,表示會有在Schedule排程動作

結束前,要呼叫的收尾函式. (实作為inline函式

post_schedule in kernel/sched.c),目前只有Real-Time Scheduling

Class有支援這個機制(會呼叫函式has_pushable_tasks

in kernel/sched_rt.c).*/

intpost_schedule;

/*當RunQueue中此值為1,表示這個RunQueue正在進行

Fair Scheduling的Load Balance,此時會呼叫stop_one_cpu_nowait

暫停該RunQueue所屬處理器的排程,並透過函式

active_load_balance_cpu_stop,把Tasks從最忙碌的處理器,

移到Idle的處理器上執行.*/

intactive_balance;

/*用以儲存目前進入Idle且負責進行 Load Balance

流程的處理器ID. 呼叫的流程為,在呼叫函式schedule時,

若該處理器RunQueue的nr_running為0 (也就是目前沒有

正在執行的Task),就會呼叫idle_balance,並觸發後續Load

Balance流程.*/

intpush_cpu;

/* cpu of this runqueue: */

/*用以儲存目前运作這個RunQueue的處理器ID*/

intcpu;

/*為1表示目前此RunQueue有在對應的處理器掛上

並執行.*/

intonline;

/*如果RunQueue中目前有Task正在執行,這個值會

等於目前該RunQueue的Load Weight除以目前RunQueue

中Task數目的均值.

(rq->avg_load_per_task = rq->load.weight / nr_running;).*/

unsignedlongavg_load_per_task;

structtask_struct *migration_thread;

structlist_head migration_queue;

/*這個值會由Real-Time Scheduling Class呼叫函式

update_curr_rt,用以統計目前Real-Time Task執行時間的

均值,在這函式中會以目前RunQueue的clock_task

減去目前Task執行的起始時間,取得執行時間的

Delta值. (delta_exec = rq->clock_task – curr->se.exec_start; ).

在透過函式sched_rt_avg_update把這Delta值跟原本

RunQueue中的rt_avg值取平均值. 以運作的週期來看,

這個值可反應目前系統中Real-Time Task平均被

分配到的執行時間值.*/

u64 rt_avg;

/*這個值主要在函式sched_avg_update更新,以笔者手中

的Linux Kernel 2.6.38.6的實作來說,當RunQueue Clock

減去age_stamp大於 0.5秒 (=sched_avg_period),就會把這值

累加0.5秒 (單位都是nanoseconds). 從函式scale_rt_power

的實作來說,age_stamp值離RunQueue Clock越遠,表示total

值越大,available值也越大,而函式scale_rt_power返回的

div_u64計算結果也越大,最終 RunQueue的cpu_power

與Scheduling Domain中的Scheduling Group的cpu_power

值也就越大. (可參考函式update_cpu_power的實作).*/

u64 age_stamp;

/*這值會在觸發Scheduling時,若判斷目前處理器

RunQueue沒有正在運作的Task,就會透過函式

idle_balance更新這值為為目前RunQueue的clock值.

可用以表示這個處理器是何時進入到Idle的

狀態*/

u64 idle_stamp;

/*會在有Task運作且idle_stamp不為0 (表示前一個

狀態是在Idle)時以目前RunQueue的clock減去

idle_stmp所計算出的Delta值為依據,更新這個值

. 可反應目前處理器進入Idle狀態的時間長短*/

u64 avg_idle;

#endif

/* calc_load related fields */

/*用以記錄下一次計算CPU Load的時間,初始值

為目前的jiffies加上五秒與1次的Scheduling Tick的

間隔 (=jiffies + LOAD_FREQ,且LOAD_FREQ=(5*HZ+1))*/

unsignedlongcalc_load_update;

/*會等於RunQueue中nr_running與nr_uninterruptible的

總和.(可參考函式calc_load_fold_active).*/

longcalc_load_active;

#ifdef CONFIG_SCHED_HRTICK

#ifdef CONFIG_SMP

/*在函式init_rq_hrtick初始化RunQueue High-Resolution

Tick時,此值預設為0.

在函式hrtick_start中,會判斷目前觸發的RunQueue

跟目前處理器所使用的RunQueue是否一致,

若是,就直接呼叫函式hrtimer_restart,反之就會

依據RunQueue中hrtick_csd_pending的值,如果

hrtick_csd_pending為0,就會透過函式

__smp_call_function_single讓RunQueue所在的另一個

處理器執行rq->hrtick_csd.func 所指到的函式

__hrtick_start. 並等待該處理器執行完畢後,

才重新把hrtick_csd_pending設定為1.

也就是說, RunQueue的hrtick_csd_pending是用來作為

SMP架構下,由處理器A觸發處理器B執行

_hrtick_start函式的一個保護機制.而有關在

SMP下如何由一個處理器觸發另一個處理器

執行函式的機制,可以參考kernel/smp.c中

相關smp_call_function_xxxxxxx的實作.s*/

inthrtick_csd_pending;

/*用以儲存hrtick機制中,要跨處理器執行的

函式結構.*/

structcall_single_data hrtick_csd;

#endif

/*為High-Resolution Tick的结构,會透過函式

hrtimer_init初始化.*/

structhrtimer hrtick_timer;

#endif

#ifdef CONFIG_SCHEDSTATS

/* latency stats */

/*為Scheduling Info.的統計結構,可以參考

include/linux/sched.h中的宣告. 例如在每次觸發

Schedule時,呼叫函式schedule_debug對上一個Task

的lock_depth進行確認(Fork一個新的Process 時,

會把此值預設為-1就是No-Lock,當呼叫

Kernel Lock時, 就會把Current Task的lock_depth加一.),

若lock_depth>=0,就會累加Scheduling Info.的bkl_count值,

用以代表Task Blocking的次數.*/

structsched_info rq_sched_info;

/*可用以表示RunQueue中的Task所得到CPU執行

時間的累加值.

在發生Task Switch時,會透過sched_info_switch呼叫

sched_info_arrive並以目前RunQueue Clock值更新

Task 的sched_info.last_arrival時���,而在Task所分配時間

結束後,會在函式sched_info_depart中以現在的

RunQueue Clock值減去Task的sched_info.last_arrival

時間值,得到的 Delta作為變數rq_cpu_time的累

加值.*/

unsignedlonglongrq_cpu_time;

/* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */

/* sys_sched_yield() stats */

/*用以統計呼叫System Call sys_sched_yield的次數.*/

unsignedintyld_count;

/* schedule() stats */

unsignedintsched_switch;

/*可用以統計觸發Scheduling的次數. 在每次觸發

Scheduling時,會透過函式schedule呼叫schedule_debug,

呼叫schedstat_inc對這變數進行累加.*/

unsignedintsched_count;

/*可用以統計進入到Idle Task的次數. 會在函式

pick_next_task_idle中,呼叫schedstat_inc對這變數進行

累加.*/

unsignedintsched_goidle;

/* try_to_wake_up() stats */

/*用以統計Wake Up Task的次數.*/

unsignedintttwu_count;

/*用以統計Wake Up 同一個處理器Task的次數.*/

unsignedintttwu_local;

/* BKL stats */

unsignedintbkl_count;

#endif

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值