실시간 스케줄링(RM/EDF)
일반 운영체제의 스케줄러는 평균 응답 시간이나 공정성을 높이는 데 초점을 둡니다. 반면 자동차 제동 제어나 항공기 제어, 오디오 재생 같은 실시간 시스템에서는 계산이 정해진 시간(마감, deadline) 안에 끝나는 것이 결과의 정확성만큼 중요합니다. 마감을 반드시 지켜야 하는 경우를 경성(hard) 실시간, 마감을 가끔 놓쳐도 응답 시간의 분포가 허용 범위 안이면 되는 경우를 연성(soft) 실시간이라고 부릅니다. 그래서 실시간 시스템은 "평균적으로 빠른가"가 아니라 "최악의 경우에도 마감을 지키는가"를 따집니다.
이 토픽에서는 주기적으로 반복되는 작업(주기 작업)을 대상으로 한 두 가지 대표 알고리즘을 봅니다. RM(Rate Monotonic)은 주기가 짧을수록 높은 고정 우선순위를 주고, EDF(Earliest Deadline First)는 마감이 가장 가까운 작업에 가장 높은 우선순위를 줍니다. 같은 작업 집합을 두 방식으로 실행해 어느 쪽이 마감을 지키는지, 그리고 CPU 이용률이 어디까지 올라가도 보장되는지 비교합니다.
핵심 개념
경성 · 연성 실시간
마감을 반드시 보장해야 하는 것이 경성, 응답 시간의 통계적 분포가 허용 범위 안이면 되는 것이 연성입니다. 경성 실시간일수록 최악의 경우를 미리 분석해야 합니다.
핵심 개념
주기 작업 모델
작업마다 한 번 실행에 필요한 최대 시간(C)과 반복 주기(P)가 있고, 마감은 흔히 다음 주기가 시작될 때로 둡니다. 이용률은 C/P를 모든 작업에 대해 더한 값입니다.
핵심 개념
RM (Rate Monotonic)
주기가 짧은(자주 실행되는) 작업일수록 높은 우선순위를 주는 고정 우선순위 방식입니다. 우선순위가 한 번 정해지면 바뀌지 않아 구현과 분석이 단순합니다.
핵심 개념
EDF (Earliest Deadline First)
실행 가능한 작업 가운데 마감이 가장 가까운 것을 먼저 실행합니다. 작업이 도착할 때마다 우선순위를 다시 정하는 동적 우선순위 방식입니다.
핵심 개념
이용률 한계
CPU 이용률이 한계 이하이면 마감을 보장할 수 있습니다. 주기와 마감이 같고 작업이 독립인 선점형 단일 프로세서에서 EDF의 한계는 100%이고, RM은 작업 수 n에 따라 n(2^(1/n) − 1)이며 작업이 많아지면 약 69%로 줄어듭니다.
핵심 개념
선점과 마감 실패
실시간 스케줄링은 높은 우선순위 작업이 낮은 작업을 선점할 수 있다고 가정합니다. 선점으로도 감당하지 못하면 어떤 작업이 마감을 넘기게 됩니다.
실시간 스케줄링의 배경
RM과 EDF는 1973년 Liu와 Layland가 논문 "Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment"에서 분석해 널리 알려졌습니다. 이 논문은 단일 프로세서에서 주기 작업을 대상으로 두 알고리즘의 최적성과 이용률 한계를 보였고(논문에서는 EDF를 deadline driven 알고리즘이라 불렀습니다), 이후 실시간 스케줄링 이론의 대표적인 기초가 되었습니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
RM과 EDF의 차이는 무엇인가요?
RM은 주기가 짧을수록 높은 우선순위를 고정으로 주고, EDF는 마감이 가까운 작업에 그때그때 높은 우선순위를 줍니다. EDF는 단일 프로세서에서 이용률 100%까지 보장하고 RM은 그보다 낮은 한계까지 보장합니다.
면접 질문
경성과 연성 실시간은 어떻게 다른가요?
경성은 마감을 반드시 지켜야 하는 경우이고, 연성은 마감을 가끔 놓쳐도 응답 시간의 분포가 허용 범위이면 되는 경우입니다. 항공기 제어는 경성에, 영상 재생은 연성에 가깝습니다.
면접 질문
CPU 이용률은 어떻게 구하나요?
각 작업의 실행 시간을 주기로 나눈 값(C/P)을 모든 작업에 대해 더합니다. 이 값을 알고리즘의 이용률 한계와 비교해 스케줄 가능성을 판단합니다.
면접 질문
일반 스케줄러와 무엇이 다른가요?
일반 스케줄러는 평균 응답 시간과 공정성이 목표이고, 실시간 스케줄러는 최악의 경우에 마감을 지키는 것이 목표입니다. 그래서 실시간은 작업 시간과 주기를 알고 우선순위를 엄격하게 정합니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
C (POSIX) — 실시간 우선순위로 RM 구현
#include <sched.h> #include <stdio.h> #include <stdlib.h> /* 주기가 짧은 작업일수록 큰 우선순위 값 (RM) */ int set_rm_priority(int prio) { struct sched_param sp = { .sched_priority = prio }; if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) { perror("sched_setscheduler"); /* 권한이 없으면 실패 */ return -1; } return 0; } int main(int argc, char **argv) { /* ./task 60 (T1, 주기 5), ./task 50 (T2, 주기 7) */ int prio = argc > 1 ? atoi(argv[1]) : 50; return set_rm_priority(prio); }
sched_setscheduler는 대상(0이면 호출한 스레드, 단일 스레드 프로세스라면 자기 자신)의 스케줄링 정책과 우선순위를 정합니다. SCHED_FIFO는 정적 우선순위를 쓰는 실시간 정책이라, 주기가 짧은 작업에 더 큰 값을 주면 RM과 같은 우선순위 배치가 됩니다. SCHED_FIFO 우선순위는 리눅스에서 1~99이고, 실시간 정책을 쓰려면 CAP_SYS_NICE 권한(보통 root)이 있거나 RLIMIT_RTPRIO 한도 안이어야 합니다. 마감을 확인하는 기능은 이 정책에 들어 있지 않아, 우선순위만 맞게 정하는 것은 개발자의 몫입니다. 위 코드는 두 프로세스를 각각 실행하는 상황을 보이는 최소 예시입니다.
C (리눅스) — SCHED_DEADLINE으로 EDF 사용
#define _GNU_SOURCE #include <sched.h> #include <stdint.h> #include <sys/syscall.h> #include <unistd.h> #ifndef SCHED_DEADLINE #define SCHED_DEADLINE 6 #endif struct sched_attr { uint32_t size; uint32_t sched_policy; uint64_t sched_flags; int32_t sched_nice; uint32_t sched_priority; uint64_t sched_runtime; /* 주기마다 보장받을 실행 시간(ns) */ uint64_t sched_deadline; /* 상대 마감(ns) */ uint64_t sched_period; /* 주기(ns) */ }; int main(void) { struct sched_attr attr = { .size = sizeof(attr), .sched_policy = SCHED_DEADLINE, .sched_runtime = 2 * 1000 * 1000, /* 2ms */ .sched_deadline = 5 * 1000 * 1000, /* 5ms */ .sched_period = 5 * 1000 * 1000, /* 5ms */ }; return syscall(SYS_sched_setattr, 0, &attr, 0) == -1; }
SCHED_DEADLINE은 작업마다 runtime, deadline, period를 정해 주면 커널이 EDF 방식으로 스케줄링하는 리눅스의 정책입니다. 주기(period)마다 runtime만큼의 CPU 시간을 deadline 안에 받는 것을 목표로 하며, runtime ≤ deadline ≤ period 관계가 필요합니다. 요청한 대역폭(runtime ÷ period)의 합이 커널이 허용하는 상한(기본은 CPU당 95%)을 넘으면 커널이 수용 검사에서 거절합니다. 이 검사는 필요 조건이라 통과해도 모든 마감이 보장되는 것은 아니며, 이 정책은 CAP_SYS_NICE 권한이 필요합니다. glibc 2.40 이하처럼 sched_setattr 래퍼와 sched_attr 정의가 없는 환경을 기준으로 직접 정의하고 syscall로 부르는 최소 예시입니다. glibc 2.41 이상은 sched.h가 sched_attr 구조체와 sched_setattr 함수를 제공하므로 직접 정의하지 말고 그대로 부릅니다. 실제 사용은 sched(7) 문서를 따라야 합니다.
Python — 이용률과 한계 계산
tasks = [(2, 5), (4, 7)] # (실행 시간 C, 주기 P) u = sum(c / p for c, p in tasks) n = len(tasks) rm_bound = n * (2 ** (1 / n) - 1) print(f"U = {u:.3f}, RM 한계 = {rm_bound:.3f}") print("RM 마감 보장:", u <= rm_bound) # False (보장 없음) print("EDF 마감 보장:", u <= 1) # True
이용률 U는 작업별 실행 시간을 주기로 나눈 값의 합이고, 이 예시는 2/5 + 4/7 ≈ 0.971입니다. RM의 한계 n(2^(1/n) − 1)은 작업이 2개일 때 약 0.828이라 U가 이를 넘고, EDF의 한계는 1이라 U가 이를 넘지 않습니다. RM 한계를 넘었다고 반드시 마감을 놓치는 것은 아니고 보장이 없다는 뜻이며, 이 시각화의 작업 집합에서는 실제로 RM 실행이 마감을 놓칩니다. 이 검사는 주기와 마감이 같고 작업이 서로 독립이며 선점 가능한 단일 프로세서라는 가정에서만 성립하므로, 락이나 여러 코어가 있는 시스템에서는 더 자세한 분석이 필요합니다.
Q1
Rate Monotonic(RM) 스케줄링은 우선순위를 어떻게 정할까?
Q2
EDF(Earliest Deadline First) 스케줄링의 규칙은?
Q3
단일 프로세서에서 EDF가 RM보다 이론적으로 유리한 점은?