RISC-V 명령어셋 레퍼런스

RISC-V 아키텍처의 명령어를 GAS 문법 기준으로 종합 정리합니다. 모듈형 ISA 설계 철학과 RV64I 기본 명령어셋, M(곱셈)/A(원자적)/F(단정도)/D(배정도)/C(압축)/V(벡터) 표준 확장, 32개 범용 레지스터와 CSR 체계, R/I/S/B/U/J 6가지 인코딩 타입, 데이터 전송·산술·논리·분기·스택·시스템·원자적·벡터 명령어 카테고리 표, 인코딩 다이어그램, 커널 핵심 명령어(ECALL, SRET, LR/SC, SFENCE.VMA) 심화까지 Linux 커널 개발에 필요한 RISC-V ISA 전체를 다룹니다.

전제 조건: 어셈블리 종합을 먼저 읽으세요. RISC-V 명령어 레퍼런스는 RISC Load/Store 모델, 레지스터-레지스터 연산, 특권 모드에 대한 기본 이해가 필요합니다.
일상 비유: RISC-V는 오픈소스 레고 블록 시스템과 같습니다. 기본 블록 세트(RV64I)에 필요한 확장 팩(M, A, F, D, V)을 자유롭게 추가할 수 있으며, 설계도(ISA 스펙) 자체가 공개되어 누구나 구현할 수 있습니다.

핵심 요약

  • 모듈형 ISA — RV64I 기본 + M/A/F/D/C/V 확장. RV64G = RV64IMAFD (범용 세트).
  • 32개 범용 레지스터 — x0=zero(항상 0), x1=ra, x2=sp, x10-x17=a0-a7(인자).
  • 고정 32-bit 명령어 — C 확장 사용 시 16-bit 압축 명령어도 혼용 가능.
  • CSR 체계 — mstatus/sstatus, mtvec/stvec, mepc/sepc 등 특권 레벨별 제어/상태 레지스터.
  • 특권 모드 — Machine(M), Supervisor(S), User(U). Linux는 S-mode에서 실행.

단계별 이해

  1. 레지스터와 ABI 이름 파악
    x0-x31의 ABI 이름(zero, ra, sp, a0-a7, s0-s11, t0-t6)과 용도를 먼저 익힙니다.
  2. 기본 ISA(RV64I) 학습
    Load/Store, 산술, 논리, 분기, 점프 명령어가 기본입니다.
  3. 확장 모듈 이해
    M(곱셈/나눗셈), A(원자적), F/D(부동소수점), C(압축), V(벡터)를 순서대로 학습합니다.
  4. CSR과 특권 명령어
    ECALL, CSRRW, SFENCE.VMA 등 커널 관련 명령어를 학습합니다.

아키텍처 개요

RISC-V는 UC Berkeley에서 2010년 시작한 오픈소스 RISC ISA입니다. 모듈형 설계로 기본 정수 ISA(RV32I/RV64I)에 표준 확장을 선택적으로 추가합니다.

특성RISC-V
설계 철학모듈형 오픈소스 RISC
명령어 길이고정 32-bit (C 확장 시 16/32-bit 혼합)
엔디언리틀 엔디언
범용 레지스터32개 (x0=hardwired zero)
기본 ISARV32I / RV64I / RV128I
표준 확장M(곱셈), A(원자적), F(단정도FP), D(배정도FP), C(압축), V(벡터), Zicsr, Zifencei
RV64G= RV64IMAFD (General-purpose 세트)
특권 모드Machine(M), Supervisor(S), User(U)
주소 공간Sv39(39-bit), Sv48(48-bit), Sv57(57-bit)

모듈형 ISA 확장 구조

RISC-V의 핵심 설계 철학은 모듈형(modular) ISA입니다. RV64I 기본 정수 명령어셋을 중심으로 필요한 확장을 선택적으로 추가하여 SoC 설계자가 목적에 맞는 최적의 프로세서를 구성할 수 있습니다. 표준 확장 조합인 RV64G(= RV64IMAFD)는 범용 운영체제 실행에 필요한 최소 세트이며, Linux 커널은 이를 기본으로 요구합니다.

RISC-V 모듈형 ISA 확장 구조 RV64I 기본 정수 명령어셋 (47개) Load/Store, ALU, Branch, Jump M 확장 곱셈/나눗셈 (MUL, DIV) A 확장 원자적 (LR/SC, AMO) F 확장 단정도 FP (32-bit) D 확장 배정도 FP (64-bit) C 확장 압축 16-bit 명령어 V 확장 가변 길이 벡터 (VLEN-bit) Zicsr + Zifencei CSR 접근 + I-fence RV64G = RV64I + M + A + F + D (Linux 커널 기본 요구 사항)
특권 모드 계층 구조: RISC-V는 3단계 특권 모드를 정의합니다.
  • Machine (M-mode) — 최고 권한. 하드웨어에 직접 접근. 모든 CSR 사용 가능. 펌웨어/SBI가 실행됨.
  • Supervisor (S-mode) — OS 커널 실행 모드. 가상 메모리, 인터럽트 관리. Linux 커널이 이 모드에서 동작.
  • User (U-mode) — 최소 권한. 사용자 프로세스 실행. 특권 명령어 사용 시 트랩 발생.
모드 전환은 ECALL(상위 모드로 트랩), MRET(M→이전 모드), SRET(S→이전 모드)로 이루어집니다. 트랩 위임(delegation) 레지스터 medeleg/mideleg를 통해 M-mode가 특정 예외/인터럽트를 S-mode에 위임할 수 있습니다.
RISC-V 특권 모드 전환 (Trap / Return) Machine (M) SBI / Firmware / 최고 권한 Supervisor (S) Linux Kernel / OS User (U) 사용자 프로세스 ECALL (시스템 콜) ECALL (SBI 호출) SRET (트랩 복귀) MRET (트랩 복귀) medeleg 트랩 위임 PRV=3 PRV=1 PRV=0

레지스터 셋

범용 레지스터 (x0-x31)

레지스터ABI 이름용도보존
x0zero하드와이어 제로 (항상 0)
x1ra복귀 주소 (Return Address)Caller
x2sp스택 포인터Callee
x3gp전역 포인터
x4tp스레드 포인터
x5-x7t0-t2임시 레지스터Caller
x8s0/fpSaved/프레임 포인터Callee
x9s1Saved 레지스터Callee
x10-x17a0-a7함수 인자 / 반환값 (a0-a1)Caller
x18-x27s2-s11Saved 레지스터Callee
x28-x31t3-t6임시 레지스터Caller

레지스터 파일 시각적 맵

32개 범용 레지스터를 용도별로 그룹화하고 Caller/Callee-saved 여부를 색상으로 구분한 시각적 맵입니다. 함수 호출 시 어떤 레지스터를 저장해야 하는지 파악하는 데 유용합니다.

RISC-V 범용 레지스터 맵 (x0-x31) — 용도별 그룹 & Caller/Callee 구분 특수 용도 (고정) Caller-saved (호출자 보존) Callee-saved (피호출자 보존) 시스템 예약 x0 (zero) 항상 0 x1 (ra) 복귀 주소 x2 (sp) 스택 포인터 x3 (gp) 전역 포인터 x4 (tp) 스레드 포인터 임시 (Caller-saved): x5 (t0) 임시 x6 (t1) 임시 x7 (t2) 임시 x28 (t3) 임시 x29 (t4) 임시 x30 (t5) 임시 x31 (t6) 임시 보존 (Callee-saved): x8 (s0/fp) 프레임 ptr x9 (s1) saved x18-x27 (s2-s11) — 10개 Saved 레지스터 함수 호출 전후로 값이 보존됨 (피호출자가 저장/복원 책임) 인자/반환 (Caller-saved): x10 (a0) 인자1/반환 x11 (a1) 인자2/반환 x12 (a2) 인자3 x13 (a3) 인자4 x14 (a4) 인자5 x15 (a5) 인자6 x16 (a6) 인자7 x17 (a7) 인자8 총 32개: 고정(x0) 1 + 시스템(gp,tp) 2 + Caller-saved(ra,t0-t6,a0-a7) 16 + Callee-saved(sp,s0-s11) 13 Linux 커널에서의 특수 용도 tp (x4): 커널 모드에서 current task_struct 가리킴 (per-CPU 데이터 접근) gp (x3): 전역 포인터 — 커널 이미지의 .sdata/.sbss 접근 최적화 sscratch: 트랩 진입 시 tp와 교환하여 커널/유저 모드 전환에 사용

CSR (Control and Status Registers) — 주요

CSR설명모드
mstatus / sstatus머신/슈퍼바이저 상태 (인터럽트 활성화, 이전 모드 등)M / S
mtvec / stvec트랩 벡터 베이스 주소M / S
mepc / sepc예외 PC (복귀 주소)M / S
mcause / scause트랩 원인 (인터럽트/예외 코드)M / S
mtval / stval트랩 값 (폴트 주소 등)M / S
mie / sie인터럽트 활성화M / S
mip / sip인터럽트 펜딩M / S
satpS-mode 주소 변환 (페이지 테이블 루트 + 모드)S
mscratch / sscratch트랩 핸들러 스크래치 레지스터M / S
mhartid하드웨어 스레드 IDM
cycle / time / instret사이클/시간/명령어 카운터 (읽기 전용)U

CSR 주소 공간 레이아웃

RISC-V CSR 주소는 12-bit (0x000-0xFFF)로 인코딩됩니다. 상위 비트들이 접근 권한과 특권 레벨을 결정하므로, CSR 번호만으로도 접근 가능 여부를 하드웨어가 즉시 판단할 수 있습니다.

비트 [11:10]비트 [9:8]의미주소 범위예시
0000 (U)User 읽기/쓰기0x000-0x0FFustatus, fflags, frm
0001 (S)Supervisor 읽기/쓰기0x100-0x1FFsstatus, sie, stvec, satp
0011 (M)Machine 읽기/쓰기0x300-0x3FFmstatus, mie, mtvec
0100 (U)User 읽기/쓰기0x400-0x4FF(예약)
0101 (S)Supervisor 읽기/쓰기0x500-0x5FF(예약, Hypervisor)
0111 (M)Machine 읽기/쓰기0x700-0x7FFmhpmcounterN (debug)
10**커스텀/디버그 읽기/쓰기0x800-0xBFFdscratch, dpc (debug)
1100 (U)User 읽기 전용0xC00-0xCFFcycle, time, instret
1101 (S)Supervisor 읽기 전용0xD00-0xDFF(예약)
1111 (M)Machine 읽기 전용0xF00-0xFFFmvendorid, mhartid
CSR 12-bit 주소 인코딩 bit [11:10] bit [9:8] bit [7:0] 읽기/쓰기 권한 11 = 읽기 전용 (RO) 최소 특권 레벨 00=U, 01=S, 11=M 레지스터 식별자 256개 슬롯 (같은 권한/레벨 내) 예시: sstatus = 0x100 = 0b 00_01_00000000 ^^=RW ^^=S-mode ^^^^^^^^=레지스터 #0 예시: mhartid = 0xF14 = 0b 11_11_00010100 ^^=RO ^^=M-mode ^^^^^^^^=레지스터 #20

부동소수점 / 벡터 레지스터

확장레지스터크기
F/D 확장f0-f3132/64-bit 부동소수점
F/D 제어fcsr (frm + fflags)라운딩 모드 + 예외 플래그
V 확장v0-v31VLEN-bit (구현 의존, 128+)
V 제어vl, vtype, vstart, vxsat, vxrm벡터 길이/타입/시작/포화/라운딩

주소 지정 모드

RISC-V는 매우 단순한 주소 모드를 사용합니다: 베이스 레지스터 + 12-bit 부호 확장 즉시값 오프셋만 지원합니다.

패턴문법설명예제
베이스+오프셋offset(rs1)[rs1 + sign_ext(offset)]lw a0, 8(sp)
32-bit 절대lui + addi상위 20 + 하위 12 조합lui a0, %hi(sym); addi a0, a0, %lo(sym)
PC 상대auipc + addiPC + 상위 20 + 하위 12auipc a0, %pcrel_hi(sym); addi a0, a0, %pcrel_lo(.)
의사 명령어 lili rd, imm어셈블러가 lui+addi로 확장li a0, 0x12345678
의사 명령어 lala rd, symbolauipc+addi로 확장la a0, my_var
설계 철학 — 왜 주소 모드가 이렇게 단순한가? RISC-V는 의도적으로 base+offset 단일 주소 모드만 제공합니다. 이는 하드웨어 설계를 단순화하여 파이프라인 스톨을 줄이고, 면적과 전력을 절약하기 위함입니다. 복잡한 주소 계산(인덱스 시프트, pre/post-increment 등)은 컴파일러가 별도 명령어로 분해하여 처리하므로, 결과적으로 하드웨어 복잡도는 낮추면서 컴파일러 최적화의 자유도를 높이는 설계입니다.
주소 모드 비교: RISC-V vs x86_64 vs ARM64
주소 모드RISC-Vx86_64ARM64
기본 (레지스터+오프셋)lw a0, 8(a1)mov eax, [rbx+8]ldr w0, [x1, #8]
레지스터+레지스터지원 안 함mov eax, [rbx+rcx]ldr w0, [x1, x2]
스케일드 인덱스지원 안 함mov eax, [rbx+rcx*4]ldr w0, [x1, x2, lsl #2]
Pre-increment지원 안 함지원 안 함ldr w0, [x1, #8]!
Post-increment지원 안 함지원 안 함ldr w0, [x1], #8
PC-상대auipc+addi (2개 명령어)mov eax, [rip+off]adr/adrp + ldr
절대 주소lui+addi (2개 명령어)mov eax, [addr]지원 안 함 (ADRP+ADD)
RISC-V는 주소 모드 수가 가장 적지만, 이로 인해 디코더 복잡도가 낮아 고주파 동작에 유리합니다.

데이터 전송 명령어

명령어문법설명확장
LB / LBUlb a0, 0(a1)바이트 로드 (부호/제로 확장)I
LH / LHUlh a0, 0(a1)하프워드 로드 (부호/제로 확장)I
LW / LWUlw a0, 0(a1)워드 로드 (RV64: LWU=제로확장)I
LDld a0, 0(a1)더블워드 로드RV64I
SB / SH / SW / SDsw a0, 0(a1)바이트/하프/워드/더블 저장I
LUIlui a0, 0x12345상위 20-bit 즉시값 로드I
AUIPCauipc a0, 0x12345PC + 상위 20-bitI
FLW / FLDflw fa0, 0(a1)부동소수점 로드F / D
FSW / FSDfsw fa0, 0(a1)부동소수점 저장F / D
C.LW / C.LDc.lw a0, 0(a1)압축 로드 (16-bit)C
C.SW / C.SDc.sw a0, 0(a1)압축 저장 (16-bit)C
FENCE 명령어 변형과 메모리 순서:
  • FENCE iorw, iorw — 전체 메모리 배리어. predecessor 집합(i=입력, o=출력, r=읽기, w=쓰기)의 모든 연산이 successor 집합의 연산보다 먼저 관측됨을 보장합니다. 예: fence rw, rw는 이전 모든 읽기/쓰기가 이후 읽기/쓰기보다 먼저 완료됨을 보장.
  • FENCE.I — 명령어 페치 배리어 (Zifencei 확장). 이전 저장(store)이 이후 명령어 페치에 반영됨을 보장합니다. 자기 수정 코드(JIT, 모듈 로딩) 시 필수. Linux 커널의 flush_icache_range()에서 사용.
  • FENCE.TSO — TSO(Total Store Order) 배리어. FENCE rw, rw보다 약한 순서로, store→load 재배치만 방지합니다. x86 메모리 모델을 에뮬레이션할 때 유용합니다.

LUI + ADDI로 32-bit 상수 로딩

RISC-V 명령어의 즉시값은 최대 12-bit이므로, 더 큰 상수를 레지스터에 로드하려면 LUI(상위 20-bit)와 ADDI(하위 12-bit)를 조합해야 합니다. 여기서 주의할 점은 ADDI가 부호 확장을 수행하므로, 하위 12-bit의 MSB가 1이면 LUI에 1을 더해 보정해야 한다는 것입니다.

/* 예: a0 = 0x12345678 로드 */
/* 상위 20-bit: 0x12345, 하위 12-bit: 0x678 */
/* 0x678의 MSB=0이므로 보정 불필요 */
    lui     a0, 0x12345           /* a0 = 0x12345000 */
    addi    a0, a0, 0x678         /* a0 = 0x12345678 */

/* 예: a0 = 0x12345800 로드 */
/* 하위 12-bit: 0x800, MSB=1 → ADDI가 -2048로 부호 확장 */
/* 보정: LUI에 1을 더함 (0x12345 + 1 = 0x12346) */
    lui     a0, 0x12346           /* a0 = 0x12346000 */
    addi    a0, a0, -0x800        /* a0 = 0x12346000 - 0x800 = 0x12345800 */

/* 어셈블러 의사 명령어 li가 이를 자동 처리: */
    li      a0, 0x12345800        /* 어셈블러가 위 보정을 자동 수행 */

산술 명령어

명령어문법설명확장
ADDadd a0, a1, a2덧셈I
ADDIaddi a0, a1, 42즉시값 덧셈I
SUBsub a0, a1, a2뺄셈I
ADDW / ADDIW / SUBWaddw a0, a1, a232-bit 연산 + 부호 확장 (RV64)RV64I
MULmul a0, a1, a2곱셈 (하위 XLEN bit)M
MULH / MULHU / MULHSUmulh a0, a1, a2곱셈 상위 (부호/부호없는/혼합)M
DIV / DIVUdiv a0, a1, a2나눗셈 (부호/부호없는)M
REM / REMUrem a0, a1, a2나머지 (부호/부호없는)M
MULW / DIVW / REMWmulw a0, a1, a232-bit 곱셈/나눗셈/나머지 (RV64M)RV64M
FADD.S/Dfadd.s fa0, fa1, fa2부동소수점 덧셈F/D
FMUL.S/Dfmul.d fa0, fa1, fa2부동소수점 곱셈F/D
FDIV.S/Dfdiv.s fa0, fa1, fa2부동소수점 나눗셈F/D
FSQRT.S/Dfsqrt.d fa0, fa1부동소수점 제곱근F/D
FMADD.S/Dfmadd.s fa0, fa1, fa2, fa3FMA: fa1*fa2 + fa3F/D

논리/시프트/비트 조작 명령어

명령어문법설명확장
AND / ANDIand a0, a1, a2비트 ANDI
OR / ORIor a0, a1, a2비트 ORI
XOR / XORIxor a0, a1, a2비트 XORI
SLL / SLLIsll a0, a1, a2논리 좌측 시프트I
SRL / SRLIsrl a0, a1, a2논리 우측 시프트I
SRA / SRAIsra a0, a1, a2산술 우측 시프트I
SLT / SLTIslt a0, a1, a2부호 있는 < 비교 (1/0)I
SLTU / SLTIUsltu a0, a1, a2부호 없는 < 비교I
SLLW / SRLW / SRAWsllw a0, a1, a232-bit 시프트 (RV64)RV64I
CLZ / CTZ / CPOPclz a0, a1선행/후행 제로, 팝카운트Zbb
ANDN / ORN / XNORandn a0, a1, a2AND-NOT / OR-NOT / XNORZbb
MIN / MAX / MINU / MAXUmin a0, a1, a2최솟값/최댓값Zbb
ROL / RORrol a0, a1, a2순환 시프트Zbb
REV8rev8 a0, a1바이트 순서 반전Zbb
BCLR / BEXT / BINV / BSETbext a0, a1, a2단일 비트 조작Zbs

비교/분기 명령어

명령어문법설명
BEQbeq a0, a1, label같으면 분기 (±4KB)
BNEbne a0, a1, label다르면 분기
BLTblt a0, a1, label부호 있는 <
BGEbge a0, a1, label부호 있는 >=
BLTUbltu a0, a1, label부호 없는 <
BGEUbgeu a0, a1, label부호 없는 >=
JALjal ra, label점프 + 링크 (±1MB)
JALRjalr ra, 0(a0)간접 점프 + 링크
의사 명령어: j label = jal zero, label, call func = auipc ra, ...; jalr ra, ...(ra), ret = jalr zero, 0(ra), beqz a0, label = beq a0, zero, label, mv a0, a1 = addi a0, a1, 0, nop = addi zero, zero, 0

주요 의사 명령어(pseudo-instruction) 확장 표

RISC-V 어셈블러는 프로그래머 편의를 위해 다양한 의사 명령어를 제공합니다. 아래 표는 자주 사용되는 의사 명령어와 그것이 실제로 확장되는 기본 명령어를 보여줍니다.

의사 명령어확장되는 기본 명령어설명
nopaddi zero, zero, 0아무 동작 없음
li rd, immlui rd, upper; addi rd, rd, lower즉시값 로드 (32/64-bit)
la rd, symbolauipc rd, delta_hi; addi rd, rd, delta_lo주소 로드 (PC 상대)
mv rd, rsaddi rd, rs, 0레지스터 복사
not rd, rsxori rd, rs, -1비트 반전
neg rd, rssub rd, zero, rs부호 반전 (2의 보수)
negw rd, rssubw rd, zero, rs32-bit 부호 반전 (RV64)
sext.w rd, rsaddiw rd, rs, 032-bit 부호 확장 (RV64)
seqz rd, rssltiu rd, rs, 1rs == 0이면 1
snez rd, rssltu rd, zero, rsrs != 0이면 1
sltz rd, rsslt rd, rs, zerors < 0이면 1
sgtz rd, rsslt rd, zero, rsrs > 0이면 1
beqz rs, labelbeq rs, zero, label0이면 분기
bnez rs, labelbne rs, zero, label0이 아니면 분기
blez rs, labelbge zero, rs, labelrs <= 0이면 분기
bgez rs, labelbge rs, zero, labelrs >= 0이면 분기
bltz rs, labelblt rs, zero, labelrs < 0이면 분기
bgtz rs, labelblt zero, rs, labelrs > 0이면 분기
bgt rs, rt, labelblt rt, rs, labelrs > rt이면 분기 (피연산자 교환)
ble rs, rt, labelbge rt, rs, labelrs <= rt이면 분기 (피연산자 교환)
j labeljal zero, label무조건 점프 (링크 없음)
jr rsjalr zero, 0(rs)간접 점프 (링크 없음)
retjalr zero, 0(ra)함수 복귀
call funcauipc ra, off_hi; jalr ra, off_lo(ra)원거리 함수 호출
tail funcauipc t1, off_hi; jalr zero, off_lo(t1)꼬리 호출 (tail call)
왜 RISC-V에는 조건 플래그(condition flags)가 없는가? x86의 EFLAGS나 ARM의 NZCV처럼 별도의 조건 플래그 레지스터를 두지 않고, 비교-분기(compare-and-branch) 방식을 채택한 이유:
  • 파이프라인 의존성 제거: 플래그 레지스터는 모든 산술 명령어에 대한 암묵적 WAW(Write-After-Write) 의존성을 만듭니다. 비교-분기 방식은 분기 명령어가 직접 두 레지스터를 비교하므로, 비순차 실행(OoO) 파이프라인에서 불필요한 직렬화를 방지합니다.
  • 마이크로아키텍처 자유도: 플래그 레지스터 이름 변경(renaming) 없이도 효율적인 비순차 실행이 가능합니다.
  • 코드 간결성: blt a0, a1, label 한 명령어로 비교+분기를 수행하므로, CMP + Bcc 두 명령어 조합보다 명령어 수가 줄어들 수 있습니다.

스택/함수 호출 명령어

RISC-V에는 PUSH/POP이 없습니다. addi spsd/ld로 수동 관리합니다.

/* 함수 프롤로그/에필로그 */
my_func:
    addi    sp, sp, -32           /* 스택 프레임 할당 */
    sd      ra, 24(sp)             /* 복귀 주소 저장 */
    sd      s0, 16(sp)             /* 프레임 포인터 저장 */
    addi    s0, sp, 32             /* 프레임 포인터 설정 */
    /* ... 함수 본문 ... */
    ld      s0, 16(sp)             /* 프레임 포인터 복원 */
    ld      ra, 24(sp)             /* 복귀 주소 복원 */
    addi    sp, sp, 32             /* 스택 프레임 해제 */
    ret                             /* jalr zero, 0(ra) */
RISC-V 호출 규약:
  • 인자: a0-a7 (최대 8개 정수/포인터)
  • 반환값: a0 (+ a1)
  • Callee-saved: s0-s11, sp
  • Caller-saved: t0-t6, a0-a7, ra
  • SP 16-byte 정렬

시스템/특권 명령어

명령어문법설명
ECALLecall환경 콜 (U→S→M 트랩)
EBREAKebreak디버그 브레이크포인트
MRETmretM-mode 트랩 복귀
SRETsretS-mode 트랩 복귀
WFIwfi인터럽트 대기
CSRRWcsrrw a0, sstatus, a1CSR 읽기+쓰기 (atomic swap)
CSRRScsrrs a0, sstatus, a1CSR 읽기+비트 세트
CSRRCcsrrc a0, sstatus, a1CSR 읽기+비트 클리어
CSRRWI / CSRRSI / CSRRCIcsrrsi a0, sstatus, 2즉시값 버전 CSR 조작
FENCEfence rw, rw메모리 순서 펜스
FENCE.Ifence.i명령어 펜스 (I-cache 동기화)
FENCE.TSOfence.tsoTSO 메모리 순서 펜스
SFENCE.VMAsfence.vma a0, a1TLB 무효화 (주소, ASID)
CSR 의사 명령어: csrr rd, csr = csrrs rd, csr, zero (읽기), csrw csr, rs = csrrw zero, csr, rs (쓰기), csrs csr, rs = csrrs zero, csr, rs (비트 세트), csrc csr, rs = csrrc zero, csr, rs (비트 클리어)

트랩 위임(Trap Delegation) 메커니즘

기본적으로 모든 트랩은 M-mode로 전달되지만, medeleg(예외 위임)와 mideleg(인터럽트 위임) CSR을 설정하면 특정 트랩을 S-mode에서 직접 처리할 수 있습니다. Linux 부팅 시 SBI(OpenSBI 등)가 이 위임을 설정합니다.

트랩 위임 메커니즘: medeleg / mideleg User (U-mode) ECALL / 페이지 폴트 / 예외 발생 Supervisor (S-mode) 위임된 트랩 처리: stvec → 핸들러 scause, sepc, stval 사용 SRET으로 복귀 Machine (M-mode) 위임되지 않은 트랩: mtvec → 핸들러 MRET으로 복귀 위임됨 medeleg[N]=1 위임 안 됨 medeleg[N]=0 위임 CSR (M-mode가 설정) medeleg: 예외 위임 비트마스크 mideleg: 인터럽트 위임 비트마스크 예: 페이지 폴트(13,15), 시스콜(8) 위임 일반적으로 Linux 부팅 시: 페이지 폴트, 유저 ECALL, 타이머/외부 S-인터럽트를 S-mode에 위임. 머신 타이머, 비정렬 접근은 M-mode에서 처리.

SFENCE.VMA 변형 (TLB 무효화)

SFENCE.VMA는 인자에 따라 무효화 범위가 달라집니다. 불필요하게 넓은 범위를 플러시하면 성능 저하가 발생하므로, 커널은 가능한 한 좁은 범위의 무효화를 사용합니다.

형식rs1 (주소)rs2 (ASID)효과커널 사용 사례
sfence.vma zero, zero무시무시모든 TLB 엔트리 전체 플러시satp 변경 후, 전체 페이지 테이블 교체
sfence.vma addr, zero가상 주소무시해당 주소의 모든 ASID TLB 플러시단일 페이지 매핑 변경 (커널 주소)
sfence.vma zero, asid무시ASID해당 ASID의 모든 TLB 플러시프로세스 전체 주소 공간 무효화
sfence.vma addr, asid가상 주소ASID해당 주소+ASID의 TLB만 플러시단일 프로세스의 단일 페이지 (가장 세밀)

Sv39 페이지 테이블 워크 구조

Linux RISC-V는 주로 Sv39 (39-bit 가상 주소, 3단계 페이지 테이블)를 사용합니다. satp CSR이 루트 페이지 테이블의 물리 주소와 ASID를 저장합니다.

Sv39 가상 주소 변환 (3단계 페이지 테이블 워크) 39-bit 가상 주소 분해: VPN[2] [38:30] VPN[1] [29:21] VPN[0] [20:12] Offset [11:0] 9 + 9 + 9 + 12 = 39 bits satp CSR MODE=8(Sv39) | ASID | PPN L2 페이지 테이블 512 PTE (VPN[2] 인덱스) L1 페이지 테이블 512 PTE (VPN[1] 인덱스) L0 페이지 테이블 512 PTE (VPN[0] 인덱스) 물리 페이지 PPN + Offset PPN PTE.PPN PTE.PPN PTE.PPN PTE 포맷 (64-bit): Reserved [63:54] PPN[2:0] [53:10] = 44 bits RSW [9:8] D A G U X W R V [7:0] V=Valid, R=Read, W=Write, X=eXecute, U=User, G=Global, A=Accessed, D=Dirty, RSW=커널 소프트웨어용 4KB 페이지(L0), 2MB 메가페이지(L1), 1GB 기가페이지(L2) — 리프 PTE의 R/W/X 중 하나라도 1이면 리프

원자적/동기화 명령어 (A 확장)

명령어문법설명
LR.W / LR.Dlr.d a0, (a1)Load-Reserved (독점 로드)
SC.W / SC.Dsc.d a2, a0, (a1)Store-Conditional (a2=0이면 성공)
AMOSWAP.W/Damoswap.d a0, a1, (a2)원자적 교환
AMOADD.W/Damoadd.d a0, a1, (a2)원자적 덧셈
AMOAND.W/Damoand.d a0, a1, (a2)원자적 AND
AMOOR.W/Damoor.d a0, a1, (a2)원자적 OR
AMOXOR.W/Damoxor.d a0, a1, (a2)원자적 XOR
AMOMAX.W/Damomax.d a0, a1, (a2)원자적 MAX (부호)
AMOMIN.W/Damomin.d a0, a1, (a2)원자적 MIN (부호)
AMOMAXU / AMOMINUamomaxu.d a0, a1, (a2)원자적 MAX/MIN (부호 없음)
순서 지정자: .aq(acquire), .rl(release), .aqrl(순차 일관성). 예: lr.d.aq, sc.d.rl, amoswap.d.aqrl

LR/SC CAS 루프 패턴

/* compare_and_swap(addr=a0, expected=a1, desired=a2) → old in a0 */
cas_loop:
    lr.d.aq  a3, (a0)             /* Load-Reserved + Acquire */
    bne      a3, a1, 1f           /* expected와 다르면 실패 */
    sc.d.rl  a4, a2, (a0)          /* Store-Conditional + Release */
    bnez     a4, cas_loop          /* SC 실패 시 재시도 */
1:
    mv       a0, a3               /* 이전 값 반환 */
    ret
AMO vs LR/SC — 언제 어떤 것을 사용할까?
특성AMO (Atomic Memory Operation)LR/SC (Load-Reserved / Store-Conditional)
복잡도단일 명령어로 완결루프 필요 (최소 4개 명령어)
연산 유형swap, add, and, or, xor, min, max만 가능임의의 read-modify-write 가능
ABA 문제해당 없음 (단일 연산)면역 (예약이 깨지면 SC 실패)
진행 보장항상 완료 (하드웨어 보장)SC가 반복 실패할 수 있음 (단, 스펙은 결국 성공 보장)
캐시 프로토콜캐시 라인 수준 원자성예약 세트 추적 필요
대표 용례atomic_add, 스핀락CAS, cmpxchg, 복잡한 원자적 갱신
일반적으로 단순한 원자적 갱신에는 AMO가 더 효율적이고, CAS나 복잡한 조건부 갱신에는 LR/SC가 필수입니다.

AMOSWAP 기반 스핀락 구현

/* 스핀락: lock=a0 (0=해제, 1=잠김) */
spin_lock:
    li       t0, 1
1:
    amoswap.w.aq t1, t0, (a0)      /* t1 = old, *a0 = 1 (acquire) */
    bnez     t1, 1b               /* 이미 잠겨있으면 재시도 */
    ret                             /* 획득 성공, acquire 의미론 보장 */

spin_unlock:
    amoswap.w.rl zero, zero, (a0)  /* *a0 = 0, release 의미론 */
    ret

/* 최적화된 버전: test-and-set with backoff */
spin_lock_optimized:
    li       t0, 1
1:  lw       t1, (a0)              /* 일반 로드로 먼저 확인 (캐시 친화적) */
    bnez     t1, 1b               /* 잠겨있으면 바쁜 대기 */
    amoswap.w.aq t1, t0, (a0)      /* 해제된 것 같으면 시도 */
    bnez     t1, 1b               /* 실패 시 다시 대기 */
    ret
FENCE 순서 의미론 상세: FENCE의 predecessor/successor 집합은 4가지 접근 타입을 조합합니다:
  • i (Input) — 장치 입력 (MMIO 읽기)
  • o (Output) — 장치 출력 (MMIO 쓰기)
  • r (Read) — 메모리 읽기
  • w (Write) — 메모리 쓰기
Linux 커널 매핑: mb() = fence iorw, iorw, rmb() = fence ir, ir, wmb() = fence ow, ow, smp_mb() = fence rw, rw, smp_rmb() = fence r, r, smp_wmb() = fence w, w. .aq/.rl 비트는 AMO/LR/SC에만 적용되며, 각각 acquire/release 의미론을 부여합니다.

벡터 명령어 (V 확장)

RISC-V V 확장은 가변 길이 벡터(VLEN: 구현 의존, 128-bit 이상)를 지원합니다. vsetvli로 벡터 길이와 요소 타입을 동적으로 설정합니다.

명령어설명
vsetvli rd, rs1, vtypei벡터 길이/타입 설정 (SEW, LMUL)
vsetivli rd, uimm, vtypei즉시값으로 벡터 길이 설정
VLE8/16/32/64.V vd, (rs1)벡터 로드 (8/16/32/64-bit 요소)
VSE8/16/32/64.V vs3, (rs1)벡터 저장
VLSE32.V vd, (rs1), rs2스트라이드 벡터 로드
VLUXEI32.V vd, (rs1), vs2인덱스 벡터 로드 (gather)
VADD.VV vd, vs2, vs1벡터 덧셈
VSUB.VV vd, vs2, vs1벡터 뺄셈
VMUL.VV vd, vs2, vs1벡터 곱셈
VAND.VV / VOR.VV / VXOR.VV벡터 논리 연산
VSLL.VV / VSRL.VV / VSRA.VV벡터 시프트
VMSEQ.VV / VMSNE.VV / VMSLT.VV벡터 비교 → 마스크
VREDSUM.VS vd, vs2, vs1벡터 리덕션 합계
VREDMAX.VS / VREDMIN.VS벡터 리덕션 최대/최소
VMAND.MM / VMOR.MM / VMXOR.MM마스크 논리 연산
VSLIDEUP.VI / VSLIDEDOWN.VI벡터 슬라이드
VRGATHER.VV vd, vs2, vs1벡터 인덱스 기반 재배치
VCOMPRESS.VM vd, vs2, vs1마스크 기반 압축

LMUL (Length MULtiplier) 개념

LMUL은 하나의 벡터 연산이 사용하는 레지스터 그룹의 크기를 결정합니다. LMUL=1이면 단일 레지스터, LMUL=2이면 연속 2개 레지스터를 하나의 벡터로 묶어 더 긴 벡터를 처리합니다. 반대로 LMUL=1/2, 1/4, 1/8 분수값도 가능하여 좁은 요소를 효율적으로 처리합니다.

LMUL (Length MULtiplier) — 레지스터 그룹핑 가정: VLEN = 256-bit (구현 예) LMUL=1: v0 (256-bit = VLEN) 벡터 레지스터 32개 사용 가능, 벡터 길이 = VLEN/SEW LMUL=2: v0 v1 512-bit 논리 벡터 v0-v1이 그룹 → 16개 벡터 그룹 사용 가능 (v0,v2,v4,...,v30) LMUL=4: v0 v1 v2 v3 1024-bit v0-v3이 그룹 → 8개 벡터 그룹 사용 가능 (v0,v4,v8,...,v28) LMUL=8: v0-v7 (8개 레지스터 = 2048-bit) → 4개 벡터 그룹 가능 핵심 공식 VLMAX = (VLEN / SEW) * LMUL 예: VLEN=256, SEW=32, LMUL=4 → VLMAX = (256/32)*4 = 32개 요소 사용 가능 벡터 그룹 수 = 32 / LMUL LMUL이 커질수록 벡터 길이 증가, 레지스터 수 감소 (트레이드오프)

vsetvli와 VLEN/SEW/LMUL 관계

vsetvli 명령어는 벡터 연산 전에 반드시 호출하여 SEW(Selected Element Width)와 LMUL을 설정합니다. 하드웨어는 요청된 벡터 길이(AVL)와 VLMAX를 비교하여 실제 처리할 요소 수(VL)를 결정합니다.

/* vsetvli rd, rs1, vtypei */
/* rd: 실제 설정된 벡터 길이 (VL) */
/* rs1: 요청하는 벡터 길이 (AVL). rs1=zero이면 VL 변경 없이 vtype만 변경 */
/* vtypei: SEW, LMUL, ta(tail agnostic), ma(mask agnostic) 인코딩 */

/* 예: SEW=8, LMUL=1, tail-agnostic, mask-agnostic */
    vsetvli  t0, a2, e8, m1, ta, ma  /* t0 = min(a2, VLMAX) */

/* SEW 옵션: e8(8-bit), e16(16-bit), e32(32-bit), e64(64-bit) */
/* LMUL 옵션: mf8(1/8), mf4(1/4), mf2(1/2), m1(1), m2(2), m4(4), m8(8) */
/* 꼬리 정책: ta(tail agnostic) / tu(tail undisturbed) */
/* 마스크 정책: ma(mask agnostic) / mu(mask undisturbed) */

벡터화된 memcpy 예제

RISC-V V 확장의 강력한 점은 vsetvli 스트립마이닝 루프입니다. 하드웨어가 한 번에 처리할 수 있는 최대 요소 수를 자동으로 결정하므로, VLEN에 독립적인 코드를 작성할 수 있습니다.

/* void *memcpy_v(void *dst, const void *src, size_t n) */
/* a0=dst, a1=src, a2=n (바이트 수) */
memcpy_v:
    mv       a3, a0                /* 반환용 dst 주소 보존 */
.Lloop:
    vsetvli  t0, a2, e8, m8, ta, ma /* t0 = 이번에 복사할 바이트 수 */
                                      /* SEW=8(바이트), LMUL=8(최대 처리량) */
    vle8.v   v0, (a1)              /* src에서 t0 바이트 로드 */
    vse8.v   v0, (a0)              /* dst에 t0 바이트 저장 */
    add      a1, a1, t0            /* src += t0 */
    add      a0, a0, t0            /* dst += t0 */
    sub      a2, a2, t0            /* n -= t0 */
    bnez     a2, .Lloop            /* 남은 바이트가 있으면 반복 */
    mv       a0, a3                /* dst 주소 반환 */
    ret
스트립마이닝(strip-mining) 패턴의 장점:memcpy_v 코드는 VLEN=128이든 VLEN=1024이든 수정 없이 동작합니다. vsetvli가 하드웨어 VLEN에 맞게 VL을 자동 조정하므로, ARM SVE처럼 벡터 길이 불가지(Vector Length Agnostic) 프로그래밍이 가능합니다. LMUL=8을 사용하면 한 번에 최대 8 * VLEN / 8 바이트를 처리합니다 (VLEN=256이면 256바이트).

커널 핵심 명령어 심화

ECALL — SBI 호출 패턴

/* SBI 호출: a7=EID, a6=FID, a0-a5=인자 */
/* 반환: a0=error, a1=value */
    li      a7, 0x10              /* SBI_EXT_BASE */
    li      a6, 0                  /* SBI_BASE_GET_SPEC_VERSION */
    ecall                            /* S-mode → M-mode 트랩 */
    /* a0=error code, a1=spec version */

트랩 핸들러 진입

/* stvec → _handle_exception */
_handle_exception:
    csrrw   tp, sscratch, tp       /* tp ↔ sscratch 교환 */
    /* tp는 이제 커널 task_struct, sscratch는 유저 tp */
    sd      sp, TASK_TI_USER_SP(tp)    /* 유저 sp 저장 */
    ld      sp, TASK_TI_KERNEL_SP(tp)  /* 커널 스택으로 전환 */
    addi    sp, sp, -PT_SIZE       /* pt_regs 공간 할당 */
    /* 레지스터 저장... */
    sd      ra, PT_RA(sp)
    sd      gp, PT_GP(sp)
    /* ... a0-a7, s0-s11, t0-t6 저장 ... */
    csrr    a0, scause             /* 트랩 원인 */
    csrr    a1, sepc               /* 예외 PC */
    csrr    a2, stval              /* 트랩 값 */

SRET — 트랩 복귀

    /* 레지스터 복원 후 */
    csrw    sepc, a0               /* 복귀 PC 설정 */
    csrw    sstatus, a1            /* 상태 복원 */
    csrrw   tp, sscratch, tp       /* tp ↔ sscratch 복원 */
    sret                            /* PC←sepc, 모드←sstatus.SPP */

SFENCE.VMA — TLB 무효화

/* arch/riscv/include/asm/tlbflush.h */
static inline void local_flush_tlb_page(unsigned long addr)
{
    asm volatile("sfence.vma %0" :: "r"(addr) : "memory");
}
static inline void local_flush_tlb_all(void)
{
    asm volatile("sfence.vma" ::: "memory");
}

SBI (Supervisor Binary Interface) 확장 표

SBI는 S-mode(커널)가 M-mode(펌웨어)의 서비스를 호출하는 표준 인터페이스입니다. RISC-V에서 ARM의 PSCI나 x86의 BIOS 서비스에 해당하며, OpenSBI가 대표적 구현체입니다. 호출 규약은 a7=EID(Extension ID), a6=FID(Function ID), a0-a5=인자, 반환은 a0=error, a1=value입니다.

EID확장 이름주요 함수 (FID)설명
0x10SBI_EXT_BASEget_spec_version(0), get_impl_id(1), get_impl_version(2), probe_extension(3)SBI 기본 정보 조회
0x54494D45SBI_EXT_TIME (TIME)set_timer(0)타이머 인터럽트 설정. 커널 tick 소스
0x735049SBI_EXT_IPIsend_ipi(0)프로세서 간 인터럽트 전송
0x52464E43SBI_EXT_RFENCEremote_fence_i(0), remote_sfence_vma(1), remote_sfence_vma_asid(2)원격 TLB/Icache 무효화
0x48534DSBI_EXT_HSMhart_start(0), hart_stop(1), hart_get_status(2), hart_suspend(3)Hart 상태 관리 (SMP 부팅)
0x53525354SBI_EXT_SRSTsystem_reset(0)시스템 리셋/종료
0x504D55SBI_EXT_PMUnum_counters(0), counter_get_info(1), counter_start(2), counter_stop(3)성능 모니터링 카운터
0x4442434ESBI_EXT_DBCNwrite(0), read(1), write_byte(2)디버그 콘솔 (earlycon)
0x535553SBI_EXT_SUSPsuspend(0)시스템 서스펜드

완전한 컨텍스트 스위치 예제

Linux 커널의 __switch_to()는 프로세스 간 컨텍스트를 전환합니다. RISC-V에서는 callee-saved 레지스터(s0-s11, sp, ra)만 저장/복원하면 됩니다 (caller-saved는 C 호출 규약에 의해 호출자가 이미 처리).

/* arch/riscv/kernel/entry.S — __switch_to(prev, next) */
/* a0 = prev->thread (struct thread_struct *) */
/* a1 = next->thread (struct thread_struct *) */
__switch_to:
    /* === prev 컨텍스트 저장 === */
    sd      ra,  THREAD_RA(a0)     /* 복귀 주소 */
    sd      sp,  THREAD_SP(a0)     /* 스택 포인터 */
    sd      s0,  THREAD_S0(a0)     /* callee-saved s0 (fp) */
    sd      s1,  THREAD_S1(a0)
    sd      s2,  THREAD_S2(a0)
    sd      s3,  THREAD_S3(a0)
    sd      s4,  THREAD_S4(a0)
    sd      s5,  THREAD_S5(a0)
    sd      s6,  THREAD_S6(a0)
    sd      s7,  THREAD_S7(a0)
    sd      s8,  THREAD_S8(a0)
    sd      s9,  THREAD_S9(a0)
    sd      s10, THREAD_S10(a0)
    sd      s11, THREAD_S11(a0)

    /* === next 컨텍스트 복원 === */
    ld      ra,  THREAD_RA(a1)     /* next의 복귀 주소 */
    ld      sp,  THREAD_SP(a1)     /* next의 스택 */
    ld      s0,  THREAD_S0(a1)
    ld      s1,  THREAD_S1(a1)
    ld      s2,  THREAD_S2(a1)
    ld      s3,  THREAD_S3(a1)
    ld      s4,  THREAD_S4(a1)
    ld      s5,  THREAD_S5(a1)
    ld      s6,  THREAD_S6(a1)
    ld      s7,  THREAD_S7(a1)
    ld      s8,  THREAD_S8(a1)
    ld      s9,  THREAD_S9(a1)
    ld      s10, THREAD_S10(a1)
    ld      s11, THREAD_S11(a1)

    /* tp(스레드 포인터)를 next의 task_struct로 전환 */
    mv      tp, a2                 /* a2 = next task_struct */

    ret                             /* ra가 next의 복귀 주소이므로 next 프로세스로 점프 */
컨텍스트 스위치에서 저장하지 않는 레지스터:
  • t0-t6, a0-a7 — Caller-saved이므로 C 함수 호출 규약에 의해 switch_to() 호출 전에 이미 스택에 저장됨
  • gp — 커널 이미지 전체에서 동일한 값 (프로세스 간 공유)
  • zero — 항상 0
  • FP/벡터 레지스터 — 지연 저장(lazy save): 커널에서 FP/벡터를 사용하기 직전에만 저장

SFENCE.VMA 커널 사용 패턴

/* 1. 단일 페이지 매핑 변경 후 */
static inline void local_flush_tlb_page(unsigned long addr)
{
    /* 해당 가상 주소의 TLB 엔트리만 무효화 */
    asm volatile("sfence.vma %0" :: "r"(addr) : "memory");
}

/* 2. 프로세스 전체 주소 공간 무효화 (특정 ASID) */
static inline void local_flush_tlb_all_asid(unsigned long asid)
{
    asm volatile("sfence.vma zero, %0" :: "r"(asid) : "memory");
}

/* 3. 전체 TLB 플러시 (satp 변경 후) */
static inline void local_flush_tlb_all(void)
{
    asm volatile("sfence.vma" ::: "memory");
}

/* 4. 원격 TLB 무효화 (SBI를 통해 다른 hart에 IPI 전송) */
void flush_tlb_range(struct vm_area_struct *vma,
                     unsigned long start, unsigned long end)
{
    /* SBI RFENCE 확장 사용 */
    /* 로컬 hart: sfence.vma */
    /* 원격 hart: sbi_remote_sfence_vma() IPI */
}

명령어 인코딩

RISC-V는 6가지 기본 인코딩 타입을 사용합니다. 모든 타입에서 opcode는 [6:0], rd는 [11:7]에 위치하여 디코딩을 단순화합니다.

RISC-V 32-bit 기본 인코딩 타입 (6종) 31 25 24 20 19 15 14 12 11 7 6 0 R: funct7 rs2 rs1 f3 rd opcode I: imm[11:0] rs1 f3 rd opcode S: imm[11:5] rs2 rs1 f3 imm[4:0] opcode B: imm[12|10:5] | rs2 | rs1 | f3 | imm[4:1|11] opcode U: imm[31:12] rd opcode J: imm[20|10:1|11|19:12] rd opcode opcode [6:0] rd [11:7] rs1/rs2 funct3 immediate funct7 R: ADD/SUB/AND/OR/XOR/SLL/SRL/SRA | I: ADDI/LW/JALR | S: SW/SD | B: BEQ/BNE/BLT/BGE | U: LUI/AUIPC | J: JAL

인코딩 예제: add a0, a1, a2

add a0, a1, a2는 R-type 인코딩입니다. 각 필드가 어떻게 매핑되는지 구체적으로 보겠습니다.

필드비트 범위이진수설명
funct7[31:25]0x000000000ADD (SUB는 0100000)
rs2[24:20]12 (a2=x12)01100소스 레지스터 2
rs1[19:15]11 (a1=x11)01011소스 레지스터 1
funct3[14:12]0x0000ADD 연산
rd[11:7]10 (a0=x10)01010목적 레지스터
opcode[6:0]0x330110011OP (레지스터-레지스터 연산)
/* add a0, a1, a2 의 32-bit 인코딩 */
/*  funct7  | rs2   | rs1   | f3  | rd    | opcode  */
/*  0000000 | 01100 | 01011 | 000 | 01010 | 0110011 */
/*  = 0x00C58533                                    */

/* 검증: objdump 출력 */
/*   0: 00c58533    add    a0, a1, a2              */

/* 비교: sub a0, a1, a2 → funct7만 다름 (0100000)  */
/*  0100000 | 01100 | 01011 | 000 | 01010 | 0110011 */
/*  = 0x40C58533                                    */
C 확장 (압축 명령어) 16-bit 인코딩: C 확장은 자주 사용되는 명령어를 16-bit로 압축하여 코드 크기를 ~25-30% 줄입니다.
  • 식별: 32-bit 명령어는 하위 2비트가 11, 16-bit 명령어는 00, 01, 10 중 하나
  • 제약: 압축 명령어는 레지스터 x8-x15(s0-s1, a0-a5)만 사용 가능 (3-bit 레지스터 필드)
  • 포맷: CR(레지스터), CI(즉시값), CSS(스택 저장), CIW(와이드 즉시값), CL(로드), CS(저장), CB(분기), CJ(점프) 8가지
압축 명령어확장되는 32-bit 명령어크기 절약
c.add a0, a1add a0, a0, a12바이트 (50%)
c.li a0, 5addi a0, zero, 52바이트
c.lw a0, 4(a1)lw a0, 4(a1)2바이트
c.sw a0, 4(a1)sw a0, 4(a1)2바이트
c.beqz a0, labelbeq a0, zero, label2바이트
c.j labeljal zero, label2바이트
c.mv a0, a1add a0, zero, a12바이트
c.nopaddi zero, zero, 02바이트
Linux 커널은 CONFIG_RISCV_ISA_C 옵션으로 C 확장 사용 여부를 결정합니다. 대부분의 RISC-V 리눅스 배포판은 RV64GC를 기본으로 사용합니다.

SBI (Supervisor Binary Interface)

SBI는 S-mode(리눅스 커널)가 M-mode(펌웨어, OpenSBI)에 서비스를 요청하는 표준 인터페이스입니다. x86의 BIOS/UEFI Runtime Services, ARM의 PSCI(Power State Coordination Interface)에 대응하며, RISC-V 커널이 하드웨어 추상화 없이 이식성을 확보하는 핵심 메커니즘입니다.

RISC-V SBI (Supervisor Binary Interface) 아키텍처 U-mode — 유저 프로세스 ecall → S-mode 트랩 S-mode — 리눅스 커널 ecall → M-mode 트랩 (SBI 호출) M-mode — OpenSBI / BBL SBI 서비스 처리 → sret 복귀 SBI 호출 규약 a7 = Extension ID (EID) a6 = Function ID (FID) a0-a5 = 인자 (최대 6개) ecall 실행 → M-mode 트랩 반환: a0 = error, a1 = value struct sbiret { long error; long value; } error: 0=성공, -1=실패, -2=미지원, -3=DENIED SBI 확장 (Extension) 목록 EID 이름 용도 커널 함수 0x10 BASE SBI 버전, 확장 탐지 sbi_get_spec_version() 0x54494D45 TIME 타이머 설정 (stimecmp) sbi_set_timer() 0x735049 IPI Inter-Processor Interrupt sbi_send_ipi() 0x52464E43 RFENCE 원격 SFENCE.VMA (TLB 플러시) sbi_remote_sfence_vma() 0x48534D HSM Hart 시작/중지/상태 (SMP 부팅) sbi_hart_start() 0x53525354 SRST 시스템 리셋/셧다운 sbi_system_reset() 0x504D55 PMU 성능 카운터 관리 perf 서브시스템 연동 0x44424E43 DBCN 디버그 콘솔 입출력 earlycon sbi 콘솔 0x535553 SUSP 시스템 서스펜드 (절전) suspend_ops
/* arch/riscv/kernel/sbi.c — SBI 호출 구현 */
struct sbiret sbi_ecall(int ext, int fid,
                       unsigned long arg0, unsigned long arg1,
                       unsigned long arg2, unsigned long arg3,
                       unsigned long arg4, unsigned long arg5)
{
    struct sbiret ret;
    register unsigned long a0 asm("a0") = arg0;
    register unsigned long a1 asm("a1") = arg1;
    register unsigned long a2 asm("a2") = arg2;
    register unsigned long a3 asm("a3") = arg3;
    register unsigned long a4 asm("a4") = arg4;
    register unsigned long a5 asm("a5") = arg5;
    register unsigned long a6 asm("a6") = fid;
    register unsigned long a7 asm("a7") = ext;

    asm volatile ("ecall"
        : "+r" (a0), "+r" (a1)
        : "r" (a2), "r" (a3), "r" (a4), "r" (a5),
          "r" (a6), "r" (a7)
        : "memory");

    ret.error = a0;  /* SBI_SUCCESS=0, SBI_ERR_FAILED=-1, ... */
    ret.value = a1;
    return ret;
}

/* SMP 부팅: HSM 확장으로 다른 hart 시작 */
/* sbi_hart_start(hartid, start_addr, opaque) */
/* → M-mode에서 해당 hart를 start_addr로 점프시킴 */
/* → secondary_start_sbi() → 커널 진입 */

가상 메모리 심화 (Sv39/Sv48/Sv57)

RISC-V의 가상 메모리는 satp CSR로 제어되며, Sv39(39비트 VA, 3레벨), Sv48(48비트 VA, 4레벨), Sv57(57비트 VA, 5레벨) 모드를 지원합니다. 리눅스 커널은 Sv48을 기본으로 사용하며, Sv57은 선택적으로 활성화됩니다.

RISC-V 가상 메모리 (Sv39/Sv48/Sv57) satp CSR (Supervisor Address Translation and Protection) MODE [63:60] ASID [59:44] PPN [43:0] — 루트 페이지 테이블 물리 페이지 번호 (PPN × 4KB = 물리 주소) MODE: 0=Bare(물리), 8=Sv39, 9=Sv48, 10=Sv57 Sv39 (3레벨, 512GB) VA[38:30] → VPN[2] (L2) VA[29:21] → VPN[1] (L1) VA[20:12] → VPN[0] (L0) VA[11:0] → offset VA 공간: ±256GB Megapage: 2MB (L1 leaf) Gigapage: 1GB (L2 leaf) 초기 RISC-V Linux 기본 Sv48 (4레벨, 256TB) VA[47:39] → VPN[3] (L3) VA[38:30] → VPN[2] (L2) VA[29:21] → VPN[1] (L1) VA[20:12] → VPN[0] (L0) VA[11:0] → offset VA 공간: ±128TB + Terapage: 512GB (L3 leaf) 현재 Linux 기본 모드 Sv57 (5레벨, 128PB) VA[56:48] → VPN[4] (L4) VA[47:39] → VPN[3] (L3) ... VA[11:0] → offset VA 공간: ±64PB + Petapage: 256TB x86 LA57에 대응 CONFIG_64BIT 필수 RISC-V PTE (Page Table Entry) 비트 필드 N [63] PBMT[62:61] Rsvd[60:54] PPN [53:10] — 44비트 물리 페이지 번호 RSW[9:8] D A G U X W R V [7:0] V(Valid) R(Read) W(Write) X(eXecute) U(User) G(Global) A(Accessed) D(Dirty) R=W=X=0 → 비리프(다음 레벨 포인터) | R|X ≠ 0 → 리프(최종 매핑) N(NAPOT): 자연 정렬 파워-of-2 대형 페이지 (Svnapot 확장) PBMT: Page-Based Memory Types (00=PMA, 01=NC, 10=IO) — Svpbmt 확장 RSW: 소프트웨어 예약 비트 (커널이 더티/young 추적에 사용)
비교 항목Sv39Sv48Sv57x86-64 4-Level
VA 비트39485748
페이지 테이블 레벨3454
VA 공간512GB256TB128PB256TB
PA 비트56565652
페이지 크기4KB4KB4KB4KB
대형 페이지2MB, 1GB2MB, 1GB, 512GB+256TB2MB, 1GB
PTE 크기8B8B8B8B
ASID 비트16비트 (최대 65536 프로세스)12비트 (PCID)
TLB 플러시sfence.vmainvlpg
ℹ️

sfence.vma 변형: sfence.vma zero, zero(전체 TLB 플러시), sfence.vma addr, zero(VA 기반), sfence.vma zero, asid(ASID 기반), sfence.vma addr, asid(VA+ASID). 커널은 flush_tlb_range()에서 VA+ASID 기반 세밀한 플러시를 사용하여 불필요한 전체 플러시를 피합니다. Svadu 확장(v1.0 비준)은 하드웨어 A/D 비트 자동 갱신을 지원하여 소프트웨어 page fault 핸들링 오버헤드를 제거합니다.

다음 학습: