일반적인 컴퓨팅 시스템이 평균 처리량(average throughput)을 최적화하는 것을 목표로 하는 반면, 실시간 시스템은 정해진 시간 제약, 즉 마감시한(deadline)을 충족하고 예측 가능한 결정론적(deterministic) 응답을 제공하는 능력으로 정의된다는 근본적인 패러다임 전환을 요구한다.1 실시간 시스템의 핵심 과제는 단순히 속도가 아니라 시간적 정확성(temporal correctness)이다. 마감시한 이후에 생성된 결과는 논리적으로 옳더라도 종종 실패로 간주된다.3 이 원칙은 본 보고서에서 다루는 모든 후속 분석의 기반이 된다.
이러한 맥락에서 주요 관심 지표는 평균 실행 시간이 아닌 최악 실행 시간(Worst-Case Execution Time, WCET)이다. WCET는 특정 태스크가 어떤 상황에서도 실행을 완료하는 데 걸리는 최대 시간을 나타내며, 이는 일반용 운영체제(General-Purpose Operating System, GPOS)의 평균 성능 중심 접근 방식과 뚜렷하게 대조된다.3 시스템 설계자는 애플리케이션의 모든 중요한 태스크가 WCET 내에 완료될 수 있음을 보장해야만 시스템의 실시간성을 입증할 수 있다.
GPOS와 실시간 운영체제(RTOS) 설계 철학 사이의 근본적인 충돌은 이 보고서에서 논의되는 대부분의 문제의 근원이다. GPOS는 ‘공정성’과 평균적인 속도를 위해 구축된다. 이들은 전체 시스템 처리량을 향상시키기 위해 복잡한 휴리스틱을 사용하고, 작업을 지연시키며, 연산을 일괄 처리한다.3 반면, RTOS는 ‘예측 가능성’을 위해 설계된다. 엄격한 우선순위 기반 스케줄링을 사용하고 무한한 지연 시간을 유발할 수 있는 모든 요소를 최소화한다.1
PREEMPT_RT 패치와 같은 기술은 본질적으로 GPOS 중심의 처리량 지향 메커니즘을 체계적으로 제거하거나 변경하고, 이를 RTOS와 유사한 지연 시간 지향 메커니즘으로 대체하려는 시도이다. 따라서 아키텍트가 RTOS와 실시간 리눅스 중에서 선택하는 것은 단순히 제품을 선택하는 것이 아니라, 순수한 예측 가능성과 풍부한 기능의 유연성 사이의 스펙트럼에서 한 지점을 선택하는 것이다. 이 선택은 시스템 설계의 다른 모든 측면에 연쇄적인 영향을 미친다.
실시간 시스템은 마감시한 준수의 엄격성에 따라 크게 세 가지로 분류되며, 각 유형에서 마감시한을 놓쳤을 때의 결과는 극명하게 다르다.
실시간 성능을 달성하기 위한 운영체제 수준의 접근 방식은 크게 두 가지로 나뉜다. 하나는 처음부터 결정론을 위해 설계된 RTOS를 사용하는 것이고, 다른 하나는 GPOS를 실시간 요구사항에 맞게 수정하는 것이다.
RTOS는 결정론을 최우선 목표로 설계된 운영체제이다. 핵심적인 특징으로는 우선순위 기반 선점형 스케줄링, 빠르고 예측 가능한 인터럽트 처리, 그리고 세마포어(semaphore)나 메시지 큐(message queue)와 같은 태스크 동기화 메커니즘이 있다.1 RTOS 아키텍처는 일반적으로 경량 커널을 특징으로 하며, 최소한의 오버헤드를 유지한다. 또한 시스템 분석 및 디버깅을 위한 도구를 제공하여 개발자가 시스템의 시간적 동작을 검증할 수 있도록 지원한다.2 특히 메모리 관리는 비결정적 동작과 단편화를 방지하기 위해 표준 동적 할당을 피하고 정적 할당이나 메모리 풀링 같은 기법을 사용하는 등 매우 신중하게 제어된다.12 QNX, VxWorks와 같은 상용 RTOS와 FreeRTOS 같은 오픈소스 RTOS는 다양한 산업 분야에서 그 신뢰성과 성능을 입증하며 널리 사용되고 있다.2
PREEMPT_RT 접근법은 리눅스와 같은 GPOS를 실시간 요구사항에 맞게 조정하는 방식으로, 리눅스의 방대한 기능과 하드웨어 지원의 이점을 누리면서 실시간 태스크에 필요한 예측 가능성을 확보하려는 시도이다.8
PREEMPT_RT 패치셋은 커널 코드의 비선점 영역을 수정하고, 스핀락(spinlock)을 선점 가능한 뮤텍스(mutex)로 대체하며, 인터럽트 핸들러를 커널 스레드로 전환하여 표준 리눅스 커널을 완전히 선점 가능한 커널로 변환한다.15
PREEMPT_RT가 메인라인 커널에 완전히 통합되기까지 20년이 걸렸다는 사실은 실시간 시스템에서 원시 속도보다 신뢰성이 얼마나 중요한지를 잘 보여준다. 안전 필수 시스템에서 예측 불가능한 지연이나 버그는 용납될 수 없기 때문이다. 이 긴 개발 기간은 산업 및 자동차 애플리케이션에서 요구하는 높은 수준의 신뢰성을 달성하기 위해 필수적인 과정이었다.19
우선순위 역전(Priority Inversion)은 선점형 우선순위 기반 시스템에서 발생하는 위험한 상태로, 높은 우선순위의 태스크가 낮은 우선순위의 태스크 때문에 실행을 대기하게 되어 실질적으로 실행 우선순위가 뒤바뀌는 현상을 의미한다.20
이 문제는 스케줄러와 뮤텍스나 세마포어 같은 동기화 프리미티브 간의 상호작용에서 발생한다. 낮은 우선순위 태스크(L)가 높은 우선순위 태스크(H)에 필요한 공유 자원의 락(lock)을 보유하고 있을 때, H는 L이 락을 해제할 때까지 차단(block)된다. 이때 만약 락이 필요 없는 중간 우선순위 태스크(M)가 준비 상태가 되면, 스케줄러는 M을 실행하기 위해 L을 선점한다. 이로 인해 L은 락을 해제할 기회를 얻지 못하고, 결과적으로 H의 대기 시간은 예측 불가능하게 길어지게 된다.20
이 현상의 실제적 위험성은 1997년 화성 탐사선 패스파인더(Pathfinder) 임무에서 명확히 드러났다. 탐사선에서 반복적인 시스템 리셋이 발생했는데, 원인 분석 결과 이 문제가 바로 우선순위 역전 버그 때문인 것으로 밝혀졌다. 이는 우선순위 역전이 실험실의 이론적 문제를 넘어 실제 미션 크리티컬 시스템에 치명적인 결과를 초래할 수 있음을 보여주는 대표적인 사례이다.23
우선순위 역전 문제를 해결하기 위해 여러 프로토콜이 개발되었으며, 그중 가장 대표적인 것은 우선순위 상속 프로토콜과 우선순위 실링 프로토콜이다.
PIP는 우선순위 역전 문제를 해결하는 가장 기본적인 방법이다. 높은 우선순위 태스크(H)가 낮은 우선순위 태스크(L)가 보유한 자원 때문에 차단될 경우, L은 일시적으로 H의 우선순위를 상속받는다. 이렇게 되면 중간 우선순위 태스크(M)가 L을 선점할 수 없게 되어, L이 신속하게 임계 구역(critical section)을 실행하고 자원을 해제할 수 있게 된다.21 PIP는 구현이 비교적 간단하여 널리 사용되지만 26, 교착 상태(deadlock)를 방지하지 못하며, 하나의 태스크가 여러 개의 낮은 우선순위 태스크에 의해 순차적으로 차단되는 연쇄 차단(chained blocking) 문제가 발생할 수 있다.
PCP는 PIP보다 더 강력한 해결책을 제공한다. 이 프로토콜에서는 각 공유 자원에 ‘우선순위 실링(priority ceiling)’이 할당되는데, 이는 해당 자원을 잠글 수 있는 모든 태스크 중 가장 높은 우선순위와 같다. 태스크는 자신의 우선순위가 현재 다른 태스크들에 의해 잠겨 있는 모든 자원들의 우선순위 실링보다 엄격하게 높을 때만 새로운 자원을 잠글 수 있다.27 태스크가 자원을 성공적으로 잠그면, 그 태스크의 우선순위는 해당 자원의 실링 우선순위로 즉시 올라간다(이는 ICPP, 즉 즉시 실링 우선순위 프로토콜이라 불리는 일반적인 구현 방식이다).26
PCP의 가장 큰 장점은 우선순위 역전의 기간을 제한할 뿐만 아니라, 교착 상태를 원천적으로 방지한다는 것이다.28 이로 인해 PCP는 높은 신뢰성이 요구되는 복잡한 시스템에 더 적합한 솔루션으로 평가받는다.
| 기능 | 우선순위 상속 프로토콜 (PIP) | 우선순위 실링 프로토콜 (PCP) |
|---|---|---|
| 핵심 메커니즘 | 낮은 우선순위 태스크가 높은 우선순위 태스크의 우선순위를 ‘상속’받아 실행 | 자원마다 ‘우선순위 실링’을 정의하고, 태스크는 자신의 우선순위가 모든 잠긴 자원의 실링보다 높을 때만 자원 획득 가능 |
| 교착 상태 방지 | 방지하지 못함 | 원천적으로 방지함 29 |
| 연쇄 차단 | 발생 가능 | 발생하지 않음. 태스크는 최대 한 번만 차단됨 |
| 구현 복잡성 | 비교적 간단함 26 | 더 복잡함. 모든 자원의 우선순위 실링을 사전에 알아야 함 26 |
| 오버헤드 | 차단이 발생할 때만 우선순위 조정이 일어나므로 평균 오버헤드가 낮을 수 있음 | 자원을 획득할 때마다 우선순위 조정이 발생하여 오버헤드가 더 높을 수 있음 26 |
실시간 시스템의 성능은 어떤 스케줄링 알고리즘을 사용하느냐에 따라 크게 좌우된다. 가장 중요한 두 알고리즘은 정적 우선순위 방식의 RM과 동적 우선순위 방식의 EDF이다.
RM 스케줄링은 정적 우선순위 알고리즘으로, 태스크의 주기(rate)에 따라 우선순위를 할당한다. 주기가 짧은 태스크(즉, 더 자주 실행되는 태스크)가 더 높은 우선순위를 부여받는다.32 RM은 구현이 간단하고 시스템의 동작을 오프라인에서 분석하기 용이하다는 장점이 있다. 시스템의 스케줄링 가능성은 류와 레이랜드(Liu & Layland)가 제시한 CPU 이용률 기반 테스트를 통해 판단할 수 있다.34 RM은 모든 정적 우선순위 스케줄링 알고리즘 중에서 최적으로 알려져 있다.35
EDF 스케줄링은 동적 우선순위 알고리즘으로, 실행 가능한 태스크들 중에서 절대적인 마감시한이 가장 가까운 태스크에 가장 높은 우선순위를 부여한다.32 EDF는 이론적으로 모든 스케줄링 알고리즘 중에서 최적이며, 이상적인 조건에서는 CPU 이용률을 100%까지 달성할 수 있다.34 하지만 우선순위가 동적으로 변하기 때문에 구현이 더 복잡하고, 특히 시스템에 과부하가 걸렸을 때 하나의 태스크가 마감시한을 놓치면 다른 태스크들도 연쇄적으로 마감시한을 놓치는 ‘도미노 효과’가 발생할 수 있어 예측 가능성이 떨어진다는 단점이 있다.
RM과 EDF 사이의 선택은 단순성과 예측 가능성, 그리고 원시적인 효율성 사이의 고전적인 공학적 절충을 반영한다. RM은 정적 우선순위를 사용하여 시스템의 동작이 고정되어 있고 오프라인 분석이 용이하다. 이러한 예측 가능성은 안전 필수 시스템에서 매우 중요하게 여겨진다. 반면, EDF는 동적 우선순위를 사용하여 CPU를 더 ‘영리하게’ 활용하고 더 높은 이용률을 달성할 수 있다. 그러나 이러한 동적성은 특히 장애 상황에서 시스템의 동작을 예측하기 어렵게 만든다. 따라서 RM을 선택하는 아키텍트는 CPU를 일부 덜 사용하더라도 모든 조건에서 분석 가능하고 안정적인 동작을 우선시하는 것이다. EDF를 선택하는 아키텍트는 약간의 복잡성과 과부하 시 잠재적인 불안정성을 감수하고서라도 최대의 CPU 효율성을 우선시하는 것이다. 이는 프로젝트의 주요 목표(예: 안전 인증 대 주어진 하드웨어에서의 성능 극대화)에 따라 내려져야 할 중요한 전략적 결정이다.
| 특성 | RM (Rate-Monotonic) 스케줄링 | EDF (Earliest Deadline First) 스케줄링 |
|---|---|---|
| 우선순위 유형 | 정적 (주기에 따라 고정) 33 | 동적 (마감시한에 따라 변화) 32 |
| 최적성 | 정적 우선순위 알고리즘 중 최적 35 | 모든 우선순위 알고리즘 중 최적 |
| CPU 이용률 한계 | n(21/n−1), n–»∞ 일 때 약 69% 34 | 이론적으로 100% 가능 34 |
| 구현 복잡성 | 상대적으로 간단함 | 더 복잡함 |
| 과부하 시 동작 | 낮은 우선순위 태스크부터 마감시한을 놓쳐 예측 가능 | 어떤 태스크가 마감시한을 놓칠지 예측하기 어려움 (도미노 효과) |
이 섹션은 종종 단일 코어 환경에서 개발된 고전적인 RTOS 이론과 현대 멀티코어 프로세서의 현실 사이의 간극을 메운다. 여러 개의 코어는 더 많은 처리 능력을 제공하지만, 동시에 공유 하드웨어 자원 경합이라는 새롭고 교활한 비결정성의 원인을 도입한다.37 멀티코어 호스트는 독립적인 프로세서의 집합이 아니라, 상자 안의 긴밀하게 결합된 분산 시스템과 같으며, 그에 맞게 설계되어야 한다.
전통적인 분산 시스템은 네트워크로 연결된 노드들로 구성되며 네트워크 대역폭을 두고 경쟁한다. 마찬가지로 멀티코어 시스템은 메모리 버스와 공유 캐시로 연결된 코어들로 구성되며, 메모리 대역폭과 캐시 공간을 두고 경쟁한다.37 한 코어의 ‘시끄러운 이웃’은 다른 코어의 성능에 직접적인 영향을 미쳐 예측 불가능한 지연 시간을 유발한다. 이 문제들은 공유 통신 매체에 대한 경합이라는 점에서 유사하며, 해결책 또한 유사하다. CPU 피닝은 특정 서버에서 서비스를 실행하여 로컬 디스크/상태를 활용하는 것과 같고, 캐시 파티셔닝은 대역폭을 보장하기 위해 네트워크에서 서비스 품질(QoS)을 구현하거나 전용 가상 채널을 만드는 것과 같다. 따라서 아키텍트는 멀티코어 CPU를 단순히 ‘더 많은 CPU’로 보아서는 안 되며, 결정론을 달성하기 위해 기계 네트워크에서와 마찬가지로 격리, 파티셔닝, 자원 관리 원칙을 적용해야 한다. ‘단일 코어 등가(Single-Core Equivalent, SCE)’ 기술 39은 바로 이 아이디어를 공식화한 것으로, 각 코어가 마치 격리된 것처럼 예측 가능하게 동작하도록 만드는 것을 목표로 한다.
“시끄러운 이웃(Noisy Neighbor)” 효과는 한 코어에서 실행 중인 태스크가 공유 자원을 공격적으로 사용하여 다른 코어의 태스크 성능에 부정적인 영향을 미치는 현상을 말한다.37 이러한 간섭의 주요 원인은 다음과 같다.
CPU 선호도(affinity) 또는 피닝(pinning)은 프로세스나 스레드를 특정 CPU 코어 또는 코어 집합에 바인딩하는 기술이다.44 이 기술의 주된 이점은 캐시 성능 향상이다. 태스크가 항상 동일한 코어에서 실행되도록 보장함으로써, 해당 태스크의 데이터와 명령어가 그 코어의 L1/L2 캐시에 남아있을 확률(캐시 지역성, cache locality)을 높여 캐시 미스를 줄인다.45 이는 하드웨어 간섭의 일부를 완화하기 위한 소프트웨어 수준의 기법이다.
하지만 선호도 설정만으로는 공유 LLC나 메모리 버스에서의 경합 문제를 완전히 해결할 수 없다. 또한 신중하게 관리하지 않으면 할당된 코어에 과부하가 걸릴 경우 부하 분산 문제를 야기할 수도 있다.45
인텔의 캐시 할당 기술(Cache Allocation Technology, CAT)은 마지막 레벨 캐시(LLC)를 파티셔닝할 수 있게 해주는 하드웨어 기능이다. 이를 통해 캐시의 특정 부분(ways)을 특정 코어나 애플리케이션에 독점적으로 예약할 수 있다.40
이 기술은 강력한 시간적 격리(temporal isolation)를 제공한다. 자신만의 캐시 파티션을 할당받은 실시간 태스크는 다른 코어의 “시끄러운 이웃”으로부터 보호받게 되어, 메모리 접근 시간이 훨씬 더 예측 가능해지고 지터(jitter)가 감소한다.40 이는 공유 캐시 문제를 직접적으로 해결하는 하드웨어 수준의 솔루션이다.
pqos와 같은 도구를 사용하여 서비스 클래스(Class of Service, COS)를 정의하고 코어를 할당함으로써 격리된 캐시 영역을 생성할 수 있으며 50, 이 기능은 하이퍼바이저 환경을 위해 가상화(vCAT)될 수도 있다.49
| 기능 | CPU 선호도/피닝 | 캐시 파티셔닝 (Intel CAT) |
|---|---|---|
| 격리 범위 | L1/L2 캐시 (코어 전용) | L3/LLC (코어 간 공유) 49 |
| 메커니즘 | 소프트웨어 (OS 스케줄러) 45 | 하드웨어 (프로세서 기능) 49 |
| 주요 이점 | 캐시 지역성 향상, 캐시 재사용률 증가 45 | 코어 간 캐시 간섭 제거, 예측 가능성 향상 40 |
| 하드웨어 요구사항 | 없음 (멀티코어 CPU) | Intel CAT 지원 프로세서 49 |
| “시끄러운 이웃”에 대한 효과 | 간접적 완화 (L1/L2 캐시 보호) | 직접적 제거 (LLC 수준에서 격리) 40 |
이 두 기술은 상호 배타적인 것이 아니라 상호 보완적이다. CPU 피닝은 캐시 지역성을 향상시키기 위한 기본적인 소프트웨어 전략이며, 캐시 파티셔닝은 LLC 간섭을 제거하기 위한 강력한 하드웨어 강제 전략이다. 진정한 결정론을 위해서는 두 가지 모두가 필요할 가능성이 높다. 즉, 중요한 스레드를 특정 코어에 고정시킨 다음, 그 코어에 LLC의 전용 조각을 할당하는 것이다.
인터럽트 지연 시간(Interrupt Latency)은 외부 이벤트 발생 시점부터 해당 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)의 첫 번째 명령어가 실행되기까지 걸리는 시간을 의미한다.52 지터(Jitter)는 이러한 지연 시간의 변동성을 나타낸다.54
지연 시간은 프로세서가 인터럽트를 감지하고, 현재 문맥(context)을 저장하며, ISR로 전환하는 데 걸리는 시간 때문에 발생한다. 이 시간은 인터럽트가 비활성화된 구간, 높은 시스템 부하, 또는 더 높은 우선순위의 다른 인터럽트 처리 등으로 인해 길어질 수 있다.53 반면 지터는 신호 전파의 예측 불가능한 변화, 클럭 소스의 불안정성, 전원 노이즈, 네트워크 혼잡과 같은 시스템 수준의 요인들로 인해 발생한다.54 실시간 시스템에서 높은 지연 시간과 지터는 마감시한 위반의 직접적인 원인이 될 수 있다.
인터럽트 처리로 인한 오버헤드를 줄이고 예측 가능성을 높이기 위한 전략은 다음과 같다.
직접 메모리 접근(Direct Memory Access, DMA)은 주변장치가 CPU의 개입 없이 주 메모리에 직접 데이터를 읽고 쓸 수 있게 해주는 하드웨어 기능이다.62 CPU는 데이터 전송을 시작시킨 후 다른 작업을 자유롭게 수행할 수 있으며, DMA 컨트롤러가 전송을 완료하면 단일 인터럽트를 통해 CPU에 알린다.63
실시간 시스템에서 DMA는 네트워크 카드나 저장 장치로부터의 대용량 데이터 전송과 관련된 CPU 오버헤드를 극적으로 줄여준다. CPU가 바이트 단위로 데이터를 복사하는 루프에 묶여 있는 대신, 중요한 실시간 태스크를 계속 실행할 수 있게 된다. 이는 전체 시스템의 성능, 응답성, 그리고 결정론을 향상시킨다.65
기존 운영체제 커널의 네트워킹 스택은 여러 번의 메모리 복사와 컨텍스트 스위칭을 수반하여 상당한 지연 시간을 유발하며, 높은 데이터 전송률에서는 병목 지점이 된다.68 커널 바이패스(Kernel Bypass) 프레임워크는 이러한 문제를 해결하기 위해 등장했다. DPDK(Data Plane Development Kit)와 같은 프레임워크는 사용자 공간 애플리케이션이 커널을 완전히 우회하여 네트워크 인터페이스 카드(NIC)의 메모리 버퍼와 직접 상호작용할 수 있도록 라이브러리와 폴링 모드 드라이버(Poll-Mode Drivers, PMDs)를 제공한다.68
이 방식은 커널 스택의 오버헤드를 제거하여 10 Gbps 이상의 속도에서 라인 레이트(line-rate) 패킷 처리와 극도로 낮은 지연 시간을 가능하게 한다. 이는 초단타 매매(High-Frequency Trading, HFT)나 네트워크 기능 가상화(Network Function Virtualization, NFV)와 같이 극도의 저지연이 요구되는 애플리케이션에 필수적이다.71
낮은 지연 시간을 달성하기 위한 핵심 원칙은 처리를 CPU 코어 자체에서 멀리 밀어내는 것이다. 이는 전용 하드웨어(DMA)로 내려보내거나 애플리케이션 공간(커널 바이패스)으로 내보내는 방식으로 이루어진다. CPU는 애플리케이션 로직 실행을 위한 가장 가치 있고 경합이 심한 자원이다. 전통적인 I/O 방식은 CPU를 데이터 복사기로 전락시켜 인터럽트를 처리하고 주변장치와 메모리 간에 데이터를 이동시키는 데 시간을 낭비하게 만든다.60 DMA는 이러한 ‘데이터 복사’ 작업을 전문 하드웨어 블록에 위임하여 CPU를 해방시키는 첫 번째 수준의 오프로드이다.66 커널 바이패스는 여기서 한 단계 더 나아가, 네트워크 패킷 처리에 있어 OS 커널이라는 중개자를 제거한다. 애플리케이션이 직접 NIC를 관리하는 책임을 진다.68 이는 최대의 실시간 성능을 위해 주 CPU는 계산에만 전념하고, 데이터 이동 및 I/O 관리는 가능한 한 전문화되고 더 결정론적인 하위 시스템에 위임해야 한다는 강력한 아키텍처 원칙을 보여준다.
malloc이나 free와 같은 표준 라이브러리 함수는 범용적인 사용에 최적화되어 있어 매우 비결정론적이다. 이 함수들의 실행 시간은 예측 불가능한데, 이는 내부적으로 자유 목록(free list)과 같은 복잡한 자료구조를 탐색하거나, 컨텍스트 스위칭을 유발할 수 있는 운영체제에 메모리를 요청해야 할 수 있기 때문이다.74
더욱이, 가변 크기 블록의 반복적인 할당 및 해제는 메모리 단편화(memory fragmentation)를 유발한다. 이로 인해 전체적으로는 충분한 여유 메모리가 있음에도 불구하고, 요청된 크기의 연속된 메모리 블록이 없어 할당이 실패할 수 있다.74 이러한 문제들로 인해 표준 malloc은 경성 실시간 시스템의 임계 구역 내에서 사용하기에 부적합하다.
메모리 풀(Memory Pool)은 초기화 시점에 미리 할당된 크고 연속적인 메모리 블록이다. 이 블록은 여러 개의 고정 크기 청크(chunk)로 분할된다. 런타임에 메모리를 할당하고 해제하는 작업은 단순히 자유 목록에서 청크를 가져오거나 반환하는 것으로, 이는 매우 빠르고 시간 복잡도가 $O(1)$인 연산이다.74
메모리 풀은 빠르고 무엇보다 결정론적인 할당 시간을 제공한다는 큰 장점이 있다. 블록이 미리 할당되고 크기가 고정되어 있기 때문에 풀 내부의 단편화가 발생하지 않으며, 런타임 중에 비결정적인 OS 할당자를 호출할 필요가 없다.75 이로 인해 메모리 풀은 실시간 태스크나 심지어 ISR 내부에서 동적 메모리를 사용해야 할 때 선호되는 방법이다.78
전통적인 가비지 컬렉터(Garbage Collector, GC)는 애플리케이션 실행을 완전히 멈추는 “stop-the-world” 중단(pause)을 유발한다. 이러한 중단 시간의 길이와 시점은 예측 불가능하여 실시간 시스템에는 부적합하다.79
이에 반해 실시간 가비지 컬렉션(RTGC) 알고리즘은 점진적(incremental)이고 동시적(concurrent)으로 동작하도록 설계되었다. RTGC는 GC 작업을 작고 예측 가능한 단위로 분할하여 애플리케이션 실행과 병행하여 처리한다. ZGC나 Shenandoah와 같은 현대적인 컬렉터는 마킹(marking)이나 압축(compaction) 같은 작업을 애플리케이션 스레드와 동시에 수행함으로써 극도로 짧고 제한된 중단 시간을 목표로 한다.79
비록 여전히 복잡한 분야이지만, G1이나 ZGC와 같은 현대적인 RTGC 덕분에 제한적이고 수 밀리초 수준의 중단 시간이 허용되는 일부 연성 실시간 애플리케이션에서는 자바(Java)나 C#과 같은 관리형 언어를 사용하는 것이 실현 가능해졌다.80
결정론적 메모리 관리는 할당 비용을 런타임에서 초기화 시간으로 이전함으로써 달성된다. malloc의 비결정성은 호출 시점에 수행해야 하는 작업, 즉 블록 탐색, 여유 블록 병합, OS 페이지 요청 등에서 비롯된다.74 반면 메모리 풀링은 프로그램 시작 시 OS로부터 하나의 큰 메모리 덩어리를 요청하는 등 모든 비용이 많이 드는 작업을 미리 수행한다.77 런타임에서의 ‘할당’은 단순한 포인터 조작에 불과하며, 이는 매우 빠르고 예측 가능한 실행 시간을 갖는다.75 이는 실시간 설계의 핵심 원칙을 보여준다: 자원 할당과 같이 예측 불가능하고 잠재적으로 오래 실행되는 모든 작업은 실시간 루프가 시작되기 전, 시스템의 초기화 단계에서 수행해야 한다. 이를 통해 시간에 민감한 운영 단계가 가능한 한 결정론적이 되도록 보장할 수 있다.
락(lock) 기반 동시성 제어는 교착 상태, 앞서 논의된 우선순위 역전, 그리고 높은 경합 상황에서의 성능 병목 현상과 같은 근본적인 문제들을 야기한다. 스레드가 락을 기다리며 차단되고 컨텍스트 스위칭이 빈번하게 발생하면서 상당한 오버헤드가 발생하기 때문에, 이에 대한 대안을 모색하게 되었다.81
CAS(Compare-and-Swap)는 대부분의 락프리(lock-free) 프로그래밍의 기초를 형성하는 원자적(atomic) 하드웨어 명령어이다. 이 명령어는 메모리 위치에서 값을 읽고, 이를 예상 값과 비교한 후, 두 값이 일치할 경우에만 새로운 값으로 해당 위치를 갱신하는 세 단계를 분리 불가능한 단일 연산으로 수행한다.84
CAS는 고전적인 “검사 후 실행(check-then-act)” 경쟁 상태(race condition)를 해결한다. 스레드는 공유 변수의 값을 읽어 새로운 값을 계산한 다음, CAS를 사용하여 그 사이에 다른 스레드가 값을 변경하지 않았을 경우에만 변경 사항을 커밋할 수 있다. 만약 CAS 연산이 실패하면, 해당 스레드는 다른 스레드에 의해 값이 변경되었음을 인지하고, 갱신된 값을 가지고 연산을 재시도해야 한다.84
CAS 루프를 이용한 락프리 스택이나 큐의 구현은 논블로킹(non-blocking) 알고리즘의 대표적인 예이다. 예를 들어, 락프리 스택에 데이터를 추가(push)하는 과정은 다음과 같다. 스레드는 먼저 현재의 top 포인터를 읽어 로컬 변수에 저장한다. 그 후 새로운 노드를 생성하고, 이 노드의 next 포인터가 방금 읽은 top 포인터를 가리키도록 설정한다. 마지막으로 compareAndSet(currentTop, newNode) 연산을 호출하여 top 포인터를 원자적으로 새로운 노드로 변경한다. 만약 이 과정 중에 다른 스레드가 먼저 노드를 추가하여 top 포인터의 값이 바뀌었다면 CAS 연산은 실패하고, 스레드는 루프를 처음부터 다시 시작한다.87
이 접근법의 핵심적인 속성은 논블로킹이라는 점이다. 한 스레드의 CAS 연산 실패가 다른 스레드들의 진행을 막지 않는다. 오히려, 실패는 다른 어떤 스레드가 성공했음을 의미하므로, 시스템 전체적으로는 항상 진행이 보장된다.86
락프리 알고리즘이 항상 우월한 것은 아니다. 재시도 루프로 인해 낮은 경합 상황에서는 락 기반 방식보다 더 높은 오버헤드를 가질 수 있으며, 올바르게 구현하기가 더 복잡하다.81 락프리 알고리즘의 진정한 성능적 이점은 다수의 코어가 높은 경합을 벌이는 환경에서 나타난다. 이러한 환경에서 락 기반 접근법은 심각한 병목 현상과 컨텍스트 스위칭 폭풍으로 인해 성능이 급격히 저하된다.82
경성 실시간 시스템의 관점에서 락프리(특히 웨이트프리, wait-free) 알고리즘의 가장 중요한 장점은 예측 불가능한 차단을 제거한다는 점이다. 평균 성능이 다소 저하되더라도, 락 연쇄나 우선순위 역전과 같은 최악의 지연 시간을 피할 수 있어 시스템의 예측 가능성을 크게 향상시킨다.82
락프리 프로그래밍은 “낙관적 동시성 제어(optimistic concurrency control)” 모델의 알고리즘적 구현체이다. 전통적인 락킹은 “비관적(pessimistic)”이다. 즉, 충돌이 발생할 것이라 가정하고, 다른 스레드가 데이터에 관심이 없더라도 작업을 수행하기 전에 배타적인 락을 획득하도록 강제한다. 반면, 락프리 프로그래밍은 “낙관적(optimistic)”이다. 충돌이 드물다고 가정하고, 스레드는 데이터의 로컬 복사본에 대해 투기적으로(speculatively) 작업을 진행한다. 충돌 여부는 작업을 커밋하려는 마지막 순간에 CAS 연산을 통해 확인한다.89 충돌이 없었다면 최소한의 오버헤드로 작업이 성공하고, 충돌이 있었다면 작업은 실패하고 스레드는 재시도한다. 이는 락킹과 락프리 사이의 선택을 단순한 성능 문제를 넘어, 애플리케이션의 예상되는 경합 패턴에 기반한 아키텍처적 결정으로 재구성한다. 읽기 위주 데이터나 낮은 경합 시나리오에서는 낙관주의가 유리하지만, 쓰기 위주의 높은 경합 시나리오에서는 반복적인 재시도 비용이 비관적인 락을 더 효율적으로 만들 수 있다. 다만, 최악의 경우 예측 가능성은 락프리 방식이 더 우수하다.
실시간 결정론을 달성하는 것은 단일 해결책을 찾는 것이 아니라, 전체 시스템 스택에 걸쳐 여러 기술을 조합하여 적용하는 문제임을 강조해야 한다. 완벽하게 스케줄링된 RTOS라 할지라도, 태스크가 멀티코어 캐시 간섭이나 비결정적 메모리 할당으로 인해 고통받는다면 여전히 마감시한을 놓칠 수 있다. 따라서 성공적인 실시간 시스템 설계는 운영체제, 하드웨어, 메모리 관리, 애플리케이션 패러다임 등 모든 계층을 아우르는 종합적인 시각을 요구한다.
아키텍트가 시스템 요구사항에 맞는 올바른 기술 조합을 선택할 수 있도록 돕는 의사결정 프레임워크를 제시한다. 핵심적인 고려사항은 다음과 같다.
위의 프레임워크를 바탕으로, 일반적인 애플리케이션 프로파일에 따른 권장 전략을 제시할 수 있다.
PREEMPT_RT 리눅스 사용. RM 또는 EDF 스케줄링 알고리즘 적용. 중요 태스크를 특정 코어에 고정(CPU 피닝)하고, 해당 코어에 전용 캐시 파티션을 할당(Cache Partitioning). 동적 할당이 필요할 경우 반드시 메모리 풀링 사용. ISR은 최소한으로 유지하고 신중하게 관리.PREEMPT_RT 리눅스 활용. 대용량 데이터 전송을 위해 DMA 사용. 네트워크 처리량이 매우 높을 경우 커널 바이패스(DPDK 등) 고려. I/O 처리 코어와 애플리케이션 로직 처리 코어를 CPU 피닝으로 분리하여 상호 간섭 최소화.단일 호스트 컴퓨터에서 실시간 애플리케이션의 장애와 블로킹을 회피하는 것은 단일 기술의 적용이 아닌, 시스템 전반에 걸친 다층적이고 전략적인 접근을 요구하는 복잡한 과제이다. 본 분석을 통해, 결정론적 성능을 저해하는 근본적인 원인들이 운영체제의 스케줄링 철학, 멀티코어 아키텍처의 공유 자원 간섭, I/O 처리 방식, 그리고 메모리 관리의 비예측성 등 시스템의 여러 계층에 깊숙이 자리 잡고 있음을 확인했다.
성공적인 실시간 시스템 아키텍처는 다음의 핵심 원칙들을 종합적으로 고려해야 한다.
PREEMPT_RT 리눅스와 같이 결정론을 위해 설계된 운영 환경을 선택하는 것에서 시작된다.궁극적으로, 장애 없는 실시간 시스템을 구축하는 길은 애플리케이션의 요구사항(경성/연성), 워크로드의 특성(CPU/I/O 집약), 그리고 사용 가능한 하드웨어의 역량을 깊이 이해하고, 이에 맞춰 스케줄링, 동기화, 자원 격리, 메모리 관리, I/O 처리 기술들을 최적으로 조합하는 아키텍트의 역량에 달려 있다. 본 보고서에서 제시된 분석과 프레임워크는 이러한 복잡한 의사결정 과정에서 신뢰할 수 있는 지침을 제공할 것이다.
| 실시간 운영 체제(RTOS)란 무엇인가요? | IBM, accessed July 3, 2025, https://www.ibm.com/kr-ko/think/topics/real-time-operating-system |
| Linux PREEMPT-RT vs. commercial RTOSs: how big is the performance gap? | Request PDF - ResearchGate, accessed July 3, 2025, https://www.researchgate.net/publication/236952897_Linux_PREEMPT-RT_vs_commercial_RTOSs_how_big_is_the_performance_gap |
| Next Stop on the Grand Tour of Affinity: Processor Affinity | by Tom Herbert | Medium, accessed July 3, 2025, https://medium.com/@tom_84912/next-stop-on-the-grand-tour-of-affinity-processor-affinity-cbe855a001e6 |
| Why It’s Important To Care Interrupt Latency? | Take The Notes, accessed July 3, 2025, https://takethenotes.com/interrupt-latency/ |
| Boost Performance with Direct Memory Access (DMA) | Lenovo US, accessed July 3, 2025, https://www.lenovo.com/us/en/glossary/what-is-dma/ |
| On Kernel-Bypass Networking and Programmable Packet Processing | by Pekka Enberg, accessed July 3, 2025, https://medium.com/@penberg/on-kernel-bypass-networking-and-programmable-packet-processing-799609b06898 |
| What is kernel bypass and how is it used in trading? | Databento Microstructure Guide, accessed July 3, 2025, https://databento.com/microstructure/kernel-bypass |
| A quick introduction to Memory Pools | by Devansh - Medium, accessed July 3, 2025, https://machine-learning-made-simple.medium.com/a-quick-introduction-to-memory-pools-cc3198d004db |
| Memory management in RTOS | Embedded Systems Design Class Notes - Fiveable, accessed July 3, 2025, https://library.fiveable.me/embedded-systems-design/unit-9/memory-management-rtos/study-guide/9hKi6cSykkdL3fbR |
| Memory Pools | DZX Real-Time Kernel (RTOS), accessed July 3, 2025, https://www.dzxdesigns.com/doc/kernel/pools.aspx |
| Unlocking Performance: A Guide to Lock-Free Data Structures in Go | by Nathan B. Crocker, accessed July 3, 2025, https://medium.com/@nathanbcrocker/unlocking-performance-a-guide-to-lock-free-data-structures-in-go-924344cb3774 |
| Understanding Compare-and-Swap (CAS) Operations | CodeSignal Learn, accessed July 3, 2025, https://codesignal.com/learn/courses/concurrency-essentials-in-cpp/lessons/understanding-compare-and-swap-cas-operations |
| Introduction to Lock-Free Data Structures with Java Examples | Baeldung, accessed July 3, 2025, https://www.baeldung.com/lock-free-programming |
| What every systems programmer should know about lockless concurrency [pdf] | Hacker News, accessed July 3, 2025, https://news.ycombinator.com/item?id=15607869 |