리눅스 커널의 CPU 스케줄러(CFS 등)는 범용 워크로드에 맞춰 설계됐지만, 게이밍처럼 짧은 지연시간이 중요한 워크로드와 데이터센터의 처리량 위주 워크로드, 배터리 절약이 우선인 모바일 워크로드를 하나의 스케줄링 정책으로 동시에 만족시키기는 어렵다. 과거에는 이런 문제를 해결하려면 커널 소스를 직접 고쳐 스케줄러를 다시 빌드해야 했는데, sched_ext는 BPF 프로그램으로 작성한 스케줄러를 커널 재컴파일 없이 그때그때 로드하고 교체할 수 있게 해준다. 이 글에서는 sched_ext의 동작 방식과, 배포된 스케줄러를 설치·실행하는 방법, 그리고 직접 BPF 스케줄러를 작성하는 기본 구조를 정리한다.
sched_ext란
sched_ext는 BPF의 struct_ops 메커니즘을 이용해 커널 스케줄러를 사용자 공간에서 작성한 BPF 프로그램으로 대체하는 스케줄러 클래스다. 커널 공식 문서에 따르면 sched_ext는 Linux 6.12부터 메인라인에 정식으로 포함됐다. 동작 흐름은 아래 그림처럼 사용자 공간에서 BPF 스케줄러를 clang으로 컴파일해 커널에 로드하면, 그 이후 태스크의 enqueue/dequeue 같은 스케줄링 이벤트가 이 BPF 프로그램의 콜백으로 전달되는 구조다.

요구 사항
직접 커널을 빌드한다면 다음 설정이 활성화되어 있어야 한다.
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_DEBUG_INFO_BTF=y
CONFIG_SCHED_CLASS_EXT=y
직접 빌드하지 않아도 되는 경우도 있다. Ubuntu 25.04부터는 기본 커널에 sched_ext 지원이 포함되어 있어 커널을 따로 준비할 필요가 없다. 24.04 LTS처럼 이전 버전을 쓰고 있다면 ppa:arighi/sched-ext PPA로 sched_ext 지원 커널을 추가로 설치해야 한다.
# Ubuntu 24.04에서 sched_ext 지원 커널 추가 (25.04 이상은 불필요)
sudo add-apt-repository -y --enable-source ppa:arighi/sched-ext
sudo apt install -y linux-generic-wip scx
스케줄러 설치하고 실행하기
직접 BPF 코드를 작성하지 않아도, sched-ext/scx 프로젝트가 이미 여러 목적에 맞는 스케줄러를 배포하고 있다. 대표적으로 scx_rusty(범용 부하 분산), scx_lavd(게이밍용 지연시간 최적화), scx_bpfland(상호작용 워크로드 우선), scx_simple(vtime 기반 기본 스케줄러)가 있다. 소스에서 직접 빌드하려면 Rust 툴체인이 필요하다.
# 빌드 의존성 설치
sudo apt install -y build-essential cmake cargo rustc clang llvm \
pkg-config libelf-dev protobuf-compiler libseccomp-dev libbpf-dev pahole
# 소스 클론 및 전체 빌드
git clone https://github.com/sched-ext/scx.git
cd scx
cargo build --release
# 특정 스케줄러만 빌드
cargo build --release -p scx_rusty
스케줄러를 직접 실행할 수도 있고, D-Bus 기반 데몬인 scx_loader와 그 CLI 클라이언트 scxctl로 전환·관리할 수도 있다. 둘 다 crates.io를 통해 설치 가능하다.
# scx_loader / scxctl 설치
cargo install scx_loader
cargo install scxctl
# 스케줄러를 직접 실행 (5초 간격으로 통계 출력)
sudo scx_rusty --monitor 5
# 현재 sched_ext 활성화 상태 확인
cat /sys/kernel/sched_ext/state
실행 중인 스케줄러를 끄려면 Ctrl+C로 종료하면 된다. 프로세스가 비정상 종료돼도 워치독(watchdog) 타이머가 동작해 커널 기본 스케줄러(CFS)로 자동 복귀하므로, 실행 중 터미널이 끊기더라도 시스템이 계속 멈춰있지는 않는다.
BPF 스케줄러 직접 작성하기
커널 소스 트리의 tools/sched_ext/에는 scx_simple, scx_qmap, scx_central 같은 참고용 예제가 포함되어 있다. 이 디렉터리를 그대로 빌드하면 최소 구성의 스케줄러를 직접 실행해볼 수 있다.
# 커널 소스 트리에서 예제 스케줄러 빌드
make -j$(nproc) -C tools/sched_ext
# 빌드된 scx_simple 실행
sudo tools/sched_ext/build/bin/scx_simple
BPF 스케줄러는 struct sched_ext_ops를 SEC(".struct_ops")로 선언하고, 그 안에 콜백 함수들을 채워 넣는 구조다. 필수 필드는 유효한 BPF 오브젝트 이름을 지정하는 ops.name 하나뿐이고, 나머지 콜백은 선택적으로 구현한다.
- select_cpu: 태스크가 깨어날 때 어느 CPU로 보낼지 결정한다.
scx_bpf_select_cpu_dfl()로 유휴 CPU를 찾아SCX_DSQ_LOCAL에 바로 디스패치할 수 있다. - enqueue:
select_cpu에서 곧바로 디스패치되지 않은 태스크가 대기열(DSQ)에 들어갈 때 호출된다. - dispatch: CPU가 다음에 실행할 태스크를 찾을 때 호출된다. 내장 DSQ만 사용한다면 구현하지 않아도 된다.
- init / exit: 스케줄러가 로드/언로드될 때 호출되며,
init은BPF_STRUCT_OPS_SLEEPABLE매크로로 정의한다.
각 콜백은 BPF_STRUCT_OPS(함수이름, 인자...) 매크로로 정의한다. 예를 들어 scx_simple의 CPU 선택 콜백은 BPF_STRUCT_OPS(simple_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags) 형태의 시그니처를 가진다. 로드된 스케줄러의 상태와 통계는 /sys/kernel/sched_ext/ 아래에서 확인할 수 있다.
# 전체 sched_ext 상태
cat /sys/kernel/sched_ext/state
# 현재 로드된 스케줄러의 ops 이름
cat /sys/kernel/sched_ext/root/ops
주의사항
- 아직 실험적인 기능이다. sched-ext/scx 프로젝트 자체가 프로덕션 환경에서의 사용을 권장하지 않는다고 명시하고 있으므로, 서버 환경보다는 데스크톱·게이밍 환경에서 먼저 검증하는 것이 안전하다.
- root 권한이 필요하다. 스케줄러 로드·해제는 특권 작업이라
sudo없이는 실행되지 않는다. - 커널 버전에 따라 API가 달라질 수 있다.
sched_ext는 여전히 활발히 개발 중이라struct sched_ext_ops의 필드나 헬퍼 함수가 커널 마이너 버전 사이에서도 바뀔 수 있다. 실행 중인 커널 버전에 맞는scx릴리스를 사용해야 한다. - WSL2 같은 커스텀 커널에서는 동작하지 않을 수 있다. WSL2 기본 커널처럼
CONFIG_SCHED_CLASS_EXT가 빠진 커스텀 빌드에서는/sys/kernel/sched_ext자체가 존재하지 않는다. 실습 전에ls /sys/kernel/sched_ext로 지원 여부를 먼저 확인하는 것이 좋다.
마무리
sched_ext는 커널을 고치지 않고도 워크로드에 맞는 스케줄링 정책을 실험할 수 있게 해준다는 점에서, eBPF가 네트워킹·트레이싱 영역에서 해온 역할을 스케줄러 영역으로 확장한 셈이다. 이미 배포된 scx_rusty, scx_lavd 같은 스케줄러를 설치해 워크로드별 성능 차이를 체감해보는 것으로 시작해서, 필요하다면 tools/sched_ext의 예제 코드를 기반으로 직접 콜백을 구현해보는 순서로 접근하는 것을 권장한다.