
실시간 운영체제(RTOS): 시간이 생명인 시스템
윈도우 업데이트 때문에 에어백이 늦게 터진다면? 빠르다는 뜻이 아닙니다. '예측 가능하다'는 뜻입니다.

윈도우 업데이트 때문에 에어백이 늦게 터진다면? 빠르다는 뜻이 아닙니다. '예측 가능하다'는 뜻입니다.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

시속 120km로 고속도로를 달리다가 갑자기 앞차가 급정거했다. 충돌 순간, 에어백이 터져야 한다. 그런데 계기판에 "시스템 업데이트 중입니다... (58%)"라는 메시지가 뜬다면? 나는 죽는다.
이게 말도 안 된다고 생각할 수 있지만, 실제로 우리가 매일 쓰는 윈도우나 맥OS로 자동차를 만든다면 충분히 가능한 시나리오다. 일반 OS는 "공평함"을 목표로 설계되었기 때문이다. 유튜브도 돌려야 하고, 엑셀도 켜야 하고, 카톡도 받아야 한다. 모든 프로그램에게 골고루 CPU 시간을 나눠주는 게 중요하다.
하지만 심장 박동 조절기(pacemaker)는 다르다. 환자의 심장이 멈췄을 때, OS가 "지금 다른 작업 처리 중이니까 0.5초만 기다려~"라고 하면 환자는 사망한다. 미사일 유도 시스템도 마찬가지다. 목표물이 시속 3,000km로 날아오는데 "잠시만요, 로그 파일 정리 중입니다"라고 하면 요격 실패다.
창업 초기, 우리 IoT 센서가 자꾸 데이터를 늦게 보냈다. 나는 당연히 "CPU를 더 빠른 걸로 바꿔야 하나?"라고 생각했다. 하드웨어 엔지니어한테 물어봤더니 웃으면서 이렇게 말했다.
"문제는 속도가 아니라 '예측 가능성'입니다. 지금 리눅스 쓰고 계신데, 그거 언제 응답할지 아무도 몰라요. RTOS로 바꾸면 항상 정확히 5ms 안에 응답해요."나는 충격받았다. "빠르다"와 "정확하다"가 다른 개념이라는 걸 그때 처음 깨달았다.
일반 OS는 평균적으로 빠르다. 100번 실행하면 평균 10ms 걸린다. 하지만 어떤 때는 5ms, 어떤 때는 50ms 걸린다. 운에 맡기는 거다.
RTOS는 항상 정확하다. 100번 실행하면 100번 모두 정확히 10ms 안에 끝난다. 이게 바로 결정성(Determinism)이다.
예를 들어 두 택시 기사가 있다고 치자.
비행기 타야 하는 사람한테는 RTOS 기사가 필요하다.
RTOS도 두 종류가 있다는 걸 알았을 때 또 한 번 놀랐다.
데드라인을 놓쳐도 치명적이지 않은 시스템이다.
Soft Real-time은 "최선을 다하지만, 못 지켜도 세상이 끝나진 않는다"는 태도다.
데드라인을 놓치면 재앙이 벌어진다.
Hard Real-time은 "못 지키면 사람 죽는다"는 각오로 만든다.
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는 그런 거 없다. 생사가 걸린 문제니까.
주기적으로 실행되는 태스크들을 스케줄링하는 알고리즘이다. 이론은 단순하다.
"실행 주기가 짧은 태스크일수록 우선순위를 높게 준다."예를 들어:
왜 이게 합리적이냐? 태스크 A는 10ms마다 실행돼야 하니까 못 실행하면 바로 데드라인을 놓친다. 태스크 B는 100ms 주기니까 좀 기다려도 괜찮다.
RMS는 수학적으로 증명된 최적 알고리즘이다. 하지만 한계가 있다. CPU 활용률이 약 69%를 넘으면 데드라인 보장이 안 된다. 그래서 실제 RTOS에서는 보통 CPU를 50~60%만 쓴다. 나머지는 예측 못한 상황 대비용 버퍼다.
EDF(Earliest Deadline First)는 더 공격적이다.
"데드라인이 가장 가까운 태스크부터 실행한다."예:
→ A를 먼저 실행한다. 당연하다.
EDF는 RMS보다 이론적으로 효율적이다. CPU 활용률 100%까지도 스케줄링 가능하다. 하지만 현실에서는 RMS를 더 많이 쓴다. 왜냐하면:
인터럽트 레이턴시(Interrupt Latency)는 하드웨어 인터럽트 발생부터 ISR(Interrupt Service Routine) 실행까지 걸리는 시간이다.
예를 들어 에어백 센서가 충돌을 감지하면 CPU에게 인터럽트를 보낸다. 그때부터 에어백 전개 코드가 실행되기까지 걸리는 시간이 인터럽트 레이턴시다.
RTOS는 이걸 보장하기 위해 몇 가지 기법을 쓴다.
이게 진짜 무서운 버그다. 실제로 1997년 화성 탐사선 Mars Pathfinder가 이 문제로 리부팅을 반복했다.
시나리오:
태스크 H (High priority): 우선순위 10
태스크 M (Medium priority): 우선순위 5
태스크 L (Low priority): 우선순위 1
공유 자원: Mutex (뮤텍스로 보호됨)
이게 우선순위 역전이다. 급한 놈(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가 활성화되어 있다.
RTOS에서 자주 쓰는 안전장치가 워치독 타이머(Watchdog Timer)다.
비유하자면, 계속 짖는 개를 생각해보자. 주인이 주기적으로 간식을 주면 조용하다. 근데 주인이 쓰러져서 간식을 안 주면? 개가 짖어서 이웃에게 알린다.
워치독 타이머도 마찬가지다.
// 워치독 타이머 사용 예제
void CriticalTask(void *pvParameters) {
initWatchdog(500); // 500ms 타임아웃 설정
while(1) {
doImportantWork();
kickWatchdog(); // "나 살아있어!" 신호
vTaskDelay(100); // 100ms 대기 (500ms보다 짧음, 안전)
}
}
만약 doImportantWork()에서 무한루프에 빠져서 kickWatchdog()를 호출 못 하면? 500ms 후에 워치독 타이머가 시스템 전체를 리셋한다.
자동차 ECU, 의료기기, 산업용 로봇은 거의 다 워치독 타이머를 쓴다. "잘못되면 리셋해서 다시 시작하는 게 먹통보다 낫다"는 철학이다.
자동차는 ECU(Electronic Control Unit)가 수십~백 개 들어간다. 엔진, 브레이크, 에어백, 인포테인먼트 등등.
AUTOSAR(Automotive Open System Architecture)는 이 ECU들의 소프트웨어 표준이다. RTOS도 AUTOSAR 규격에 맞춰야 차량용으로 쓸 수 있다.
AUTOSAR 기반 RTOS:
테슬라 같은 회사는 자체 RTOS를 만들기도 하지만, 대부분 자동차 회사는 검증된 상용 RTOS를 쓴다. 사고 나면 회사 망하니까.
심장 박동 조절기, 인슐린 펌프, 수술 로봇은 모두 RTOS 위에서 돌아간다.
IEC 62304 (의료기기 소프트웨어 표준)를 만족해야 한다. 여기서 요구하는 것:
그래서 의료기기 RTOS는 개발 기간이 길고 비싸다. 하지만 사람 목숨이 걸려 있으니 어쩔 수 없다.
공장에서 자동차 조립하는 로봇 팔을 생각해보자. 갑자기 작업자가 안전구역에 들어오면 즉시 정지해야 한다.
그래서 산업용 로봇 제어기는 거의 다 RTOS 쓴다. 주로 VxWorks, QNX.
IoT 기기는 특이하다. 실시간성도 필요하지만 배터리 수명도 중요하다.
예: 스마트 온도 조절기
FreeRTOS, Zephyr 같은 RTOS는 Tickless Idle 기능이 있다. 다음 태스크 실행까지 시간이 남으면 CPU를 완전히 끈다. 전력 소모가 1/100로 줄어든다.
일반 OS로도 대부분의 일은 할 수 있다. 유튜브 보고, 문서 쓰고, 게임하고.
하지만 생명, 안전, 돈이 걸린 시스템은 다르다. "평균적으로 괜찮다"가 아니라 "최악의 경우도 보장된다"가 필요하다.
RTOS는 그 보장을 해주는 대신 대가를 받는다.
하지만 자동차가 고속도로에서 에어백을 터뜨려야 할 때, 심장 박동 조절기가 환자 심장을 뛰게 해야 할 때, 그 대가는 아무것도 아니다.
RTOS를 공부하면서 깨달은 건, "빠르다"와 "정확하다"는 완전히 다른 개념이라는 것이다. 세상에는 빠른 것보다 정확한 게 중요한 순간들이 있다. 그 순간을 위해 RTOS가 존재한다.