과거 산업 제어, 로보틱스, 고속 데이터 수집 시스템은 고가의 전용 하드웨어와 실시간 운영체제(RTOS)에 의존하는 것이 일반적이었다. 그러나 라즈베리 파이와 같은 저비용 고성능 단일 보드 컴퓨터(SBC)의 등장은 임베디드 시스템 개발의 패러다임을 근본적으로 바꾸어 놓았다.1 이러한 SBC는 단순한 데이터 처리를 넘어 물리적 세계와 직접 상호작용하며, 정해진 시간 내에 예측 가능한 반응을 보장해야 하는 실시간(Real-Time) 요구사항을 동반하는 경우가 많다.3
라즈베리 파이는 본래 교육 및 취미용으로 개발되었으며, 기본적으로 탑재되는 라즈베리 파이 OS는 리눅스 커널에 기반한 범용 운영체제(GPOS)이다. GPOS의 설계 목표는 시스템의 평균 처리량(Throughput)을 극대화하는 데 초점이 맞춰져 있다. 이는 최악의 경우 응답 시간(Worst-Case Response Time) 보장을 최우선으로 하는 실시간 시스템의 목표와 본질적으로 상충된다.5 범용 리눅스 커널은 평균 성능을 위해 스케줄링, 인터럽트 처리, 메모리 관리 등에서 비결정적(Non-deterministic) 지연 요소를 내포하고 있기 때문이다.
본 보고서는 라즈베리 파이라는 범용 하드웨어 플랫폼에서, 범용 운영체제인 리눅스의 커널을 수정하고 시스템 전반을 최적화하여 신뢰성 있는 실시간 시스템을 구축하는 전 과정을 이론적, 실증적으로 고찰하는 것을 목적으로 한다. 이를 위해 실시간 시스템의 핵심 개념 정의부터 시작하여, 리눅스를 실시간 운영체제로 변환하는 핵심 기술인 PREEMPT_RT 커널의 아키텍처와 구현 방법, 하드웨어부터 애플리케이션에 이르는 다계층적 최적화 전략, 그리고 구축된 시스템의 성능을 객관적으로 검증하는 방법론을 심도 있게 다룬다. 최종적으로는 실제 산업 현장에서의 적용 사례를 통해 라즈베리 파이 기반 실시간 시스템의 가능성과 한계를 종합적으로 조망한다.
실시간 시스템을 성공적으로 구현하기 위해서는 그 기반이 되는 핵심 개념들에 대한 명확한 이해가 선행되어야 한다. 이는 단순히 시스템을 ‘빠르게’ 만드는 것을 넘어, ‘예측 가능하게’ 만드는 과정이며, 결정성, 지연시간, 지터라는 세 가지 핵심 지표를 통해 평가된다.
실시간 시스템의 성능은 평균적인 속도가 아닌, 최악의 경우에도 주어진 시간 제약을 만족시킬 수 있는지의 여부로 판단된다. 이러한 특성은 결정성, 지연시간, 지터라는 세 가지 밀접하게 연관된 개념으로 설명된다.
결정성 (Determinism): 결정성은 실시간 시스템의 가장 핵심적인 속성으로, 시스템이 주어진 입력과 조건 하에서 항상 예측 가능한 시간 범위 내에 태스크를 완료하는 능력을 의미한다.7 이는 ‘빠르다’는 것과는 다른 개념으로, ‘예측 가능성’과 ‘시간 제약 준수’에 중점을 둔다.9 예를 들어, 로봇 팔 제어 시스템은 10ms 이내에 모터에 명령을 전달해야 한다는 마감 시간(Deadline)이 있다면, 이 마감 시간을 100%의 확률로 준수할 수 있을 때 결정성을 확보했다고 말할 수 있다.7
지연시간 (Latency): 지연시간은 특정 이벤트가 발생한 시점부터 시스템이 해당 이벤트에 대한 반응을 완료하기까지 걸리는 총 시간을 의미한다.11 실시간 시스템에서는 평균 지연시간보다 최악의 경우 최대 지연시간(Worst-Case Latency)을 관리하고 상한을 보장하는 것이 무엇보다 중요하다. 시스템의 총 지연시간
$L$은 인터럽트 인지 지연, 스케줄링 지연, 태스크 실행 시간 등 다양한 요소의 합으로 표현될 수 있다. \(L = L_{interrupt} + L_{scheduling} + L_{execution}\)
지터 (Jitter): 지터는 지연시간의 변동성, 즉 응답 시간의 일관성을 나타내는 척도이다.11 동일한 작업을 반복 수행할 때 응답 시간이 매번 달라진다면 지터가 크다고 말하며, 이는 시스템의 응답 시간을 예측하기 어렵게 만들어 결정성을 해치는 주요 원인이 된다.14 수학적으로 지터 $J$는 특정 기간 동안 측정된 지연시간의 최대값($L_{max}$)과 최소값($L_{min}$)의 차이로 단순하게 정의할 수 있다.13
\(J = L_{max} - L_{min}\)
이 세 가지 개념은 유기적으로 연결되어 있다. 실시간 시스템의 궁극적인 목표는 결정성을 확보하는 것이며, 이를 위해서는 시스템의 최대 지연시간을 마감 시간 이내로 제어하고, 지터를 최소화하여 응답 시간의 예측 가능성을 높여야 한다. 낮은 평균 지연시간을 갖는 시스템이라도 간헐적으로 매우 큰 지연시간 스파이크(spike)가 발생한다면(즉, 지터가 크다면) 실시간 시스템으로서는 부적합하다. 따라서 실시간 시스템의 성능 평가는 ‘평균’이 아닌 ‘최악의 경우’에 초점을 맞춰야 하며, 이는 cyclictest와 같은 벤치마크 도구가 평균값(Avg)보다 최대값(Max)을 더 중요한 지표로 삼는 이유이기도 하다.
실시간 시스템은 마감 시간을 준수하지 못했을 때 발생하는 결과의 심각성에 따라 경성 실시간과 연성 실시간으로 분류된다.
SCHED_FIFO와 SCHED_RR은 주로 연성 실시간 태스크를 구현하는 데 사용된다.16라즈베리 파이에 실시간 리눅스를 구현하는 것은 이러한 경성 또는 연성 실시간 요구사항을 저비용 플랫폼에서 달성하려는 시도이며, 적용 분야에 따라 요구되는 결정성의 수준이 달라진다.
표준 리눅스 커널을 실시간 시스템에 적용하기 어려운 근본적인 이유는 커널 내부에 존재하는 예측 불가능한 지연 요소 때문이다. 이를 해결하기 위해 커널의 구조를 변경하는 다양한 접근법이 시도되어 왔으며, 대표적으로 단일 커널을 수정하는 PREEMPT_RT와 이중 커널 구조를 채택하는 Xenomai가 있다.
표준 리눅스 커널은 시스템의 전반적인 처리량을 높이는 데 최적화되어 있다. 이를 위해 커널 코드의 특정 임계 영역(Critical Section)을 실행하는 동안에는 다른 태스크가 끼어드는 선점(Preemption)을 허용하지 않는다. 대표적인 비선점 구간으로는 인터럽트 핸들러(Interrupt Handler)와 스핀락(Spinlock)으로 보호되는 영역이 있다.17
이러한 구조는 심각한 문제를 야기한다. 예를 들어, 우선순위가 매우 높은 실시간 태스크가 실행될 준비가 되더라도, 우선순위가 낮은 태스크가 커널 모드에서 스핀락을 잡고 있다면, 낮은 우선순위의 태스크가 락을 해제할 때까지 무기한 대기해야 할 수 있다. 이러한 예측 불가능한 대기 시간이 바로 스케줄링 지연시간(Scheduling Latency)을 증가시키고 결정성을 해치는 주된 원인이다.
PREEMPT_RT(Real-Time Preemption)는 기존 리눅스 커널의 소스 코드를 직접 수정하여 커널의 거의 모든 영역을 선점 가능하게 만드는 패치셋(Patch Set)이다.5 이 접근법은 리눅스의 단일 커널 구조와 풍부한 생태계를 그대로 유지하면서 경성 실시간 운영체제로 변환하는 것을 목표로 한다.18 PREEMPT_RT의 핵심적인 구조 변경 사항은 다음과 같다.
스레드 기반 인터럽트 핸들러 (Threaded IRQs): PREEMPT_RT는 대부분의 인터럽트 핸들러(IRQ Handler) 실행을 별도의 커널 스레드로 분리한다.20 하드웨어 인터럽트가 발생하면, 커널은 하드웨어 상태를 저장하는 최소한의 작업(Top-half)만 신속하게 처리한 후, 나머지 복잡한 처리 로직(Bottom-half)을 담당하는 커널 스레드를 깨운다. 이 스레드는 일반 스레드와 마찬가지로 우선순위에 따라 스케줄링되므로, 인터럽트를 처리하는 도중에도 더 높은 우선순위의 실시간 태스크가 있다면 즉시 선점될 수 있다. 또한, 각 인터럽트 스레드에 우선순위를 부여하여 중요도에 따라 처리 순서를 제어할 수 있게 된다.5
슬리핑 스핀락 (Sleeping Spinlocks) 및 RT-Mutex: 표준 커널의 스핀락은 락을 기다리는 동안 CPU를 계속 점유하며 대기(Busy-waiting)하고, 이 구간 동안 선점을 비활성화한다. PREEMPT_RT는 이러한 스핀락을 RT-Mutex라는 새로운 동기화 메커니즘으로 대체한다.21 RT-Mutex를 획득하려는 태스크는 락이 해제될 때까지 CPU를 낭비하지 않고 수면 상태(Sleep)로 전환된다. 이 과정에서
우선순위 상속(Priority Inheritance) 프로토콜이 핵심적인 역할을 한다. 만약 낮은 우선순위의 태스크(L)가 락을 점유한 상태에서 높은 우선순위의 태스크(H)가 동일한 락을 기다리게 되면, 커널은 일시적으로 태스크 L의 우선순위를 태스크 H의 수준으로 끌어올린다. 이로써 중간 우선순위의 태스크(M)가 태스크 L을 선점하여 락 해제를 지연시키는 우선순위 역전(Priority Inversion) 문제를 방지하고, 높은 우선순위 태스크의 대기 시간을 최소화한다.5
Xenomai는 PREEMPT_RT와는 근본적으로 다른 이중 커널(Dual-kernel) 아키텍처를 사용한다.13
이 두 접근법의 선택은 단순한 성능 지표 비교를 넘어선 아키텍처적 트레이드오프(Trade-off) 결정이다. PREEMPT_RT는 ‘통합’과 ‘개발 용이성’을 우선시하는 반면, Xenomai는 ‘격리’를 통한 ‘극한의 결정성’을 추구한다. 최근 PREEMPT_RT의 상당 부분이 리눅스 메인라인 커널에 통합되고 지속적으로 개선되면서, 잘 튜닝된 PREEMPT_RT 시스템은 많은 실제 애플리케이션 환경에서 Xenomai와 필적하는 성능을 보이기도 한다.19 따라서 풍부한 리눅스 생태계(드라이버, 네트워크 스택, 파일 시스템 등)를 적극적으로 활용하면서 수십~수백 마이크로초 수준의 실시간성을 요구하는 애플리케이션에는 PREEMPT_RT가, 리눅스 기능 의존도가 낮고 수 마이크로초 수준의 정밀도가 최우선인 애플리케이션에는 Xenomai가 더 적합한 선택이 될 수 있다.
| 구분 | PREEMPT_RT (단일 커널) | Xenomai (이중 커널) |
|---|---|---|
| 아키텍처 | 표준 리눅스 커널 직접 수정 (통합형) | 실시간 마이크로커널 + 비실시간 리눅스 (격리형) |
| 인터럽트 처리 | 스레드화된 IRQ, 우선순위 기반 스케줄링 | I-pipe가 인터럽트 선점 후 실시간 커널 우선 처리 |
| 최소 지연시간 | 수십 µs ~ 수백 µs | 수 µs ~ 수십 µs (일반적으로 더 우수) |
| 지터 | Xenomai에 비해 상대적으로 클 수 있음 | 매우 낮음 (높은 결정성) |
| API | 표준 POSIX API (pthread 등) | 고유 API (Alchemy, psos+, etc.) 또는 POSIX 에뮬레이션(‘skin’) |
| 개발 편의성 | 높음 (기존 리눅스 개발 경험 활용 가능) | 상대적으로 낮음 (별도 학습 및 커널 관리 필요) |
| 커뮤니티/지원 | 리눅스 커널 메인라인에 통합 중, 거대 커뮤니티 | 별도 커뮤니티, 상대적으로 규모 작음 |
| 적합 애플리케이션 | 로보틱스, 산업 자동화, 실시간 오디오 등 | 고속 데이터 수집(DAQ), 하드웨어-인더-루프(HIL) 시뮬레이션 |
라즈베리 파이에서 PREEMPT_RT 커널을 사용하기 위해서는 커널 소스를 직접 컴파일하고 설치하는 과정이 필요하다. 이 장에서는 교차 컴파일 환경을 구축하고, 커널을 설정, 빌드, 배포하는 전체 과정을 단계별로 상세히 설명한다.
라즈베리 파이와 같은 저사양 SBC에서 직접 커널을 컴파일하는 것은 매우 오랜 시간이 소요되므로 비효율적이다. 따라서 일반적으로 더 강력한 성능의 x86 기반 PC에서 라즈베리 파이용(ARM 아키텍처) 코드를 컴파일하는 교차 컴파일(Cross-compile) 방식을 사용한다.23
개발 환경의 복잡성과 의존성 문제를 해결하기 위해 Docker를 활용하는 것이 효과적이다. Docker는 빌드에 필요한 모든 도구(컴파일러, 라이브러리 등)를 컨테이너 이미지 하나에 패키징하여, 어떤 환경에서든 동일한 빌드 결과를 재현할 수 있게 해준다. jonhpark7966/rpi-rt-kernel과 같은 공개된 저장소는 이러한 Docker 기반 교차 컴파일 환경을 쉽게 구축할 수 있는 스크립트와 Dockerfile을 제공한다.23
커널 빌드의 첫 단계는 대상 하드웨어와 운영체제 버전에 맞는 커널 소스 코드와 PREEMPT_RT 패치를 준비하는 것이다.
커널 소스 다운로드: 라즈베리 파이 재단이 유지보수하는 공식 리눅스 커널 소스 저장소에서 원하는 버전의 소스 코드를 git clone 명령으로 내려받는다.
PREEMPT_RT 패치 다운로드: 리눅스 커널 공식 아카이브의 RT 프로젝트 페이지에서 커널 소스 버전과 정확히 일치하는 버전의 PREEMPT_RT 패치 파일을 다운로드한다.23
패치 적용: 다운로드한 커널 소스 디렉터리 내에서 patch 명령어를 사용하여 RT 패치를 적용한다. 예를 들어, 압축된 패치 파일을 파이프로 연결하여 적용할 수 있다.
xzcat../patch-5.15.60-rt49.patch.xz | patch -p1
rpi-rt-kernel과 같은 자동화 스크립트는 이 과정을 내부적으로 수행해준다.23
패치가 성공적으로 적용되었다면, make menuconfig 명령어를 실행하여 텍스트 기반의 커널 설정 인터페이스에 진입한다. 여기서 실시간 성능에 최적화된 커널을 만들기 위해 몇 가지 중요한 옵션을 설정해야 한다.
General setup —> Preemption Model(X) Fully Preemptible Kernel (PREEMPT_RT)CPU Power Management —> CPU Frequency scalingDefault CPUFreq governor를 performance로 변경하거나, 해당 기능을 완전히 비활성화(disable)한다.Kernel features —> Timer frequency(X) 1000 Hz이 외에도 시스템 디버깅 관련 옵션(Kernel hacking 메뉴 등)이나 불필요한 전력 관리 기능들은 비활성화하여 시스템 노이즈를 최소화하는 것이 좋다.
커널 설정이 완료되면, 교차 컴파일러를 지정하여 실제 빌드를 진행하고 결과물을 라즈베리 파이에 설치한다.
커널 빌드: 다음 명령어를 사용하여 커널 이미지(Image), 커널 모듈(modules), 그리고 디바이스 트리 바이너리(dtbs)를 컴파일한다. ARCH는 타겟 아키텍처를, CROSS_COMPILE은 교차 컴파일러의 접두사를 지정한다.23
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
결과물 전송: 빌드가 완료되면 생성된 파일들을 라즈베리 파이로 전송해야 한다. scp나 rsync와 같은 네트워크 파일 전송 도구를 사용하여 빌드된 커널 소스 디렉터리 전체를 라즈베리 파이로 복사한다.
라즈베리 파이에서 설치: 라즈베리 파이에 접속하여 전송된 디렉터리로 이동한 후, 다음 명령어들을 순서대로 실행하여 커널 모듈과 디바이스 트리를 시스템에 설치한다.23
sudo make modules_install
sudo make dtbs_install
부트 파티션 업데이트: 마지막으로, 새로 빌드된 커널 이미지와 디바이스 트리 파일들을 부트 파티션(/boot 또는 /boot/firmware)에 복사하여 시스템이 새로운 커널로 부팅되도록 설정한다. 기존 커널과 구별하기 위해 새로운 이름(예: kernel_rt.img)으로 저장하는 것이 안전하다.23
sudo cp arch/arm64/boot/Image /boot/firmware/kernel_rt.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb /boot/firmware/
sudo cp -r arch/arm64/boot/dts/overlays/*.dtb* /boot/firmware/overlays/
이후 /boot/firmware/config.txt 파일에 kernel=kernel_rt.img와 같은 라인을 추가하여 부팅할 커널을 지정한다.
검증: 라즈베리 파이를 재부팅한 후, 터미널에서 uname -a 명령어를 실행한다. 출력된 커널 버전 정보에 PREEMPT_RT라는 문자열이 포함되어 있다면 성공적으로 실시간 커널이 설치된 것이다.23
PREEMPT_RT 커널을 설치하는 것은 실시간 시스템 구축의 첫걸음일 뿐이다. 진정한 결정성을 확보하기 위해서는 커널뿐만 아니라 하드웨어, 펌웨어, 시스템 설정 등 다계층에 걸친 종합적인 최적화가 필수적이다. 이러한 최적화의 핵심 원리는 ‘격리(Isolation)’로 요약될 수 있다. 즉, 실시간 태스크의 실행을 방해할 수 있는 모든 비실시간적 요소를 최대한 분리하고 차단하는 것이다.
4.1. 하드웨어 및 펌웨어(BIOS) 설정 튜닝
실시간 성능은 소프트웨어뿐만 아니라 하드웨어 및 펌웨어 수준의 동작 방식에 크게 좌우된다. CPU의 동적인 전력 관리 기능(C-states, P-states), 메모리 오류 감지 및 수정(ECC) 기능, 그리고 운영체제가 제어할 수 없는 시스템 관리 인터럽트(SMI) 등은 예측 불가능한 지연시간을 유발하는 주요 원인이다.24 따라서 고성능 서버급 시스템에서는 BIOS/UEFI 설정에 진입하여 이러한 기능들을 비활성화하거나 최소화하는 것이 일반적인 튜닝의 시작이다. 라즈베리 파이는 전통적인 BIOS가 없지만, /boot/firmware/config.txt 파일을 통해 CPU 클럭 고정, 특정 하드웨어 기능 비활성화 등 유사한 수준의 제어가 가능하다.
리눅스 커널은 부팅 시 특정 파라미터를 전달받아 동작 방식을 변경할 수 있다. 실시간 시스템에서는 다음 파라미터들을 조합하여 실시간 태스크를 위한 전용 실행 환경을 구축한다. 이 파라미터들은 라즈베리 파이의 /boot/firmware/cmdline.txt 파일에 추가하여 적용한다.
isolcpus: 이 파라미터에 지정된 CPU 코어는 리눅스 스케줄러의 일반적인 부하 분산 대상에서 제외된다. 즉, 일반적인 사용자 프로세스나 커널 스레드들이 이 코어에 할당되지 않는다. 이렇게 격리된 코어에는 taskset이나 cgroups 같은 도구를 사용하여 중요한 실시간 애플리케이션을 명시적으로 할당함으로써, 다른 프로세스들의 간섭을 원천적으로 차단할 수 있다.26nohz_full: isolcpus로 격리된 CPU의 결정성을 더욱 향상시키기 위한 파라미터이다. 이 옵션이 적용된 CPU에서는 실행 중인 태스크가 단 하나일 경우, 주기적으로 발생하여 컨텍스트 스위칭을 유발하는 타이머 틱(Timer Tick) 인터럽트가 중단된다. 타이머 틱이 사라지면 불필요한 스케줄러 호출, RCU 처리 등의 커널 오버헤드가 제거되어 시스템 노이즈(Jitter)가 극적으로 감소한다.26rcu_nocbs: RCU(Read-Copy-Update)는 커널 내 데이터 구조를 락 없이 안전하게 읽기 위해 널리 사용되는 동기화 메커니즘이다. 하지만 RCU 업데이트 후 발생하는 콜백(callback) 처리 작업이 지연을 유발할 수 있다. rcu_nocbs 파라미터는 이 콜백 처리 부담을 지정된 CPU에서 다른 ‘하우스키핑’ CPU로 이전(offload)시킨다. nohz_full 파라미터를 사용하면 이 기능이 자동으로 활성화되는 경우가 많아, 격리된 CPU는 순수하게 애플리케이션 코드 실행에만 집중할 수 있게 된다.28이러한 파라미터들을 통해 실시간 태스크를 다른 프로세스로부터(CPU 격리), 커널 내부 활동으로부터(시간적 격리), 그리고 하드웨어 인터럽트로부터(이벤트 격리) 다층적으로 보호하는 것이 가능하다.
irqbalance 데몬 비활성화: 대부분의 리눅스 배포판은 기본적으로 irqbalance 데몬을 활성화한다. 이 데몬은 시스템 부하에 따라 하드웨어 인터럽트(IRQ) 처리 부담을 여러 CPU 코어에 동적으로 분산시켜 평균적인 시스템 성능을 향상시킨다. 그러나 실시간 시스템에서는 이러한 동적 재분배가 오히려 캐시 효율을 떨어뜨리고 예측 불가능한 지연을 유발할 수 있다. 따라서 IRQ 처리를 특정 코어에 고정하여 예측 가능성을 높이는 것이 중요하므로, irqbalance 데몬은 반드시 비활성화해야 한다.24IRQ 선호도(Affinity) 수동 설정: irqbalance를 비활성화한 후에는 /proc/irq/<IRQ_NUMBER>/smp_affinity 파일을 직접 수정하여 각 인터럽트가 어느 CPU에서 처리될지 수동으로 지정해야 한다. 핵심 전략은 네트워크, 산업용 버스 카드 등 실시간 데이터 처리와 관련된 중요한 IRQ를 isolcpus로 격리되지 않은 ‘하우스키핑’ CPU에 할당하는 것이다. 반대로, 실시간 태스크가 실행될 격리된 CPU에서는 타이머와 같은 필수적인 IRQ를 제외한 모든 IRQ를 제거하여, 하드웨어 인터럽트로 인한 간섭을 최소화해야 한다.24
| 파라미터 | 목표 | 동작 원리 | 사용 예시 (cmdline.txt) |
상호작용 및 주의사항 |
|---|---|---|---|---|
isolcpus |
CPU 격리 | 특정 CPU를 일반 스케줄러의 로드 밸런싱에서 제외. | isolcpus=2,3 |
격리된 CPU에 실시간 태스크를 taskset 등으로 명시적으로 할당해야 함. |
nohz_full |
타이머 틱 제거 | 격리된 CPU에 실행 가능한 태스크가 1개일 때 주기적인 타이머 인터럽트를 중단하여 OS 지터 감소. | nohz_full=2,3 |
isolcpus와 동일한 CPU 목록을 지정하는 것이 일반적. 완벽한 tickless를 위해선 애플리케이션 조건(posix timer 미사용 등)이 충족되어야 함. |
rcu_nocbs |
RCU 콜백 오프로드 | RCU 콜백 처리 작업을 격리된 CPU가 아닌 다른 CPU의 rcuo 커널 스레드로 이전. |
rcu_nocbs=2,3 |
nohz_full 사용 시 자동으로 해당 CPU에 대해 활성화되므로 명시적으로 지정할 필요가 없는 경우가 많음. |
irqaffinity |
IRQ 처리 CPU 지정 | 부팅 시 기본 IRQ 처리 CPU를 지정. 특정 CPU를 IRQ 처리에서 제외하는 데 사용. | irqaffinity=0-1 |
isolcpus로 지정된 CPU는 이 목록에서 제외하여 IRQ 간섭을 최소화. |
커널과 시스템 수준의 최적화가 완료되었다면, 다음 단계는 실시간 애플리케이션 자체를 최적화하여 시스템의 잠재력을 최대한 활용하는 것이다. 이는 올바른 스케줄링 정책을 선택하고 메모리 관리로 인한 지연을 방지하는 두 가지 핵심 기법으로 이루어진다.
리눅스는 실시간 태스크를 위해 두 가지 주요 스케줄링 정책을 제공한다. 이 정책들은 일반 정책인 SCHED_OTHER(CFS 스케줄러가 사용)보다 항상 높은 우선순위를 가지며, 1(가장 낮음)부터 99(가장 높음)까지의 정적 우선순위 값을 사용한다.39
SCHED_FIFO (First-In, First-Out): 이 정책은 가장 단순하고 강력한 실시간 스케줄링 방식이다. 일단 SCHED_FIFO 태스크가 CPU를 점유하면, 더 높은 우선순위의 태스크가 나타나거나, 스스로 I/O 대기 등의 이유로 실행을 멈추지 않는 한 계속해서 실행된다. 동일한 우선순위를 가진 다른 SCHED_FIFO 태스크가 있더라도 선점되지 않는다. 즉, 타임 슬라이스(Time Slice)나 시간 할당량(Quantum) 개념이 없다.16
SCHED_RR (Round-Robin): 이 정책은 SCHED_FIFO의 변형이다. 우선순위에 따라 스케줄링되는 점은 동일하지만, 동일한 우선순위를 가진 태스크들 사이에서는 정해진 타임 슬라이스만큼만 실행한 후, 실행 큐의 다음 태스크에게 CPU를 양보하는 라운드 로빈 방식을 사용한다.16
SCHED_RR로 설정하면, 모든 모터가 주기적으로 제어 신호를 받을 수 있도록 보장할 수 있다.43애플리케이션의 특성을 정확히 파악하고, 태스크 간의 관계를 고려하여 SCHED_FIFO와 SCHED_RR을 적절히 조합하는 것이 중요하다. chrt 유틸리티나 sched_setscheduler() 시스템 콜을 사용하여 프로세스나 스레드의 스케줄링 정책과 우선순위를 동적으로 변경할 수 있다.39
문제점: 페이지 폴트의 비결정성: 현대 운영체제는 물리적 RAM보다 큰 주소 공간을 사용하기 위해 가상 메모리 시스템을 활용한다. 애플리케이션이 특정 메모리 주소에 접근하려 할 때, 해당 데이터가 담긴 메모리 페이지가 물리적 RAM에 존재하지 않고 디스크(스왑 공간)에 내려가 있을 수 있다. 이 경우 ‘페이지 폴트(Page Fault)’라는 예외가 발생하고, 커널은 디스크 I/O를 통해 해당 페이지를 RAM으로 다시 읽어 들여와야 한다. 이 디스크 I/O 작업은 수 밀리초(ms)에 달하는 매우 길고 예측 불가능한 지연을 유발하며, 이는 실시간 시스템의 결정성을 심각하게 훼손하는 주범이다.
해결책: mlockall() 시스템 콜: 이러한 페이지 폴트로 인한 지연을 원천적으로 방지하기 위해 mlockall() 시스템 콜을 사용한다.45 이 함수는 호출한 프로세스가 사용하는 모든 메모리 페이지(코드, 데이터, 스택, 공유 라이브러리 등)를 물리적 RAM에 강제로 고정(Lock)하여, 운영체제가 해당 페이지들을 디스크로 스왑 아웃(Swap-out)하는 것을 금지한다.46
사용법 및 주요 플래그: 실시간 애플리케이션의 초기화 코드 부분에서 mlockall()을 호출하는 것이 일반적이다.
#include <sys/mman.h>
int main(void) {
//... 초기화 코드...
// 현재 매핑된 메모리와 앞으로 매핑될 모든 메모리를 RAM에 고정한다.
if (mlockall(MCL_CURRENT | MCL_FUTURE)!= 0) {
perror("mlockall failed");
// 오류 처리
}
//... 실시간 핵심 로직...
munlockall(); // 프로그램 종료 전 메모리 락 해제
return 0;
}
MCL_CURRENT: 함수 호출 시점에 이미 프로세스의 주소 공간에 매핑되어 있는 모든 페이지를 잠근다.45MCL_FUTURE: 함수 호출 이후에 malloc(), mmap() 등을 통해 동적으로 할당되거나 매핑될 모든 페이지도 자동으로 잠기도록 설정한다.45실시간 애플리케이션에서 mlockall(MCL_CURRENT | MCL_FUTURE)를 사용하는 것은 페이지 폴트로 인한 예측 불가능한 지연을 제거하여 시스템의 결정성을 확보하기 위한 필수적인 프로그래밍 기법이다.45
실시간 시스템을 구축하고 최적화하는 과정은 지속적인 측정과 분석을 통해 검증되어야 한다. 하드웨어 자체의 한계를 파악하는 것부터 시작하여, 다양한 부하 조건에서 시스템의 스케줄링 지연시간을 정량적으로 평가하는 것이 중요하다.
목적: hwlatdetect는 운영체제(OS)나 애플리케이션의 영향을 배제하고, 순수하게 하드웨어 아키텍처나 펌웨어(BIOS, SMI 등) 자체에서 발생하는 고유한 지연시간을 측정하는 도구이다.25 이는 특정 하드웨어 플랫폼이 실시간 워크로드를 실행하기에 근본적으로 적합한지 여부를 판단하는 첫 번째 관문 역할을 한다.
동작 원리: hwlatdetect는 내부적으로 커널 스레드를 실행하여 CPU의 타임스탬프 카운터(TSC)를 매우 짧은 주기로 반복해서 읽는다. 정상적인 상황에서는 연속된 읽기 값의 차이가 일정해야 한다. 만약 이 시간 값에 비정상적인 ‘점프’나 ‘공백’이 발견된다면, 이는 시스템 관리 인터럽트(SMI)와 같이 OS가 제어할 수 없는 이벤트로 인해 CPU가 잠시 멈췄음을 의미한다.48
사용법 및 결과 해석: 일반적으로 시스템에 다른 부하를 주지 않은 상태에서 장시간(수 분 ~ 수 시간) 실행하여 최대 지연시간을 관찰한다.
sudo hwlatdetect --duration=300s
실행 결과에서 가장 중요한 지표는 Max Latency이다. 예를 들어, Max Latency: 18us라는 결과는 펌웨어나 하드웨어로 인해 최대 18 마이크로초의 예측 불가능한 지연이 발생할 수 있음을 의미한다. 만약 구축하려는 시스템이 10 마이크로초 미만의 지연시간을 요구한다면, 이 하드웨어는 부적합하다고 판단할 수 있다. 이 경우, BIOS 설정 변경(전력 관리 기능 비활성화 등)이나 시스템 공급업체를 통한 펌웨어 업데이트를 고려해야 한다.25
목적: cyclictest는 PREEMPT_RT 개발자들이 커널의 실시간 성능을 측정하기 위해 만든 표준 벤치마크 도구이다. 이 도구는 실시간 스레드가 주기적으로 깨어나도록 설정된 시간과, 운영체제 스케줄러에 의해 실제로 깨어난 시간 사이의 차이, 즉 스케줄링 지연시간을 정밀하게 측정한다.49
주요 옵션: cyclictest는 매우 다양한 옵션을 제공하지만, 실시간 성능 측정 시 필수적으로 사용되는 옵션은 다음과 같다.49
--smp (-S): SMP 시스템에서 각 CPU 코어별로 측정 스레드를 생성하고 해당 코어에 고정(pinning)하여 코어별 성능을 측정한다.--priority=<p> (-p <p>): 측정 스레드의 실시간 우선순위를 1에서 99 사이의 값으로 설정한다.--interval=<i> (-i <i>): 스레드가 깨어나는 주기를 마이크로초(µs) 단위로 설정한다.--histogram=<val> (-h <val>): 측정된 지연시간의 분포를 히스토그램 형태로 출력하여 데이터의 분포를 시각적으로 분석할 수 있게 한다.--mlockall (-m): 페이지 폴트로 인한 측정 오류를 방지하기 위해 mlockall()을 호출하여 메모리를 잠근다.결과 분석: cyclictest를 실행하면 각 스레드별로 Min, Act, Avg, Max 지연시간이 실시간으로 출력된다.
# T: 0 ( 959) P:95 I:1000 C: 34650 Min: 1 Act: 8 Avg: 12 Max: 377
Max Latency의 중요성: 이 결과에서 가장 주목해야 할 값은 단연 Max이다.49
Avg(평균) 지연시간이 아무리 낮더라도, 단 한 번의 긴 Max 지연시간이 발생하면 경성 실시간 시스템의 마감 시간을 놓칠 수 있기 때문이다. 실시간 시스템 튜닝의 목표는 바로 이 Max 값을 애플리케이션이 허용하는 범위 내로 안정적으로 유지하는 것이다.
히스토그램 분석: 히스토그램은 지연시간 값의 분포를 보여준다. 이상적인 실시간 시스템의 히스토그램은 대부분의 값이 낮은 지연시간 쪽에 집중되어 있고, 오른쪽으로 긴 꼬리(long tail)가 없는 형태를 띤다. 만약 특정 값에서 빈도가 높게 나타나거나 긴 꼬리가 관찰된다면, 이는 시스템에 간헐적으로 지연을 유발하는 특정 노이즈 소스가 존재함을 시사한다.
현실적인 부하 테스트: cyclictest는 실제 애플리케이션과 유사한 부하(네트워크 트래픽, 디스크 I/O, 그래픽 작업 등)를 동시에 가하면서 측정해야만 의미 있는 결과를 얻을 수 있다.52 부하가 없는 상태에서의
Max 값은 시스템의 잠재력을 보여줄 뿐, 실제 운영 환경에서의 성능을 보장하지 않는다. 또한, 측정된 최대값은 테스트 기간 동안의 최대값일 뿐 시스템의 이론적인 최악 실행 시간(WCET)을 보장하는 것은 아니라는 점을 인지해야 한다.52
| 항목 | 레이블 | 설명 | 실시간 성능 해석 가이드라인 |
|---|---|---|---|
| 스레드 정보 | T | 측정 스레드의 인덱스 및 ID. | SMP 테스트 시 각 코어의 성능을 개별적으로 확인하는 데 사용. |
| 우선순위 | P | 측정 스레드의 실시간 우선순위. | 실제 애플리케이션의 우선순위와 동일하게 설정하여 테스트. |
| 측정 횟수 | C | 총 측정된 샘플의 수. | 신뢰도 있는 Max 값을 얻으려면 충분히 긴 시간(수 시간~수 일) 동안 실행하여 C 값을 높여야 함. |
| 최소 지연시간 | Min | 측정된 가장 짧은 지연시간. | 시스템이 이상적으로 동작할 때의 응답 시간. 일반적으로 10µs 미만. |
| 평균 지연시간 | Avg | 모든 측정값의 산술 평균. | 시스템의 전반적인 응답성을 나타내지만, 최악의 경우를 보장하지 않으므로 실시간 평가에서는 참고용. |
| 최대 지연시간 | Max | 측정된 가장 긴 지연시간. | 가장 중요한 지표. 이 값이 시스템의 결정성을 좌우하며, 애플리케이션의 마감 시간 요구사항을 충족해야 함. 수십 µs 수준을 목표로 튜닝. |
| 히스토그램 | (별도 출력) | 지연시간 값의 빈도 분포. | 분포가 좁고 왼쪽으로 치우칠수록 좋음. 오른쪽으로 긴 꼬리가 있다면 간헐적인 지연 유발 요인이 있음을 시사. |
라즈베리 파이에 PREEMPT_RT 커널을 적용하고 시스템을 최적화하는 기술은 이론에만 머무르지 않고, 이미 다양한 산업 및 연구 분야에서 실질적인 가치를 창출하고 있다. 저비용, 고성능, 높은 접근성을 바탕으로 과거에는 상상하기 어려웠던 영역까지 그 활용 범위를 넓혀가고 있다.
첨단 연구 사례: 최근 연구에서는 반능동형(semi-active) 의족의 실시간 제어를 위해 라즈베리 파이 4에 PREEMPT_RT 커널을 적용하고, 실시간 산업용 통신 프로토콜인 EtherCAT을 구현한 사례가 보고되었다. 이 시스템은 250 마이크로초($\mu s$)의 빠른 샘플링 주기를 달성했으며, 통신 주기의 지터(표준편차)를 4.51 $\mu s$라는 매우 낮은 수준으로 제어하는 데 성공했다.56 이는 라즈베리 파이가 적절한 소프트웨어 최적화를 통해 수백 마이크로초 단위의 엄격한 경성 실시간 제어 분야에서도 충분히 활용될 수 있음을 실증적으로 보여주는 중요한 사례이다.
결론적으로, 라즈베리 파이는 더 이상 교육용 및 취미용 플랫폼에 머무르지 않는다. 실시간 리눅스와의 결합을 통해, 라즈베리 파이는 산업 자동화, 로보틱스, 그리고 차세대 임베디드 시스템 개발을 위한 신뢰성 있고 경제적인 핵심 플랫폼으로 자리매김하고 있으며, 그 잠재력은 앞으로 더욱 커질 것으로 전망된다.
| 20.2. irqbalance 데몬 비활성화 | 짧은 대기 시간 작업을 위해 RHEL 9 …, 8월 17, 2025에 액세스, https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux_for_real_time/9/html/optimizing_rhel_9_for_real_time_for_low_latency_operation/proc_disabling-the-irqbalance-daemon_assembly_binding-interrupts-and-processes |
| 짧은 대기 시간 작업을 위해 RHEL 9 for Real Time 최적화 | Red Hat …, 8월 17, 2025에 액세스, https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux_for_real_time/9/html-single/optimizing_rhel_9_for_real_time_for_low_latency_operation/index |
| CPU Isolation – Nohz_full – by SUSE Labs (part 3) | SUSE …, 8월 17, 2025에 액세스, https://www.suse.com/c/cpu-isolation-nohz_full-part-3/ |
| Getting Gigabit Networking on a Raspberry Pi 2, 3 and B+ | Jeff Geerling, 8월 17, 2025에 액세스, https://www.jeffgeerling.com/blogs/jeff-geerling/getting-gigabit-networking |
| Testing PCIe on the Raspberry Pi 5 | Jeff Geerling, 8월 17, 2025에 액세스, https://www.jeffgeerling.com/blog/2023/testing-pcie-on-raspberry-pi-5 |
| 2.2. Using mlock to Avoid Page I/O | Reference Guide | Red Hat Enterprise Linux for Real Time, 8월 17, 2025에 액세스, https://docs.redhat.com/en/documentation/red_hat_enterprise_linux_for_real_time/7/html/reference_guide/using_mlock_to_avoid_page_io |
| SUSE Linux Enterprise RT 15 SP7 | SLE RT Hardware Testing, 8월 17, 2025에 액세스, https://documentation.suse.com/en-us/sle-rt/15-SP7/html/SLE-RT-all/article-hardware-testing.html |