1. OOM Killer機制學(xué)習(xí)

        共 10189字,需瀏覽 21分鐘

         ·

        2022-12-18 01:04

        當(dāng)系統(tǒng)內(nèi)存不足以分配時,Linux內(nèi)核會使用一種OOM Killer(Out-Of-Memory Killer)機制釋放內(nèi)存,該機制通過一系列比較選擇出最適合的進程并將其kill掉,從而達到保障系統(tǒng)穩(wěn)定運行的目的。那么在內(nèi)核中,OOM Killer具體是怎么運轉(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進行內(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機制(此值默認為0).

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

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

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

        具體的選擇過程在select_bad_process中進行:

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

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

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

        再回到out_of_memory中,得到p值后,需要對其進行判斷:

        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掉的進程,內(nèi)核發(fā)出一個panic。當(dāng)p不是0時,即找到了可以kill掉的進程,則通過oom_kill_process將其kill。

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


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

        因為物理內(nèi)存頁的分配發(fā)生在使用的瞬間而非分配的瞬間。若某個進程申請了200MB內(nèi)存,但實際上只使用了100MB,未使用到的100MB根本沒有分配物理內(nèi)存頁。當(dāng)進程需要內(nèi)存時,進程從內(nèi)核得到的只是虛擬地址的使用權(quán),而不是實際的物理地址,實際的物理內(nèi)存只有當(dāng)進程真的去訪問新獲取的虛擬地址時,產(chǎ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的機制就是overcommit。

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

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

        0 ——默認值,啟發(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)存的分配機制,以及overcommit的存在,導(dǎo)致了在物理內(nèi)存不夠時的OOM Killer。OOM Killer機制很有意思,它為了保護整個系統(tǒng)的安全穩(wěn)定運行,需要找出一個最合適的進程kill掉。這是不得已而為之,內(nèi)核必須在kill掉進程和系統(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.

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

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

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


        瀏覽 53
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
          
          

            1. 日韩A级毛片免费视频 | 无遮挡黄片 | 青青草国产产无码乱码精 | 亚洲成人视频网 | 极品人妻AV一区二区 |