Linux 進程調度

來源:互聯網
上載者:User

CFS調度器類

首先明確一點,CFS並不是調度器,而是一種調度器類。

傳統的調度器使用時間片的概念,對系統中的進程分別計算時間片,使得進程運行至時間片結束。在所有的進程時間片都以用盡後,重新計算時間片。而CFS調度器完全摒棄了時間片,會重點考慮進程的等待時間。

CFS調度器的目的是,向系統中每個啟用的進程提供最大的公正性,或者說確保沒有進程被虧待。注意CFS調度器僅對作用於調度類型為SCHED_NORMAL, SCHED_IDLE和SCHED_BATCH的進程。對於調度類型為SCHED_RR和SCHED_FIFO的即時進程,CFS調度器類並不起作用

CFS引入了虛擬時鐘的概念,該時鐘的流逝速度小於系統時鐘,精確的速度依賴於當前等待調度器挑選的進程的數目。假如隊列上有4個進程,那麼虛擬時鐘的速度是實際時鐘的1/4。如果系統時鐘過了20秒,那麼虛擬時鐘僅僅是5秒,從完全公平的方式來分配CPU,那麼虛擬時鐘的5秒恰好可以作為這個基準。

假定就緒隊列的虛擬時鐘為fair_clock,進程的等待時間儲存在wait_runtime,fair_clock表達了在完全公平條件下,進程需要啟動並執行時間,而wait_runtime則表明了該進程受到的不公平程度。fait_clock - wait_runtime越小表示越不公平,因此越需要被調度執行。

CFS引入了紅/黑樹狀結構來管理就緒隊列中的進程,索引值使用fair_clock-wait_runtime,因此最左邊的節點表示最不公平的進程。在這個進程獲得運行後,索引值要減去已經啟動並執行時間,這樣在這棵紅/黑樹狀結構中被移動到了右邊的一點。此時,另外一個進程變成了最左邊的節點,下一次會被調度器選擇執行。

上面的模型僅僅是一種理想的情況,實際運用中,我們還要考慮很多因素

  • 進程的不同優先順序,高優先順序的進程理應獲得更多的時間
  • 進程不能切換太頻繁,切換會導致進程上下文改變,頁表需要重新重新整理,代價很大。

調度器資料結構

調度器

調度器使用一系列資料結構來排序和管理系統中的進程。調度器的工作方式與這些結構的設計密切相關。

可以使用兩種方法啟用調度:

1. 直接啟用,比如進程打算休眠或者其他原因放棄CPU

2. 周期性機制,核心以固定的頻率,不時的檢測是否有必要進行進程切換。

task_struct成員

各個進程的task_struct有幾個結構和調度相關。

struct task_struct {......         int prio, static_prio, normal_prio;        struct list_head run_list;        const struct sched_class *sched_class;        struct sched_entity se;        unsigned int policy;        cpumask_t cpus_allowed;        unsigned int time_slice;        unsigned int rt_priority;.......} 

static_prio是進程的靜態優先順序,是在進程啟動時分配的優先順序,可以通過nice調用設定優先權

prio和normal_prio是進程的動態優先順序,

rt_priority是即時進程的優先順序,最低的即時優先順序是0,最高的即時優先順序是99,值越大優先順序越高,這和普通進程的靜態優先順序恰好相反

sched_class 是進程所屬的調度器類

policy表示了該進程的調度策略,當前支援5種調度策略:

  • SCHED_NORMAL,我們主要講這一類進程,CFS調度策略可以處理此類進程
  • SCHED_BATCH和SCHED_IDLE也通過CFS調度策略處理此類進程
  • SCHED_RR和SCHED_FIFO用於實現軟即時進程。這些不是由CFS調度類來出的,而是使用即時調度器類來處理的。

調度類

調度類用來判斷接下來運行哪個進程。核心支援不同的調度策略(完全公平調度,即時調度,在無事可做時調度空閑進程)。每一個進程剛好屬於某一個調度類,各個調度類負責管理所屬的進程。

調度器類提供了通用調度器和各個調度方法之間的關聯。調度器類由特定資料結構中彙集的幾個函數指標表示。全域調度器請求的各個操作都是可以由一個指標表示。這使得無需瞭解不同調度器類的內部工作原理,即可建立通用調度器。

對於各個調度類,都必須提供struct sched_class的一個執行個體。調度類之間的階層是平坦的:即時進程最終要,在完全公平進程之前處理;而完全公平進程則優先於空閑進程,空閑進程只有CPU無事可做時才處於活動狀態。

使用者進程無法直接和調度器類互動,他們只需要定義policy為SCHED_xyz。調度器會負責根據調度策略找到相應的調度器類。SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE會映射為fair_sched_class,而SCHED_RR和SCHED_FIFO與rt_sched_class關聯。fair_sched_class和rt_sched_class都是struct sched_class的執行個體,分別表示完全公平調度器和即時調度器。

就緒隊列

核心調度器用來管理活動進程的主要資料結構稱為就緒隊列。各個CPU都有自己的就緒隊列,各個活動進程只出現在一個就緒隊列中。在多個CPU中同時運行一個進程是不可能的。

就緒隊列是全域調度器許多操作的起點。但是注意,進程並不是由就緒隊列的成員直接管理的。這是調度器的職責,因此在就緒隊列中內嵌了特定與調度器類的子就緒隊列。

就緒隊列是使用下列資料結構實現的。

所有的就緒隊列都在runqueues數組中,該數組的每個元素分別對應於系統中的一個CPU。在單一處理器系統中,由於只有一個CPU,因此數組僅有一個元素。

 271 /* 272  * This is the main, per-CPU runqueue data structure. 273  * 274  * Locking rule: those places that want to lock multiple runqueues 275  * (such as the load balancing or the thread migration code), lock 276  * acquire operations must be ordered by ascending &runqueue. 277  */ 278 struct rq { 279     /* runqueue lock: */ 280     spinlock_t lock; 281  282     /* 283      * nr_running and cpu_load should be in the same cacheline because 284      * remote CPUs use both these fields when doing load calculation. 285      */ 286     unsigned long nr_running; 287     #define CPU_LOAD_IDX_MAX 5 288     unsigned long cpu_load[CPU_LOAD_IDX_MAX]; 289     unsigned char idle_at_tick; 290 #ifdef CONFIG_NO_HZ 291     unsigned char in_nohz_recently; 292 #endif 293     /* capture load from *all* tasks on this cpu: */ 294     struct load_weight load; 295     unsigned long nr_load_updates; 296     u64 nr_switches; 297  298     struct cfs_rq cfs; 299 #ifdef CONFIG_FAIR_GROUP_SCHED 300     /* list of leaf cfs_rq on this cpu: */ 301     struct list_head leaf_cfs_rq_list; 302 #endif 303     struct rt_rq rt; 304  305     /* 306      * This is part of a global counter where only the total sum 307      * over all CPUs matters. A task can increase this counter on 308      * one CPU and if it got migrated afterwards it may decrease 309      * it on another CPU. Always updated under the runqueue lock: 310      */ 311     unsigned long nr_uninterruptible; 312  313     struct task_struct *curr, *idle; 314     unsigned long next_balance; 315     struct mm_struct *prev_mm; 316  317     u64 clock, prev_clock_raw; 318     s64 clock_max_delta; 319  320     unsigned int clock_warps, clock_overflows; 321     u64 idle_clock; 322     unsigned int clock_deep_idle_events; 323     u64 tick_timestamp; 324  325     atomic_t nr_iowait; 326  327 #ifdef CONFIG_SMP 328     struct sched_domain *sd; 329  330     /* For active balancing */ 331     int active_balance; 332     int push_cpu; 333     /* cpu of this runqueue: */ 334     int cpu; 335  336     struct task_struct *migration_thread; 337     struct list_head migration_queue; 304  305     /* 306      * This is part of a global counter where only the total sum 307      * over all CPUs matters. A task can increase this counter on 308      * one CPU and if it got migrated afterwards it may decrease 309      * it on another CPU. Always updated under the runqueue lock: 310      */ 311     unsigned long nr_uninterruptible; 312  313     struct task_struct *curr, *idle; 314     unsigned long next_balance; 315     struct mm_struct *prev_mm; 316  317     u64 clock, prev_clock_raw; 318     s64 clock_max_delta; 319  320     unsigned int clock_warps, clock_overflows; 321     u64 idle_clock; 322     unsigned int clock_deep_idle_events; 323     u64 tick_timestamp; 324  325     atomic_t nr_iowait; 326  327 #ifdef CONFIG_SMP 328     struct sched_domain *sd; 329  330     /* For active balancing */ 331     int active_balance; 332     int push_cpu; 333     /* cpu of this runqueue: */ 334     int cpu; 335  336     struct task_struct *migration_thread; 337     struct list_head migration_queue;

nr_running指定了隊列上可運行進程的數目,包括所有優先順序和調度類上的可運行進程。

load提供了就緒隊列當前的負載度量,負載本質上和當前就緒隊列上活動進程的數目成正比,其中每個進程還要使用他們的優先順序作為權重。每個就緒隊列的虛擬時鐘就依賴於這個資訊。

cpu_load 可以用來跟蹤此前的CPU負荷負載狀態,在周期性調度scheduler_tick中會調用update_cpu_load函數來更新CPU負荷狀態,這個數組在負載遷移時會用到。

idle指向idle進程的task_struct結構。

cfs和rt是嵌入的子就緒隊列,分別用於CFS調度和即時調度。

clock,prev_clock_raw,用於實現就緒隊列自身的時鐘,周期性調度器會調用__update_rq_clock更新這兩個值,clock記錄的是本次更新時的時鐘值,而prev_clock_raw是上一次調用__update_rq_clock的值。

tick_timestamp,每次調用scheduler都會更新tick_timestamp為當前的tick


不重要成員變數

clock_warps 和clock_overflows,由於硬體的原因,在更新就緒隊列時鐘時系統會出現時間復原,或者時間前跳的問題,這兩個變數是為了統計需要,記錄復原和前跳發生的次數。

nr_load_updates 是系統調用update_cpu_load的次數。

由於調度器需要調度更具體化的實體,因此需要一個適當的資料結構來描述此類實體。定義如下:

struct sched_entity {        struct load_weight      load;           /* for load-balancing */        struct rb_node          run_node;        unsigned int            on_rq;        u64                     exec_start;        u64                     sum_exec_runtime;        u64                     vruntime;        u64                     prev_sum_exec_runtime;} 

load制定了權重,決定了各個實體占隊列總負荷的比例。計算負荷權重是調度器的一項重要任務。因為CFS所需的虛擬時鐘的速度最終依賴於負荷。

run_node是紅/黑樹狀結構的節點,調度實體通過這個節點掛接在紅/黑樹狀結構上

on_rq  表示該實體當前是否在就緒隊列上調度。

sum_exec_runtime 在進程運行時,我們需要記錄消耗的CPU時間,以變用於CFS調度。這個時間是實際時間。跟蹤已耗用時間是由update_curr不斷積累完成的。每次調用update_curr,都會計算目前時間和exec_start之間的差值,累加到sum_exec_runtime上,然後exec_start更新為目前時間。

vruntime 是執行期間,虛擬時間的流逝數量。

prev_sum_exec_runtime 在進程被撤銷CPU時,sum_exec_runtime儲存到prev_sum_exec_runtime中。在進程搶佔時會用到該資料。

每一個task都會內嵌一個sched_entity,所以進程是一個可調度實體,當然可調度實體不一定是進程。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.