리눅스의 기본 스케줄러(Completely Fair Scheduler). 가상 실행 시간(vruntime)이 가장 적은 태스크를 고른다.
발상 — 시간 조각이 아니라 비율
-
전통적 방식 — "각자 10ms 씩" — 고정 시간 조각
-
CFS — "각자 CPU 의 1/n 을" — 비율로 공평
-
각 태스크의 vruntime 을 누적하고, 가장 적게 쓴 태스크를 다음에 돌린다
-
자연스럽게 균등해진다
우선순위는 가중치로 반영된다
nice 값이 낮을수록(우선순위 높을수록) 가중치가 크다 vruntime 증가 속도 = 실제 실행 시간 / 가중치
- 우선순위가 높으면 vruntime 이 천천히 늘어
- 더 자주 선택된다
nice 1 차이당 약 10% CPU 비중 차이
자료구조
레드-블랙 트리에 vruntime 순으로 정렬 다음 태스크 선택 = 가장 왼쪽 노드 → O(1) (캐싱) 삽입·삭제 O(log n)
MLFQ와의 비교
-
MLFQ — 여러 큐를 두고 행동을 보고 태스크를 올리고 내린다
- CPU 를 많이 쓰면 강등, I/O 로 자주 양보하면 유지
- 대화형 작업을 자동으로 우대하는 효과
- 기아를 막으려면 주기적 승격(aging) 이 필요
-
CFS — 큐 계층 없이 vruntime 하나로 공평성을 달성
- 새로 깨어난 태스크는 vruntime 이 작아 자연히 우선된다
- 별도 규칙 없이 대화형 반응성이 나온다
컨테이너와의 연결
cgroup 의 cpu.shares · cpu.max 가 CFS 위에서 동작한다
shares 가중치 (상대적 비율)
max 하드 상한 (주기당 최대 실행 시간)
쿠버네티스의 requests/limits 가 이것으로 구현된다
vruntime 이 새 태스크에 주는 효과
새로 만들어진/깨어난 태스크의 vruntime 을 0 으로 두면 CPU 를 독점하게 된다 (누적이 0 이므로 계속 뽑힌다)
- 실행 큐의 최소 vruntime(min_vruntime) 근처 값을 준다
- 약간의 보너스만 주어 반응성은 살리고 독점은 막는다
I/O 대기가 잦은 대화형 작업은 자연히 vruntime 이 덜 쌓여 깨어날 때마다 우선 선택된다 — 별도 규칙 없이 얻어지는 성질이다
스케줄링 지연과 최소 조각
sched_latency 모든 실행 대기 태스크가 한 번씩 도는 목표 주기
sched_min_granularity 한 번 잡으면 최소한 보장하는 실행 시간
태스크가 많아지면 조각이 무한히 작아질 수 없으므로
min_granularity 가 하한을 잡는다 → 실제 주기가 늘어난다
조각이 작으면 반응성↑ 처리량↓ (컨텍스트 스위치·캐시 무효화 증가)
서버 튜닝에서 이 값을 키워 처리량을 얻는 경우가 있다
EEVDF 로의 교체
리눅스 6.6 부터 기본 스케줄러가 EEVDF(Earliest Eligible Virtual Deadline First)로 바뀌었다. vruntime 개념은 유지하면서 각 태스크에 가상 데드라인을 부여해, 지연에 민감한 작업이 더 잘 보장되도록 한 것이다. "공평한 비율" 위에 "언제까지 실행돼야 하는가"를 얹은 형태로 이해하면 된다.
면접 함정
- ❌ "CFS는 라운드로빈" → 고정 조각이 아니라 vruntime 기반이다.
- ❌ "nice를 낮추면 항상 먼저 실행" → 비중이 커질 뿐 독점하지 않는다.