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 한 개를 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 으로 늘었다. 고르는 일 자체보다 갈아타는 일이 늘어난 것이 이 숫자다.
항목 지도목차 ›
- 계정과 권한
- 1 로그인 — 로그인하면 무슨 일이 일어나나
- 2 계정 만들기 — 계정을 만들면 어디에 무엇이 쓰이나
- 3 권한 주기 — chmod 가 바꾸는 것은 어디에 있나
- 4 권한 검사 — 파일을 열 때 권한을 언제 보나
- 파일과 디렉터리
- 5 파일 만들기 — 파일을 만들면 무엇이 생기나
- 6 디렉터리 — 디렉터리를 만들면 무엇이 생기나
- 7 파일 읽기 — 파일을 읽으면 어디를 거치나
- 8 파일 지우기 — 파일을 지우면 정말 사라지나
- 프로세스
- 9 명령 실행 — 명령 하나를 치면 무슨 일이 벌어지나
- 10 파일 디스크립터 — 프로세스가 파일을 열면 무엇이 늘어나나
- 11 CPU 나누기 — CPU 하나를 여럿이 어떻게 나눠 쓰나
- 메모리
- 12 메모리 요청 — 메모리를 달라고 하면 바로 주나
- 13 페이지 폴트 — 처음 건드릴 때 무슨 일이 일어나나
- 14 OOM — 메모리가 모자라면 누가 죽나
- 네트워크
- 15 소켓 만들기 — 소켓을 만들면 무엇을 받나
- 16 연결 받기 — 연결이 들어오면 누가 먼저 받나
- 17 주고받기 — 데이터가 오갈 때 무엇이 움직이나