<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          CPU:一核有難,八核圍觀

          共 9162字,需瀏覽 19分鐘

           ·

          2021-08-11 13:05

          在《一文讀懂 | 進(jìn)程怎么綁定 CPU》這篇文章中介紹過,在 Linux 內(nèi)核中會(huì)為每個(gè) CPU 創(chuàng)建一個(gè)可運(yùn)行進(jìn)程隊(duì)列,由于每個(gè) CPU 都擁有一個(gè)可運(yùn)行進(jìn)程隊(duì)列,那么就有可能會(huì)出現(xiàn)每個(gè)可運(yùn)行進(jìn)程隊(duì)列之間的進(jìn)程數(shù)不一樣的問題,這就是所謂的 負(fù)載不均衡 問題,如下圖所示:

          (圖1)

          最極端的情況是,一個(gè) CPU 的可運(yùn)行進(jìn)程隊(duì)列擁有非常多的進(jìn)程,而其他 CPU 的可運(yùn)行進(jìn)程隊(duì)列為空,這就是著名的 一核有難,多核圍觀,如下圖:

          (圖2)

          為了避免這個(gè)問題的出現(xiàn),Linux 內(nèi)核實(shí)現(xiàn)了 CPU 可運(yùn)行進(jìn)程隊(duì)列之間的負(fù)載均衡。接下來,我們將會(huì)介紹 CPU 間的負(fù)載均衡的實(shí)現(xiàn)原理。

          本文使用的內(nèi)核版本為:Linux-2.6.23

          CPU 間負(fù)載均衡原理

          CPU 間負(fù)載不均衡的根本原因就是,CPU 的可運(yùn)行進(jìn)程隊(duì)列中的進(jìn)程數(shù)量不均衡導(dǎo)致的。所以,要解決 CPU 間負(fù)載不均衡的方法就是:將最繁忙的 CPU 可運(yùn)行進(jìn)程隊(duì)列的一些進(jìn)程遷移到其他比較空閑的 CPU 中,從而達(dá)到 CPU 間負(fù)載均衡的目的。

          當(dāng)然,在 2.6.0 版本的內(nèi)核的確是這樣實(shí)現(xiàn)的,我們可以看看其實(shí)現(xiàn)代碼:

          static void 
          load_balance(runqueue_t *this_rq, int idle, cpumask_t cpumask)
          {
              int imbalance, idx, this_cpu = smp_processor_id();
              runqueue_t *busiest;
              prio_array_t *array;
              struct list_head *head, *curr;
              task_t *tmp;

              // 1. 找到最繁忙的 CPU 運(yùn)行隊(duì)列
              busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance, cpumask);
              if (!busiest)
                  goto out;
              ...

              head = array->queue + idx;
              curr = head->prev;

          skip_queue:
              // 2. 從最繁忙運(yùn)行隊(duì)列中取得一個(gè)進(jìn)程
              tmp = list_entry(curr, task_t, run_list);
              ...

              // 3. 把進(jìn)程從最繁忙的可運(yùn)行隊(duì)列中遷移到當(dāng)前可運(yùn)行隊(duì)列中
              pull_task(busiest, array, tmp, this_rq, this_cpu);
              ...
          }

          load_balance 函數(shù)主要用于解決 CPU 間負(fù)載均衡問題,其主要完成以下 3 個(gè)步驟:

          • 從所有 CPU 的可運(yùn)行隊(duì)列中找到最繁忙的可運(yùn)行隊(duì)列。
          • 從最繁忙可運(yùn)行隊(duì)列中取得一個(gè)進(jìn)程。
          • 把進(jìn)程從最繁忙的可運(yùn)行隊(duì)列中遷移到當(dāng)前可運(yùn)行隊(duì)列中。

          這是 2.6.0 版本的解決方案,但這個(gè)方案并不是最優(yōu)的,因?yàn)楝F(xiàn)代 CPU 架構(gòu)是非常復(fù)雜的,比如一個(gè)物理 CPU 有多個(gè)核心(多核),而每個(gè)核心又可以通過超線程(Hyper-Threading)來實(shí)現(xiàn)多個(gè)邏輯 CPU,如下圖所示:

          (圖3)

          如上圖所示,一個(gè)物理 CPU 中擁有 4 個(gè)核心,而每個(gè)核心又擁有 2 個(gè)超線程。在 Linux 內(nèi)核中,會(huì)為每個(gè)超線程定義一個(gè)可運(yùn)行進(jìn)程隊(duì)列,所以 Linux 內(nèi)核會(huì)為上面的 CPU 定義 8 個(gè)可運(yùn)行進(jìn)程隊(duì)列。

          現(xiàn)在問題來了,在上面的 CPU 架構(gòu)中,不同的可運(yùn)行隊(duì)列之間的進(jìn)程遷移代價(jià)是不一樣的。因?yàn)橥粋€(gè)核心的不同超線程共用了所有的緩存,所以同一個(gè)核心不同超線程間的進(jìn)程遷移代價(jià)是最小的。

          而同一個(gè)物理 CPU 不同核心間也會(huì)共用某些緩存,所以不同核心間的進(jìn)程遷移的代價(jià)會(huì)比同一核心不同超線程間的進(jìn)程遷移稍大。由于現(xiàn)在很多主板都支持安裝多個(gè)物理 CPU,而不同物理 CPU 間基本不會(huì)共用緩存,所以不同物理 CPU 間的進(jìn)程遷移代價(jià)最大。如下圖所示(圖中的 L1、L2 和 L3 分別指一級(jí)、二級(jí)和三級(jí)緩存):

          (圖4)

          為了解決進(jìn)程遷移成本的問題,新版本的 Linux 內(nèi)核引入了 調(diào)度域 和 調(diào)度組

          調(diào)度域與調(diào)度組

          從前面的分析可知,根據(jù) CPU 的物理架構(gòu)可以劃分為:不同的物理 CPU、相同 CPU 不同的核心、相同核心不同的超線程等,如下圖所示:

          (圖5)

          在 Linux 內(nèi)核中,把這個(gè)層級(jí)成為 調(diào)度域。從前面的分析可知,越下層的調(diào)度域共用的緩存就越多,所以在進(jìn)程遷移時(shí),優(yōu)先從底層的調(diào)度域開始進(jìn)行。

          由于內(nèi)核為每個(gè)超線程定義一個(gè)可運(yùn)行隊(duì)列,所以圖 3 中的 CPU 擁有 8 個(gè)可運(yùn)行隊(duì)列。而根據(jù)不同的調(diào)度域,可以把這 8 個(gè)可運(yùn)行隊(duì)列劃分為不同的 調(diào)度組,如下圖所示:

          (圖6)

          如上圖所示,由于每個(gè)超線程都擁有一個(gè)可運(yùn)行隊(duì)列,所以圖 3 的 CPU 擁有 8 個(gè)可運(yùn)行隊(duì)列,而這些可運(yùn)行隊(duì)列可以根據(jù)不同的核心來劃分為 4 個(gè)調(diào)度組,而這 4 個(gè)調(diào)度組可以根據(jù)不同的物理 CPU 來劃分成 1 個(gè)調(diào)度組。

          由于越底層的調(diào)度域共用的緩存越多,所以對(duì) CPU 可運(yùn)行隊(duì)列進(jìn)行負(fù)載均衡時(shí),優(yōu)先從底層調(diào)度域開始。比如把 Thread0 可運(yùn)行隊(duì)列的進(jìn)程遷移到 Thread1 可運(yùn)行隊(duì)列的代價(jià)要比遷移到 Thread2 可運(yùn)行隊(duì)列的小,這是由于 Thread0 與 Thread1 屬于同一個(gè)核心,同一個(gè)核心共用所有的 CPU 緩存。

          在 Linux 內(nèi)核中,調(diào)度域使用 sched_domain 結(jié)構(gòu)表示,而調(diào)度組使用 sched_group 結(jié)構(gòu)表示。我們來看看 sched_domain 結(jié)構(gòu)的定義:

          struct sched_domain {
              struct sched_domain *parent;    /* top domain must be null terminated */
              struct sched_domain *child;     /* bottom domain must be null terminated */
              struct sched_group  *groups;    /* the balancing groups of the domain */
              cpumask_t            span;      /* span of all CPUs in this domain */
              ...
          };

          下面介紹一下 sched_domain 結(jié)構(gòu)各個(gè)字段的作用:

          • parent:由于調(diào)度域是分層的,上層調(diào)度域是下層的調(diào)度域的父親,所以這個(gè)字段指向的是當(dāng)前調(diào)度域的上層調(diào)度域。
          • child:如上所述,這個(gè)字段用來指向當(dāng)前調(diào)度域的下層調(diào)度域。
          • groups:每個(gè)調(diào)度域都擁有一批調(diào)度組,所以這個(gè)字段指向的是屬于當(dāng)前調(diào)度域的調(diào)度組列表。
          • span:這個(gè)字段主要用來標(biāo)記屬于當(dāng)前調(diào)度域的 CPU 列表(每個(gè)位表示一個(gè) CPU)。

          我們接著分析一下 sched_group 結(jié)構(gòu),其定義如下:

          struct sched_group {
              struct sched_group *next;
              cpumask_t           cpumask;
              ...
          };

          下面介紹一下 sched_group 結(jié)構(gòu)各個(gè)字段的作用:

          • next:指向?qū)儆谕粋€(gè)調(diào)度域的下一個(gè)調(diào)度組。
          • cpumask:用于標(biāo)記屬于當(dāng)前調(diào)度組的 CPU 列表(每個(gè)位表示一個(gè) CPU)。

          它們之間的關(guān)系如下圖所示:

          (圖7)

          CPU 間負(fù)載均衡實(shí)現(xiàn)

          要實(shí)現(xiàn) CPU 間的負(fù)載均衡,只需要將最繁忙的可運(yùn)行隊(duì)列中的一部分進(jìn)程遷移到空閑的可運(yùn)行隊(duì)列中即可。但由于 CPU 緩存的原因,對(duì)使用不同的 CPU 緩存的可運(yùn)行隊(duì)列之間進(jìn)行進(jìn)程遷移,將會(huì)導(dǎo)致緩存丟失,從而導(dǎo)致性能損耗。所以,Linux 內(nèi)核會(huì)優(yōu)先對(duì)使用相同 CPU 緩存的可運(yùn)行隊(duì)列之間進(jìn)行進(jìn)程遷移。

          1. CPU 間負(fù)載均衡觸發(fā)時(shí)機(jī)

          當(dāng) CPU 的負(fù)載不均衡時(shí),內(nèi)核就需要對(duì) CPU 進(jìn)行負(fù)載均衡。負(fù)載均衡的觸發(fā)時(shí)機(jī)比較多,如進(jìn)程被創(chuàng)建、進(jìn)程被喚醒、進(jìn)程休眠和時(shí)鐘中斷等,這里我們介紹一下在時(shí)鐘中斷時(shí)怎么進(jìn)行 CPU 間的負(fù)載均衡。

          在 Linux 內(nèi)核中是通過 rq 結(jié)構(gòu)來描述一個(gè)可運(yùn)行進(jìn)程隊(duì)列的,它有個(gè)名為 sd 的字段用于指向其所屬的 調(diào)度域 層級(jí)的最底層,如下所示:

          struct rq {
              ...
              struct sched_domain *sd;
              ...
          }

          它與調(diào)度域和調(diào)度組的關(guān)系如下圖所示:

          (圖8)

          在時(shí)鐘中斷下半部處理中,會(huì)通過調(diào)用 run_rebalance_domains 函數(shù)來對(duì) CPU 進(jìn)行負(fù)載均衡處理,而 run_rebalance_domains 接著會(huì)通過調(diào)用 rebalance_domains 函數(shù)來完成負(fù)載均衡的工作,其實(shí)現(xiàn)如下:

          static inline void 
          rebalance_domains(int cpu, enum cpu_idle_type idle)
          {
              int balance = 1;
              struct rq *rq = cpu_rq(cpu);
              unsigned long interval;
              struct sched_domain *sd;
              unsigned long next_balance = jiffies + 60*HZ;
              int update_next_balance = 0;

              // 遍歷可運(yùn)行隊(duì)列的調(diào)度組層級(jí) (從最底層開始)
              for_each_domain(cpu, sd) {
                  ...
                  // 由于對(duì) CPU 進(jìn)行負(fù)載均衡可能會(huì)導(dǎo)致 CPU 緩存丟失
                  // 所以對(duì) CPU 進(jìn)行負(fù)載均衡不能太頻繁, 必須隔一段時(shí)間才能進(jìn)行
                  // 這里就是判斷上次進(jìn)行負(fù)載均衡與這次的間隔是否已經(jīng)達(dá)到合適的時(shí)間
                  // 如果時(shí)間間隔已經(jīng)達(dá)到一段時(shí)間, 那么就調(diào)用 load_balance 函數(shù)進(jìn)行負(fù)載均衡
                  if (time_after_eq(jiffies, sd->last_balance + interval)) {
                      if (load_balance(cpu, rq, sd, idle, &balance)) {
                          idle = CPU_NOT_IDLE;
                      }
                      sd->last_balance = jiffies;
                  }
                  ...
              }
              ...
          }

          由于每個(gè) CPU(超線程)都有一個(gè)可運(yùn)行隊(duì)列,而 rebalance_domains 函數(shù)的工作就是獲取當(dāng)前 CPU (超線程)的可運(yùn)行隊(duì)列,然后從最底層開始遍歷其調(diào)度域?qū)蛹?jí)(由于越底層的調(diào)度域,進(jìn)行進(jìn)程遷移的代價(jià)越?。?。

          由于對(duì) CPU 進(jìn)行負(fù)載均衡可能會(huì)導(dǎo)致 CPU 緩存丟失,所以對(duì) CPU 進(jìn)行負(fù)載均衡不能太頻繁(需要隔一段時(shí)間才能進(jìn)行)。那么在對(duì) CPU 進(jìn)行負(fù)載均衡前,就需要判斷上次進(jìn)行負(fù)載均衡與這次的時(shí)間間隔是否合理。如果時(shí)間間隔合理, 那么就調(diào)用 load_balance 函數(shù)對(duì)調(diào)度域進(jìn)行負(fù)載均衡。

          load_balance 函數(shù)實(shí)現(xiàn)如下:

          static int
          load_balance(int this_cpu, struct rq *this_rq, struct sched_domain *sd,
                       enum cpu_idle_type idle, int *balance)

          {
              ...

          redo:
              // 1. 從調(diào)度域中找到一個(gè)最繁忙的調(diào)度組
              group = find_busiest_group(sd, this_cpu, &imbalance, idle, &sd_idle,
                                         &cpus, balance);
              ...

              // 2. 從最繁忙的調(diào)度組中找到一個(gè)最繁忙的運(yùn)行隊(duì)列
              busiest = find_busiest_queue(group, idle, imbalance, &cpus);
              ...

              if (busiest->nr_running > 1) {
                  ...
                  // 3. 從最繁忙的運(yùn)行隊(duì)列中遷移一些任務(wù)到當(dāng)前任務(wù)隊(duì)列
                  ld_moved = move_tasks(this_rq, this_cpu, busiest, imbalance, sd, idle,
                                        &all_pinned);
                  ...
              }
              ...
              return 0;
          }

          load_balance 函數(shù)主要完成 3 個(gè)工作:

          • 從 調(diào)度域 中找到一個(gè)最繁忙的 調(diào)度組。
          • 從最繁忙的 調(diào)度組 中找到一個(gè)最繁忙的 可運(yùn)行隊(duì)列。
          • 從最繁忙的 可運(yùn)行隊(duì)列 中遷移一些任務(wù)到當(dāng)前 可運(yùn)行隊(duì)列。

          這樣就完成了 CPU 間的負(fù)載均衡處理。

          瀏覽 1194
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  手机免费av | AV乱伦片 | 影音先锋男人资源av | 成人性生交大片免费看黄106季 | 日皮太爽了我要看免费视频 |