에어백이 "로딩 중..."이면 안 되는 이유
시속 120km로 고속도로를 달리다가 갑자기 앞차가 급정거했다. 충돌 순간, 에어백이 터져야 한다. 그런데 계기판에 "시스템 업데이트 중입니다... (58%)"라는 메시지가 뜬다면? 나는 죽는다.
이게 말도 안 된다고 생각할 수 있지만, 실제로 우리가 매일 쓰는 윈도우나 맥OS로 자동차를 만든다면 충분히 가능한 시나리오다. 일반 OS는 "공평함"을 목표로 설계되었기 때문이다. 유튜브도 돌려야 하고, 엑셀도 켜야 하고, 카톡도 받아야 한다. 모든 프로그램에게 골고루 CPU 시간을 나눠주는 게 중요하다.
하지만 심장 박동 조절기(pacemaker)는 다르다. 환자의 심장이 멈췄을 때, OS가 "지금 다른 작업 처리 중이니까 0.5초만 기다려~"라고 하면 환자는 사망한다. 미사일 유도 시스템도 마찬가지다. 목표물이 시속 3,000km로 날아오는데 "잠시만요, 로그 파일 정리 중입니다"라고 하면 요격 실패다.
내가 RTOS를 오해했던 순간
창업 초기, 우리 IoT 센서가 자꾸 데이터를 늦게 보냈다. 나는 당연히 "CPU를 더 빠른 걸로 바꿔야 하나?"라고 생각했다. 하드웨어 엔지니어한테 물어봤더니 웃으면서 이렇게 말했다.
"문제는 속도가 아니라 '예측 가능성'입니다. 지금 리눅스 쓰고 계신데, 그거 언제 응답할지 아무도 몰라요. RTOS로 바꾸면 항상 정확히 5ms 안에 응답해요."
나는 충격받았다. "빠르다"와 "정확하다"가 다른 개념이라는 걸 그때 처음 깨달았다.
일반 OS는 평균적으로 빠르다. 100번 실행하면 평균 10ms 걸린다. 하지만 어떤 때는 5ms, 어떤 때는 50ms 걸린다. 운에 맡기는 거다.
RTOS는 항상 정확하다. 100번 실행하면 100번 모두 정확히 10ms 안에 끝난다. 이게 바로 결정성(Determinism)이다.
예를 들어 두 택시 기사가 있다고 치자.
- 일반 OS 기사: "보통 30분 걸려요. 근데 막히면 1시간 걸릴 수도 있어요." (평균은 빠르지만 예측 불가)
- RTOS 기사: "무조건 45분 안에 데려다드립니다. 늦으면 환불해드릴게요." (느릴 수 있지만 보장됨)
비행기 타야 하는 사람한테는 RTOS 기사가 필요하다.
결정성의 정체: Hard vs Soft Real-time
RTOS도 두 종류가 있다는 걸 알았을 때 또 한 번 놀랐다.
Soft Real-time (연성 실시간)
데드라인을 놓쳐도 치명적이지 않은 시스템이다.
- 동영상 스트리밍: 넷플릭스가 프레임 하나 늦게 보내면? 화면 좀 끊긴다. 짜증나지만 아무도 안 죽는다.
- 음성 통화: VoIP 패킷이 0.1초 늦으면? 목소리가 약간 뚝뚝 끊긴다. 불편하지만 통화는 된다.
- 게임 렌더링: 60fps 목표인데 가끔 50fps로 떨어지면? 게이머가 욕하지만 게임은 돌아간다.
Soft Real-time은 "최선을 다하지만, 못 지켜도 세상이 끝나진 않는다"는 태도다.
Hard Real-time (경성 실시간)
데드라인을 놓치면 재앙이 벌어진다.
- 자동차 에어백: 충돌 감지 후 0.05초 이내에 터져야 한다. 0.06초면 운전자 사망.
- 인공 심장 박동기: 심장이 뛰어야 할 타이밍에서 0.01초라도 늦으면 환자 사망.
- 원자력 발전소 제어봉: 온도 임계점 감지 후 0.1초 내에 삽입 안 되면 멜트다운.
- 산업용 로봇 팔: 사람 감지 후 0.02초 내에 정지 안 하면 사람 부상.
Hard Real-time은 "못 지키면 사람 죽는다"는 각오로 만든다.
RTOS vs GPOS: 근본적인 설계 철학 차이
GPOS(General Purpose OS)와 RTOS는 태생부터 목표가 다르다.
| 특성 | GPOS (윈도우, 리눅스) | RTOS (FreeRTOS, VxWorks) |
|---|---|---|
| 목표 | 처리량(Throughput) 최대화 | 데드라인 준수 |
| 스케줄링 | 공평함 (모두에게 기회) | 우선순위 절대적 |
| 응답 시간 | 평균적으로 빠름, 최악은 예측 불가 | 최악의 경우도 보장 |
| 컨텍스트 스위칭 | 느림 (마이크로초~밀리초) | 매우 빠름 (나노초~마이크로초) |
| 인터럽트 레이턴시 | 예측 불가 | 보장됨 (보통 수 마이크로초) |
| 메모리 관리 | 가상 메모리, 페이징 | 고정 메모리, 페이징 없음 |
| 크기 | 거대함 (수 GB) | 작음 (수 KB~MB) |
내가 처음 FreeRTOS 코드를 봤을 때 충격받은 게, 커널 전체가 10KB도 안 된다는 거였다. 윈도우는 몇 GB인데 말이다. 하지만 생각해보면 당연하다. 에어백 제어기에 웹브라우저나 워드프로세서는 필요 없으니까.
우선순위 기반 선점형 스케줄링의 냉혹함
RTOS의 스케줄러는 단순하고 냉혹하다. "우선순위 높은 놈이 왔으면 무조건 지금 당장 실행한다."
// FreeRTOS 태스크 생성 예제
#include "FreeRTOS.h"
#include "task.h"
// 에어백 제어 태스크 (최고 우선순위)
void AirbagTask(void *pvParameters) {
while(1) {
if (detectCollision()) {
deployAirbag(); // 즉시 실행, 절대 지연 안 됨
}
vTaskDelay(1); // 1ms 대기
}
}
// 음악 재생 태스크 (낮은 우선순위)
void MusicTask(void *pvParameters) {
while(1) {
playNextSample(); // 에어백이 오면 즉시 중단됨
vTaskDelay(10);
}
}
// 메인 함수
int main(void) {
// 우선순위: 숫자 높을수록 급함
xTaskCreate(AirbagTask, "Airbag", 128, NULL, 10, NULL); // 우선순위 10
xTaskCreate(MusicTask, "Music", 128, NULL, 1, NULL); // 우선순위 1
vTaskStartScheduler(); // 스케줄러 시작
return 0;
}
위 코드에서 MusicTask가 음악 재생 중이더라도, 충돌이 감지되는 순간 즉시 중단되고 AirbagTask가 실행된다. 이게 선점형(Preemptive) 스케줄링이다.
일반 OS는 "음악 재생 작업이 좀 진행 중이니까 구간 끝날 때까지 기다려주자"라고 생각할 수 있다. RTOS는 그런 거 없다. 생사가 걸린 문제니까.
Rate Monotonic Scheduling (RMS): 주기가 짧을수록 급하다
주기적으로 실행되는 태스크들을 스케줄링하는 알고리즘이다. 이론은 단순하다.
"실행 주기가 짧은 태스크일수록 우선순위를 높게 준다."
예를 들어:
- 태스크 A: 10ms마다 센서 읽기 (주기 짧음) → 우선순위 높음
- 태스크 B: 100ms마다 로그 저장 (주기 김) → 우선순위 낮음
왜 이게 합리적이냐? 태스크 A는 10ms마다 실행돼야 하니까 못 실행하면 바로 데드라인을 놓친다. 태스크 B는 100ms 주기니까 좀 기다려도 괜찮다.
RMS는 수학적으로 증명된 최적 알고리즘이다. 하지만 한계가 있다. CPU 활용률이 약 69%를 넘으면 데드라인 보장이 안 된다. 그래서 실제 RTOS에서는 보통 CPU를 50~60%만 쓴다. 나머지는 예측 못한 상황 대비용 버퍼다.
Deadline Scheduling (EDF): 마감 임박한 놈부터
EDF(Earliest Deadline First)는 더 공격적이다.
"데드라인이 가장 가까운 태스크부터 실행한다."
예:
- 태스크 A: 지금부터 5ms 후 데드라인
- 태스크 B: 지금부터 20ms 후 데드라인
→ A를 먼저 실행한다. 당연하다.
EDF는 RMS보다 이론적으로 효율적이다. CPU 활용률 100%까지도 스케줄링 가능하다. 하지만 현실에서는 RMS를 더 많이 쓴다. 왜냐하면:
- 구현이 복잡하다: 매번 모든 태스크의 남은 데드라인을 계산해야 함.
- 오버헤드가 크다: 데드라인이 계속 바뀌니까 우선순위도 계속 바뀜.
- 예측 가능성이 떨어진다: RMS는 우선순위가 고정이라 디버깅이 쉬움.
인터럽트 레이턴시 - RTOS의 진짜 실력
인터럽트 레이턴시(Interrupt Latency)는 하드웨어 인터럽트 발생부터 ISR(Interrupt Service Routine) 실행까지 걸리는 시간이다.
예를 들어 에어백 센서가 충돌을 감지하면 CPU에게 인터럽트를 보낸다. 그때부터 에어백 전개 코드가 실행되기까지 걸리는 시간이 인터럽트 레이턴시다.
- GPOS: 수십 마이크로초~밀리초. 예측 불가.
- RTOS: 수 마이크로초 이하. 보장됨.
RTOS는 이걸 보장하기 위해 몇 가지 기법을 쓴다.
- 인터럽트 비활성화 구간 최소화: 커널 내부에서 인터럽트를 끄는 시간을 극도로 짧게 만든다.
- 우선순위 역전 방지: 나중에 설명할 Priority Inversion 해결.
- Zero-latency interrupt: 아예 OS를 거치지 않고 직접 실행되는 초고속 인터럽트.
우선순위 역전(Priority Inversion) - RTOS의 악몽
이게 진짜 무서운 버그다. 실제로 1997년 화성 탐사선 Mars Pathfinder가 이 문제로 리부팅을 반복했다.
시나리오:
태스크 H (High priority): 우선순위 10
태스크 M (Medium priority): 우선순위 5
태스크 L (Low priority): 우선순위 1
공유 자원: Mutex (뮤텍스로 보호됨)
- L이 Mutex를 잡고 작업 중이다.
- H가 깨어나서 같은 Mutex가 필요하다.
- H는 L이 Mutex를 풀 때까지 기다린다. (여기까진 정상)
- 그런데 M이 깨어나서 실행된다!
- M은 Mutex가 필요 없지만, L보다 우선순위가 높으니까 L을 선점한다.
- 결과: H(우선순위 10)가 M(우선순위 5)을 기다리는 상황 발생!
이게 우선순위 역전이다. 급한 놈(H)이 덜 급한 놈(M) 때문에 기다린다.
// 우선순위 역전 시나리오 (FreeRTOS)
SemaphoreHandle_t xMutex;
void TaskL(void *pvParameters) { // 우선순위 1
xSemaphoreTake(xMutex, portMAX_DELAY); // 뮤텍스 잡음
// 긴 작업 중...
for(int i = 0; i < 1000000; i++) {
doSlowWork();
}
xSemaphoreGive(xMutex); // 뮤텍스 해제
}
void TaskM(void *pvParameters) { // 우선순위 5
// Mutex 필요 없음
while(1) {
doMediumPriorityWork(); // L을 선점해버림!
vTaskDelay(10);
}
}
void TaskH(void *pvParameters) { // 우선순위 10
xSemaphoreTake(xMutex, portMAX_DELAY); // L이 풀 때까지 대기
// 치명적 작업 (에어백 등)
deployCriticalSystem();
xSemaphoreGive(xMutex);
}
해결책은 Priority Inheritance(우선순위 상속)이다. L이 H가 기다리는 Mutex를 잡고 있으면, L의 우선순위를 일시적으로 H의 우선순위(10)로 올려준다. 그러면 M(우선순위 5)이 L을 선점하지 못한다.
FreeRTOS는 이걸 자동으로 해준다. xSemaphoreCreateMutex()로 만든 뮤텍스는 기본적으로 Priority Inheritance가 활성화되어 있다.
Watchdog Timer: 멈춘 시스템을 때리는 망치
RTOS에서 자주 쓰는 안전장치가 워치독 타이머(Watchdog Timer)다.
비유하자면, 계속 짖는 개를 생각해보자. 주인이 주기적으로 간식을 주면 조용하다. 근데 주인이 쓰러져서 간식을 안 주면? 개가 짖어서 이웃에게 알린다.
워치독 타이머도 마찬가지다.
// 워치독 타이머 사용 예제
void CriticalTask(void *pvParameters) {
initWatchdog(500); // 500ms 타임아웃 설정
while(1) {
doImportantWork();
kickWatchdog(); // "나 살아있어!" 신호
vTaskDelay(100); // 100ms 대기 (500ms보다 짧음, 안전)
}
}
만약 doImportantWork()에서 무한루프에 빠져서 kickWatchdog()를 호출 못 하면? 500ms 후에 워치독 타이머가 시스템 전체를 리셋한다.
자동차 ECU, 의료기기, 산업용 로봇은 거의 다 워치독 타이머를 쓴다. "잘못되면 리셋해서 다시 시작하는 게 먹통보다 낫다"는 철학이다.
실제 RTOS들: FreeRTOS, VxWorks, Zephyr, QNX
FreeRTOS
- 가장 인기 많음: IoT 기기, 드론, 로봇에 널리 쓰임.
- MIT 라이선스: 완전 무료, 상업적 사용 가능.
- 아마존 지원: AWS IoT와 통합 쉬움.
- 크기: 커널 10KB 미만.
- 단점: Hard Real-time 보장은 약함. Soft Real-time에 가까움.
VxWorks
- 최고급 Hard Real-time: NASA 화성 탐사선, F-35 전투기, 보잉 787.
- 비쌈: 라이선스 비용이 엄청남.
- 인증: DO-178C (항공), IEC 61508 (산업안전) 인증 받음.
- 신뢰성: 수십 년간 검증됨.
Zephyr
- Linux Foundation 주도: 오픈소스.
- IoT 특화: 블루투스, Thread, LoRa 등 무선 프로토콜 기본 지원.
- 점유율 급증 중: FreeRTOS 대안으로 떠오름.
QNX
- 마이크로커널 구조: 커널이 극도로 작고, 나머지는 유저 공간에서 실행.
- 자동차 표준: 테슬라, 폭스바겐, 벤츠 등 인포테인먼트 시스템.
- POSIX 호환: 유닉스 프로그램 포팅 쉬움.
AUTOSAR: 자동차 산업의 RTOS 표준
자동차는 ECU(Electronic Control Unit)가 수십~백 개 들어간다. 엔진, 브레이크, 에어백, 인포테인먼트 등등.
AUTOSAR(Automotive Open System Architecture)는 이 ECU들의 소프트웨어 표준이다. RTOS도 AUTOSAR 규격에 맞춰야 차량용으로 쓸 수 있다.
AUTOSAR 기반 RTOS:
- AUTOSAR OS: VxWorks, OSEK 같은 상용 RTOS 사용.
- 안전 등급: ASIL-D (가장 높은 자동차 안전 등급).
- 테스트: 수백만 시간 검증.
테슬라 같은 회사는 자체 RTOS를 만들기도 하지만, 대부분 자동차 회사는 검증된 상용 RTOS를 쓴다. 사고 나면 회사 망하니까.
의료기기 - 사람 목숨이 걸린 RTOS
심장 박동 조절기, 인슐린 펌프, 수술 로봇은 모두 RTOS 위에서 돌아간다.
IEC 62304 (의료기기 소프트웨어 표준)를 만족해야 한다. 여기서 요구하는 것:
- Worst-case execution time 증명: 최악의 경우에도 데드라인 지키는지 수학적으로 증명.
- 트레이서빌리티: 모든 코드 라인이 어떤 요구사항에서 나왔는지 추적 가능.
- 정적 분석: 버퍼 오버플로우, 메모리 누수 같은 버그 자동 탐지.
그래서 의료기기 RTOS는 개발 기간이 길고 비싸다. 하지만 사람 목숨이 걸려 있으니 어쩔 수 없다.
산업용 로봇 - 0.01초의 반응
공장에서 자동차 조립하는 로봇 팔을 생각해보자. 갑자기 작업자가 안전구역에 들어오면 즉시 정지해야 한다.
- 센서 감지 → 로봇 정지: 20ms 이내 보장.
- 못 지키면: 작업자 부상, 회사 소송.
그래서 산업용 로봇 제어기는 거의 다 RTOS 쓴다. 주로 VxWorks, QNX.
IoT 기기 - 배터리와 실시간성의 균형
IoT 기기는 특이하다. 실시간성도 필요하지만 배터리 수명도 중요하다.
예: 스마트 온도 조절기
- 온도 센서 읽기: 1초마다 (데드라인 지켜야 함)
- 나머지 시간: Sleep 모드 (배터리 절약)
FreeRTOS, Zephyr 같은 RTOS는 Tickless Idle 기능이 있다. 다음 태스크 실행까지 시간이 남으면 CPU를 완전히 끈다. 전력 소모가 1/100로 줄어든다.
마치며 - RTOS는 보험이다
일반 OS로도 대부분의 일은 할 수 있다. 유튜브 보고, 문서 쓰고, 게임하고.
하지만 생명, 안전, 돈이 걸린 시스템은 다르다. "평균적으로 괜찮다"가 아니라 "최악의 경우도 보장된다"가 필요하다.
RTOS는 그 보장을 해주는 대신 대가를 받는다.
- 더 비싸다 (VxWorks 라이선스는 수천만 원).
- 더 복잡하다 (개발자가 모든 타이밍을 계산해야 함).
- 덜 유연하다 (범용 앱 못 돌림).
하지만 자동차가 고속도로에서 에어백을 터뜨려야 할 때, 심장 박동 조절기가 환자 심장을 뛰게 해야 할 때, 그 대가는 아무것도 아니다.
RTOS를 공부하면서 깨달은 건, "빠르다"와 "정확하다"는 완전히 다른 개념이라는 것이다. 세상에는 빠른 것보다 정확한 게 중요한 순간들이 있다. 그 순간을 위해 RTOS가 존재한다.