• OOM Killer는 희생시킬 프로세스를 고르기 위해서 각 프로세스에 점수를 매기는 과정을 진행

  • OOM Killer는 메모리가 부족해졌을 때 프로세스 중 oom_score가 가장 높은 점수를 받은 순서대로 프로세스를 죽임

  • oom_score 점수는 기본적으로 프로세스에게 할당된 메모리의 양으로 점수가 메겨짐 → 해당 프로세스로부터 fork()된 자식 프로세스들의 메모리도 추가됨

  • 오래 돌고 있는 프로세스의 경우 oom_score 점수를 낮춰주고, 프로세스 순위가 낮은 프로세스는 oom_score 점수를 높여서 사용할 필요가 있음

  • 중요하게 취급되어지는 프로세스들(superuser에 의해서 실행되거나, 하드웨어와 직접적인 관련이 있는)은 값을 크게 낮춰줘서 OOM Killer를 통해서 kill되지 않도록 해야함

  • 각 서비스의 oom_score를 확인하는 방식 (PID → 프로세스 ID)

    # 기본 형식
    $ cat /proc/<pid>/oom_score
    oom_socre 출력
    
    # 19862 프로세스의 oom_score 확인
    $ cat /proc/19862/oom_score
    12

※ nice

  • 프로세스의 우선 순위를 낮춰주는 점수를 의미

oom_score_adj, oom_score, oom_adj 커널 파라미터

1. oom_score_adj

  • 앱이 포그라운드(foreground)에 있는지, 백그라운드(background)에 있는지로 결정
  • 정확한 값은 앱이 백그라운드 서비스를 가지고 있는지, 얼마나 오랫동안 포그라운드에 빠져 있는지에 따라 달라짐
  • oom_score_adj 는 -1000 ~ 1000 의 값을 가지며, 낮은 값 일수록 우선순위에서 밀려남
  • 프로세스가 OOM Killer 에 의해 죽지 않길 원한다면 oom_score 를 조정하는게 아니라 oom_score_adj 값을 변경해야함

2. oom_score

  • 기본적으로 프로세스에게 할당된 메모리의 양으로 점수가 메겨짐

3. oom_adj

  • -17 ~ 15 의 값을 가지며, 낮은 값 일수록 우선순위에서 밀려남
  • -17이면 OOM을 완전히 비활성화시킴
    /proc/<pid>/oom_adj
    /proc/<pid>/oom_score
    /proc/<pid>/oom_score_adj


OOM Killer의 대상에서 벗어나기

  • OOM Killer를 끄는 것은 불가능
  • 서비스의 안정성을 위해서 꺼지면 안 될 프로세스도 존재 → 특정한 작업을 하고 있던 worker 프로세스
  • OOM Killer를 직접적으로 끌 수는 없지만, OOM Killer의 scoring를 통해 OOM Killer가 멈추는 대상은 벗어날수 있음
  • /proc//oom_adj 값을 -17로 설정 → -17 값은 OOM_DISABLE의 상수 값
    $ echo -17 > /proc/<pid>/oom_adj
  • /proc/oom_scoring_adj 값을 -1000으로 설정
  • -1000 값으로 지정하는 것은 OOM Scoring을 비활성화한다기보다는 충분히 낮은 값을 주어서 OOM Killer에 벗어남
    $ echo -1000 > /proc/<pid>/oom_adj

관련해서 리눅스에서 실행(kill) 하는 순서

  1. alloc_pages
  2. out_of_memory
  3. select_bad_process
  4. oom_evaluate_task
  5. oom_badness

OOM Scoring과 관련된 함수들

1. select_bad_process

  • bad 프로세스를 선택

  • select_bad_process 함수 내용

    static void select_bad_process(struct oom_control *oc)
    {
      if (is_memcg_oom(oc))
          mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc);
      else {
          struct task_struct *p;
    
            // RCU: Read-Copy Update
          rcu_read_lock();
    
          for_each_process(p)
              if (oom_evaluate_task(p, oc))
                  break;
          rcu_read_unlock();
      }
      oc->chosen_points = oc->chosen_points * 1000 / oc->totalpages;
    }

2. oom_evaluate_task

  • oom 선정된 프로세스가 OOM Killer를 통해 멈춰되 되는지 검증

  • oom_evaluate_task 함수 내용

    static int oom_evaluate_task(struct task_struct *task, void *arg)
    {
      struct oom_control *oc = arg;
      unsigned long points;
    
      if (oom_unkillable_task(task, NULL, oc->nodemask))
          goto next;
      /*
       * This task already has access to memory reserves and is being killed.
       * Don't allow any other task to have access to the reserves unless
       * the task has MMF_OOM_SKIP because chances that it would release
       * any memory is quite low.
       */
      if (!is_sysrq_oom(oc) && tsk_is_oom_victim(task)) {
          if (test_bit(MMF_OOM_SKIP, &task->signal->oom_mm->flags))
              goto next;
          goto abort;
      }
    
      /*
       * If task is allocating a lot of memory and has been marked to be
       * killed first if it triggers an oom, then select it.
       */
      if (oom_task_origin(task)) {
          points = ULONG_MAX;
          goto select;
      }
    
      points = oom_badness(task, NULL, oc->nodemask, oc->totalpages);
      if (!points || points < oc->chosen_points)
          goto next;
    
      /* Prefer thread group leaders for display purposes */
      if (points == oc->chosen_points && thread_group_leader(oc->chosen))
          goto next;
    select:
      if (oc->chosen)
          put_task_struct(oc->chosen);
      get_task_struct(task);
      oc->chosen = task;
      oc->chosen_points = points;
    next:
      return 0;
    abort:
      if (oc->chosen)
          put_task_struct(oc->chosen);
      oc->chosen = (void *)-1UL;
      return 1;
    }

3. oom_badness

  • kill 했을 때 가장 높은 메모리를 확보할 수 있는 task 인 경우, 높은 점수를 return 한다는 개념

  • oom_badness() 함수가 리턴해주는 점수가 가장 높은 task가 bad_process로 선정 되어 죽게 됨

  • oom_score_adj 라는 값을 oom_badness 에서 LONG_MIN 을 리턴하기 위해 사용 → LONG_MIN 을 리턴한다는 개념은 낮은 점수를 줘서 kill 하지 못하게 하겠다는 의미

  • RSS (프로세스가 사용하고 있는 물리 메모리) + 프로세스의 스왑 메모리 + (프로세스의 pagetable / page_size) 의 값이 프로세스 (task)의 점수 (point) 가 됨

  • 해당 코드만 보면 프로세스가 점유하고 있는 메모리가 클 경우 score가 높아진다고 이해 가능

  • oom_badness 함수 내용

    /**
     * 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.
     */
    long oom_badness(struct task_struct *p, unsigned long totalpages)
    {
        long points;
        long adj;
    
        if (oom_unkillable_task(p))
            return LONG_MIN;
    
        p = find_lock_task_mm(p);
        if (!p)
            return LONG_MIN;
    
        /*
         * Do not even consider tasks which are explicitly marked oom
         * unkillable or have been already oom reaped or the are in
         * the middle of vfork
         */
        adj = (long)p->signal->oom_score_adj;
        if (adj == OOM_SCORE_ADJ_MIN ||
                test_bit(MMF_OOM_SKIP, &p->mm->flags) ||
                in_vfork(p)) {
            task_unlock(p);
            return LONG_MIN;
        }
    
        /*
         * 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) + get_mm_counter(p->mm, MM_SWAPENTS) +
            mm_pgtables_bytes(p->mm) / PAGE_SIZE;
        task_unlock(p);
    
        /* Normalize to oom_score_adj units */
        adj *= totalpages / 1000;
        points += adj;
    
        return points;
    }

4. get_mm_rss 함수

  • get_mm_rss() 함수를 사용하여 여유 메모리(free memory)가 얼마나 되는지 추정
    static inline unsigned long get_mm_rss(struct mm_struct *mm)
    {
        return get_mm_counter(mm, MM_FILEPAGES) +
            get_mm_counter(mm, MM_ANONPAGES) +
            get_mm_counter(mm, MM_SHMEMPAGES);
    }

5. get_mm_counter 함수

  • get_mm_counter 함수를 통해 swap 메모리(swap memory)가 얼마나 되는지 추정

    static inline unsigned long get_mm_counter(struct mm_struct *mm, int member)
    {
        long val = atomic_long_read(&mm->rss_stat.count[member]);
    
        #ifdef SPLIT_RSS_COUNTING
        /*
         * counter is updated in asynchronous manner and may go to minus.
         * But it's never be expected number for users.
         */
        if (val < 0)
            val = 0;
        #endif
        return (unsigned long)val;
    }

+ Recent posts