본문으로 건너뛰기
Practical RTOS Internals · 17/53

RISC-V Context Switch 분석 — ECALL·mret·CSR

· Hawk · 5분 읽기

#한 줄 요약

RISC-V는 ISA를 단순하게 유지하는 대신 push를 모두 SW에 맡깁니다. Cortex-M의 HW auto-push가 없기 때문에 모든 레지스터를 직접 save해야 합니다.

#RISC-V Privilege Levels

Mode비트용도
M (Machine)11Highest — bare-metal, RTOS
S (Supervisor)01Linux kernel
U (User)00Application

임베디드 RTOS(FreeRTOS, Zephyr)는 M-mode에서만 동작합니다. Linux는 M, S, U를 모두 사용합니다.

#RISC-V Registers

x0 (zero) — hardwired 0
x1 (ra) — return address
x2 (sp) — stack pointer
x3 (gp) — global pointer
x4 (tp) — thread pointer
x5-7 (t0-t2) — temporaries
x8 (s0/fp) — saved/frame pointer
x9 (s1) — saved
x10-x17 (a0-a7) — arguments/return
x18-x27 (s2-s11) — saved
x28-x31 (t3-t6) — temporaries

GP 레지스터가 32개입니다. ARM의 16개보다 많아서 context save 크기도 그만큼 커집니다.

#Key CSRs (Control and Status Registers)

CSR의미
mstatusStatus (IE bit, MPP 모드 등)
mepcException PC — interrupt 복귀
mcauseException 원인
mtvalFault address (memory fault 시)
mtvecVector table base
mscratchFree scratch — context save에 활용
mipInterrupt pending
mieInterrupt enable

#Interrupt 진입 — Cortex-M보다 적은 자동화

External IRQ 발생:

1. mepc ← 현재 PC

2. mcause ← interrupt number

3. mstatus.MIE → mstatus.MPIE (save)

4. mstatus.MIE = 0 (disable)

5. PC ← mtvec

레지스터 자동 push가 전혀 없습니다. handler가 모든 레지스터를 SW로 저장해야 합니다.

#ISR Save Sequence

isr_handler:
csrrw sp, mscratch, sp # ISR stack으로 swap
# 32 regs 모두 push
addi sp, sp, -128
sw x1, 0(sp)
sw x2, 4(sp) # original sp 저장 (mscratch에 있음)
sw x3, 8(sp)
...
sw x31, 124(sp)
# CSR도 push
csrr t0, mepc
sw t0, 128(sp)
csrr t0, mstatus
sw t0, 132(sp)
# actual ISR
jal isr_body
# restore (역순)
lw t0, 132(sp)
csrw mstatus, t0
lw t0, 128(sp)
csrw mepc, t0
lw x31, 124(sp)
...
lw x1, 0(sp)
addi sp, sp, 128
csrrw sp, mscratch, sp # task stack 복귀
mret # MEPC로 jump

길고 수작업이 많습니다. Cortex-M의 12 cycle auto-push와 큰 차이를 보입니다.

#mret — Exception Return

mret
# 1. PC ← mepc
# 2. mstatus.MIE ← mstatus.MPIE
# 3. Mode ← mstatus.MPP (보통 M으로)

ARM의 bx lr과 special return value 조합과 달리, RISC-V는 단일 명령으로 끝납니다. 그만큼 단순합니다.

#ECALL — System Call / OS API

User mode에서 ecall을 실행하면 M-mode로 trap합니다.

user_task:
li a7, SYS_YIELD
ecall # trap → mtvec
...
m_mode_trap:
csrr t0, mcause
li t1, CAUSE_ECALL_M # 또는 CAUSE_ECALL_U
beq t0, t1, handle_syscall
# ...

RTOS API 호출은 보통 ecall을 거칩니다. M-mode에서만 동작하는 RTOS라면 trap 없이 함수 호출로 처리할 수도 있습니다.

#mscratch — Context Save 트릭

ISR에 진입했을 때 task의 SP를 어디에 잠시 보관해야 할까요. 답은 mscratch입니다.

// init
mscratch = ISR_stack_top;
// ISR entry
csrrw sp, mscratch, sp
// 이제 sp = ISR stack, mscratch = task SP

ARM의 PSP/MSP HW 자동 전환과 비슷한 효과를 내지만, RISC-V에서는 모두 수동입니다.

#FreeRTOS RISC-V Port

portasm.S
xPortStartFirstTask:
/* mscratch에 stack top 저장 */
la t0, xISRStackTop
lw t0, 0(t0)
csrw mscratch, t0
/* pxCurrentTCB 로드 */
la t1, pxCurrentTCB
lw sp, 0(t1) /* sp = TCB->pxTopOfStack */
/* CSR 복원 */
lw t0, 0(sp)
csrw mstatus, t0
addi sp, sp, 4
/* GP regs 복원 */
lw x1, 0(sp)
lw x3, 8(sp)
...
lw x31, 116(sp)
addi sp, sp, 124
mret /* mepc로 jump = task 시작 */

#CLIC vs PLIC

RISC-V interrupt controller는 두 가지 표준으로 나뉩니다.

PLIC (Platform-Level Interrupt Controller)CLIC (Core-Local Interrupt Controller)
표준화RISC-V 공식extension (대부분 vendor)
외부 IRQ다수 (1000+)256
Nesting없음 (SW 처리)HW preemption (priority)
표준 채택Linux SoC (HiFive, JH7110)MCU (ESP32-C3, Greenwich SiFive E)

CLIC는 Cortex-M의 NVIC와 유사하게 nested IRQ와 priority를 지원합니다.

#비트맵·CLZ

RISC-V는 CLZ(Count Leading Zeros) 명령이 기본으로 없습니다. Zbb extension에 포함되어 있지만 옵션입니다. 그래서 FreeRTOS 포트는 generic mode를 사용합니다.

// Cortex-M
uxTopPriority = 31 - __clz(uxTopReadyPriority); // 1 cycle
// RISC-V (Zbb 없음)
while (...) --uxTopPriority; // O(P)

성능 차이 자체는 작습니다. RTOS scheduler는 호출 빈도가 낮기 때문입니다.

#RISC-V vs Cortex-M — 요약

항목Cortex-MRISC-V
Register count16 GP32 GP
Auto-push on IRQ8 word HW0 (SW)
Stack pointers2 (MSP/PSP)1 + mscratch trick
Context switch~70 cycle~150-200 cycle
Interrupt latency12 cycle6 cycle (entry만)
Bit manipulationCLZ 1 cycleZbb 옵션

RISC-V는 ISA가 더 단순한 대신 SW overhead가 커서 살짝 느립니다. 최근에는 HW extension으로 그 격차를 좁히는 흐름이 이어지고 있습니다.

#ESP32-C3 / GD32V — MCU 예

RISC-V 32-bit MCU 채택이 빠르게 확산되고 있습니다. ESP32-C3, BL602, GD32V 등이 대표적입니다. FreeRTOS port가 그대로 동작합니다.

FreeRTOSConfig.h
#define configMTIME_BASE_ADDRESS ( 0x4400BFF8UL ) // ESP32-C3
#define configMTIMECMP_BASE_ADDRESS ( 0x44004000UL )

#자주 하는 실수

⚠️ Manual push를 빠뜨립니다

Cortex-M 감각 그대로 handler 첫 줄에 곧장 코드를 작성하면 레지스터가 깨집니다. RISC-V는 모든 레지스터를 직접 push해야 합니다.

⚠️ mscratch를 다른 용도로 씁니다

ISR 외의 코드에서 mscratch 값을 바꾸면 context save가 깨집니다. ISR entry와 exit에서만 사용해야 합니다.

⚠️ mepc를 복원하지 않습니다

mret 전에 mepc를 반드시 복원해야 합니다. 그러지 않으면 엉뚱한 PC로 jump하게 됩니다.

⚠️ CSR의 atomicity를 가정합니다

csrrcsrw 사이에는 interrupt가 끼어들 수 있습니다. atomic이 필요하면 csrrw, csrrs, csrrc를 사용해야 합니다.

#정리

  • RISC-V는 32개의 GP 레지스터를 가지며, 모든 레지스터를 SW로 save합니다.
  • ECALL로 trap하고 mret로 복귀하는 단순한 모델입니다.
  • mscratch CSR로 ISR stack을 swap하는 트릭을 사용합니다.
  • CLIC가 Cortex-M의 NVIC 역할을 합니다.
  • ESP32-C3, BL602 같은 RISC-V MCU가 빠르게 늘고 있습니다.

다음 편은 tick과 타이머입니다. SysTick과 generic timer를 다룹니다.

#관련 항목

Practical RTOS Internals · 18 of 53

  1. 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
  2. 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
  3. 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
  4. 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
  5. 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
  6. 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
  7. 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
  8. 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
  9. 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
  10. 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
  11. 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
  12. 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
  13. 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
  14. 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
  15. 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
  16. 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
  17. 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
  18. 18RISC-V Context Switch 분석 — ECALL·mret·CSR
  19. 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
  20. 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
  21. 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
  22. 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
  23. 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
  24. 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
  25. 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
  26. 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
  27. 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
  28. 28Priority Ceiling Protocol — Immediate vs Original 비교
  29. 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
  30. 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
  31. 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
  32. 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
  33. 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
  34. 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
  35. 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
  36. 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
  37. 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
  38. 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
  39. 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
  40. 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
  41. 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
  42. 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
  43. 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
  44. 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
  45. 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
  46. 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
  47. 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
  48. 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
  49. 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
  50. 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
  51. 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
  52. 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
  53. 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL