RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
#한 줄 요약
“RTOS는 print만으로는 안 보입니다.” — Task switch, ISR, IPC 이벤트를 시간축에 펼쳐야 인과가 보입니다. Tracealyzer·SystemView·ITM이 그 도구입니다.
#어떤 문제를 푸는가
RTOS 위에서 버그를 잡아 본 사람은 한 번쯤 겪는 무력감이 있습니다. UART 데이터가 가끔 깨지고, 어떤 task가 늦게 깨어나고, 어디서 우선순위 역전이 일어나는 것 같은데 printf로는 도무지 순서가 잡히지 않습니다.
문제는 RTOS가 만들어내는 이벤트가 수십 종류에 μs 단위로 발생한다는 점입니다.
- Task switch — 어느 시점에 누가 누구를 밀어냈는가
- ISR 진입/종료 — 어떤 인터럽트가 얼마나 길게 실행되었는가
- Queue/semaphore/mutex 동작 — 누가 보내고 누가 받았는가
- Tick, timer expiry, software event
printf를 그 안에 박으면 그 자체가 실행 시간을 바꿔서 버그가 사라지거나 새로 생깁니다. 필요한 건 오버헤드가 무시할 만한 trace 인프라와, 그 trace를 사람이 읽을 수 있게 시각화해 주는 도구입니다.
이 글에서는 Tracealyzer, SystemView, 그리고 그 아래에서 동작하는 ITM·ETM 하드웨어 트레이스를 살펴봅니다.
#두 가지 접근 — SW Trace vs HW Trace
| SW Trace | HW Trace | |
|---|---|---|
| 예 | Tracealyzer, SystemView, FreeRTOS+Trace | ITM, ETM, PTM |
| 데이터 출처 | RTOS hook에서 event record | CPU/bus가 자동으로 생성 |
| 오버헤드 | event당 ~100 ns | ≈0 (별도 trace port) |
| 정보량 | 이벤트 단위 | 명령 단위까지 가능 |
| 호스트 전송 | RTT, UART, USB | TPIU, ETB, off-chip |
| 디바이스 요구 | 없음 (메모리만) | trace 지원 칩 + 프로브 |
SW trace는 대부분의 칩에서 작동하고 RTOS 이벤트 수준에서 충분히 풍부합니다. HW trace는 프로파일링·드물게 발생하는 버그 같은 어려운 문제에서 빛납니다. 둘은 서로를 대체하지 않습니다.
#Tracealyzer — 시각화의 결정판
Percepio Tracealyzer는 RTOS 이벤트의 시간축 시각화 도구입니다. FreeRTOS, Zephyr, ThreadX, SafeRTOS, embOS 등 주요 RTOS를 지원합니다.
수집 방법은 두 가지입니다.
- Snapshot mode: 타겟의 ring buffer에 trace를 쌓아 두고, 필요할 때 디버거로 한꺼번에 덤프 합니다. 호스트 연결 불필요.
- Streaming mode: J-Link RTT, USB, TCP 등으로 실시간 전송합니다. 장시간 캡처 가능.
호스트 GUI에서는 task별 timeline, CPU load 히트맵, IPC graph, responsiveness chart 같은 view가 제공됩니다. 같은 데이터를 여러 관점에서 펼쳐 볼 수 있는 게 핵심 가치입니다.
#define configUSE_TRACE_FACILITY 1#define configUSE_STATS_FORMATTING_FUNCTIONS 1#define configGENERATE_RUN_TIME_STATS 1
// TraceRecorder 통합#include "trcRecorder.h"
void main(void) { /* RTOS 시작 전 trace 초기화 */ xTraceInitialize(); xTraceEnable(TRC_START);
/* 태스크 생성 */ xTaskCreate(task_a, "A", 256, NULL, 3, NULL); xTaskCreate(task_b, "B", 256, NULL, 2, NULL); vTaskStartScheduler();}xTraceEnable() 호출 이후 모든 RTOS 이벤트가 자동으로 기록됩니다. 추가 코드는 보통 필요 없습니다.
#SystemView — RTT의 친구
SEGGER SystemView는 SystemView 자체 호스트 GUI와 Real Time Transfer (RTT) 인프라를 묶은 솔루션입니다.
RTT는 SEGGER가 만든 호스트-타겟 통신 채널입니다. 타겟 메모리에 ring buffer를 두고, J-Link가 SWD/JTAG로 CPU를 멈추지 않고 그 메모리를 읽어 갑니다. 평균 오버헤드가 μs 단위입니다.
// SEGGER RTT + SystemView 통합#include "SEGGER_SYSVIEW.h"
void main(void) { SEGGER_SYSVIEW_Conf(); SEGGER_SYSVIEW_Start();
/* RTOS 시작 */ vTaskStartScheduler();}
// 사용자 이벤트SEGGER_SYSVIEW_PrintfHost("DMA complete: %d bytes", count);SEGGER_SYSVIEW_MarkStart(0x42);process_frame();SEGGER_SYSVIEW_MarkStop(0x42);MarkStart/MarkStop은 사용자 정의 구간을 timeline 위의 색 bar로 보여 줍니다. 함수 실행 시간을 시각적으로 비교하기에 편합니다.
Tracealyzer와의 차이를 정리하면 이렇습니다.
| Tracealyzer | SystemView | |
|---|---|---|
| 가격 | 상용 (eval 가능) | 무료 (J-Link 필요) |
| RTOS 지원 폭 | 매우 넓음 | FreeRTOS·embOS 중심 |
| 분석 view | 다양함 (graph·heatmap·responsiveness) | 기본 timeline + table |
| 전송 채널 | RTT/UART/USB/TCP | RTT 위주 |
| 라이센스 부담 | 있음 | J-Link 묶음 |
소규모 프로젝트나 초기 디버깅엔 SystemView가 가볍게 시작하기 좋고, 제품 단위 분석에는 Tracealyzer가 더 깊이 들어갑니다.
#ITM — Cortex-M의 Trace Macrocell
Instrumentation Trace Macrocell은 Cortex-M3/M4/M7에 들어 있는 하드웨어 trace port입니다. CPU가 직접 32개 stimulus port에 데이터를 쓰면, 그 데이터가 *TPIU(Trace Port Interface Unit)*를 거쳐 외부 trace probe로 나갑니다.
// ITM port 0에 한 byte 전송 — 거의 0 오버헤드static inline void itm_putc(char c) { if (ITM->TCR & ITM_TCR_ITMENA_Msk) { // ITM enabled? while (ITM->PORT[0].u32 == 0); // port ready? ITM->PORT[0].u8 = c; }}CPU 입장에선 memory-mapped register에 write 한 번이 전부입니다. 데이터 export는 TPIU가 알아서 합니다.
ITM이 자동으로 만들어 주는 이벤트도 있습니다.
- PC sampling — 일정 주기로 program counter 캡처
- Exception trace — IRQ 진입/종료
- Watchpoint trace — 특정 주소 access
이걸 OpenOCD/J-Link가 받아서 flame graph 생성에 쓰면 별도 코드 없이 profiling이 됩니다.
#ETM — 명령어 단위 트레이스
Embedded Trace Macrocell은 실행된 모든 명령을 압축해서 export하는 더 강력한 trace입니다. ITM은 명시적 print만 보내지만 ETM은 모든 branch를 자동으로 기록합니다.
ETM 출력 예 (압축 해제 후):0x0800_1234 ldr r0, [r1]0x0800_1238 cmp r0, #00x0800_123C beq 0x0800_1300 ← taken0x0800_1300 mov r2, #1...수 GB/s를 export하므로 off-chip TPIU나 *ETB(Embedded Trace Buffer)*가 필요합니다. Lauterbach TRACE32, ARM DS-5/DS, SEGGER J-Trace 같은 고가 프로브가 받아 줍니다.
ETM이 진짜 빛나는 경우는 재현 안 되는 crash입니다. ETB에 마지막 수천 명령이 남아 있으면 crash까지의 control flow를 정확히 재구성할 수 있습니다.
#Run-Time Stats — 가장 가벼운 시작
configGENERATE_RUN_TIME_STATS = 1을 켜면 FreeRTOS가 task별 누적 실행 시간을 추적합니다. 추가 도구 없이도 CPU 점유율을 볼 수 있는 가장 가벼운 옵션입니다.
#define configGENERATE_RUN_TIME_STATS 1#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() timer_init()#define portGET_RUN_TIME_COUNTER_VALUE() timer_get_cycles()조회 코드는 다음과 같습니다.
char buf[512];vTaskGetRunTimeStats(buf);printf("%s\n", buf);
// 출력 예// Task Abs Time % Time// IDLE 1234567 65%// AUDIO_TASK 234567 12%// NET_TASK 345678 18%// LOG_TASK 45678 2%오버헤드는 task switch당 timer read 두 번입니다. 보통 2~3% 미만입니다. 본격적인 trace 도구를 붙이기 전, 첫 진단 도구로 쓰기 좋습니다.
#측정 — 오버헤드 비교
대표적인 실측 오버헤드입니다(Cortex-M4F @ 168 MHz 기준).
| 도구 | 이벤트당 비용 | 전체 CPU 오버헤드 |
|---|---|---|
printf over UART | 100~500 μs | 측정 불가 (system 망가짐) |
| Run-time stats only | 50 ns | <1% |
| SystemView RTT | 100~200 ns | 2~5% |
| Tracealyzer streaming | 100~300 ns | 3~7% |
| ITM stimulus | 5~10 ns | <0.1% |
| ETM (full instr) | 0 (별도 port) | 0% (CPU 부담 없음) |
ITM이 SW trace보다 10~30배 가볍습니다. 단, ETM 수준의 정보 밀도는 대용량 export bandwidth를 요구해서 별도 HW를 깔아야 합니다.
#실전 — IPC 인과 추적
다음과 같은 시나리오를 생각해 봅니다.
“Task A가 큐에 메시지를 보냈는데, Task B가 가끔 5 ms 지나서 깨어납니다. 평소엔 100 μs면 깨는데요.”
Tracealyzer로 이 trace를 캡처하면 timeline 위에 무엇이 일어났는지 한눈에 보입니다.
t=0 Task A: xQueueSend → Task B를 ready로 표시t=0 Scheduler: Task B preempt? — 더 높은 priority Task X가 RUNNINGt=0 ... Task X 계속 실행t=5ms Task X: vTaskDelay → blockt=5ms Scheduler: Task B run → xQueueReceive return원인은 Task X의 priority가 B보다 높았고, 그동안 X가 자기 일을 했기 때문이라는 게 분명해집니다. Print 디버깅으로는 시간 정렬이 안 되어 보이지 않을 일입니다.
#자주 보는 함정과 안티패턴
⚠️ Trace buffer overflow
Snapshot mode에서 buffer가 작으면 오래된 이벤트가 지워집니다. 버그 발생 시점이 buffer보다 더 옛날이면 trace에 안 잡힙니다. Streaming mode로 바꾸거나, buffer를 수 MB 수준으로 늘립니다.
⚠️ Hook 안에서 RTOS API 호출
Trace hook은 RTOS 내부에서 호출되므로 그 안에서 xQueueSend 같은 API를 다시 부르면 재진입 무한 루프가 발생할 수 있습니다. Hook은 기록만 합니다.
⚠️ USB/UART trace의 bandwidth 한계
Full-speed USB는 12 Mbps입니다. Trace 데이터가 그보다 빠르게 만들어지면 buffer가 차서 drop이 발생합니다. RTT가 수십 Mbps로 훨씬 여유 있습니다.
⚠️ 디버거 의존성
RTT나 ITM은 J-Link/ST-Link 연결이 필요합니다. 필드 디바이스에서는 못 씁니다. 그럴 땐 flash에 ring buffer로 dump하고 나중에 회수하는 방식이 필요합니다.
⚠️ Trace 켜면 동작이 달라지는 버그
5% 오버헤드라도 race condition에는 영향을 줍니다. Trace 켜면 사라지는 버그는 trace를 다시 끄고 가벼운 ITM stimulus 몇 줄로 재현해 봅니다.
⚠️ 시계 기준이 다른 두 trace 비교
호스트 시각과 타겟 cycle count가 동기화되지 않은 채 비교하면 인과가 뒤집힙니다. 두 trace를 합치려면 공통 trigger 이벤트를 정해 시각을 정렬합니다.
#정리
- RTOS는
printf로는 안 보이고, 시간축 시각화가 본질적으로 필요합니다. - Tracealyzer는 다양한 RTOS와 풍부한 view를 제공하며, snapshot/streaming 두 모드를 지원합니다.
- SystemView는 J-Link RTT 기반으로 가벼운 시작에 좋습니다.
- ITM은 Cortex-M의 하드웨어 trace port로 5~10 ns 수준의 stimulus를 보냅니다.
- ETM은 명령 단위 트레이스로 재현 안 되는 crash 분석에 빛납니다.
- Run-time stats는 FreeRTOS 자체 기능으로 1% 미만 오버헤드에 CPU 점유율을 보여 줍니다.
- Trace는 동작을 살짝 바꿉니다. race condition은 lightweight ITM으로 재확인합니다.
다음 편은 2-12: 임베디드 시스템의 시간 동기화 — Tick, PTP, IEEE 1588을 다룹니다.
#관련 항목
Practical RTOS Internals · 22 of 53
- 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
- 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
- 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
- 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
- 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
- 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
- 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
- 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
- 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
- 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
- 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
- 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
- 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
- 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
- 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
- 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
- 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
- 18RISC-V Context Switch 분석 — ECALL·mret·CSR
- 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
- 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
- 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
- 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
- 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
- 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
- 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
- 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
- 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
- 28Priority Ceiling Protocol — Immediate vs Original 비교
- 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
- 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
- 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
- 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
- 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
- 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
- 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
- 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
- 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
- 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
- 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
- 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
- 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
- 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
- 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
- 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
- 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
- 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
- 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
- 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
- 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
- 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
- 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
- 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
- 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL
관련 글
PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL
2024년 9월 Linux 6.12 mainline에 합류한 PREEMPT_RT의 핵심 변경을 정리하고, Xenomai 4·EVL과 함께 RTOS와의 선택 기준을 비교합니다. threaded IRQ·sleeping spinlock·cyclictest까지 한 지도에 모읍니다.
Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
NuttX의 POSIX-compliant 구조를 따라가며 PX4 autopilot과 NASA Ingenuity 화성 헬리콥터 채택 배경을 정리합니다. Flat/Protected/Kernel 빌드, VFS, 네트워크, NSH, micro-ROS 통합까지 한 지도로 모읍니다.
RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
FreeRTOS·Zephyr·ThreadX·RT-Thread·NuttX·VxWorks·QNX·INTEGRITY·SafeRTOS·µC/OS·PX5를 한 표에 모아 비교합니다. IoT·자동차·항공·산업·의료·웨어러블·드론별 추천과 결정 기준을 정리합니다.
이 글을 참조하는 글 (7)
- 연속 프로파일링 — Parca·Pixie·Pyroscope·Tetragon— Embedded Performance Engineering
- Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용— Embedded Performance Engineering
- ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구— Embedded Performance Engineering
- ftrace 활용 — function·function_graph·latency tracer— Embedded Performance Engineering
- Linux perf 고급 — Raw Event·Tracepoint·perf script— Embedded Performance Engineering
- Linux perf 기초 — stat·record·report 활용— Embedded Performance Engineering
- RTOS 디버깅 기법 — Tracealyzer·SystemView·Stack 추적— Modern Embedded Recipes