Linux CPU 스케줄러 동작: task·run queue·dispatcher 구분하기

반응형

Linux에서 CPU 스케줄러는 실행 가능한 task 가운데 다음에 CPU를 사용할 대상을 고른다. run queue는 그런 실행 가능 대상을 관리하는 스케줄러의 자료구조이고, context switch는 선택된 task의 실행 문맥으로 CPU를 전환하는 과정이다. 교재에서 쓰는 dispatcher라는 말은 이 전환 단계 전체를 설명하는 추상화에 가깝다.

처음 이 주제를 정리할 때는 CPU, scheduler, dispatcher, run queue, task를 각각 정의했다. 다시 살펴보니 용어 사전보다 “task가 깨어난 뒤 CPU에서 실행되기까지”를 한 흐름으로 보는 편이 관계를 이해하기 쉬웠다.

task가 CPU에서 실행되기까지 어떤 일이 일어나는가

흐름을 단순화하면 다음과 같다.

sleeping task
    │ I/O 완료·타이머·락 해제 등으로 깨어남
    ▼
runnable task
    │ 스케줄러의 실행 가능 자료구조에 들어감
    ▼
scheduler가 다음 task 선택
    │ 필요하면 이전 문맥 저장·다음 문맥 복원
    ▼
running on a logical CPU

실제 커널에는 스케줄링 클래스, CPU별 자료구조, 선점, CPU affinity, 부하 분산 같은 단계가 더 있다. 위 그림은 용어의 관계를 잡기 위한 축약이지, 모든 커널 경로를 그대로 그린 것은 아니다.

CPU와 논리 CPU는 어떻게 구분해야 하나

CPU는 명령어를 실행하는 하드웨어다. 운영체제 도구에서 CPU라고 표시되는 값은 흔히 스케줄 가능한 논리 CPU를 뜻한다. 하나의 물리 코어가 SMT로 여러 논리 CPU를 제공할 수도 있다.

lscpu
nproc

스케줄러는 task를 논리 CPU에 배치한다. 따라서 “8코어니까 task 8개가 항상 동시에 돈다”라고 단정할 수 없다.

  • SMT 구성에 따라 물리 코어 수와 논리 CPU 수가 다르다.
  • CPU affinity가 일부 CPU만 허용할 수 있다.
  • 컨테이너의 CPU quota가 실제 실행 시간을 제한할 수 있다.
  • idle CPU가 있어도 락·I/O를 기다리는 task는 실행 가능 상태가 아니다.

성능 분석에서는 하드웨어 개수뿐 아니라 해당 프로세스가 실제로 사용할 수 있는 CPU 범위와 시간을 확인해야 한다.

Linux에서 task는 프로세스인가, 스레드인가

Linux 커널은 스케줄 가능한 실행 흐름을 task_struct로 관리한다. 사용자 공간에서 하나의 프로세스로 보이는 프로그램도 여러 스레드를 만들면 스케줄 대상 task가 여러 개다.

Linux 커널의 task 통계 문서는 용어를 다음처럼 설명한다.

  • pid, tid, task: 개별 task_struct를 가리키는 문맥에서 사용한다.
  • tgid, process, thread group: 주소 공간을 공유하는 전통적 Unix 프로세스의 문맥에서 사용한다.

“Linux에서는 프로세스와 스레드가 완전히 같다”라고 줄이면 공유 자원과 thread group의 차이가 사라진다. 더 정확하게는 각 스레드가 스케줄 가능한 task이고, 여러 task가 주소 공간과 자원을 공유해 하나의 프로세스를 이룰 수 있다고 보는 편이 낫다.

# 프로세스 안의 스레드별 상태와 최근 실행 CPU 확인
ps -L -p PID -o pid,tid,psr,stat,comm

runnable과 running은 어떻게 다른가

  • runnable: 실행할 준비가 되어 있으며 CPU를 받을 수 있는 상태다.
  • running: 지금 특정 논리 CPU에서 실제 명령을 실행 중인 상태다.

관측 도구는 두 상태를 항상 분리해서 보여 주지 않는다. 예를 들어 psR은 running 또는 runnable을 함께 나타낸다. 한 번의 상태 문자만으로 CPU 대기 시간을 판단하면 안 되는 이유다.

# 1초 간격으로 runnable task 수와 CPU 사용 추세 보기
vmstat 1

# load average와 현재 runnable/전체 scheduling entity 보기
cat /proc/loadavg

# 전체 runnable thread 스냅샷
grep '^procs_running' /proc/stat

/proc/loadavg의 앞 세 값은 1분·5분·15분 평균이며, run queue의 R 상태뿐 아니라 디스크 I/O를 기다리는 일부 D 상태도 포함한다. 네 번째 필드의 슬래시 앞 값은 현재 runnable scheduling entity 수다. load average와 “지금 run queue 길이”를 같은 값으로 읽으면 안 된다.

run queue는 단순한 FIFO 큐인가

아니다. run queue라는 이름만 보고 하나의 FIFO 목록을 떠올리기 쉽지만, 실제 자료구조와 선택 규칙은 스케줄링 클래스와 커널 버전에 따라 달라진다.

Linux는 보통 CPU별 run queue를 두고 여러 스케줄링 클래스를 조정한다. 대표 정책은 다음처럼 구분할 수 있다.

범주 대표 정책 핵심 목적
일반 시분할 SCHED_OTHER 보통 애플리케이션에 공정한 CPU 시간 제공
실시간 SCHED_FIFO, SCHED_RR 고정 우선순위 기반 실행
기한 기반 SCHED_DEADLINE runtime·deadline·period 제약을 기준으로 실행

과거 CFS 설명에서는 runnable entity를 가상 실행 시간 순으로 관리하는 red-black tree가 핵심이었다. Linux 6.6부터 공정 스케줄링 클래스는 EEVDF 선택 로직으로 전환되기 시작했다. 따라서 현재 서버를 분석할 때는 “Linux는 항상 CFS의 왼쪽 노드만 고른다”라고 외우기보다 커널 버전과 해당 스케줄러 문서를 확인해야 한다.

uname -r
chrt -p PID

chrt로 정책을 조회하는 것과 운영 서버에서 실시간 정책을 변경하는 것은 다르다. 잘못된 우선순위 설정은 다른 작업이 CPU를 얻지 못하게 만들 수 있으므로, 변경은 별도 검증과 권한 통제 없이 시도하지 않는다.

dispatcher는 Linux의 독립 모듈인가

운영체제 교재에서 dispatcher는 스케줄러가 고른 프로세스나 스레드에 CPU 제어를 넘기는 기능을 뜻한다. 보통 다음 항목이 묶여 설명된다.

  • 이전 task의 레지스터와 실행 문맥 저장
  • 다음 task의 실행 문맥 복원
  • 주소 공간이나 커널 상태 전환
  • 선택된 위치에서 실행 재개

이 설명은 개념을 잡는 데 유용하다. 다만 Linux 소스에서 Dispatcher라는 하나의 독립 컴포넌트를 찾아야 한다는 뜻은 아니다. 실제 동작은 스케줄러 코어, 아키텍처별 context switch 코드, 인터럽트·시스템 콜 반환 경로 등에 나뉜다.

dispatcher가 결정을 내린다기보다 scheduler가 선택하고, context switch 경로가 그 선택을 실행한다고 구분하면 역할이 선명해진다.

장기·중기·단기 스케줄러는 Linux에도 그대로 있나

장기·중기·단기 스케줄러는 운영체제 이론에서 작업 흐름을 설명할 때 유용한 분류다.

  • 장기: 어떤 작업을 시스템에 받아들일지 결정한다.
  • 중기: 메모리 압박 등에 따라 작업을 잠시 중단하거나 다시 들인다.
  • 단기: 지금 CPU에서 실행할 task를 고른다.

하지만 현대 Linux에 이 이름을 가진 세 모듈이 그대로 존재한다고 이해하면 곤란하다. 프로세스 생성은 애플리케이션과 서비스 관리자, 메모리 회수는 VM 서브시스템, CPU 선택은 커널 스케줄러가 담당하는 식으로 기능이 나뉜다.

이 분류는 교재의 역할 모델로 두고, 실제 시스템에서는 어떤 커널·사용자 공간 컴포넌트가 그 역할을 수행하는지 따로 확인한다.

Round Robin 예제는 Linux 스케줄러를 재현하나

원문에 있던 Python 큐 예제는 각 작업에 같은 quantum을 주고 미완료 작업을 뒤로 보내는 Round Robin의 직관을 보여 준다. 다만 이를 Linux의 기본 스케줄러 구현으로 보면 안 된다.

Linux의 SCHED_RR은 실시간 스케줄링 정책이다. 같은 정적 우선순위의 runnable task가 정해진 quantum을 번갈아 사용한다. 일반 프로세스가 기본으로 쓰는 SCHED_OTHER와 목적과 우선순위 규칙이 다르다.

따라서 학습 예제에는 다음 경계를 함께 적는 편이 안전하다.

교육용 deque 시뮬레이션
  → Round Robin 선택 순서를 이해하는 예제
  ≠ Linux의 전체 스케줄러·context switch·부하 분산 구현

CPU 스케줄링 병목은 어떤 순서로 확인할까

  1. lscpu, nproc, affinity, cgroup quota로 사용할 수 있는 CPU 범위를 확인한다.
  2. vmstat 1/proc/loadavg로 runnable·blocked 추세를 분리한다.
  3. 프로세스 CPU 사용률만 보지 말고 스레드별 사용률과 상태를 본다.
  4. 짧은 순간값 대신 지연이 발생한 시간대의 연속 데이터를 본다.
  5. CPU가 바쁜지, CPU를 기다리는지, I/O·락을 기다리는지 구분한다.
  6. 필요하면 검증 환경에서 perf sched 같은 스케줄링 지연 도구를 사용한다.
# 스레드별 CPU 사용 추세
top -H -p PID

# 검증 환경에서 스케줄링 이벤트 기록·요약
sudo perf sched record -- sleep 10
sudo perf sched latency

perf sched는 권한과 커널 설정이 필요하고 기록 자체에도 비용이 있다. 운영 환경에서는 수집 범위와 시간을 먼저 제한한다.

CPU·레지스터·Linux task·goroutine의 층위 차이는 CPU 레지스터와 Linux task, goroutine 구분에서, 관측과 실험을 시작하는 기준은 시스템 성능 엔지니어링 1장 독서노트에서 이어서 볼 수 있다.

이 글은 운영체제 용어와 Linux 공식 문서를 다시 대조한 학습 정리다. 특정 서버에서 스케줄러 병목을 재현하거나 설정을 바꿔 해결한 사례를 뜻하지 않는다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글