<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      Fork me on GitHub
      側邊欄

      CFS任務的負載均衡(load balance)

      前言

      我們描述CFS任務負載均衡的系列文章一共三篇,第一篇是框架部分,第二篇描述了task placement和active upmigration兩個典型的負載均衡場景。本文是第三篇,主要是分析各種負載均衡的觸發和具體的均衡邏輯過程。

      本文出現的內核代碼來自Linux5.10.61,為了減少篇幅,我們盡量刪除不相關代碼,如果有興趣,讀者可以配合代碼閱讀本文。

      一、幾種負載均衡的概述

      整個Linux的負載均衡器有下面的幾個類型:

      1

      實際上內核的負載均衡器(本文都是特指CFS任務的)有兩種,一種是為繁忙CPU們準備的periodic balancer,用于CFS任務在busy cpu上的均衡。還有一種是為idle cpu們準備的idle balancer,用于把繁忙CPU上的任務均衡到idle cpu上來。idle balancer有兩種,一種是nohz idle balancer,另外一種是new idle balancer。

      周期性負載均衡(periodic load balance或者tick load balance)是指在tick中,周期性的檢測系統的負載均衡狀況。周期性負載均衡是一個自底向上的均衡過程。即從該CPU對應的base sched domain開始,向上直到頂層sched domain,在各個level的domain上進行負載均衡。具體在某個特定的domain上進行負載均衡是比較簡單,找到domain中負載最重的group和CPU,將其上的runnable任務拉到本CPU以便讓該domain上各個group的負載處于均衡的狀態。由于Linux上的負載均衡僅支持任務拉取,周期性負載均衡只能在busy cpu之間均衡,不能把任務push到其他空閑CPU上,要想讓系統中的idle cpu“燥起來”就需要借助idle load balance。

      NOHZ load balance是指其他的cpu已經進入idle(錯過new idle balance),本CPU任務太重,需要通過ipi將其他idle的CPUs喚醒來進行負載均衡。為什么叫NOHZ load balance呢?那是因為這個balancer只有在內核配置了NOHZ(即tickless mode)下才會生效。如果CPU進入idle之后仍然有周期性的tick,那么通過tick load balance就能完成負載均衡了,不需要IPI來喚醒idle的cpu。和周期性均衡一樣,NOHZ idle load balance也是通過busy cpu上tick驅動的,如果需要kick idle load balancer,那么就會通過GIC發送一個ipi中斷給選中的idle cpu,讓它代表系統所有的idle cpu們進行負載均衡。NOHZ load balance具體均衡的方式和tick balance類似,也是自底向上,在整個sched domain hierarchy進行均衡的過程,不同的是NOHZ load balance會在多個CPU上執行這個均衡過程。

      (1)在tick中觸發load balance。我們稱之tick load balance或者periodic load balance。具體的代碼執行路徑是:

      (2)調度器在pick next的時候,當前cfs runque中沒有runnable,只能執行idle線程,讓CPU進入idle狀態。我們稱之new idle load balance。具體的代碼執行路徑是:

      (3)其他的cpu已經進入idle,本CPU任務太重,需要通過ipi將其idle的cpu喚醒來進行負載均衡。我們稱之nohz idle load banlance,具體的代碼執行路徑是:

      1. 周期性負載均衡 (Periodic / Tick Load Balance)

      • 觸發場景
        • CPU處于busy狀態,周期性tick觸發。
        • 目標:在busy CPU之間均衡CFS任務。
      • 特點
        • 自底向上遍歷 sched_domain
        • 只支持任務拉取(pull),不能主動push到idle CPU。
      • 主要函數
        • scheduler_tick()trigger_load_balance()rebalance_domains()load_balance()

      2. New Idle Load Balance

      • 觸發場景
        • 某CPU剛進入idle狀態(調度器發現沒有任務)。
        • 目標:嘗試從其他CPU拉取任務,讓自己忙起來。
      • 特點
        • 只在CPU剛變idle時執行一次。
        • 如果沒有拉到任務,就不再繼續。
      • 主要函數
        • newidle_balance()load_balance()

      3. NOHZ Idle Load Balance

      • 觸發場景
        • 系統開啟NOHZ(tickless模式)。
        • 有CPU已經idle且錯過了new idle balance。
        • busy CPU發現負載過重,需要kick idle CPU來幫忙均衡。
      • 特點
        • busy CPU通過IPI喚醒一個idle CPU執行均衡。
        • 也是自底向上遍歷 sched_domain
      • 主要函數
        • nohz_balancer_kick()nohz_idle_balance()rebalance_domains()load_balance()

      總結對比

      類型 觸發條件 目標 關鍵函數鏈路
      Periodic tick周期性觸發 busy CPU之間均衡 scheduler_tick()rebalance_domains()
      New Idle CPU剛進入idle 拉任務到idle CPU newidle_balance()
      NOHZ Idle tickless模式,busy CPU kick idle CPU idle CPU幫忙均衡 nohz_idle_balance

      二、周期性負載均衡

      1、觸發

      當tick到來的時候,在scheduler_tick函數中會調用trigger_load_balance來觸發周期性負載均衡,相關的代碼如下:

      // Trigger the SCHED_SOFTIRQ if it is time to do periodic load balancing.
      void trigger_load_balance(struct rq *rq) {
          if (time_after_eq(jiffies, rq->next_balance))
              raise_softirq(SCHED_SOFTIRQ);-----觸發periodic balance
              nohz_balancer_kick(rq);-----觸發nohz idle balance
      }
      

      整個代碼非常的簡單,主要的邏輯就是調用raise_softirq觸發SCHED_SOFTIRQ,當然要滿足均衡間隔時間的要求。nohz_balancer_kick用來觸發nohz idle balance的,這是后面章節要仔細描述的內容。上面的代碼片段,我特地保留了函數的注釋,這里看起似乎注釋不對,因為這個函數不但觸發的周期性均衡,也觸發了nohz idle balance。然而,其實nohz idle balance本質上也是另外一種意義上的周期性負載均衡,只是因為CPU進入idle,無法產生tick,因此讓能產生tick的busy CPU來幫忙觸發tick balance。而實際上tick balance和nohz idle balance都是通過SCHED_SOFTIRQ的軟中斷來處理,最后都是執行run_rebalance_domains這個函數,也就是說著兩種均衡本質都是一樣的。

      2、均衡處理

      SCHED_SOFTIRQ類型的軟中斷處理函數是run_rebalance_domains,代碼邏輯如下:

      if (nohz_idle_balance(this_rq, idle))------nohz idle balance
      	return;
      update_blocked_averages(this_rq->cpu);
      rebalance_domains(this_rq, idle);-----周期性均衡
      

      nohz idle balance和periodic load balance都是通過SCHED_SOFTIRQ類型的軟中斷來完成,也就是說它們兩個都是通過SCHED_SOFTIRQ注冊的handler函數run_rebalance_domains來完成其功能的,這時候就有一個先后順序的問題了,哪一個先執行?

      從上面的代碼可見調度器優先處理nohz idle balance,畢竟nohz idle balance是一個全局的事情(代表系統所有idle cpu做均衡),而periodic load balance只是均衡自己的各階sched domain。如果先執行this cpu的均衡,那么在執行rebalance_domains有可能拉取負載到this cpu,這會導致在執行nohz_idle_balance的時候會忽略其他idle cpu而直接退出(nohz idle balance要求選中的cpu是idle的)。如果成功進行了nohz idle balance,那么就沒有必要進行周期性均衡了。

      周期性負載均衡的主要代碼邏輯在rebalance_domains函數中(也是nohz idle balance的主入口函數),如下:

      for_each_domain(cpu, sd) {
          if (time_after(jiffies, sd->next_decay_max_lb_cost)) {-------A
              sd->max_newidle_lb_cost = (sd->max_newidle_lb_cost * 253) / 256;
              sd->next_decay_max_lb_cost = jiffies + HZ;
              need_decay = 1;
          }
      
      	max_cost += sd->max_newidle_lb_cost;
      
          if (!continue_balancing) {--------------------B
              if (need_decay) continue;
              break;
          }
      
          interval = get_sd_balance_interval(sd, busy);
          if (time_after_eq(jiffies, sd->last_balance + interval)) {-------------C
              if (load_balance(cpu, rq, sd, idle, &continue_balancing)) {
                  idle = idle_cpu(cpu) ? CPU_IDLE : CPU_NOT_IDLE;
                  busy = idle != CPU_IDLE && !sched_idle_cpu(cpu);
              }
              sd->last_balance = jiffies;
              interval = get_sd_balance_interval(sd, busy);
      	}
      out:
          if (time_after(next_balance, sd->last_balance + interval)) {------D
              next_balance = sd->last_balance + interval;
              update_next_balance = 1;
          }
      }
      

      A、max_newidle_lb_cost是sched domain上的最大newidle load balance的開銷。這個開銷會隨著時間進行衰減,每1秒衰減1%。此外,這里還匯聚了各個sched domain上的max_newidle_lb_cost,賦值給rq->max_idle_balance_cost,用來控制new idle balance的深度。具體細節后面會詳細描述。

      B、這里的循環控制是從base domain直到頂層domain,但是實際上,越是上層的sched domain,其覆蓋的cpu就越多,如果每一個CPU的周期性負載均衡都對高層domain進行均衡那么高層domain被擼的遍數也太多了,所以這里通過continue_balancing控制均衡的level。這里還有一個特殊場景:需要更新runqueue的max_idle_balance_cost(need_decay等于true)的時候,這個場景仍然需要遍歷各個domain,但是僅僅是更新new idle balance開銷(把各個層級衰減的max_newidle_lb_cost體現到rq的max_idle_balance_cost)。

      C、在滿足該sched domain負載均衡間隔的情況下,調用load_balance在指定的domain進行負載均衡。如果load_balance的確完成了某些任務搬移,那么需要更新this cpu的busy狀態。這里并不能直接確定this cpu的繁忙狀態,因為load_balance可能會修改dst cpu,從而導致任務搬移并非總是拉任務到本CPU。CPU繁忙狀態的變更也導致我們需要重新調用get_sd_balance_interval獲取

      D、每個level的sched domain都會計算下一次均衡的時間點,這里記錄最近的那個均衡時間點(傾向性能,盡快進行均衡),并在后面賦值給rq->next_balance。這樣,在下次tick中,我們通過rq->next_balance來判斷是否需要觸發周期性負載均衡(trigger_load_balance),從而降低均衡次數,避免不必要均衡帶來的開銷。Nohz idle balance稍微復雜一些,因此還要考慮更新blocked load的場景。具體下一章描述。

      rebalance_domains第二段代碼主要內容是把各個層級sched domain上的變化傳遞到runqueue上去的,具體如下:

      if (need_decay) {--------------------------------------A
      	rq->max_idle_balance_cost =
      	max((u64)sysctl_sched_migration_cost, max_cost);
      }
      if (likely(update_next_balance)) {----------------B
      	rq->next_balance = next_balance;
      	if ((idle == CPU_IDLE) && time_after(nohz.next_balance, rq->next_balance))
      		nohz.next_balance = rq->next_balance;--------C
      }
      

      A、如果該cpu的任何一個level的domain衰減了idle balance cost,那么就需要更新到rq->max_idle_balance_cost,該值是匯聚了各個level的domain的new idle balance的最大開銷。

      B、rq->next_balance是綜合考慮各個level上domain的下次均衡點最近的那個時間點,在這里完成更新。

      C、如果本CPU處于idle狀態,那么有可能還需要更新到nohz.next_balance,用于觸發nohz idle balance,是否更新主要是看是否該cpu下次均衡時間點距離當前時間點更近。

      3、Sched domain的均衡間隔控制

      負載均衡執行的頻次其實是在延遲和開銷之間進行平衡。不同level的sched domain上負載均衡帶來的開銷是不一樣的。在手機平臺上,MC domain在inter-cluster之內進行均衡,對性能的影響小一點。但是DIE domain上的均衡需要在cluster之間遷移任務,對性能和功耗的影響都比較大一些(例如cache命中率,或者一個任務遷移到原來深度睡眠的大核CPU)。因此執行均衡的時間間隔應該是和domain的層級相關的。此外,負載狀況也會影響均衡的時間間隔,在各個CPU負載比較重的時候,均衡的時間間隔可以拉大,畢竟大家都忙,讓子彈先飛一會,等塵埃落定之后在執行均衡也不遲。

      struct sched_domain中和均衡間隔控制相關的數據成員包括:

      成員 描述
      last_balance 最近在該sched domain上執行均衡操作的時間點。判斷sched domain是否需要進行均衡的標準是對比當前jiffies值和last_balance+interval這里的interval是get_sd_balance_interval實時獲取的
      min_intervalmax_interval 做均衡也是需要開銷的,我們不能時刻去檢查調度域的均衡狀態,這兩個參數定義了檢查該sched domain均衡狀態的時間間隔的范圍。min_interval缺省設定為sd weight,即sched domain內CPU的個數。max_interval等于2倍的min_interval。
      balance_interval 定義了該sched domain均衡的基礎時間間隔,一方面,該值和sched domain所處的層級有關,層級越高,覆蓋的CPU越多,balance_interval越大。另外一方面,在調用load_balance的時候,會根據實際的均衡情況對其進行加倍或者保持原值。
      busy_factor 正常情況下,balance_interval定義了均衡的時間間隔,如果cpu繁忙,那么均衡要時間間隔長一些,即時間間隔定義為busy_factor x balance_interval。缺省值是32

      具體控制均衡間隔的函數是get_sd_balance_interval,代碼如下:

      unsigned long interval = sd->balance_interval;-----------------A
      if (cpu_busy)------------------------B
          interval *= sd->busy_factor;
          interval = msecs_to_jiffies(interval);
      if (cpu_busy)-----------------------C
          interval -= 1;
          interval = clamp(interval, 1UL, max_load_balance_interval);
      

      A、sd->balance_interval是均衡間隔的基礎值。balance_interval是一個不斷跟隨sched domain的不均衡程度而變化的值。初值一般從min_interval開始,如果sched domain仍然處于不均衡狀態,那么sd->balance_interval保持min_interval,隨著不均衡的狀況在變好,無任務可以搬移,需要通過主動遷移來完成均衡,這時候balance_interval會逐漸變大,從而讓均衡的間隔變大,直到max_interval。對于一個4+4的手機平臺,在MC domain上,小核和大核cluster的min_interval都是4ms,而max_interval等于8ms。而在DIE domain層級上,由于CPU個數是8,其min_interval是8ms,而max_interval等于16ms。

      B、由于各個cpu上的tick大約是同步到來,因此自下而上的周期性均衡在各個CPU上幾乎是同時觸發。如果sched domain覆蓋更多的cpu,那么它的均衡由于要收集更多的信息而會稍稍慢一些。這樣就會產生這樣的一種現象:低階的sched domain剛剛完成遷移的任務就會被高階的sched domain選中被拉到其他CPU上去。為了降低這種低階和高階domain的均衡同步效應,調頻間隔減去一,使得高階sched domain和低階sched domain的interval不是整數倍數的關系。此外,調頻間隔最大也不能超過100ms。

      最后強調一下,這一小節的內容適用于periodic balance和nozh idle balance。

      三、nohz idle balance

      1、Nohz idle均衡的觸發條件

      nohz idle均衡的觸發上一章已經描述了部分過程:scheduler_tick函數中調用trigger_load_balance函數,最終通過nohz_balancer_kick函數來觸發,具體代碼邏輯如下:

      if (unlikely(rq->idle_balance))----------------------A
      	return;
      nohz_balance_exit_idle(rq);------------------------B
      if (likely(!atomic_read(&nohz.nr_cpus)))---------C
      	return;
      if (READ_ONCE(nohz.has_blocked) &&
          time_after(now, READ_ONCE(nohz.next_blocked)))
      	flags = NOHZ_STATS_KICK;------------------D
      if (time_before(now, nohz.next_balance))-----------E
      	goto out;
      

      A、nohz idle balance是本cpu繁忙,需求其他idle cpu來協助,這里如果本CPU也是空閑的,那么也就沒有必要觸發nohz idle balance了。

      B、當CPU從idle狀態醒來,第一個tick會更新全局變量nohz的狀態以及sched domain的cpu busy狀態。雖然nohz idle balance本質上是tick balance,但是它會發IPI,會喚醒idle的cpu,帶來額外的開銷,所以還是要控制觸發nohz idle balance的頻次。為了方便控制觸發nohz idle balance,調度器定義了一個nohz的全局變量,其數據結構如下:

      成員 描述
      idle_cpus_mask 統中哪些cpu進入了idle狀態
      nr_cpus 多少個cpu進入了idle狀態
      has_blocked 這些idle的CPUs是否需要更新blocked load
      next_balance 下一次觸發nohz idle balance的時間
      next_blocked 下一次更新blocked load的時間點

      在nohz_balance_exit_idle函數中,我們會更新nr_cpus和idle_cpus_mask這兩個成員。

      C、nr_cpus和idle_cpus_mask這兩個成員可以讓調度器了解當前系統idle CPU的情況,從而選擇合適的CPU來執行nohz idle balance。如果系統中根本沒有idle cpu,那么也就沒有必要觸發nohz idle load balance了。

      D、nohz idle balance有兩部分的功能:

      (1)更新idle cpu上的blocked load

      (2)負載均衡。可以只更新blocked load,但是負載均衡必須要包括更新blocked load功能。如果當前idle的cpu上有需要衰減的負載,那么標記之。負載更新不是本文的內容,不再詳述。

      E、next_balance是用來控制觸發nohz idle balance的時間點,這個時間點應該是和系統中所有idle cpu的rq->next_balance相關的,也就是說,如果系統中所有idle cpu都還沒有到達均衡時間點,那么根本也就沒有必要觸發nohz idle balance。在執行nohz idle balance的時候,調度器實際上會遍歷idle cpu找到rq->next_balance最小的(即最近需要均衡的)賦值給nohz.next_balance,這個值作為觸發nohz idle balance的時間點。

      上面是一些基本條件的判斷,下面會根據cpu runqueue的任務情況進行判定:

      if (rq->nr_running >= 2) {---------------------------A
      	flags = NOHZ_KICK_MASK; goto out;
      }
      sd = rcu_dereference(rq->sd);-------------------B
      if (sd) {
          if (rq->cfs.h_nr_running >= 1 && check_cpu_capacity(rq, sd)) {
              flags = NOHZ_KICK_MASK; goto unlock;
          }
      }
      sd = rcu_dereference(per_cpu(sd_asym_cpucapacity, cpu));
      if (sd) {
          if (check_misfit_status(rq, sd)) {------------------C
              flags = NOHZ_KICK_MASK; goto unlock;
          }
          goto unlock;----------------------D
      }
      sds = rcu_dereference(per_cpu(sd_llc_shared, cpu));
      if (sds) {
      	nr_busy = atomic_read(&sds->nr_busy_cpus);-------------E
      	if (nr_busy > 1) {
      		flags = NOHZ_KICK_MASK; 
      		goto unlock;
      	}
      }
      

      A、要觸發nohz idle balance之前,需要保證自己有可以被拉取的任務。本cpu runqueue上如果有大于等于2個以上的任務,那么就基本確定可以發起nohz idle balance了

      B、雖然本cpu runqueue上只有1個cfs任務,但是這個CPU用于cfs任務的算力已經已經衰減到一定程度了(由于rt任務或者irq等的影響),這時候也需要發起nohz idle balance

      C、在異構系統中,我們還需要考慮misfit task。當本CPU上有misfit task,即便只有一個任務也是需要發起nohz idle balance

      D、對于異構系統,我們忽略了LLC check,這是因為功耗的考量。小核cluster有busy的CPU并不說明需要進行均衡,只要小核CPU有足夠的算力能夠容納當前運行的任務,那么沒有必要發起nohz idle balance把大核給搞起來,增加額外的功耗。

      E、在同構系統中,我們還是期望負載能夠在各個LLC domain上進行均衡(畢竟可以增加整個系統的cache使用率),同時,我們也希望在LLC domain內部的CPU上能夠任務均布。不過我們也不知道其他LLC domain的情況,因此只要有2個及以上的CPU處于busy,那么就發起nohz idle balance。

      一旦確定要進行nohz idle balance,我們就會調用kick_ilb函數來選擇一個適合的CPU作為代表,來進行負載均衡。

      2、選擇哪一個CPU?

      kick_ilb函數代碼邏輯大致如下:

      if (flags & NOHZ_BALANCE_KICK)
          nohz.next_balance = jiffies+1;------------------A
          ilb_cpu = find_new_ilb();----------------------B
      if (ilb_cpu >= nr_cpu_ids)
      	return;
      
      flags = atomic_fetch_or(flags, nohz_flags(ilb_cpu));--------C
      
      if (flags & NOHZ_KICK_MASK)
      	return;
      
      smp_call_function_single_async(ilb_cpu, &cpu_rq(ilb_cpu)->nohz_csd);--------D
      

      A、如果是需要做均衡(而不是僅僅更新負載),那么我們需要更新nohz.next_balance到下一個jiffies。更新之后,其他的CPU的tick(同一個jiffies)將不會再觸發nohz balance的檢查。如果nohz idle balance順利完成,那么nohz.next_balance會響應的進行更新,如果nohz idle balance被中斷(參考下一節_nohz_idle_balance函數中的B段代碼),那么這里可以確保在下一個tick可以繼續完成之前未完的nohz idle balance。

      B、如果不考慮功耗,那么從所有的idle cpu中選擇一個就OK了,然而,在異構系統中(例如手機環境),我們要考慮更多。例如:如果大核CPU和小核CPU都處于idle狀態,那么選擇喚醒大核CPU還是小核CPU?大核CPU雖然算力強,但是功耗高。如果選擇小核,雖然能省功耗,但是提供的算力是否足夠。此外,發起idle balance請求的CPU在那個cluster?是否首選同一個cluster的cpu來執行nohz idle balance?還有cpu idle的深度如何?很多思考點,不過本文就不詳述了,畢竟標準內核選擇的最簡單的算法:選擇nohz全局變量idle cpu mask中的第一個。

      C、確保選擇的cpu沒有正在進行nohz idle load balance,如果有pending的請求,那么不需要重復發生IPI,觸發nohz idle balance。

      D、我們定義發起nohz idle balance的CPU叫做kicker;接收請求來執行均衡操作的CPU叫做kickee。Kicker和kickee之間的交互是這樣的:

      a) Kicker通知kickee已經被選中執行nohz idle balance,具體是通過設定kickee cpu runqueue的nohz_flags成員來完成的。

      b) Send ipi把kickee喚醒

      c) Kickee被中斷喚醒,執行scheduler_ipi來處理這個ipi中斷。當發現其runqueue的nohz_flags成員被設定了,那么知道自己被選中,后續的流程其實和周期性均衡一樣的,都是觸發一次SCHED_SOFTIRQ類型的軟中斷。

      我們再強調一下:被kick的那個idle cpu并不是負責拉其他繁忙cpu上的任務到本CPU上就完事了,kickee是為了重新均衡所有idle cpu(tick被停掉)的負載,也就是說被選中的idle cpu僅僅是一個系統所有idle cpu的代表,它被喚醒是要把系統中繁忙CPU的任務均衡到系統中所有的idle cpu們。

      3、均衡處理

      和tick balance一樣,nohz idle balance的SCHED_SOFTIRQ軟中斷的處理函數run_rebalance_domains,只不過在這里調用nohz_idle_balance函數完成均衡。具體執行nohz idle balance非常簡單,遍歷系統所有的idle cpu,調用rebalance_domains來完成該cpu上的各個level的sched domain的負載均衡。

      均衡處理大部分在_nohz_idle_balance函數中完成,我們重點看看這個函數的代碼邏輯:

      WRITE_ONCE(nohz.has_blocked, 0);------------------#
      for_each_cpu(balance_cpu, nohz.idle_cpus_mask) {
          if (balance_cpu == this_cpu || !idle_cpu(balance_cpu))
              continue;-----------------A
          if (need_resched()) {------------------B
              has_blocked_load = true;
              goto abort;
      	}
      	rq = cpu_rq(balance_cpu);
      	has_blocked_load |= update_nohz_stats(rq, true);---------C
          if (time_after_eq(jiffies, rq->next_balance)) {----------D
              if (flags & NOHZ_BALANCE_KICK)
                  rebalance_domains(rq, CPU_IDLE);
          }
      
          if (time_after(next_balance, rq->next_balance)) {-----------E
              next_balance = rq->next_balance;
              update_next_balance = 1;
          }
      }
      

      A、暫時略過本cpu的均衡處理,完成其他idle CPU遍歷后會立刻對當前CPU進行均衡。此外,如果CPU已經不處于idle狀態了,那么也就沒有必要進行均衡了。

      B、如果本CPU已經有了任務要做,那么需要放棄本次負載均衡,盡快執行自己隊列上的任務,否則其隊列上的任務會有較長的調度時延,畢竟也許后面有若干個idle cpu的各個level的sched domain需要進行均衡,都是比較耗時的操作。由于終止了nohz idle balance,那么有些idle cpu的blocked load沒有更新,我們在遍歷之前就已經假設會完成所有的均衡,因此設定了系統沒有blocked load需要更新(代碼#處)。在nohz idle balance半途而廢,只能重新標記系統仍然有blocked load要更新。

      C、對該idle cpu進行blocked load的更新

      D、如果達到均衡的時間間隔的要求,那么調用rebalance_domains進行具體的負載均衡

      E、rebalance_domains中會根據情況修改rq->next_balance,因此這里需要跟蹤各個IDLE cpu上最近時間點的那個next_balance,后面會更新到nohz.next_balance,以便控制系統觸發nohz idle balance的頻次。

      _nohz_idle_balance第二段的代碼主要是處理本CPU(即被選中的idle cpu代表)的均衡,比較簡單,不再贅述。

      四、new idle load balance

      1、均衡觸發

      Newidle balance的主入口函數是newidle_balance,我們分段解讀其邏輯:

      if (this_rq->avg_idle < sysctl_sched_migration_cost ||--------------A
          !READ_ONCE(this_rq->rd->overload)) {---------------------------B
      sd = rcu_dereference_check_sched_domain(this_rq->sd);
      if (sd)
          update_next_balance(sd, &next_balance);---------------C
          nohz_newidle_balance(this_rq);--------------------------------D
          goto out;
      }
      

      A、當CPU馬上進入idle狀態的時候是否要做new idle load balance主要考慮兩個因素:一個是當前cpu的cache狀態,另外一個就是當前的整機負載情況。如果該CPU平均idle時間非常短,那么當CPU重新回來執行的任務的時候,CPU cache還是熱的,如果從其他CPU上拉取任務,那么這些新的任務會破壞CPU之前任務的cache,當之前那些任務回到CPU執行的時候性能會下降,同時也有功耗的增加。

      B、整機負載的overload狀態記錄在root domain中的overload成員中。一個CPU處于overload狀態就是指滿足下面的條件:

      a) 大于1個runnable task,即該CPU上有等待執行的任務

      b) 只有一個正在運行的任務,但是是misfit task

      滿足上面的條件我們稱這個CPU是overload狀態的,如果系統中至少有一個CPU是overload狀態,那么我們認為系統是overload狀態的。如果系統沒有overload,那么也就沒有需要拉取的任務,也就必要做new idle load balance了。

      C、由于不適合進行new idle balance(僅做阻塞負載更新),本cpu即將進入idle狀態,即CPU忙閑狀態發生變化,對應base domain的均衡間隔也需要進行相應的更新

      D、和nohz idle balance一樣,new idle balance不僅僅要處理負載均衡,同時也要負責處理blocked load的更新。如果條件不滿足,該cpu不需要進行均衡,那么在進入idle狀態之前,還需要看看系統中的哪些idle cpu們的blocked load是否需要更新了,如果需要,那么該CPU就會執行blocked load的負載更新。其背后的邏輯是:與其在nohz idle balance過程中遍歷選擇一個idle CPU來做負載更新,還不如就讓這個即將進入idle的cpu來處理。(注意:5.10的代碼仍然使用kick_ilb選擇一個CPU發起僅負載更新的均衡,最新代碼已經修復這個issue)

      上面的代碼已經過濾了不適合做newidle_balance的場景,代碼至此說明需要在這個CPU上執行均衡,代碼如下:

      update_blocked_averages(this_cpu);-------------------------A
      for_each_domain(this_cpu, sd) {
      if (this_rq->avg_idle < curr_cost + sd->max_newidle_lb_cost) {
          update_next_balance(sd, &next_balance);
          break;-----------------B
      }
      if (sd->flags & SD_BALANCE_NEWIDLE) {
      	t0 = sched_clock_cpu(this_cpu);
      	pulled_task = load_balance(this_cpu, this_rq,-----------------C
      							 sd, CPU_NEWLY_IDLE,
      							 &continue_balancing);
      							 
      domain_cost = sched_clock_cpu(this_cpu) - t0;
      
      if (domain_cost > sd->max_newidle_lb_cost)
      	sd->max_newidle_lb_cost = domain_cost;--------------D
      	curr_cost += domain_cost;---------------E
      }
      
      update_next_balance(sd, &next_balance);
      
      if (pulled_task || this_rq->nr_running > 0)
      	break;----------------E
      }
      

      A、這段代碼主要的功能是遍歷這個即將進入idle狀態CPU的各個level的sched domain,進行均衡。在均衡之前,首先更新負載

      B、上段代碼A中是從CPU視角做的決定(cache冷熱),降低了new idlebalance的次數,此外,調度器也從sched domain的角度進行檢查,進一步避免了new idlebalance發生的深度。首先我們要明確一點:做new idle load balance是有開銷的,我們辛辛苦苦找到了繁忙的CPU,從它的runqueue中拉了任務來,然而如果自己其實也沒有那么閑,可能很快就有任務放置到自己的runqueue上來,這樣,那些用于均衡的CPU時間其實都白白浪費了。怎么避免這個尷尬狀況?我們需要兩個數據:一個是當前CPU的平均idle時間,另外一個是在new idle load balance引入的開銷(max_newidle_lb_cost成員)。如果CPU的平均idle時間小于max_newidle_lb_cost+本次均衡的開銷,那么就不啟動均衡。

      C、如果該sched domain支持newidle balance,那么調用load_balance啟動均衡

      D、如果需要,更新該sched domain上的最大newidle balance開銷

      E、累計各個層級sched domain上的開銷,用于控制new idle balance的層級深度

      F、在任何一個層級的sched domain上通過均衡拉取了任務,那么new idle balance都會終止,不會進一步去更高層級上進行sched domain的均衡。同樣的,這也是為了控制new idle balance的開銷。

      2、關于new idle balance的開銷

      由于其他的均衡方式都是基于tick觸發的,因此均衡次數都比較容易控制住。New idle balance不一樣,每次cpu進入idle就會觸發,因此我們需要謹慎對待。目前內核中使用兩個參數來控制new idle balance的頻次:cpu的平均idle時間和new idle balance的最大開銷。本小節描述如何計算這兩個參數的。

      struct sched_domain數據結構中有下面的成員記錄new idle balance的開銷:

      成員 描述
      u64 max_newidle_lb_cost 在該domain上進行newidle balance的最大時間長度(即newidle balance的開銷)。每次在該domain上進行new idle balance的時候都會記錄時長,然后把最大值記錄在這個成員中。這個值會隨著時間衰減,防止一次極值會造成永久的影響。
      unsigned longnext_decay_max_lb_cost max_newidle_lb_cost會記錄最近在該sched domain上進行newidle balance的最大時間長度,這個max cost不是一成不變的,它有一個衰減過程,每秒衰減1%,這個成員就是用來控制衰減的。

      為了控制cpu無效進入new idle load balance,struct rq數據結構中有下面的成員:

      成員 描述
      idle_stamp 記錄CPU進入idle狀態的時間點,用于計算avg_idle。在該CPU執行任務期間,該值等于0
      avg_idle 記錄CPU的平均idle時間
      max_idle_balance_cost 該CPU進行new idle balance的最大開銷。

      CPU在進行new idle balance的時候需要在各個層級上執行new idle均衡,rq的max_idle_balance_cost成員就是匯聚了各個level上sched domain進行new idle balance的最大時間開銷之和,但是限制其最小值sysctl_sched_migration_cost。

      avg_idle的算法非常簡單,首先在newidle_balance的時候記錄idle_stamp,第一調用ttwu_do_wakeup的時候會計算這之間的時間,得到本次的CPU處于idle狀態的時間,然后通過下面的公式計算平均idle time:

      當前的avg_idle = 上次avg_idle + (本次idle time - 上次avg_idle)/8

      為了防止CPU一次idle太久時間帶來的影響,我們限制了avg_idle的最大值,即計算出來avg_idle的值不能大于2倍的max_idle_balance_cost值。

      五、結束語

      周期性均衡和nohz idle balance都是SCHED類型的軟中斷觸發,最后都調用了rebalance_domains來執行該CPU上各個level的sched domain的均衡,具體在某個sched domain執行均衡的函數是load_balance函數。對于new idle load balance,也是遍歷該CPU上各個level的sched domain執行均衡動作,調用的函數仍然是load_balance。因此,無論哪一種均衡,最后都萬法歸宗來到load_balance。由于篇幅原因,本文不再相信分析load_balance的邏輯,想要了解細節且聽下回分解吧。

      posted @ 2025-10-28 21:03  yooooooo  閱讀(17)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲熟女少妇乱色一区二区 | 你懂的亚洲一区二区三区| 91精品国产午夜福利| 人妻互换一二三区激情视频 | 临江市| 人人爽天天碰天天躁夜夜躁| 无码熟妇人妻av影音先锋| 无码一级视频在线| 欧美高清freexxxx性| 国产精品色三级在线观看| 欧美性猛交xxxx黑人| 武装少女在线观看高清完整版免费| 亚洲最大色综合成人av| 少妇人妻偷人精品视蜜桃| 成人3d动漫一区二区三区 | 1精品啪国产在线观看免费牛牛| 国产一区二区一卡二卡| 亚洲中文字幕无码久久2017| 午夜福利影院不卡影院| 在线看片免费不卡人成视频| 欧美xxxxhd高清| 人妻系列中文字幕精品| 高安市| 国内免费视频成人精品| 99精品人妻少妇一区| 最新精品国产自偷在自线| 天堂mv在线mv免费mv香蕉| 国产自国产自愉自愉免费24区| 美女黄网站人色视频免费国产| 国产成人综合在线女婷五月99播放| 在线 | 国产精品99传媒a| 最近中文字幕免费手机版 | 国产97人人超碰CAO蜜芽PROM| 国产精品一码在线播放| 国产精品一区 在线播放| 欧美牲交40_50a欧美牲交aⅴ| 国产精品露脸视频观看| 人妻精品动漫H无码中字| 沅陵县| 欧美人与动牲交A免费观看| 色综合色国产热无码一|