<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>

          OOM Killer機(jī)制學(xué)習(xí)

          共 10189字,需瀏覽 21分鐘

           ·

          2022-12-18 01:04

          當(dāng)系統(tǒng)內(nèi)存不足以分配時,Linux內(nèi)核會使用一種OOM Killer(Out-Of-Memory Killer)機(jī)制釋放內(nèi)存,該機(jī)制通過一系列比較選擇出最適合的進(jìn)程并將其kill掉,從而達(dá)到保障系統(tǒng)穩(wěn)定運(yùn)行的目的。那么在內(nèi)核中,OOM Killer具體是怎么運(yùn)轉(zhuǎn)的呢?

          一、觸發(fā)過程

          在申請內(nèi)存時,必然會調(diào)用alloc_page(),在__alloc_pages中有以下調(diào)用關(guān)系: 

          其中,在__alloc_pages_slowpath中,當(dāng)反復(fù)嘗試reclaim和compact后仍不成功,就會調(diào)用__alloc_pages_may_oom進(jìn)行內(nèi)存釋放。

          /*
             * If we failed to make any progress reclaiming, then we are
             * running out of options and have to consider going OOM
             */

          if (!did_some_progress) {
            if (oom_gfp_allowed(gfp_mask)) {
              if (oom_killer_disabled)
                goto nopage;
              /* Coredumps can quickly deplete all memory reserves */
              if ((current->flags & PF_DUMPCORE) &&
                  !(gfp_mask & __GFP_NOFAIL))
                goto nopage;
              page = __alloc_pages_may_oom(gfp_mask, order,
                  zonelist, high_zoneidx,
                  nodemask, preferred_zone,
                  classzone_idx, migratetype);
             ......
           }

          如果定義了oom_killer_disabled,就會直接goto到nopage,不會觸發(fā)OOM機(jī)制(此值默認(rèn)為0).

          二、工作過程(基于Linux-3.18)

          當(dāng)內(nèi)核檢測到內(nèi)存不足,執(zhí)行到out_of_memory時,OOM Killer會選擇一個進(jìn)程并把他kill掉:

          p = select_bad_process(&points, totalpages, mpol_mask, force_kill);

          具體的選擇過程在select_bad_process中進(jìn)行:

          /*
           * Simple selection loop. We chose the process with the highest
           * number of 'points'.  Returns -1 on scan abort.
           *
           * (not docbooked, we don't want this one cluttering up the manual)
           */

          static struct task_struct *select_bad_process(unsigned int *ppoints,
              unsigned long totalpages, const nodemask_t *nodemask,
              bool force_kill)

          {
            struct task_struct *g, *p;
            struct task_struct *chosen = NULL;
            unsigned long chosen_points = 0;

            rcu_read_lock();
            for_each_process_thread(g, p) {
              unsigned int points;

              switch (oom_scan_process_thread(p, totalpages, nodemask,
                      force_kill)) {
              case OOM_SCAN_SELECT:
                chosen = p;
                chosen_points = ULONG_MAX;
                /* fall through */
              case OOM_SCAN_CONTINUE:
                continue;
              case OOM_SCAN_ABORT:
                rcu_read_unlock();
                return (struct task_struct *)(-1UL);
              case OOM_SCAN_OK:
                break;
              };
              points = oom_badness(p, NULL, nodemask, totalpages);
              if (!points || points < chosen_points)
                continue;
              /* Prefer thread group leaders for display purposes */
              if (points == chosen_points && thread_group_leader(chosen))
                continue;

              chosen = p;
              chosen_points = points;
            }
            if (chosen)
              get_task_struct(chosen);
            rcu_read_unlock();

            *ppoints = chosen_points * 1000 / totalpages;
            return chosen;
          }

          select_bad_process會選擇一個points數(shù)值最高的進(jìn)程并返回。在宏for_each_process_thread循環(huán)里,通過switch和oom_scan_process_thread對一些進(jìn)程做特殊化處理,如一些進(jìn)程不適合被結(jié)束,就跳過本次循環(huán)。如果該進(jìn)程沒有特殊狀態(tài),oom_scan_process_thread返回OOM_SCAN_OK,繼續(xù)向下進(jìn)行判斷。這里使用了oom_badness對其points值進(jìn)行計算。

          /**
           * oom_badness - heuristic function to determine which candidate task to kill
           * @p: task struct of which task we should calculate
           * @totalpages: total present RAM allowed for page allocation
           *
           * The heuristic for determining which task to kill is made to be as simple and
           * predictable as possible.  The goal is to return the highest value for the
           * task consuming the most memory to avoid subsequent oom failures.
           */
          unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
                  const nodemask_t *nodemask, unsigned long totalpages)
          {
            long points;
            long adj;

            if (oom_unkillable_task(p, memcg, nodemask))
              return 0;

            p = find_lock_task_mm(p);
            if (!p)
              return 0;

            adj = (long)p->signal->oom_score_adj;
            if (adj == OOM_SCORE_ADJ_MIN) {
              task_unlock(p);
              return 0;
            }

            /*
             * The baseline for the badness score is the proportion of RAM that each
             * task's rss, pagetable and swap space use.
             */
            points = get_mm_rss(p->mm) + atomic_long_read(&p->mm->nr_ptes) +
               get_mm_counter(p->mm, MM_SWAPENTS);
            task_unlock(p);

            /*
             * Root processes get 3% bonus, just like the __vm_enough_memory()
             * implementation used by LSMs.
             */
            if (has_capability_noaudit(p, CAP_SYS_ADMIN))
              points -= (points * 3) / 100;

            /* Normalize to oom_score_adj units */
            adj *= totalpages / 1000;
            points += adj;

            /*
             * Never return 0 for an eligible task regardless of the root bonus and
             * oom_score_adj (oom_score_adj can'
          t be OOM_SCORE_ADJ_MIN here).
             */
            return points > 0 ? points : 1;
          }

          在oom_badness的上半部分,對進(jìn)程做了一些判斷,排除了不可進(jìn)行kill的進(jìn)程以及oom_score_adj為OOM_SCORE_ADJ_MIN(-1000)的進(jìn)程,進(jìn)行了return 0。接著是進(jìn)行比重計算,將rss、nr_ptes、swap空間使用量占RAM比重相加。如果是Root進(jìn)程則去掉3%的比重points -= (points * 3) / 100;。之后對adj進(jìn)行歸一化并與points相加,在返回值計算時,使用了一個三目運(yùn)算符,即當(dāng)points大于0時,返回points,否則返回1。這里注釋給出的原因是,對于有資格的進(jìn)程(即可以被OOM Killer掉的進(jìn)程),是絕不能返回0的。(這里我的理解是,如果points返回0,這個進(jìn)程可能在之后的比較中就處于劣勢,成為漏網(wǎng)之魚) 

          再回到select_bad_process中看,之后跟的一個if比較就是為了進(jìn)行取最大值的判斷,再之后判斷該進(jìn)程是否為thread_group_leader,若是則continue跳過本次循環(huán),否則該進(jìn)程就是被chosen的進(jìn)程。

          再回到out_of_memory中,得到p值后,需要對其進(jìn)行判斷:

          if (!p) {
              dump_header(NULL, gfp_mask, order, NULL, mpol_mask);
              panic("Out of memory and no killable processes...\n");
            }
            if (p != (void *)-1UL) {
              oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,
                   nodemask, "Out of memory");
              killed = 1;
            }

          當(dāng)p是0時,即沒有找到可以kill掉的進(jìn)程,內(nèi)核發(fā)出一個panic。當(dāng)p不是0時,即找到了可以kill掉的進(jìn)程,則通過oom_kill_process將其kill。

          在oom_kill_process中有個“有意思”的事是,在kill之前,會先遍歷其子進(jìn)程,重新通過oom_badness計算出一個最適合被kill掉的子進(jìn)程,該子進(jìn)程會有限考慮被kill掉,從而避免kill父進(jìn)程導(dǎo)致的接管子進(jìn)程的工作開銷。并且最終被kill掉的進(jìn)程的名字叫victim,這個單詞的中文含義是犧牲者,有點是為了整個系統(tǒng)的穩(wěn)定運(yùn)轉(zhuǎn)而犧牲的意思。在這之后OOM Killer會kill掉和victim使用相同虛擬內(nèi)存的進(jìn)程,并通過發(fā)送SIGKILL信號將其終止。 


          三、到底為什么會發(fā)生Out Of Memory?

          因為物理內(nèi)存頁的分配發(fā)生在使用的瞬間而非分配的瞬間。若某個進(jìn)程申請了200MB內(nèi)存,但實際上只使用了100MB,未使用到的100MB根本沒有分配物理內(nèi)存頁。當(dāng)進(jìn)程需要內(nèi)存時,進(jìn)程從內(nèi)核得到的只是虛擬地址的使用權(quán),而不是實際的物理地址,實際的物理內(nèi)存只有當(dāng)進(jìn)程真的去訪問新獲取的虛擬地址時,產(chǎn)生缺頁異常,從而進(jìn)入分配實際物理地址的過程,之后系統(tǒng)返回產(chǎn)生異常的地址,重新執(zhí)行內(nèi)存訪問。虛擬內(nèi)存需要物理內(nèi)存作為支撐,當(dāng)分配了太多虛擬內(nèi)存,導(dǎo)致物理內(nèi)存不夠時,就發(fā)生了Out Of Memory。這種允許超額commit的機(jī)制就是overcommit。

          overcommit即操作系統(tǒng)在應(yīng)用申請內(nèi)存空間時不去檢查是否超出當(dāng)前可用量,隨意滿足申請要求,應(yīng)用也不管實際是否有足夠多的內(nèi)存可使用,認(rèn)為我申請了2G,OS肯定就給我2G使用。最后,隨著內(nèi)存越用越多,OS發(fā)現(xiàn)內(nèi)存不夠用了,必須要收回一些內(nèi)存才行,就觸發(fā)了上述的OOM Killer機(jī)制回收內(nèi)存。

          Linux根據(jù)參數(shù) vm.overcommit_memory設(shè)置overcommit:

          0 ——默認(rèn)值,啟發(fā)式overcommit,它允許overcommit,但太明顯的overcommit會被拒絕,比如malloc一次性申請的內(nèi)存大小就超過了系統(tǒng)總內(nèi)存。

          1 ——Always overcommit. 允許overcommit,對內(nèi)存申請來者不拒。

          2 ——不允許overcommit,提交給系統(tǒng)的總地址空間大小不允許超過CommitLimit。(CommitLimit 就是overcommit的閾值,申請的內(nèi)存總數(shù)超過CommitLimit的話就算是overcommit)

          四、總結(jié)

          由于物理內(nèi)存的分配機(jī)制,以及overcommit的存在,導(dǎo)致了在物理內(nèi)存不夠時的OOM Killer。OOM Killer機(jī)制很有意思,它為了保護(hù)整個系統(tǒng)的安全穩(wěn)定運(yùn)行,需要找出一個最合適的進(jìn)程kill掉。這是不得已而為之,內(nèi)核必須在kill掉進(jìn)程和系統(tǒng)崩潰之間選擇其中一個。內(nèi)核代碼中out_of_memory注釋中也體現(xiàn)了這種無奈。> * If we run out of memory, we have the choice between either

          • killing a random task (bad), letting the system crash (worse)

          • OR try to be smart about which process to kill. Note that we

          • don't have to be perfect here, we just have to be good.

          在選擇合適的進(jìn)程時,OOM Killer會挑選一個占用內(nèi)存最大的進(jìn)程,這也很好理解,畢竟kill掉一個大的可以獲得更多的物理內(nèi)存,并且損失也比較小。如果kill掉多個小的,損失會比較大。Linux內(nèi)核總是去選擇更高效的方法。

          鏈接:https://www.codingsky.com/m/doc/2021/10/19/925.html

          (版權(quán)歸原作者所有,侵刪)


          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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 | 欧美成人免费夜夜黄啪啪 |