2014年9月14日 星期日

linux kernel run queue (struct rq)


source:rq



508 struct rq {
509         /* 用以作為runqueue的SpinLock: */
510         raw_spinlock_t lock; 
512         /*用以記錄目前CPU RUNQueue中執行的Task數量*/
516         unsigned int nr_running;
517 #ifdef CONFIG_NUMA_BALANCING
518         unsigned int nr_numa_running;
519         unsigned int nr_preferred_running;
520 #endif  
521         #define CPU_LOAD_IDX_MAX 5
/* cpu_load主要用以表示處理器的負載,目前這個Array大小為5 (定義在CPU_LOAD_IDX_MAX),在每個處理器的RunQueue中都會有對應到該處理器的cpu_load參數配置,在每次處理器觸發Scheduler Tick時, 都會呼叫函式update_cpu_load_active,進行cpu_load的更新.

在系統初始化時,會呼叫函式sched_init把RunQueue的cpu_load Array初始化為0.
了解cpu_load Array更新最好的方式應該是透過函式update_cpu_load (實作在kernel/sched.c),基於這函式的實作,我們可以把cpu_load Array每個數值公式說明如下.
cpu_load[0]會直接等於RunQueue中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] (也就是會等於RunQueue中的load.weight).而在進行CPU Blance或Migration時,就會呼叫函式source_load與target_load取得對應處理器與對應cpu_load index值,來進行計算.*/
522         unsigned long cpu_load[CPU_LOAD_IDX_MAX];
/*用以記錄最後一次更新CPU Load的時間值. (會把當時的jiffies值儲存到這變數).*/
523         unsigned long last_load_update_tick;
524 #ifdef CONFIG_NO_HZ_COMMON
525         u64 nohz_stamp;
526         unsigned long nohz_flags;
527 #endif
528 #ifdef CONFIG_NO_HZ_FULL
529         unsigned long last_sched_tick;
530 #endif
/*如果skip_clock_update的值被設定為1,此時透過函式update_rq_clock要進行RunQueue Clock值的更新時,就會直接返回.(每次呼叫update_rq_clock函式,都會讓RunQueue中的Clock值等於sched_clock_cpu的返回值.*/
531         int skip_clock_update;
533        /* capture load from *all* tasks on this cpu: */
/*參考include/linux/sched.h中struct load_weight的宣告如下
struct load_weight {
unsigned long weight, inv_weight;
};
RunQueue中的load->weight值,會是目前所執行的schedule entity的load->weight的總和,也就是說,RunQueue的load->weight越高,也表示所負責的排程單元load->weight總合越高,表示處理器所負荷的執行單元也越重*/
534         struct load_weight load;
/*在每次Scheduler Tick中呼叫update_cpu_load時,這個值都會加一,可以用來反應目前CPU Load更新的次數.*/
535         unsigned long nr_load_updates;
/*用來累加處理器進行Context Switch的次數,會在函式schedule呼叫時進行累加,並可以透過函式nr_context_switches統計目前所有處理器總共的Context Switch次數. 或是可以透過查看檔案/proc/stat中的ctxt欄位得知目前整個系統觸發Context Switch的總數.*/
536         u64 nr_switches;
/*為CFS Fair Scheduling Class的 RunQueue*/
538         struct cfs_rq cfs;
/*為Real-Time Scheduling Class的RunQueue*/
539         struct rt_rq rt;
540         struct dl_rq dl;
542 #ifdef CONFIG_FAIR_GROUP_SCHED/*用以支援可以Group CFS Tasks的機制*/
543         /* list of leaf cfs_rq on this cpu: */
/*在有設置Fair Group Scheduling的環境下,會基於原本CFS RunQueue與包含有若干Task的Group所成的排程集合,也就是說當有一個Group A,其下有20個Tasks,而這Group A就會有自己的CFS RunQueue用來排程自己所屬的Tasks,而屬於這Group A的Tasks所使用到的處理器時間,就會以這Group A總體所分得的時間為上限. 基於 Cgroup的 Fair Group Scheduling架構,可以創造出有階層性的Tasks組織,根據不同Tasks的功能群組化,在配置給該群組對應的處理器資源,讓屬於該群組下的Tasks可以透過RunQueue機制排程,使用屬於該群組所配置到的資源.

這個變數主要是用來管理CFS RunQueue List,操作上可以透過函式list_add_leaf_cfs_rq把一個Group CFS RunQueue加入到List中,或透過函式list_del_leaf_cfs_rq把一個Group CFS RunQueue移除. 並可透過for_each_leaf_cfs_rq把一個 RunQueue上的所有leaf cfs_rq走過一遍.*/
544         struct list_head leaf_cf
546         struct sched_avg avg;
547 #endif /* CONFIG_FAIR_GROUP_SCHED */
549         /*
550          * This is part of a global counter where only the total sum
551          * over all CPUs matters. A task can increase this counter on
552          * one CPU and if it got migrated afterwards it may decrease
553          * it on another CPU. Always updated under the runqueue lock:
554          */
/*一般來說,Linux Kernel的Task狀態可以為TASK_RUNNING, TASK_INTERRUPTIBLE (Sleep) , TASK_UNINTERRUPTIBLE (Deactivate Task,此時Task會從RunQueue移除)或TASK_STOPPED. (在此先忽略Trace,Zombie,Dead..etc). 透過這個變數會統計目前RunQueue中有多少Task屬於TASK_UNINTERRUPTIBLE的狀態.當呼叫函式activate_task時,會把nr_uninterruptible值減一,並透過函式enqueue_task把對應的Task依據所在的Scheduling Class放到對應的RunQueue中,並把目前RunQueue的nr_running值加一.*/
555         unsigned long nr_uninterruptible;
/*為指向三個Task的指標. (Current Task,Idle Task與最高執行級的Stop Task).
curr : 指向目前處理器正在執行的Task
idle: 指向屬於Idle-Task Scheduling Class的Idle Task.
stop: 指向目前最高等級屬於 Stop-Task Scheduling Class的Task*/
557         struct task_struct *curr, *idle, *stop;
/*基於處理器的jiffies值,用以記錄下次進行處理器Balancing的時間點*/
558         unsigned long next_balance;
/*用以儲存Context-Switch發生時,前一個Task的Memory Management結構,並可用在函式finish_task_switch中,透過函式mmdrop釋放前一個Task的記憶體資源.*/
559         struct mm_struct *prev_mm;
/*用以記錄目前RunQueue的Clock值,基本上該值會等於透過sched_clock_cpu(cpu_of(rq))的回傳值,並會在每次呼叫scheduler_tick時透過函式update_rq_clock更新目前RunQueue clock值.

在實作部分,函式sched_clock_cpu會透過sched_clock_local或ched_clock_remote取得對應的sched_clock_data,而處理器的sched_clock_data值,會透過函式sched_clock_tick在每次呼叫scheduler_tick時進行更新.
補充….
從函式update_rq_clock的實作來說,其實
delta = sched_clock_cpu(cpu_of(rq)) – rq->clock;
rq->clock += delta;
可以直接寫成
rq->clock= sched_clock_cpu(cpu_of(rq));*/
561         u64 clock;
/*這個值會等於從上一次更新RunQueue Clock時,到這次要更新RunQueue Clock時的時間間隔 Delta 減去在這段時間中,處理器用來進行Soft-IRQ與Hardware-IRQ的時間差值的累加.
簡要來說,這個值會透過函式update_rq_clock_task進行更新,用來反應出RunQueue中Task實際執行時所佔用到的處理器時間Clock累加值*/
562         u64 clock_task;
/*用以記錄目前RunQueue中有多少Task處於等待I/O的Sleep狀態.
在實際的使用上,例如當Driver接收來自Task的調用,但處於等待I/O回覆的階段時,為了充分利用處理器的執行資源,這時就可以在Driver中呼叫函式io_schedule,此時就會把目前RunQueue中的nr_iowait加一,並設定目前Task的in_iowait為1,然後觸發Scheduling讓其他Task有機會可以得到處理器執行時間*/
564         atomic_t nr_iowait;
566 #ifdef CONFIG_SMP
/*Root Domain是基於多核心架構下的機制,會由RunQueue結構記住目前採用的Root Domain,其中包括了目前的CPU Mask(包括 Span, Online 與 RT Overload), Reference Count 跟cpupri.
當Root Domain有被RunQueue參考到時, refcount就會加一,反之就是減一. 而CPU Mask Span表示RunQueue可掛上的CPU Mask, Online為RunQueue目前已經掛上排程的CPU Mask,最後的RT Overload(rto_mask)代表的是這個RunQueue可在對應的rto_mask CPU上執行Real-Time Task. 可以參考檔案kernel/sched_rt.c中函式pull_rt_task的實作,當一個RunQueue中屬於Real-Time的Task已經執行完畢(也就是rq->rt.rt_nr_running==0),就會透過函式pull_rt_task從該RunQueue中屬於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機制進行初始化*/
567         struct root_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.*/
568         struct sched_domain *sd;
/*在SMP架構下,這值可用以表示目前RunQueue所在處理器的計算能力,在函式sched_init初始化時,會把這值設定為SCHED_LOAD_SCALE (=Nice 0的Load Weight=1024).並可透過函式update_cpu_power (in kernel/sched_fair.c)更新這個值.
處理器的CPU capacitr值越大,表示這個處理器有更多的計算能力,可以分擔CPU Power值越小的處理器上的Tasks任務.*/
570         unsigned long cpu_capacity;
/*這值會等於函式idle_cpu的返回值,如果為1表示目前CPU RunQueue中執行的為Idle Task. 反之為0,則表示處理器執行的不是Idle Task (也就是說處理器正在忙碌中.)*/
572         unsigned char idle_balance;
573         /* For active balancing */
/*若這值不為0,表示會有在Schedule排程動作結束前,要呼叫的收尾函式. (實作為inline函式post_schedule in kernel/sched.c),目前只有Real-Time Scheduling Class有支援這個機制(會呼叫函式has_pushable_tasks in kernel/sched_rt.c).*/
574         int post_schedule;
/*當RunQueue中此值為1,表示這個RunQueue正在進行Fair Scheduling的Load Balance,此時會呼叫stop_one_cpu_nowait暫停該RunQueue所屬處理器的排程,並透過函式active_load_balance_cpu_stop,把Tasks從最忙碌的處理器,移到Idle的處理器上執行.*/
575         int active_balance;
/*用以儲存目前進入Idle且負責進行 Load Balance流程的處理器ID. 呼叫的流程為,在呼叫函式schedule時,若該處理器RunQueue的nr_running為0 (也就是目前沒有正在執行的Task),就會呼叫idle_balance,並觸發後續Load Balance流程.*/
576         int push_cpu;
/*用以儲存給Stopper Scheduling Class所要執行的函式與其對應參數. 會作為在函式stop_one_cpu_nowait中呼叫函式cpu_stop_queue_work的參數*/
577         struct cpu_stop_work active_balance_work;
578         /* cpu of this runqueue: */
/*用以儲存目前運作這個RunQueue的處理器ID.*/
579         int cpu;
/*表示目前此RunQueue有在對應的處理器掛上並執行*/
580         int online;
582         struct list_head cfs_tasks;
/*這個值會由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平均被分配到的執行時間值.*/
584         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的實作)*/
585         u64 age_stamp;
/*這值會在觸發Scheduling時,若判斷目前處理器RunQueue沒有正在運作的Task,就會透過函式idle_balance更新這值為為目前RunQueue的clock值.可用以表示這個處理器是何時進入到Idle的狀態.*/
586         u64 idle_stamp;
/*會在有Task運作且idle_stamp不為0 (表示前一個狀態是在Idle)時以目前RunQueue的clock減去idle_stamp所計算出的Delta值為依據,更新這個值. 可反應目前處理器進入Idle狀態的時間長短.*/
587         u64 avg_idle;
589         /* This is used to determine avg_idle's max value */
590         u64 max_idle_balance_cost;
591 #endif
593 #ifdef CONFIG_IRQ_TIME_ACCOUNTING
/*這值會透過函式update_rq_clock_task更新,可用以代表處理器執行Soft-IRQ與Hardware-IRQ所花的處理器Clock值的累加.s*/
594         u64 prev_irq_time;
595 #endif
596 #ifdef CONFIG_PARAVIRT
597         u64 prev_steal_time;
598 #endif+
599 #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING
600         u64 prev_steal_time_rq;
601 #endif
603         /* calc_load related fields */
/*用以記錄下一次計算CPU Load的時間,初始值為目前的jiffies加上五秒與1次的Scheduling Tick的間隔 (=jiffies + LOAD_FREQ,且LOAD_FREQ=(5*HZ+1))*/
604         unsigned long calc_load_update;
/*會等於RunQueue中nr_running與nr_uninterruptible的總和.(可參考函式calc_load_fold_active).*/
605         long calc_load_active;
607 #ifdef CONFIG_SCHED_HRTICK
608 #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*/
609         int hrtick_csd_pending;
/*用以儲存hrtick機制中,要跨處理器執行的函式結構.*/
610         struct call_single_data hrtick_csd;
611 #endif
/*為High-Resolution Tick的結構,會透過函式hrtimer_init初始化*/
612         struct hrtimer hrtick_timer;
613 #endif
615 #ifdef CONFIG_SCHEDSTATS
616         /* 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的次數.*/
617         struct sched_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的累加值*/
618         unsigned long long rq_cpu_time;
619         /* could above be rq->cfs_rq.exec_clock + rq->rt_rq.rt_runtime ? */
621         /* sys_sched_yield() stats */
/*用以統計呼叫System Call sys_sched_yield的次數*/
622         unsigned int yld_count;
624         /* schedule() stats */
/*可用以統計觸發Scheduling的次數. 在每次觸發Scheduling時,會透過函式schedule呼叫schedule_debug,呼叫schedstat_inc對這變數進行累加*/
625         unsigned int sched_count;
/*可用以統計進入到Idle Task的次數. 會在函式pick_next_task_idle中,呼叫schedstat_inc對這變數進行累加*/
626         unsigned int sched_goidle;
628         /* try_to_wake_up() stats */
/*用以統計Wake Up Task的次數*/
629         unsigned int ttwu_count;
/*用以統計Wake Up 同一個處理器Task的次數.*/
630         unsigned int ttwu_local;
631 #endif
633 #ifdef CONFIG_SMP
634         struct llist_head wake_list;
635 #endif
636 };

沒有留言:

張貼留言