Zephyr v3.7.0 · 커널과 서비스 소스 분석

Zephyr Tracing: 시간축 읽기

스레드가 언제 바뀌고 ISR이 어느 구간에 들어왔는지 알고 싶다면, 로그 메시지보다 이벤트 훅의 순서를 봐야 합니다. 훅에서 CTF 파일까지의 경로와 빠진 이벤트를 의심해야 하는 지점을 나눠 읽습니다.

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

이 글은 Zephyr v3.7.0, commit 36940db938a8f4a1e919496793ed439850a221c2의 공식 문서·소스를 기준으로 새로 썼다. 그림의 이벤트 열과 시간 간격은 구조를 설명하는 가상 예시이며 실제 보드 측정치가 아니다. 주 경로는 기본 로깅과 다른 CTF 트레이싱이다.

1. 어떤 질문을 풀려고 기록하는가

기존 Thread / Scheduler 글은 스레드가 실행 후보가 되는 조건과 선점 판단을 설명한다. Logging 글은 LOG_INF() 같은 메시지의 필터·처리·백엔드 경로를 다룬다. 트레이싱은 커널의 지정된 훅에서 사건을 기록해 실행의 순서와 상대적 시점을 재구성하는 도구다. 이벤트를 봤다고 해서 스케줄러의 모든 원인이나 코드의 모든 상태가 기록됐다고 말할 수는 없다.

표 1. 같은 실행을 세 층에서 읽기
층주 입력답하기 좋은 질문여기서 알 수 없는 것
스케줄러 구현ready 상태·우선순위·선점 조건다음 실행 후보를 어떻게 고르는가특정 기기에서 실제로 언제 일어났는가
Logging애플리케이션·모듈의 LOG_* 호출어떤 메시지가 어떤 수준으로 출력됐는가모든 스위치·ISR 경계가 자동으로 찍혔는가
Tracing선택한 커널 훅·포맷 구현관측된 이벤트의 순서·시각 차이는 무엇인가미계측 코드와 유실된 이벤트의 원인

근거: 3.7.0 Tracing overview, Logging 문서, tracing_macros.h 비활성 훅.

2. 훅은 관측점이고, 포맷과 수송은 그다음 선택이다

CONFIG_TRACING=n이면 SYS_PORT_TRACING_FUNC 계열은 빈 매크로다. 켠 뒤에도 Kconfig의 기본 포맷은 TRACING_NONE이므로 실제 기록을 위해 CTF·SystemView·user-defined 등 포맷을 선택해야 한다. CONFIG_TRACING_THREAD·CONFIG_TRACING_ISR 같은 범주 옵션도 어떤 훅을 보낼지 결정한다. Zephyr v3.7.0의 CONFIG_TRACING=y는 thread-switch 계측 옵션을 선택하지만, 이것만으로 특정 파일이나 호스트 시각화가 자동 생성되는 것은 아니다.

그림 1. CTF 한 이벤트가 호스트 시간축에 도착하는 다섯 경계

↔ 좁은 화면에서 커널 훅부터 호스트 뷰어까지 오른쪽으로 이동해 읽으세요.

① 커널 관측점z_thread_mark_switched_in() 같은 경로가 trace 훅을 호출한다. 비활성 범주는 여기서 빈 동작.
② 선택된 구현tracing_ctf.h가 훅을 ctf_top.c 이벤트로 연결한다. 모든 훅이 CTF 이벤트가 되는 것은 아니다.
③ CTF 직렬화이벤트 ID·필드를 바이너리로 만든다. timestamp는 TRACING_CTF_TIMESTAMP=y일 때만 앞에 붙는다.
④ 방식·백엔드sync는 호출 문맥에서 전달, async는 링에 먼저 적재. 선택된 UART·USB·POSIX·RAM 등이 뒤를 받는다.
⑤ 호스트 해석이벤트 header의 실제 바이트 배치와 맞는 metadata가 필요하다. 제공된 파일은 timestamp 필드를 전제한다.
SystemView는 자체 형식과 RTT 경로이고, user-defined는 사용자 함수가 훅을 구현한다. 위의 CTF → generic backend 흐름을 두 포맷에 그대로 대입하지 않는다.

화살표는 데이터 경계이며 물리적인 실시간 전송 완료 보증이 아니다. async는 백엔드 출력보다 앞서 이벤트를 기록한다.

근거: Tracing Kconfig 8–105행, thread.c switch 훅, CTF 훅 연결과 빈 구현, CTF_EVENT 직렬화.

3. 이벤트는 시간축의 점이지 원인 설명문이 아니다

CTF에는 thread_switched_in/out, isr_enter, isr_exit, isr_exit_to_scheduler 같은 이벤트 ID가 있다. 다음 그림은 이 종류를 엮어 만든 가상 단일 CPU 기록 예시다. 실제 장비나 샘플 실행 결과가 아니며, 모든 ISR이 스레드를 바꾸는 것도 아니다.

그림 2. 이벤트의 점들을 CPU 실행 구간으로 읽는 연습

↔ 좁은 화면에서 t0부터 t4까지 좌우로 이동해 보세요. 시간 간격은 축척이 아닙니다.

관측
t0
t1
t2
t3
t4
CPU 구간
A 실행 시작
ISR 진입
ISR 종료·스케줄 경로
A 전환 경계
B 실행 시작
CTF 점
switched_in(A)
isr_enter
isr_exit_to_scheduler
switched_out(A)
switched_in(B)

t1→t2는 이 예시에서 ISR 경계를 찾는 쌍이고, t3→t4는 전환 관측점이다. 둘 다 실제 수행 시간의 정확한 측정치로 읽으려면 훅 비용·시계 해상도·이벤트 유실을 별도로 평가해야 한다.

표 2. 기록 한 점의 의미와 한계
이벤트말할 수 있는 것그 점만으로 단정할 수 없는 것
thread_switched_in(B)B가 실행으로 선택된 관측점B가 왜 ready였는지, 언제부터 대기했는지
isr_enter / isr_exit관측된 인터럽트 경계ISR 내부의 모든 함수·하드웨어 처리
isr_exit_to_schedulerISR에서 스케줄러로 향하는 출구반드시 다른 스레드가 실행됐다는 결론
이벤트가 없음그 파일에 해당 기록이 없음사건 자체가 발생하지 않았다는 증명

CTF timestamp는 CONFIG_TRACING_CTF_TIMESTAMP=y(기본값)일 때 이벤트 직렬화에서 32비트 cycle counter를 nanosecond로 변환한 값을 32비트 필드에 넣는다. 이것은 벽시계가 아니며 긴 기록에서는 폭과 wrap 가능성을 확인해야 한다. async 방식이라도 timestamp는 나중에 백엔드가 쓰는 시각이 아니라 이벤트 생성 경로에서 붙는다.

timestamp를 끈 빌드는 별도 해석이 필요: CONFIG_TRACING_CTF_TIMESTAMP=n이면 고정 CTF_EVENT 구현은 timestamp 필드 자체를 쓰지 않는다. 반면 제공된 tsdl/metadata의 event_header는 항상 32비트 timestamp 다음에 ID가 온다고 선언한다. 따라서 기본 metadata를 그대로 복사해 이 캡처를 해석하면 header 배치가 맞지 않는다. timestamp가 없는 header에 맞는 metadata를 준비하거나 기본 y 설정으로 다시 빌드해야 한다.

근거: CTF 이벤트·timestamp 정의, CTF metadata event header, 3.7.0 시각화 도구.

4. 포맷, 전송 방식, 백엔드를 한 옵션으로 뭉치지 않는다

Kconfig는 트레이싱 포맷을 하나 고르고, CTF core에는 sync/async 전송 방식과 generic backend 선택이 더해진다. SystemView는 Zephyr의 별도 구현과 RTT를 사용한다. user-defined는 스레드 스위치·ISR·idle 등에 대응하는 함수를 앱이 구현하는 선택이다. 각 포맷이 모든 훅과 백엔드를 동일하게 지원한다고 가정하지 않는다.

표 3. 같은 TRACING 아래의 서로 다른 선택 층
선택역할3.7.0 조건·주의
TRACING=y + TRACING_CTF=y훅과 CTF 포맷·core포맷 기본값은 NONE. CTF를 명시하고 최종 .config 확인
TRACING_CTF_TIMESTAMPCTF 이벤트 header 앞의 시각 필드기본 y. n이면 제공된 metadata의 timestamp 있는 header와 불일치
TRACING_THREAD / TRACING_ISR스레드·ISR 범주 훅둘 다 기본 y지만 포맷 구현이 실제 이벤트를 매핑했는지도 확인
TRACING_SYNC / TRACING_ASYNC현재 문맥 출력 / 링 적재 뒤 tracing thread 출력async가 기본. sync는 backend 지연을 현재 문맥에서 감당해야 함
TRACING_BACKEND_POSIXnative_sim 계열 파일 출력ARCH_POSIX 및 TRACING_SYNC에 의존
TRACING_BACKEND_USBUSB 출력USB_DEVICE_STACK 및 TRACING_ASYNC에 의존
TRACING_BACKEND_RAM디버거로 읽을 RAM 출력링과 별도인 고정 용량의 최종 저장 공간
SEGGER_SYSTEMVIEW / TRACING_USERRTT 기반 SystemView / 사용자 함수CTF generic backend 경로와 포맷을 구분

근거: 포맷·방식 Kconfig, 백엔드 Kconfig, SystemView 및 user-defined 문서.

5. 비동기 링과 RAM 백엔드 저장 공간은 같은 버퍼가 아니다

CTF core의 sync 경로는 이벤트를 만든 문맥에서 backend 호출까지 진행한다. async 경로는 형식화한 패킷을 TRACING_BUFFER_SIZE 링에 먼저 넣고, 별도 tracing_thread가 꺼내 backend로 보낸다. 링에 패킷을 모두 넣을 공간이 없으면 해당 적재는 실패하고 내부 drop 카운터가 증가한다. 따라서 async는 호출자 출력 지연을 줄이는 선택이지 무손실 기록 보장이 아니다.

그림 3. 이벤트가 남는 위치와 막히는 위치

↔ 좁은 화면에서 sync·async 두 경로와 공통 최종 저장소를 비교하세요.

SYNC · 호출 문맥CTF_EVENT: 조건부 timestamp·필드 직렬화↓즉시 backend output 호출↓backend 지연이 현재 문맥의 비용
ASYNC · 두 문맥CTF_EVENT: 조건부 timestamp·필드 직렬화↓TRACING_BUFFER_SIZE 링에 적재; 부족하면 이 패킷 드롭↓tracing_thread가 나중에 backend output 호출
선택적으로 RAM backend를 쓸 경우: backend의 ram_tracing[RAM_TRACING_BUFFER_SIZE]에 다시 쓴다. 용량을 넘는 출력 묶음부터 출력이 중단되고 기존 내용을 덮어 순환 저장하지 않는다. async의 앞단 링과 다른 손실 지점이다. async thread가 링에서 꺼낸 묶음은 CTF 이벤트 한 개의 경계와 같지 않을 수 있다.

동기·비동기 경로는 CTF core 구현에 관한 설명이다. RAM 백엔드는 두 경로 중 하나와 조합될 수 있다. POSIX 파일 backend는 Kconfig상 sync만 허용한다.

표 4. 버퍼를 늘릴 때 어느 누락이 줄어드는가
위치설정·소스용량을 넘으면의미
async 앞단 링TRACING_BUFFER_SIZE · tracing_buffer.c그 패킷 적재 실패, 내부 drop 카운터 증가전송 thread가 늦으면 생산 이벤트를 잃음
RAM backend 종단RAM_TRACING_BUFFER_SIZE · ram_tracing[]새 출력 거절; 이미 저장한 바이트 유지링을 키워도 종단이 가득 차면 캡처가 늘지 않음
POSIX 파일 backendnative_sim 실행 파일의 출력 파일쓰기 실패 경고 경로를 확인RAM 용량 설정과 별개

근거: async 적재 실패, tracing thread, RAM backend, POSIX 파일 쓰기.

6. 고정 3.7.0 문서의 native_sim 경로로 CTF를 읽기

하드웨어 없이 파일 경로를 익힐 때 공식 3.7.0 예시는 native_sim과 prj_native_ctf.conf를 쓴다. 이 설정은 TRACING=y, TRACING_CTF=y, TRACING_SYNC=y, TRACING_BACKEND_POSIX=y를 명시한다. timestamp는 끄지 않으므로 Kconfig 기본 y가 적용되고, 제공된 metadata의 timestamp 있는 header와 맞는다. 아래는 문서의 명령을 재구성한 절차이며 이 글에서 빌드를 실행했다는 뜻은 아니다.

west build -b native_sim samples/subsys/tracing -- -DCONF_FILE=prj_native_ctf.conf
mkdir data
cp $ZEPHYR_BASE/subsys/tracing/ctf/tsdl/metadata data/
./build/zephyr/zephyr.exe -trace-file=data/channel0_0

이후 data/metadata와 data/channel0_0를 같은 디렉터리로 TraceCompass 또는 babeltrace에 제공한다. 이 명령의 metadata 복사는 timestamp=y인 예시에 한한다. timestamp=n 캡처에는 timestamp 필드가 없는 event header와 일치하도록 metadata를 바꿔야 한다. -trace-file 인수는 POSIX backend의 파일 이름 옵션이다. 다른 보드·백엔드에는 이 파일 생성 방식이 그대로 적용되지 않는다.

표 5. 열 수 있는 CTF 캡처의 두 조각
조각역할빠지면
channel0_0POSIX backend가 쓴 이 실행의 바이너리 이벤트 스트림사건의 실제 순서·데이터가 없음
metadata이 버전 CTF 이벤트 ID·필드·header 정의; 제공 파일은 timestamp 필드 가정없거나 실제 header와 다르면 필드를 올바르게 해석하기 어려움
최종 .config포맷·timestamp·범주·방식·backend 확인header 불일치나 특정 이벤트 누락을 판별하기 어려움

근거: 3.7.0 Using Tracing, native CTF 샘플 설정, POSIX -trace-file 옵션.

7. “이벤트가 없다”를 해석하기 전의 점검 순서

  1. 빌드의 Zephyr commit, 보드, 최종 .config를 기록한다. TRACING뿐 아니라 포맷, TRACING_THREAD·TRACING_ISR, 방식과 backend를 따로 확인한다.
  2. CTF 포맷에서 해당 훅이 실제 이벤트로 매핑되는지 확인한다. v3.7.0의 일부 sys_port_trace_*는 CTF 헤더에서 빈 매크로다.
  3. async 링이 이벤트 생산 속도를 따라가는지, RAM backend를 골랐다면 최종 저장 공간이 찼는지 분리해 본다. 백엔드 파일·전송 오류도 별도로 확인한다.
  4. CTF 데이터의 timestamp y/n에 맞춰 metadata의 event header도 확인한다. timestamp를 쓴 경우 해상도·wrap과 계측 비용을 고려한다. 단일 이벤트의 부재를 코드 경로 부재의 증거로 쓰지 않는다.
시간 측정의 경계: 훅은 실행을 관측하면서 동시에 시간을 소비한다. 링 포화, 최종 저장소 포화, 형식별 미구현 훅이 있으면 시간축에 구멍이 생긴다. 한 번의 캡처만으로 모든 스케줄 지연의 원인을 특정하지 말고 같은 설정에서 재현 조건과 누락 여부를 함께 남긴다.

8. 고정 버전 근거와 검증 범위

이 글은 Zephyr 3.7.0의 CTF 경로와 native_sim 파일 예시를 중심으로 소스와 문서를 대조했다. 특정 보드의 ISR 지속 시간, 손실률, 전송 대역폭을 측정하지 않았다. SystemView·Percepio·user-defined의 상세 프로토콜이나 모든 아키텍처 훅 구현을 전수 설명하지 않는다.

연결해서 읽기