실시간 애플리케이션(Real-Time Application, RTA)이란 입력된 데이터가 들어오면 정해진 시간 내에 처리하여 결과를 내보내는 소프트웨어를 말합니다.
실시간 애플리케이션, 언뜻보면 빠른 컴퓨팅과 헷갈릴 수 있습니다. 빠른 프로세서, RAM, 고속 버스 등 하드웨어가 좋으면 실시간 애플리케이션일까요? 아닙니다. ❌
무언가가 실시간이라고 말할 때, 시간 준수가 더 중요합니다. 주어진 타임라인에 대한 데드라인을 충족할 것을 보장하는 것이 실시간 애플리케이션입니다. ✅
아래는 실시간과 비실시간 애플리케이션의 반복된 실행과 응답 시간을 나타낸 그래프입니다.

실시간 애플리케이션은 여러 반복 동안 실행 시간 또는 응답 시간이 거의 일정합니다. 하지만 비실시간 애플리케이션을 살펴보면, 응답 시간이 다릅니다. 어떨때는 느리고, 또 어떨때는 빠릅니다. 이처럼 비실시간 애플리케이션은 시간 준수 동작을 충족하지 못합니다. 당연히 신뢰할 수 없겠죠.
비실시간 애플리케이션을 보면 시스템 부하가 높았을 때는 더 많은 시간이 걸렸고, 시스템 부하가 적었을 때는 더 짧은 시간에 실행되었다고 추측할 수 있습니다. 하지만, 실시간 애플리케이션은 시스템 부하가 어떻든 간에 항상 동일한 응답 시간을 가지는 것을 보장할 수 있습니다.
실시간 앱은 얼마나 '엄격'해야 하느냐에 따라 크게 두 가지로 나뉩니다.
우리는 이미 일상에서 수많은 실시간 애플리케이션을 사용하고 있습니다. 항공기 제어, ATM, 로봇공학, 의료 애플리케이션 등 실시간 애플리케이션이 중요한 역할을 하는 많은 다른 애플리케이션들이 있습니다.
RTOS(Real-Time Operating System, 실시간 운영체제)는 말 그대로 '정해진 시간 안에 결과값을 내놓는 것'이 가장 중요한 운영체제입니다. 실시간 애플리케이션을 위해서는 실시간 운영체제가 필요합니다.
실시간 애플리케이션을 가져다가 Windows나 Linux 같은 범용 OS(GPOS)에서 실행할 수 있을까요? 실행할 수 없습니다. 왜냐면 이런 운영체제는 엄격한 타이밍 요구사항을 충족하도록 설계되지 않았습니다. 사용자의 편의성과 여러 앱의 원활한 실행에 집중합니다.
실시간 운영체제 역시 운영체제입니다. 하지만 특별히 매우 정밀한 타이밍과 높은 신뢰성으로 애플리케이션을 실행하도록 설계되었습니다.
GPOS의 예시는 이미 알고 계실 것입니다. 모든 데스크톱 운영체제는 범용 사용 사례를 기반으로 만들어졌습니다. Linux, Windows, Mac OS, iOS, Android와 같은 잘 알려진 운영 체제들은 모두 GPOS 범주에 속합니다.

실시간 운영 체제는 대부분 처음 들어봤을 겁니다. 대표적으로 VxWorks가 있습니다. RTOS 계의 '끝판왕'으로 불립니다. 화성 탐사선(퍼서비어런스 등)과 전투기 제어에 사용될 만큼 극도의 신뢰성을 자랑합니다. VxWorks는 독점 소프트웨어이며, 애플리케이션에서 사용하려면 라이센스가 필요합니다.

또 다른 예로, QNX는 블랙베리(BlackBerry)가 제공하는 유닉스 기반의 RTOS로, 안정성이 뛰어나며, 현재 전 세계 자동차 인포테인먼트 및 자율주행 ADAS 시장의 압도적인 점유율을 차지하고 있습니다. 산업 자동화 애플리케이션, 의료 및 로봇공학에서도 사용됩니다. 마찬가지로 상용 제품입니다.

다음은 FreeRTOS입니다. FreeRTOS는 freertos.org에서 제공합니다. 이것도 개발자들 사이에서 유명한 실시간 운영 체제 중 하나입니다. 완전히 무료로 다운로드할 수 있고 로열티도 없습니다. 가전제품, IoT 기기, 간단한 산업 제어기 등에 쓸 수 있습니다.

Zephyr는 리눅스 재단에서 주도하는 오픈 소스 프로젝트입니다. 다양한 아키텍처를 지원하며 보안과 모듈화가 잘 되어 있어 최근 급부상하고 있습니다. 웨어러블 기기, 최신 IoT 임베디드 시스템에서 쓰입니다.

여기서는 FreeRTOS를 사용할 예정입니다. 참고로, Android와 iOS와 같은 모바일 운영 체제는 순수한 범용 OS 범주에 속하지 않을 수 있습니다. 이것들은 임베디드 OS 범주로 간주할 수 있습니다. 그리고 방금 소개한 모든 실시간 운영 체제는 임베디드 실시간 운영 체제 범주에 속할 겁니다.
RTOS와 GPOS의 가장 큰 차이는 '무엇을 최우선으로 하느냐'입니다. GPOS는 많은 일을 공평하고 효율적으로 처리하려 하고, RTOS는 중요한 일을 무슨 일이 있어도 정해진 시간 내에 처리하려 합니다.
GPOS (Windows, Linux 등)는 처리량(Throughput) 극대화가 목표입니다. 여기서, 처리량은 단위 시간당 실행을 완료하는 프로세스의 총 수를 의미합니다. 높은 처리량은 단일 고우선순위 태스크를 처리하는 것보다 5개의 저우선순위 태스크를 처리함으로써 달성됩니다. 때때로 고우선순위 프로세스의 실행이 지연됩니다.
따라서 GPOS에서 스케줄러는 일반적으로 공정성 정책을 사용하여 스레드와 프로세스를 CPU에 할당합니다. 이런 방식은 전체적으로 높은 전체 처리량을 가능하게 하지만, 고우선순위, 시간이 중요한 스레드 또는 프로세스가 저우선순위 스레드보다 우선적으로 실행될 것이라는 보장을 제공하지 않습니다.
반면에 RTOS에서는 스레드가 우선순위 순서대로 실행됩니다. 고우선순위 스레드가 실행 준비가 되면, 실행 중일 수 있는 저우선순위 스레드로부터 CPU를 가져갑니다. 고우선순위 스레드는 저우선순위 스레드보다 먼저 실행됩니다. 모든 "저우선순위 스레드 실행"은 일시 중지됩니다.
🤔 그렇다면, 실시간 운영 체제는 처리량 제공에 있어서 매우 좋지 않다는 뜻일까요?
실시간 운영 체제가 범용 OS보다 처리량이 낮을 수 있는데, 항상 고우선순위 태스크를 먼저 실행하도록 하기 때문입니다. 하지만 그렇다고 해서 매우 낮은 처리량을 가진다는 뜻은 아닙니다.
왜냐하면 이 두 운영 체제, 즉 GPOS와 실시간 운영 체제는 완전히 다른 시나리오에서 사용되기 때문입니다. GPOS는 항상 무거운 프로세스와 더 많은 프로세스가 로드됩니다. 하지만 실시간 운영 체제는 대부분의 경우 적은 수의 태스크가 로드됩니다.
괜찮은 RTOS는 여전히 적절한 전체 처리량을 제공하지만 결정성을 유지하거나 예측 가능성을 달성하기 위해 처리량을 약간 희생할 수 있습니다.
비유로 이해하자면, GPOS는 '뷔페'와 같습니다. 모든 손님(프로그램)이 음식을 골고루 먹을 수 있도록 효율적으로 관리합니다. 특정 손님이 조금 늦게 먹어도 전체 식당 운영에는 지장이 없습니다.
RTOS는 '응급실'과 같습니다. 환자의 위급도(우선순위)에 따라 진료 순서가 결정됩니다. 감기 환자를 진료하다가도 심정지 환자가 들어오면 즉시 중단하고 응급 환자에게 달려가야 하며, 처치 시간은 반드시 골든타임 내에 완료되어야 합니다.
태스크 전환 지연(Task Switching Latency) 측면에서 GPOS와 RTOS의 차이를 알아보겠습니다. 태스크 전환 지연은 "이벤트가 트리거되는 시점과 해당 이벤트를 처리하는 태스크가 CPU에서 실행되도록 허용되는 시점 사이의 시간 간격"을 의미합니다.
시나리오 예를 들어보겠습니다. t1 시점에 이벤트가 발생했다고 가정해봅시다. 그것은 충돌 감지일 수 있습니다. 그리고 t2 시점에 충돌 감지를 처리하는 태스크가 CPU에서 실행되도록 만들어집니다. 따라서 이 시간 간격을 태스크 전환 지연이라고 합니다.

RTOS의 경우 이 지연은 항상 제한(Bounded)됩니다. RTOS는 이 간격이 결정적이거나 시간 제한되어야 하는 시스템을 설계하는 데 도움을 줍니다. 이벤트가 언제 발생하든 이 간격은 항상 거의 일정하게 유지됩니다. 하지만 범용 운영 체제의 경우는 시간이 경과하거나 시스템 부하가 증가함에 따라 이 간격이 달라집니다(Unbounded).
아래 그래프를 살펴보세요. 범용 운영 체제의 경우 스케줄되는 태스크 수가 증가함에 따라 태스크의 전환 지연이 증가합니다. 하지만 RTOS의 경우에는 거의 일정하게 유지됩니다.

이제 인터럽트 지연(Interrupt Latency) 측면에서 RTOS와 GPOS의 차이에 대해 논의해봅시다. 인터럽트 지연은 인터럽트가 트리거되는 시점과 ISR(Interrupt Service Routine)이 CPU에서 실행을 시작하는 시점 사이의 시간 간격입니다.
태스크 1이 CPU에서 실행 중이고 t1 시점에 인터럽트가 발생했다고 가정해봅시다. 인터럽트 우선순위가 더 높다면, 이 태스크를 선점하고 ISR이 실행을 시작할 것입니다. 따라서 t1과 t2 사이의 이 시간 간격을 인터럽트 지연이라고 합니다.

인터럽트 지연은 중요한 이벤트를 처리하는 데 중요합니다. 예를 들어, 에어백 시스템이라면 이 간격은 마이크로초 또는 나노초 단위여야 합니다. 따라서 인터럽트 지연은 RTOS의 중요한 특성이며 RTOS는 이 지연을 가능한 한 최소화해야 합니다. GPOS에서는 이 시간 간격이 시스템 부하가 증가함에 따라 변동될 수 있습니다.
ISR 또는 태스크가 CPU를 떠나는 순간과 새로운 태스크가 CPU로 전환되는 시점 사이의 시간 간격을 스케줄링 지연(Scheduling Latency)이라고 합니다. 스케줄러가 "다음에 실행될 가장 중요한 태스크가 무엇인지" 결정하는 시간이라고 보면 됩니다.
이 시간 간격 동안 이전 태스크의 컨텍스트 저장 및 새 태스크의 컨텍스트 검색과 같은 다양한 활동이 발생하며, 이를 컨텍스트 스위칭(Context Switching)이라고 합니다.
실시간 애플리케이션의 경우 이 스케줄링 지연은 매우 좁아야 합니다. 결론적으로 RTOS의 스케줄링 지연과 인터럽트 지연은 모두 제한되어 있으며 시스템 부하가 증가해도 증가하지 않습니다. 하지만 GPOS에서는 제한되지 않으며 시스템 부하가 증가함에 따라 지연이 변동할 수 있습니다.
이해하기 쉽게 비유하자면 식당에서 손님이 "저기요!"라고 벨을 눌렀다고 해봅시다. 지연 시간은 벨 소리가 들린 순간부터 직원이 모든 준비를 마치고 손님 테이블 앞에 도착해 "주문하시겠습니까?"라고 말을 시작하기까지의 시간을 의미합니다.
태스크 전환은 직원이 주방 일을 하다가(기존 태스크), 벨 소리를 듣고(이벤트 트리거), 하던 일을 멈추고 손님에게 이동하는 과정을 의미합니다.
우선순위 역전(Priority Inversion) 측면에서 RTOS와 GPOS의 차이에 대해 논의해봅시다.
차를 타고 외출한다고 가정해보고, 여러분의 차를 저우선순위 차량으로 가정해보겠습니다. 혼잡한 교차로에 진입했고, 교차로를 쉽게 통과할 수 있을 것이라고 생각했습니다. 불행히도 앞선 차량들이 갑자기 멈추고 여러분은 도로 교차로 한가운데 갇혔습니다. 그러다가 갑자기 구급차의 사이렌 소리가 들립니다. 구급차는 물론 더 높은 우선순위 차량이죠.
구급차는 우리 때문에 이 교차로를 통과할 수 없습니다. 지금 갇혀 있기 때문이죠. 따라서 구급차는 이제 여러분이 길을 비워줄 때까지 기다려야 합니다. 즉, 고우선순위 차량이 저우선순위 차량이 나갈때까지 기다려야 한다는 겁니다. 이것이 바로 우선순위 역전입니다. 고우선순위 차량이 저우선순위처럼 행동하고 저우선순위 차량이 고우선순위처럼 행동하는 것입니다.

이제 이것을 컴퓨팅 세계에 적용해봅시다. 여러분의 차는 저우선순위 태스크이고, 택시들은 중간 우선순위이며, 구급차는 고우선순위 태스크라고 가정해봅시다. 그리고 이 교차로 영역은 공유 리소스입니다.
이제 저우선순위 태스크가 이 공유 영역에 접근하기 위한 키를 획득했다고 가정해봅시다. 하지만 CPU에서 실행되도록 허용되지 않습니다. 왜냐하면 많은 중간 우선순위 태스크들이 CPU에서 실행 중이며 저우선순위 태스크가 CPU를 점유하도록 허용하지 않기 때문입니다.
이제 고우선순위 태스크가 들어오면 이 공유 영역에 접근할 수 없습니다. 왜냐하면 키는 저우선순위 태스크가 가져갔기 때문입니다. 하지만 저우선순이 태스크는 실행되도록 허용되지 않습니다. 실행이 되어야 키를 포기할 수 있습니다. 따라서 저우선순위 태스크가 실행될 때까지 이 공유 리소스의 키는 잠겨 있고 고우선순위 태스크는 접근할 수 없습니다. 따라서 고우선순위 태스크는 저우선순위 태스크가 키를 해제할 때까지 기다려야 하며, 이것이 우선순위의 역전을 발생시킵니다.
GPOS에서는 이것이 중요한 문제는 아닙니다. 시스템이 조금 버벅인다고 느낄 뿐, 언젠가 중간 우선순위 태스크가 끝나면 고우선순위 태스크가 실행될 것이라 기대함. 결과적으로 지연 시간 예측 불가(Unbounded).
RTOS에서는 확실히 문제를 일으킬 것입니다. 왜냐하면 고우선순위 태스크의 실행을 차단하고 있기 때문입니다. 이것은 시스템 전체가 멈추거나 파괴되는 사고에 해당됩니다.
대부분의 실시간 커널은 이 문제를 해결하기 위해 몇 가지 기술을 사용합니다. RTOS는 고우선순위 태스크가 저우선순위 태스크를 우회할 수 있도록 하는 경로를 즉석에서 만들 수 있습니다. 일반 도로(GPOS)에서는 앞차가 비켜줘야 갈 수 있지만, RTOS는 고우선순위 태스크가 나타나면 구급차가 갑자기 급발진해서 하늘을 날아서(?) 이동할 수 있도록 할 수 있습니다.

혹은 저우순선위 태스크를 고우선순위 태스크로 만들고 키를 해제할 기회를 주는 것입니다. 이는 고우선순위 태스크가 저우선순위 태스크에 막혀있을 때, 저우선순위 태스크를 고우선순위 급으로 가속(Acceleration)시켜 방해 요소들을 빨리 치워버리게 만드는 기법입니다. 결과적으로 고우선순위 태스크가 목적지까지 도달하는 경로를 확보해 주는 역할을 합니다. 이것을 우선순위 상속(Priority Inheritance) 이라고 합니다.

정리해보겠습니다. RTOS가 가지고 있지만 GPOS가 가지고 있지 않은 기능은 무엇일까요?
마지막으로 멀티태스킹(Multitasking)에 대해 이해해보는 시간을 가져보겠습니다.
먼저 멀티태스킹에 대한 비유를 들어보겠습니다. 여러분의 일반적인 하루를 생각해보세요. 24시간이 있고 다양한 작업을 수행하려고 합니다. 예를 들어 아침 식사하기, 이메일 답장하기, 전화 받기, 메모하기, 약속 참석하기, 잠자기 등입니다. 여러분의 하루는 다양한 작업으로 구성되어 있고 하루를 더 생산적으로 만들기 위해 가능한 한 많은 것을 달성하려고 합니다.
이 모든 작업을 어떻게 달성할까요? 한 가지 방법은 각 활동에 시간 분할(Time slice)을 할당하는 것입니다. 예를 들어 아침 식사에 20분의 시간 분할, 이메일 작성에 20분 등입니다. 또 다른 방법은 생산성을 높이기 위해 업무를 보조자에게 맡기는 것입니다.

컴퓨팅 세계로 비유하자면 전자는 시분할, 후자는 멀티코어 방법인 셈이죠.
마찬가지로 멀티태스킹 가능한 컴퓨팅에서 애플리케이션은 여러 태스크로 구성될 수 있습니다. 각 태스크는 하나의 고유한 기능을 수행하도록 되어 있습니다. 예를 들어 온도 모니터 시스템이라는 애플리케이션이 있고 이 애플리케이션은 3개의 고유한 태스크를 가지고 있다고 가정해봅시다. Task1은 센서 데이터 읽기, Task2는 디스플레이 업데이트, Task3은 버튼 누르기와 같은 사용자 입력을 처리한다고 가정해봅시다.

이 애플리케이션은 여러 태스크를 구현합니다. 태스크란 CPU에서 실행되어 수행될 수 있는 코드 조각처럼 생각하면 됩니다. CPU가 하나 있고 이 모든 태스크를 실행해야 합니다. 어떻게 실행하면 될까요?
한 가지 방법은 각 태스크가 실행되도록 작은 시간 분할(time slice)을 주는 것입니다. 예를 들어, 첫번째 시간 분할에는 Task1을 수행하고, 다음에는 Task2를 실행하게 하고 계속 이런 식으로 합니다. 따라서 이를 수행하려면 스케줄러가 필요합니다.

스케줄러는 이 모든 태스크를 CPU에 스케줄링하여 실행하도록 관리하는 역할을 합니다. 따라서 멀티태스킹 시나리오에서 스케줄러는 중요한 역할을 합니다.
모든 태스크를 실행하는 또 다른 방법은 멀티코어 프로세서를 사용하는 것입니다. 4개의 코어를 가진 프로세서가 있다고 가정해봅시다. 이 경우에는 이 애플리케이션을 실행하기 위해 스케줄러가 필요하지 않습니다. 왜 그럴까요? 4개의 코어가 있고 각 코어가 하나의 태스크를 실행할 수 있기 때문입니다. 따라서 이 경우에는 3개의 태스크가 모두 동시에 실행될 수 있습니다.

임베디드 세계에서는 일반적으로 여러 코어를 가진 프로세서를 사용하지 않습니다. 일반적으로 하나의 코어와 여러 태스크 시나리오를 갖게 됩니다. 따라서 이 모든 태스크를 실행하려면 스케줄러가 필요하고 스케줄러는 어떤 태스크가 다음에 CPU를 점유해야 하는지 결정할 것입니다.
이것이 여러 태스크가 CPU에서 실행되는 방식이며, 이는 모든 태스크가 동시에 실행되고 있다는 환상을 줍니다. (환상을 준다는게 참 중요하죠)