Study · 운영체제 · CPU 나누기 · 프로세스

돌 준비가 된 것이 CPU 보다 많을 때 커널이 하나를 고른다

장면

프로세스가 CPU 개수보다 많아도 전부 도는 것처럼 보인다. 커널은 다음에 어느 것을 돌릴지 무엇을 보고 고를까?

$ nproc                     # 이 기계의 CPU 개수. 장면은 한 개로 단순화했다
16
$ cat /proc/loadavg         # 넷째 칸의 빗금 앞 숫자가 지금 돌 준비된 수다
0.54 0.21 0.07 1/543 123    # 놀고 있을 때 — 돌 준비된 것이 1 개
$ cat /proc/loadavg         # CPU 16 개짜리에서 바쁜 고리 32 개를 돌린 뒤
0.54 0.21 0.07 33/607 231   # 33 개가 한꺼번에 돌 준비가 됐다
$ ps -eo pid,stat,comm      # STAT 열의 R 이 「돌 준비가 됐다」는 뜻이다

아래 재생기의 ▶를 누르면 한 스텝씩 진행된다. 금색 테두리가 지금 이야기하는 곳이고, 캡션이 무슨 일이 일어났는지 말해 준다. 굵게 밑줄 친 말은 누르면 뜻이 나온다. 빨간 딱지는 그때 벌어지는 일의 이름이고, 캡션 바로 위 회색 글자는 지금 스텝이 속한 묶음의 이름이다.

카드 아래 회색 줄은 그 일이 실제로 적혀 있는 소스의 파일 이름 : 줄 번호와 함수 이름이다. 글 안에 (:669)처럼 줄 번호만 적힌 것은 바로 앞에 적은 파일의 그 줄이라는 뜻이다. 캡션 한가운데 박힌 긴 영어 이름과 괄호 속 파일 · 줄 번호도 같다 — 건너뛰고 한글만 읽어도 뜻이 통하게 써 두었다.

장면 · 돌 준비가 된 것이 CPU 보다 많을 때 커널이 하나를 고른다 0 / 11

▶ 를 누르면 CPU 한 개를 task 둘이 나눠 쓰는 동안 커널이 어디서 무엇을 보는지 따라간다. 요즘 기계는 CPU 가 여러 개지만, 얘기를 쉽게 하려고 여기서는 한 개로 둔다. 개수가 늘어도 규칙은 CPU 마다 똑같이 돈다. 커널은 「지금 누가 돌아야 하나」를 쉬지 않고 다시 묻지 않는다. 바꿀 때가 됐다는 표시만 켜 두었다가, 안전한 자리에서 한 번 고른다. 그 고르는 규칙과 갈아탈 때 바꾸는 값이 이 장면의 요지다.

키보드 · ← → 한 스텝 · Space 재생/멈춤 · Home 처음

돌 준비가 된 것이 CPU 보다 많으면 커널이 런큐에서 하나를 고른다. 고르는 일보다 갈아타는 일이 비싸고, 그 값이 문맥 교환 수로 드러난다.

어떻게 확인했나

소스 어디를 보고 적었는지와 무엇을 돌려서 쟀는지를 남긴 자리다. 처음 읽을 때는 건너뛰어도 된다.

  • 기본 스케줄러는 EEVDF 다. 일반 프로세스가 받는 클래스는 fair_sched_class(__setscheduler_class, kernel/sched/core.c:7622 의 마지막 반환값)이고, 그 클래스가 고르는 실제 함수가 pick_eevdf(kernel/sched/fair.c:1187) — 「Earliest Eligible Virtual Deadline First」다. 파일 머리(kernel/sched/fair.c:3)와 구조체 이름 cfs_rq 는 아직 CFS 로 남아 있어서 이름만 보면 CFS 로 착각한다. 이름은 옛것이고 규칙은 EEVDF 다.
  • 고르는 차례는 __schedule(kernel/sched/core.c:7111) → pick_next_task(:6245) → __pick_next_task(:6153) → pick_task_fair(kernel/sched/fair.c:10054) → pick_next_entity(:6518) → pick_eevdf(:1187) 다. 조건은 둘 — 받을 몫이 남아 있는가(entity_eligible), 그중 가상 마감이 가장 이른가.
  • 커널은 그 자리에서 갈아타지 않는다. sched_tick(kernel/sched/core.c:5782) → task_tick_fair(kernel/sched/fair.c:15071) → update_curr(:2032) 가 update_deadline(:1289) 로 몫을 다 썼는지 보고, resched_curr(kernel/sched/core.c:1241) 가 TIF_NEED_RESCHED 표시만 켠다. 실제 교체는 안전한 지점에서 __schedule 이 돌 때 일어난다.
  • 타임 슬라이스 기본값은 소스에 sysctl_sched_base_slice = 700000(ns, kernel/sched/fair.c:85) 으로 박혀 있고, 주석은 0.70 msec * (1 + ilog(ncpus)) 라고 적는다. 이건 소스 상수라 실행 없이 읽을 수 있다 — 다만 돌고 있는 기계의 실제 값은 /sys/kernel/debug/sched/base_slice_ns 를 봐야 안다.
  • 문맥 교환이 바꾸는 것은 둘이다. context_switch(kernel/sched/core.c:5474) 가 switch_mm_irqs_off(:5512) 로 메모리 지도를, switch_to(:5533) 로 레지스터와 스택을 바꾼다. 같은 프로세스의 스레드끼리는 switch_mm_irqs_off(arch/x86/mm/tlb.c:783) 안의 prev == next 지름길로 앞쪽을 건너뛴다.
  • vmstat 의 cs 와 /proc/stat 의 ctxt 는 rq->nr_switches(kernel/sched/core.c:7246) 를 전부 더한 값이다(nr_context_switches, :5579 → fs/proc/stat.c:143). 뺏겨서 바뀌면 nivcsw(:7168), 스스로 양보해 바뀌면 nvcsw(:7194) 가 오른다.
  • CPU 16개짜리 컨테이너에서 쟀다. vmstat 1 의 cs(문맥 교환)가 놀 때 167~364 였는데, 바쁜 고리 32개를 돌리자 3,507~5,094 로 늘었다. /proc/loadavg 의 돌 준비된 수도 1 에서 33 으로 늘었다. 고르는 일 자체보다 갈아타는 일이 늘어난 것이 이 숫자다.
‹ 이전 목차로 ›