Realtime 스케줄링이란 일반 프로세스와 다른 정책(policy)으로 태스크를 선점·배치해, “언젠가는 실행된다"가 아니라 “정해진 시간 안에 실행된다"는 보장을 얻으려는 커널 스케줄러의 기능군을 말합니다. µs 단위 지연을 다루는 시스템에서는 기본 스케줄러가 아무리 공정해도 “가끔 몇 ms씩 늦게 깨어나는” 태스크 하나가 꼬리 지연 전체를 망칠 수 있고, 반대로 실시간 정책을 잘못 쓰면 시스템 전체가 멈추는 사고로 이어질 수 있습니다. 이 장에서는 SCHED_FIFO/SCHED_RR의 동작 원리, 리눅스 6.12에 병합된 sched_ext BPF 스케줄러 프레임워크, 6.12–6.13에 걸쳐 완성된 EEVDF 전환, 그리고 멀티 LLC(Last Level Cache) 서버를 겨냥한 Cache-Aware Scheduling의 최신 진행 상황을 다룹니다.
이 장을 읽기 전에
전제 지식: 이 장은 01장: Context Switch 비용 분석과 회피에서 다룬 “선점·컨텍스트 스위치 비용” 개념과, 03장: CPU Pinning/Affinity 전략·04장: NUMA CPU Affinity·스레드 배치에서 다룬 “코어에 스레드를 고정한다"는 개념을 전제로 합니다. 이 장에서 다루는 실시간 정책은 결국 “어떤 코어에서, 어떤 순서로 실행할 것인가"를 스케줄러가 결정하는 문제이므로, affinity를 먼저 이해하지 않으면 실시간 정책의 효과를 제대로 판단하기 어렵습니다.
이 장의 깊이: 심화 난이도로, SCHED_FIFO/SCHED_RR의 선점 모델, sched_ext의 BPF 훅 구조와 안전 장치, EEVDF의 eligibility·virtual deadline·lag 개념, Cache-Aware Scheduling의 목적과 현재 상태(2026년 기준)를 다룹니다. 다루지 않는 것: CPU affinity·isolcpus 설정 자체(03장), NUMA 노드 간 스레드 배치(04장), 시간 측정 방법론(06장), eBPF/XDP의 네트워크 검증·보안 모델 전반(17장), cgroup 기반 리소스 제어 세부사항(13장)입니다.
당신의 수준에 맞는 경로
| 수준 | 읽을 부분 | 핵심 목표 |
|---|---|---|
| 초보자 | “리눅스 스케줄러 클래스의 역사” ~ “SCHED_FIFO와 SCHED_RR” | 실시간 정책이 일반 정책과 다르게 선점되는 이유 이해 |
| 중급자 | “EEVDF” ~ “sched_ext” | 기본 스케줄러 교체 배경과 커스텀 정책 프레임워크의 안전 장치 이해 |
| 전문가 | “Cache-Aware Scheduling” ~ “비판적 시각” | 최신 커널 스케줄러 변화를 판단 기준에 반영해 정책을 선택 |
리눅스 스케줄러 클래스의 역사와 계층 구조
리눅스 스케줄러는 하나의 알고리즘이 아니라 우선순위가 매겨진 여러 스케줄러 클래스(sched_class)의 체인입니다. SCHED_FIFO/SCHED_RR 같은 실시간 정책은 POSIX.1b(1993년 표준화된 POSIX 실시간 확장)에서 정의되었고, 리눅스는 이를 초기 커널부터 지원해 왔습니다. 일반 태스크를 다루는 스케줄러는 여러 세대를 거쳤습니다. Ingo Molnar가 만든 CFS(Completely Fair Scheduler)가 2007년 리눅스 2.6.23에 병합되어 약 16년간 기본 스케줄러 자리를 지켰고, Peter Zijlstra가 구현한 EEVDF(Earliest Eligible Virtual Deadline First)가 2023년 리눅스 6.6에서 CFS를 대체하기 시작해 6.12(2024년 11월)에서 전환이 완료되었습니다. 같은 6.12에는 Meta 주도로 수년간 아웃오브트리로 개발되어 온 sched_ext(BPF 확장 스케줄러 클래스)가 병합되었고, Cache-Aware Scheduling(CAS)은 2025년 말 LWN에 소개된 패치 시리즈로 논의되고 있으며, 집필 시점 기준으로는 패치 시리즈 단계이며 병합 여부와 시점이 확정되지 않았으므로 구체적인 커널 버전 번호는 단정하지 않습니다.
이 클래스들은 우선순위 순서로 태스크 선택을 위임합니다. 커널은 가장 앞의 클래스부터 “실행할 태스크가 있는지” 물어보고, 있으면 그 클래스가 고른 태스크를 실행합니다.
flowchart TD stopClass["stop_sched_class(마이그레이션/정지)"] --> dlClass["dl_sched_class(SCHED_DEADLINE)"] dlClass --> rtClass["rt_sched_class(SCHED_FIFO / SCHED_RR)"] rtClass --> extClass["ext_sched_class(sched_ext BPF 정책)"] extClass --> fairClass["fair_sched_class(EEVDF, SCHED_NORMAL/BATCH)"] fairClass --> idleClass["idle_sched_class(SCHED_IDLE)"]
SCHED_DEADLINE(EDF 기반 정책)은 이 체인에서 실시간 정책보다도 앞서지만, 커리큘럼상 이 장은 SCHED_FIFO/SCHED_RR·sched_ext·EEVDF·Cache-Aware Scheduling에 집중하므로 SCHED_DEADLINE 자체의 파라미터(runtime/deadline/period)는 깊이 다루지 않습니다. 중요한 점은, sched_ext로 커스텀 BPF 정책을 넣어도 실시간 클래스(rt_sched_class)보다 아래에 위치한다는 것입니다. 즉 SCHED_FIFO 태스크가 있으면 아무리 정교한 sched_ext 정책도 그 태스크가 끝날 때까지 밀립니다.
SCHED_FIFO와 SCHED_RR: 실시간 정책의 동작 원리
SCHED_FIFO와 SCHED_RR은 1–99 범위의 정적 우선순위를 가지며, 숫자가 높을수록 먼저 실행됩니다. 두 정책 모두 일반 정책(SCHED_NORMAL/EEVDF) 태스크보다 항상 먼저 선택되고, 더 높은 우선순위의 실시간 태스크가 러너블 상태가 되면 실행 중이던 낮은 우선순위 실시간 태스크를 즉시 선점합니다. 차이는 같은 우선순위 태스크가 여럿일 때 나타납니다. SCHED_FIFO는 시간 할당량이 없어 태스크가 스스로 양보하거나 블록될 때까지 계속 실행되는 반면, SCHED_RR은 같은 우선순위 태스크끼리 정해진 타임 퀀텀 단위로 라운드로빈 방식으로 교대합니다. 기본적으로 CAP_SYS_NICE 권한(또는 root)이 필요하며, 권한이 없으면 sched_setscheduler()가 EPERM으로 실패합니다. 다만 Linux 2.6.12부터는 비특권 스레드도 RLIMIT_RTPRIO 소프트 한도가 0이 아니면 그 한도까지 정책과 우선순위를 바꿀 수 있습니다(정확한 규칙은 man7: sched(7) 참고).
| |
위 코드는 현재 프로세스를 SCHED_FIFO 우선순위 50으로 바꿉니다. sched_setscheduler만으로는 안전하지 않습니다. 실시간 태스크가 코어를 독점하면 같은 코어의 커널 워커·소프트IRQ가 굶어 시스템이 응답하지 않을 수 있습니다. 커널은 이를 완화하려고 RT throttling을 두어, /proc/sys/kernel/sched_rt_period_us 주기 중 sched_rt_runtime_us만큼만 실시간·데드라인 태스크가 CPU를 쓰게 제한합니다(man sched(7)의 해당 절). 하지만 이 제한을 끄거나 느슨하게 둔 환경에서는 보호막이 사라지므로, 실무에서는 반드시 03장의 CPU affinity(전용 코어 격리, isolcpus)와 함께 적용하고, 페이지 폴트로 인한 지연을 없애려면 14장: Memory Locking도 함께 검토합니다.
코드를 수정하지 않고 기존 바이너리에 실시간 정책을 적용·확인하려면 chrt 명령을 씁니다. 아래는 프로세스 실행·정책 확인, 그리고 rt-tests 패키지의 cyclictest로 스케줄링 지연을 정량 측정하는 명령입니다.
| |
cyclictest는 주기적으로 깨어나도록 예약한 시각과 실제로 깨어난 시각의 차이를 재서 스레드별 최소·평균·최대 지연으로 보고합니다. 평균보다 최대값이 실시간 적합성을 판단하는 핵심 지표이며, 정책을 바꾸기 전과 후를 같은 하드웨어·부하에서 반복 측정해 비교해야 합니다. SCHED_RR의 라운드로빈 주기는 chrt -p가 출력하지 않으므로 sched_rr_get_interval(2) 시스템 콜로 읽습니다. 이 값은 커널·배포판에 따라 기본값이 다르니 단정하지 않고 실제 환경에서 확인합니다.
실시간 스레드가 뮤텍스를 공유할 때는 우선순위 역전에 주의해야 합니다. 낮은 우선순위 스레드가 락을 쥔 채 선점당하면, 그 락을 기다리는 높은 우선순위 스레드가 중간 우선순위 스레드 때문에 무한정 밀릴 수 있습니다. POSIX 뮤텍스를 PTHREAD_PRIO_INHERIT 프로토콜로 초기화하면 락 보유자가 대기자의 우선순위를 일시적으로 물려받아 이 문제를 완화합니다.
EEVDF: CFS를 대체한 가상 마감시간 스케줄링
EEVDF는 Ion Stoica와 Hussein Abdel-Wahab이 1995년 제안한 비례 공유(proportional share) 자원 할당 알고리즘을 기반으로 하며, 리눅스 6.6–6.12에 걸쳐 CFS를 대체했습니다. 핵심 아이디어는 CFS의 “최소 vruntime을 가진 태스크를 고른다"는 단순 규칙을, “적격(eligible)하면서 가상 마감시간(virtual deadline)이 가장 이른 태스크를 고른다"는 규칙으로 바꾼 것입니다. 각 태스크는 lag(공정하게 받아야 할 CPU 시간과 실제로 받은 시간의 차이)를 가지며, lag가 0 이상이면 “적격"으로 간주되어 실행 후보가 됩니다. 적격한 태스크들 중에서는 가상 마감시간이 가장 이른 태스크가 선택됩니다. 이 방식은 CFS 대비 지연 민감 태스크에 유리한 스케줄링을 목표로 합니다(자세한 정의는 커널 문서: EEVDF 스케줄러 참고).
실무에서 중요한 점은, EEVDF가 CFS의 sched_min_granularity_ns·sched_wakeup_granularity_ns 같은 일부 튜닝 노브를 없애거나 의미를 바꿨다는 것입니다. CFS 시절 성능을 맞추기 위해 조정했던 /proc/sys/kernel/sched_* 값들을 그대로 EEVDF 커널에 적용하면 효과가 다르거나 존재하지 않는 파라미터를 건드리는 셈이 될 수 있으므로, 커널 버전을 올릴 때는 스케줄러 관련 튜닝 스크립트를 반드시 재검증해야 합니다.
sched_ext: BPF로 스케줄링 정책을 정의하는 프레임워크
sched_ext는 스케줄링 정책 자체를 BPF 프로그램으로 작성해 런타임에 적재·교체할 수 있게 하는 스케줄러 클래스로, 리눅스 6.12에 병합되었습니다. 커널은 select_cpu(태스크를 어느 CPU에 둘지), enqueue(러너블 상태가 됐을 때 큐잉 방식), dispatch(코어가 유휴 상태일 때 다음 태스크 선택) 같은 훅을 struct sched_ext_ops로 노출하고, 실제 “누구를 언제 실행할지"의 판단은 로드된 BPF 프로그램이 담당합니다. 커스텀 스케줄러를 커널 재컴파일 없이 교체할 수 있다는 점이 핵심 가치이며, scx_rusty(멀티코어 워크 스틸링), scx_lavd(지연 민감 워크로드), scx_bpfland(인터랙티브 반응성) 같은 스케줄러가 이미 생태계에 존재합니다.
| |
안전 장치가 이 프레임워크의 실무 채택을 가능하게 만든 요소입니다. BPF 프로그램은 로드 시 verifier가 정적 검증하고(일반적인 BPF 검증기 제약·보안 모델은 17장: eBPF·커널 경계와 성능·안전 참고, 프레임워크 개요는 커널 문서: Extensible Scheduler Class 참고), 실행 중에는 내부 오류나 실행 가능한 태스크의 정체(stall)가 감지되면 BPF 스케줄러를 중단하고 모든 태스크를 fair 클래스 스케줄러로 되돌립니다(커널 문서). SysRq-S로 수동 복귀도 가능합니다. 이 덕분에 커스텀 정책 실험이 커널 패닉으로 이어질 위험이 out-of-tree 패치 방식보다 크게 낮아졌습니다.
Cache-Aware Scheduling: LLC 지역성을 스케줄러가 챙기다
Cache-Aware Scheduling(CAS)은 멀티 LLC(Last Level Cache) 도메인을 가진 최신 서버 CPU에서, 데이터를 공유할 가능성이 높은 태스크(같은 프로세스의 스레드들)를 같은 LLC 도메인에 묶어 배치하려는 스케줄러 확장입니다(LWN: Cache aware scheduling 참고). 이 기능은 아직 패치 시리즈 단계입니다. 위 LWN 글이 다루는 v2 시리즈는 v6.18-rc7 위에서 시험한 결과를 싣고 있으며, 병합 버전과 설정 방식은 확정되지 않았습니다. 같은 글의 시험 결과를 요약하면, Sapphire Rapids에서는 활성 스레드 수가 LLC 용량 안에 들어올 때 hackbench가 크게 개선되었고(그룹 1개 구성에서 31.85%) ChaCha20도 처리량이 개선되었으며, Genoa에서는 ChaCha20만 큰 처리량 개선을 보이고 hackbench·schbench·netperf·stream·stress-ng에서는 뚜렷한 차이가 없었습니다. 즉 효과는 워크로드와 CPU 구조에 따라 크게 갈리므로, 병합 이후에도 자신의 환경에서 재현해 보고 도입 여부를 판단해야 합니다.
CAS는 NUMA affinity와 다른 문제를 겨냥합니다. NUMA 배치(04장)가 “메모리 컨트롤러에 가까운 코어"를 고르는 문제라면, CAS는 같은 NUMA 노드 안에서도 여러 LLC 도메인으로 쪼개진 최신 서버 CPU(칩렛 설계 등)에서 “같은 캐시를 공유하는 코어"를 고르는 문제입니다. 두 메커니즘은 상호 배타적이지 않고, NUMA 배치를 먼저 맞춘 뒤 그 안에서 CAS가 LLC 수준의 미세 조정을 담당하는 관계로 이해하는 것이 정확합니다.
흔한 오개념 바로잡기
오개념 1: “SCHED_FIFO 우선순위 99면 무조건 가장 빠르고 안전하다.” 우선순위 99는 “가장 먼저 실행된다"는 뜻일 뿐, 안전을 보장하지 않습니다. 높은 우선순위가 오히려 해당 코어의 다른 작업을 모두 밀어내므로, 우선순위 숫자를 올리기 전에 어떤 작업이 그 코어에서 함께 돌아야 하는지부터 정리해야 합니다.
오개념 2: “sched_ext는 실험적이라 프로덕션에 쓰면 위험하다.” sched_ext는 오류·정체 감지 시 fair 클래스로 자동 복귀하므로 잘못된 BPF 스케줄러가 곧바로 시스템 중단으로 이어지지는 않습니다. 다만 “위험하지 않다"는 것과 “성숙했다"는 것은 다른 이야기입니다. sched_ext_ops 인터페이스는 커널 버전에 따라 확장되고 있고, 커스텀 BPF 스케줄러의 성능·공정성은 여전히 개발자의 구현 품질에 크게 좌우됩니다.
오개념 3: “EEVDF로 바뀌었어도 SCHED_NORMAL 튜닝 감각은 CFS 때와 같다.” 위에서 설명한 노브 변화 때문에 CFS 시절의 감각을 그대로 옮기면 안 됩니다. 일반 정책의 지연 특성은 이제 lag와 가상 마감시간이 결정하며, 튜닝 전에 대상 커널에 해당 노브가 존재하는지부터 확인해야 합니다.
판단 기준: 언제 무엇을 쓸까
| 상황 | 권장 | 이유·주의 |
|---|---|---|
| 일반 서버 워크로드, 처리량과 공정성이 우선 | SCHED_NORMAL(EEVDF, 기본값) | 별도 권한·격리 없이 안전하게 동작 |
| 하드 실시간에 가까운 마감(오디오·제어 루프) | SCHED_FIFO + 전용 코어 격리 | 우선순위 역전·시스템 행 방지 위해 03장의 isolcpus·affinity 병행 필수 |
| 같은 우선순위의 실시간 작업을 여러 스레드가 나눠 처리 | SCHED_RR | 라운드로빈으로 동일 우선순위 내 기아 방지 |
| 조직 특화 스케줄링 정책을 실험·점진 적용 | sched_ext | BPF verifier·fair 클래스 자동 복귀로 실험 위험이 out-of-tree 패치보다 낮음 |
| 멀티 LLC 서버에서 캐시 바운싱이 병목으로 확인됨 | 병합 소식을 추적하되 현재는 04장의 NUMA·affinity 배치로 대응 | Cache-Aware Scheduling은 패치 시리즈 단계라 도입 대상이 아님 |
| 컨테이너·Kubernetes 환경의 CPU 자원 제어 | 실시간 정책 대신 cgroup 기반 제어 우선 검토 | 세부 사항은 11장: 컨테이너/가상화 성능 고려사항·13장: cgroups v2 참고 |
비판적 시각: 한계와 트레이드오프
SCHED_FIFO/SCHED_RR은 POSIX.1b 표준화 이후 30년 넘게 검증된 안정적 메커니즘이지만, 우선순위 역전(priority inversion)과 격리 실패로 인한 시스템 행이라는 오래된 위험을 그대로 가지고 있습니다. “실시간"이라는 이름이 주는 안도감과 달리, 잘못 쓰면 오히려 전체 시스템의 신뢰성을 해칠 수 있습니다. sched_ext는 안전 장치가 있다는 점에서 진일보했지만, 커스텀 BPF 스케줄러의 품질 검증·회귀 테스트 체계는 각 조직이 스스로 구축해야 하며, 인터페이스가 커널 버전에 따라 진화하므로 장기 유지보수 부담이 있습니다. EEVDF는 이론적으로 더 정교하지만 knob이 줄어든 것이 항상 “더 좋아졌다"를 뜻하지는 않습니다. 세밀한 튜닝이 필요했던 특수 워크로드에서는 오히려 제어권을 잃었다는 불만도 있습니다. Cache-Aware Scheduling은 아직 병합 전 패치 시리즈라 프로덕션 검증 사례가 없고, 앞서 본 시험 결과처럼 CPU 구조에 따라 효과가 없을 수도 있습니다.
마무리
이 장을 읽은 뒤 다음을 스스로 확인할 수 있어야 합니다.
-
SCHED_FIFO/SCHED_RR과SCHED_NORMAL(EEVDF)의 선점 모델 차이를 설명할 수 있다. - 실시간 우선순위를 격리 없이 적용했을 때의 위험(코어 독점, 시스템 행)을 설명할 수 있다.
-
sched_ext가 BPF verifier와 fair 클래스 자동 복귀로 안전성을 확보하는 방식을 설명할 수 있다. - EEVDF의 eligibility·virtual deadline·lag 개념으로 태스크 선택 순서를 추론할 수 있다.
- Cache-Aware Scheduling이 겨냥하는 멀티 LLC 문제와, 아직 패치 시리즈 단계라는 현재 상태를 안다.
- 워크로드 특성에 따라 판단 기준 표를 이용해 적절한 스케줄링 정책을 고를 수 있다.
이전 장: NUMA CPU Affinity·스레드 배치 (챕터 04)
다음 장에서는 정밀 시간 측정을 다룹니다. 이 장에서 언급한 스케줄링 지연을 실제로 정량화하려면 RDTSC·clock_gettime 같은 정밀 타이밍 도구와, 클럭 소스·오버헤드·정밀도의 함정을 이해해야 합니다. cyclictest가 보고하는 지연 수치의 신뢰도 역시 측정 방법론에 좌우되므로, 스케줄링 정책을 바꾼 뒤의 회귀 검증은 다음 장의 도구로 이어집니다.
→ 정밀 시간 측정 (챕터 06)
![Featured image of post [OS 06] Realtime 스케줄링](/post/os-optimization/realtime-scheduling-sched-ext-eevdf/wordcloud_hu_dc8070e8022bf7a0.webp)
![[OS 06] CPU Pinning/Affinity 전략](/post/os-optimization/cpu-pinning-affinity-strategy/wordcloud_hu_80c176d7700a29.webp)
![[OS 06] NUMA CPU Affinity·스레드 배치](/post/os-optimization/numa-cpu-affinity-thread-placement/wordcloud_hu_27980902404e7114.webp)
![[OS 06] Realtime 스케줄링](/post/os-optimization/realtime-scheduling-sched-ext-eevdf/wordcloud_hu_4bea26addda21753.webp)
![[OS 06] 정밀 시간 측정](/post/os-optimization/precise-time-measurement-rdtsc-clock-gettime/wordcloud_hu_880594b44c30cd79.webp)
![[OS 06] 커널 바이패스 개요](/post/os-optimization/kernel-bypass-overview/wordcloud_hu_592d027bde39b991.webp)
![[OS 06] Context Switch 비용 분석과 회피](/post/os-optimization/context-switch-cost-avoidance/wordcloud_hu_6b2ad90b0e4290f9.webp)
![[OS 06] Signal Handling 오버헤드](/post/os-optimization/signal-handling-overhead-avoidance/wordcloud_hu_172a1f3edb861cb5.webp)
![[OS 06] Memory Locking](/post/os-optimization/memory-locking-mlock-mlockall/wordcloud_hu_36b2c51e9b0e7460.webp)
![[IO 09] Database I/O 패턴](/post/io-optimization/database-io-wal-fsync-journaling-strategy/wordcloud_hu_aa8d2bffd178ad33.webp)