RISC-V Context Switch 분석 — ECALL·mret·CSR
#한 줄 요약
RISC-V는 ISA를 단순하게 유지하는 대신 push를 모두 SW에 맡깁니다. Cortex-M의 HW auto-push가 없기 때문에 모든 레지스터를 직접 save해야 합니다.
#RISC-V Privilege Levels
| Mode | 비트 | 용도 |
|---|---|---|
| M (Machine) | 11 | Highest — bare-metal, RTOS |
| S (Supervisor) | 01 | Linux kernel |
| U (User) | 00 | Application |
임베디드 RTOS(FreeRTOS, Zephyr)는 M-mode에서만 동작합니다. Linux는 M, S, U를 모두 사용합니다.
#RISC-V Registers
x0 (zero) — hardwired 0x1 (ra) — return addressx2 (sp) — stack pointerx3 (gp) — global pointerx4 (tp) — thread pointerx5-7 (t0-t2) — temporariesx8 (s0/fp) — saved/frame pointerx9 (s1) — savedx10-x17 (a0-a7) — arguments/returnx18-x27 (s2-s11) — savedx28-x31 (t3-t6) — temporariesGP 레지스터가 32개입니다. ARM의 16개보다 많아서 context save 크기도 그만큼 커집니다.
#Key CSRs (Control and Status Registers)
| CSR | 의미 |
|---|---|
| mstatus | Status (IE bit, MPP 모드 등) |
| mepc | Exception PC — interrupt 복귀 |
| mcause | Exception 원인 |
| mtval | Fault address (memory fault 시) |
| mtvec | Vector table base |
| mscratch | Free scratch — context save에 활용 |
| mip | Interrupt pending |
| mie | Interrupt 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입니다.
// initmscratch = ISR_stack_top;
// ISR entrycsrrw sp, mscratch, sp// 이제 sp = ISR stack, mscratch = task SPARM의 PSP/MSP HW 자동 전환과 비슷한 효과를 내지만, RISC-V에서는 모두 수동입니다.
#FreeRTOS RISC-V Port
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-MuxTopPriority = 31 - __clz(uxTopReadyPriority); // 1 cycle
// RISC-V (Zbb 없음)while (...) --uxTopPriority; // O(P)성능 차이 자체는 작습니다. RTOS scheduler는 호출 빈도가 낮기 때문입니다.
#RISC-V vs Cortex-M — 요약
| 항목 | Cortex-M | RISC-V |
|---|---|---|
| Register count | 16 GP | 32 GP |
| Auto-push on IRQ | 8 word HW | 0 (SW) |
| Stack pointers | 2 (MSP/PSP) | 1 + mscratch trick |
| Context switch | ~70 cycle | ~150-200 cycle |
| Interrupt latency | 12 cycle | 6 cycle (entry만) |
| Bit manipulation | CLZ 1 cycle | Zbb 옵션 |
RISC-V는 ISA가 더 단순한 대신 SW overhead가 커서 살짝 느립니다. 최근에는 HW extension으로 그 격차를 좁히는 흐름이 이어지고 있습니다.
#ESP32-C3 / GD32V — MCU 예
RISC-V 32-bit MCU 채택이 빠르게 확산되고 있습니다. ESP32-C3, BL602, GD32V 등이 대표적입니다. FreeRTOS port가 그대로 동작합니다.
#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를 가정합니다
csrr과 csrw 사이에는 interrupt가 끼어들 수 있습니다. atomic이 필요하면 csrrw, csrrs, csrrc를 사용해야 합니다.
#정리
- RISC-V는 32개의 GP 레지스터를 가지며, 모든 레지스터를 SW로 save합니다.
- ECALL로 trap하고 mret로 복귀하는 단순한 모델입니다.
mscratchCSR로 ISR stack을 swap하는 트릭을 사용합니다.- CLIC가 Cortex-M의 NVIC 역할을 합니다.
- ESP32-C3, BL602 같은 RISC-V MCU가 빠르게 늘고 있습니다.
다음 편은 tick과 타이머입니다. SysTick과 generic timer를 다룹니다.
#관련 항목
Practical RTOS Internals · 18 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
관련 글
RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
MPU/MMU로 user task와 kernel을 분리하는 RTOS의 syscall 구조를 정리합니다. Cortex-M의 SVC trap, RISC-V의 ECALL, FreeRTOS-MPU와 Zephyr USERSPACE의 차이, capability 검사, syscall overhead 측정까지 다룹니다.
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 통합까지 한 지도로 모읍니다.