日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

CFS調度算法調度時機的理解

作者:frankzfz 更新時間: 2022-10-11 編程語言

????????上一篇文章分析了cfs調度算法中vruntime的計算,cfs以vruntime的鍵值組成紅黑樹,CFS調度算法優先調度紅黑樹中最左邊的最小值。vruntime的大小決定CFS調度算法優先選擇就緒隊列中的哪個進程進行調度。下一步就是如何進行調度,調度在什么時候發生,比如:是否分配給該進程的CPU時間消耗完了,就會主動讓出CPU,設置可以被調度的標志?

????????進程的調度分為兩種類型,第一種是自愿讓出當前的CPU,這種情況可能是由于當前正在運行的CPU沒有獲取到繼續運行的資源,需要讓出當前的CPU,進入sleep狀態,等待需要的資源獲取到后繼續運行,或者本身調用schedule()函數進行調度,第二種情況下是非自愿的情況下,需要讓出當前占用的CPU,比如:當前進程在CPU上消耗的時間已經用完或者在周期調度中斷中發現有更高優先級進程需要被調度,則當前進程會被優先級更高的進程進行搶占,這種情況下可能是由于:scheduler_tick()函數進行調度。

調度器:

????????內核中有兩個調度器,一個是主調度器,一個是周期性調度器。主調度器的函數為schedule()函數,周期性調度器的函數為scheduler_tick。周期性調度器和系統的時鐘中斷有關,這個值可以在/boot/config 文件中的配置參數查看具體的值,目前大部分x86的機型為配置為:CONFIG_HZ=1000,也就是1ms發生一次中斷,1ms調用一次scheduler_tick函數,在scheduler_tick函數中會有判斷是否需要調度當前的進程,比如:當前進程的CPU時間是否用完, 是否有更高有優先級的進程在就緒隊列,上述情況都有可能導致設置進程的標志為TIF_NEED_RESCHED,然后在中斷返回時,調用schedule()進行進程切換。

?????? 調用schedule()函數進行進程調度,進行切換的時機分為下面三種方式:

  1. 在阻塞過程中的進程,比如因為下面的互斥量mutex, ?信號量semaphore,等待隊列waitqueue等導致的阻塞。都會調用schedule()函數進行進程的調度。
  2. 在中斷返回和用戶空間返回過程中,檢測標志位:TIF_NEED_RESCHED,查看進程是否需要調度,在時間中斷處理函數中scheduler_tick,為了任務之間可以搶占,會設置該標志位。
    void scheduler_tick(void)
         {
            int cpu = smp_processor_id();
             struct rq *rq = cpu_rq(cpu);
             struct task_struct *curr = rq->curr;
             struct rq_flags rf;
             unsigned long thermal_pressure;
             u64 resched_latency;
        
             arch_scale_freq_tick();
             sched_clock_tick();
        
             rq_lock(rq, &rf);
        
             update_rq_clock(rq);
             thermal_pressure = arch_scale_thermal_pressure(cpu_of(rq));
             update_thermal_load_avg(rq_clock_thermal(rq), rq, thermal_pressure);
             curr->sched_class->task_tick(rq, curr, 0); // (1) 調用cfs調度程序周期調度函數task_tick_fair
             if (sched_feat(LATENCY_WARN))
                 resched_latency = cpu_resched_latency(rq);
             calc_global_load_tick(rq);
        
             rq_unlock(rq, &rf);
       
             if (sched_feat(LATENCY_WARN) && resched_latency)
                 resched_latency_warn(cpu, resched_latency);
     
           perf_event_task_tick();
    }
    

  3. 被喚醒的進程不會立刻調用schedule()函數進行調度,而是被加入到cfs調度隊列的就緒隊列中,并且被設置為TIF_NEED_RESCHED。如果內核的配置文件被設置了 CONFIG_PREEMPTION=y,內核會根據配置文件設置了是否可搶占,進行相應的處理。整個調度過程中,調度的觸發和執行是分開的。上述調度的時機是確定的,通過上面的三個時機,是無法完全保障進程的CPU消耗完成后,就主動調用調度函數,進行進程的切換。

在周期調度過程中,有幾個和調度相關的變量:

unsigned int sysctl_sched_min_granularity?????????? = 750000ULL;//最小調度間隔

static unsigned int sched_nr_latency = 8;???? //進程數 8

unsigned int sysctl_sched_latency?????????? = 6000000ULL //調度周期6ms

我們重點分析一下?check_preempt_tick函數:

static void
  check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
  {
       unsigned long ideal_runtime, delta_exec;
     struct sched_entity *se;
     s64 delta;
  
       ideal_runtime = sched_slice(cfs_rq, curr);
      delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
     if (delta_exec > ideal_runtime) {
           resched_curr(rq_of(cfs_rq));
           /*
           |* The current task ran long enough, ensure it doesn't get
          |* re-elected due to buddy favours.
           |*/
          clear_buddies(cfs_rq, curr);
           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.
       |確保一個被喚醒搶占的進程不必等待一個完整的調度周期才能夠被調度,目的就是減少調度時延,通過上面的注釋其實得不出來,該值為進程占用的最小CPU運行時間,delta_exec是當前進程實際已經占用的CPU時間,如果進程delta_exec(實際運行時間)大于sysctl_sched_min_granularity 就有可能會被設置可調度標志,本意就是為了確保如果有喚醒的進程,在保障當前進程最小運行時間的情況下,盡快進行調度。而不是等該進程完全消耗完CPU時間*/
      if (delta_exec < sysctl_sched_min_granularity)
          return;
se = __pick_first_entity(cfs_rq);
     delta = curr->vruntime - se->vruntime;
  //計算當前的vruntime和紅黑樹最左邊的vruntime的差值,如果當前的vruntime小于紅黑樹最左邊的vruntime就不設置調度標志。繼續運行,因為當前正在運行進程的vruntime是最小的。
       if (delta < 0)
           return;
   //如果差值大于ideal_runtime則設置可調度標志,這里的一個場景應該是,如果一個A進程剛被喚醒,為了補充A進程,設置A進程的vruntime較小,小于當前運行的進程, 其兩者的差值大于ideal_runtime,則發送調度。主要是為了不讓進程等待較多的時間。雖然當前進程的CPU時間還沒有消耗完,也需要被設置可調度。
       if (delta > ideal_runtime)
          resched_curr(rq_of(cfs_rq));
   }

調度周期(延遲):

調度周期的含義為在一個調度周期內,保證就緒隊列中的所有進程都會被調度一遍,默認的調度周期為:unsigned int sysctl_sched_latency?? ?= 6000000ULL //調度周期6ms,如果當前進程數大于sched_nr_latency ,則調度周期設置為當前就緒隊列數乘以* sysctl_sched_min_granularity ?在__sched_period()函數中計算:

static u64 __sched_period(unsigned long nr_running)
 {
     if (unlikely(nr_running > sched_nr_latency))
         return nr_running * sysctl_sched_min_granularity;
     else
         return sysctl_sched_latency;
 }

?總結:

????????從上面的分析可以得出,由于在實際調度過程中涉及到多種因素,進程的調度其實沒有嚴格按照進程理論中計算的CPU時間一樣運行,因為調度的時機是確定的,特別是時鐘中斷的調度,在時鐘中斷中對進程占用的CPU時間進行判斷時,大部分情況發生調度是在進程占用的CPU時間已經大于理論運行時間,因為這個是和時鐘中斷函數的調用周期有關系的,這是一種被動的調度。或者由于其他優先級更高的進程需要調度,當前進程應該讓出CPU等多種因素的影響。只有在絕對理想的情況下才有可能出現每個進程占用的CPU時間,等于理論運行時間。比如:當前單核CPU的服務器,系統的tick是1ms的情況下,一共三個優先級權重一樣的進程,每個進程應該分為2ms,在一個調度周期內,每個進程會占用2ms的CPU時間。但是在現實系統中基本上不存在此類情況,環境中可能是多個CPU核,tick可能是2m或者4ms,可能不斷的有進程加入到就緒隊列,也有進程進入睡眠等。都可能導致CPU的調度復雜度提升。 ???????所以在內核中使用delta_exec變量記錄進程真正占用的CPU時間,使用這個變量和理論運行時間進行對比,來判斷是否進行調度。而不是delta_exec大于理論運行時間后,就立刻被調度。

注:在較新的內核版本中上面的值都無法進行sysctl和proc進行配置。

?

原文鏈接:https://blog.csdn.net/frankzfz/article/details/127094354

欄目分類
最近更新