특권 모드와 CSR
응용 프로그램은 운영체제를, 운영체제는 펌웨어를 함부로 건드려서는 안 된다. 누구나 실수하고, 누군가는 악의를 품기 때문이다. RISC-V는 하트가 지금 얼마만큼의 권한으로 명령을 실행하는지를 특권 모드로 나누고, 하트의 동작을 조절하는 스위치와 계기판을 CSR(제어·상태 레지스터)이라는 4096칸짜리 별도 주소 공간에 모아 두었다. 이 장에서는 M·S·U 세 모드가 왜 필요한지, CSR 주소 자체에 접근 권한이 어떻게 새겨져 있는지, 그리고 misa·mstatus 같은 핵심 레지스터를 필드 단위로 해부한다.
- 특권 수준이 왜 필요한지, M·S·U 모드의 역할과 허용되는 조합을 설명한다.
- 하트는 한 번에 한 모드로만 실행되며, 모드는 트랩과 xRET으로만 바뀐다는 점을 이해한다.
- 12비트 CSR 주소의 위쪽 4비트만 보고 최소 특권과 읽기 전용 여부를 판정한다.
- misa로 하트의 ISA를 읽고, mstatus·sstatus의 각 필드가 하는 일을 안다.
- WPRI·WLRL·WARL 규칙의 차이와, 이를 이용해 하드웨어 기능을 알아내는 방법을 안다.
- 카운터 접근 제어(mcounteren·scounteren·mcountinhibit), Smstateen, Smcsrind의 목적을 안다.
왜 특권 수준이 필요한가
한 대의 컴퓨터 위에서 웹 브라우저, 운영체제 커널, 부팅 펌웨어가 함께 돈다. 브라우저의 버그가 커널 메모리를 덮어쓰거나, 악성 앱이 디스크 컨트롤러를 직접 조작할 수 있다면 시스템 전체가 위험해진다. 그래서 하드웨어는 “지금 실행 중인 코드가 무엇을 할 수 있는가”를 구분해야 한다. 이 구분이 특권 수준(privilege level)이다. 규격은 특권 수준을 소프트웨어 스택의 구성 요소 사이를 보호하는 수단으로 정의하고, 현재 모드에서 허락되지 않은 일을 하려 하면 예외가 발생한다고 정한다.
RISC-V 하트는 언제나 어느 한 특권 수준에서 실행되며, 그 수준은 2비트로 표현된다. 지금 정의된 수준은 셋이다.
| 레벨 | 인코딩 | 이름 | 약자 | 주로 실행하는 것 |
|---|---|---|---|---|
| 0 | 00 | User/Application | U | 응용 프로그램 |
| 1 | 01 | Supervisor | S | 운영체제 커널 |
| 2 | 10 | Reserved | — | 예약 (모드로는 쓰지 않음) |
| 3 | 11 | Machine | M | 펌웨어, SBI 구현, 보안 모니터 |
sret·mret으로 내려오는 길이다. 모드를 바꾸는 방법은 이 두 가지뿐이다. 왼쪽 칩의 숫자는 각 모드의 2비트 인코딩이다.규격은 M 모드를 가장 높은 권한을 가진, 유일한 필수 모드로 정한다. M 모드 코드는 기계의 구현 세부에 낮은 수준으로 접근할 수 있으므로 본질적으로 신뢰받는 코드다. 그 위에 얼마나 많은 모드를 둘지는 구현이 고른다. 모드가 적을수록 하드웨어는 싸지지만 격리는 약해진다.
| 모드 수 | 지원 모드 | 규격이 드는 용도 | 예 |
|---|---|---|---|
| 1 | M | 단순한 임베디드 시스템 | 센서 노드의 펌웨어 하나만 도는 MCU |
| 2 | M, U | 보안이 필요한 임베디드 시스템 | RTOS 커널(M) + 격리된 태스크(U) |
| 3 | M, S, U | 유닉스 계열 운영체제를 돌리는 시스템 | OpenSBI(M) + 리눅스(S) + 앱(U) |
모드가 M 하나뿐이면 잘못된 응용 코드로부터 아무것도 지킬 수 없다. 그래도 PMP(물리 메모리 보호)의 잠금(L) 비트를 쓰면 M 모드 자신에게도 접근 제한을 걸 수 있어, 부트 ROM 같은 영역을 제한적으로나마 보호할 수 있다(20장).
“수준”과 “모드”는 다르다
규격은 코드가 어느 수준을 위해 작성되었는가와 어느 모드에서 실제로 실행되는가를 구분한다. 보통은 같지만, 고전적인 가상 머신 모니터는 S 수준용으로 작성된 게스트 운영체제를 U 모드에서 돌린다. 이때 게스트가 S 수준 명령이나 CSR을 쓰면 모두 트랩이 걸려 더 높은 모드의 감시자가 대신 흉내 낸다. 같은 바이너리가 두 환경에서 모두 돌 수 있는 것은 운영체제가 하드웨어가 아니라 SBI라는 약속에 맞춰 작성되기 때문이다.
구현은 칩 밖 디버깅과 제조 시험을 위해 D 모드를 둘 수 있다. M보다도 접근 권한이 많은 추가 특권 모드로 볼 수 있으며, CSR 주소 몇 개(0x7B0–0x7BF)를 D 모드 전용으로 예약한다. 자세한 동작은 별도의 디버그 규격이 정한다(22장).
하트는 한 번에 한 모드: 실행 환경과 모드 전환
특권 규격은 소프트웨어 스택을 “위의 소프트웨어가 아래와 대화하는 인터페이스”와 “그 인터페이스를 구현하는 실행 환경”의 짝으로 설명한다. 응용 프로그램은 ABI(Application Binary Interface)로 응용 실행 환경(AEE)과 대화하고, 운영체제는 SBI(Supervisor Binary Interface)로 감독자 실행 환경(SEE)과 대화한다. 하이퍼바이저가 있으면 그 아래에 HBI와 HEE가 하나 더 생긴다.
이 스택을 하트 하나의 시간 축으로 바꿔 보면 특권 아키텍처의 기본 규칙이 드러난다. 하트는 어느 순간에도 정확히 하나의 특권 모드에서 실행된다. 그리고 모드는 다음 두 가지 방법으로만 바뀐다.
- 트랩 —
ecall, 예외, 인터럽트가 일어나면 하트는 처리기로 뛰면서 같거나 더 높은 특권 모드로 들어간다. 특권이 올라가는 트랩을 수직 트랩(vertical trap), 같은 수준에 머무는 트랩을 수평 트랩(horizontal trap)이라 한다. 규격은 “트랩은 결코 더 특권 높은 모드에서 더 낮은 모드로 옮겨 가지 않는다”고 못 박는다. - xRET — 처리기가 끝나면
mret이나sret이 트랩 때 저장해 둔 이전 모드(mstatus.MPP·SPP)로 돌아간다. 특권을 낮추는 유일한 정규 경로다.
트랩 때 무엇이 어떻게 저장되고 복원되는지는 18장에서 한 단계씩 따라간다. 여기서는 모드가 시간에 따라 오르내리는 모습만 눈에 익혀 두자.
하트 한 개의 특권 모드 타임라인
4096칸의 CSR 주소 공간
CSR(Control and Status Register)은 정수 레지스터 x0–x31과 별개인 저장 공간이다. CSR 명령의 12비트 csr 필드(명령어 비트 31–20)로 번지를 고르므로 최대 4096개까지 둘 수 있다. 특권 아키텍처 전체가 이 공간 위에 지어진다. 트랩 처리기 주소(mtvec), 인터럽트 허용 비트(mie), 페이지 테이블 위치(satp)가 모두 CSR이다.
RISC-V가 영리한 점은 주소 자체에 접근 규칙을 새겨 넣었다는 것이다. 주소의 위쪽 2비트 csr[11:10]는 읽기·쓰기 가능(00, 01, 10)인지 읽기 전용(11)인지를, 그다음 2비트 csr[9:8]는 그 CSR에 접근할 수 있는 가장 낮은 특권을 나타낸다. 하드웨어는 표를 뒤질 필요 없이 비트 4개만 보고 1차 검사를 끝낼 수 있다.
“The top two bits (csr[11:10]) indicate whether the register is read/write (00,01, or 10) or read-only (11). The next two bits (csr[9:8]) encode the lowest privilege level that can access the CSR, with the pattern 10 representing hypervisor CSRs.”
Privileged Vol. II, §1.1.1 CSR Address Mapping Conventions — 특권 인코딩에서는 예약된 10이 CSR 주소에서는 하이퍼바이저(HS) 수준을 뜻한다는 점에 주의하자.
규격은 이 규칙 위에 표준과 사용자 정의(custom) 영역을 나눠 둔 할당표를 정한다. custom 영역에 정의된 CSR은 앞으로 표준 확장이 다시 정의하지 않겠다고 약속되어 있어, 칩 회사가 자기만의 CSR을 안심하고 둘 수 있다.
| 최소 특권 [9:8] | 표준 읽기·쓰기 | custom 읽기·쓰기 | 표준 읽기 전용 | custom 읽기 전용 |
|---|---|---|---|---|
| U (00) | 0x000–0x0FF 0x400–0x4FF | 0x800–0x8FF | 0xC00–0xCBF | 0xCC0–0xCFF |
| S (01) | 0x100–0x1FF 0x500–0x5BF 0x900–0x9BF | 0x5C0–0x5FF 0x9C0–0x9FF | 0xD00–0xDBF | 0xDC0–0xDFF |
| HS (10) | 0x200–0x2FF 0x600–0x6BF 0xA00–0xABF | 0x6C0–0x6FF 0xAC0–0xAFF | 0xE00–0xEBF | 0xEC0–0xEFF |
| M (11) | 0x300–0x3FF 0x700–0x79F 0xB00–0xBBF디버그용 0x7A0–0x7AF | 0x7C0–0x7FF 0xBC0–0xBFF | 0xF00–0xFBF | 0xFC0–0xFFF |
0x7B0–0x7BF는 D 모드에서만 보이는 디버그 전용 CSR(dcsr, dpc 등)이며, 규격은 M 모드가 이곳에 접근하면 illegal-instruction 예외를 내야 한다고 권한다. 아래 그림은 4096칸 전체를 16칸씩 묶어 색으로 칠한 지도다. 칸을 누르면 아래 해독기로 그 번지가 넘어간다.
csr[11:8]), 가로 칸은 csr[7:4]다. 같은 줄 안에서는 권한이 같다는 점, 그리고 각 줄의 오른쪽 끝([7:6]=11)이 대개 custom 영역이라는 점이 한눈에 보인다.규격은 낮은 수준에서 읽기 전용인 CSR을 높은 수준에서 읽기·쓰기로 쓰고 싶을 때 주소를 따로 둔다. 예를 들어 cycle(0xC00, U 읽기 전용)은 mcycle(0xB00, M 읽기·쓰기)의 그림자다. 이렇게 하면 허락된 읽기는 트랩 없이 바로 실행되고, 금지된 쓰기만 걸러진다. 현재 이런 그림자 CSR을 가진 것은 카운터뿐이다.
CSR 주소 해독기
0x300(mstatus)을 0xF14(mhartid)로 바꿔 보자. 위쪽 두 비트가 11이 되는 순간 모든 “쓰기” 칸이 빨갛게 변한다. 또 0xC80(cycleh)을 고른 뒤 XLEN을 RV64로 바꾸면 어떻게 되는지 확인하자. 표의 “읽기”는 csrr(=csrrs rd, csr, x0), “쓰기”는 csrw처럼 실제로 CSR에 값을 쓰는 명령을 뜻한다.CSR 명령과 illegal-instruction 예외
CSR은 Zicsr 확장의 여섯 명령으로만 읽고 쓴다(16장). 여섯 명령은 모두 “읽고-고치고-쓰기”를 원자적으로 하지만, 피연산자에 따라 읽기나 쓰기를 아예 하지 않을 수 있다. 이 차이가 권한 검사에 그대로 영향을 준다. 읽기 전용 CSR을 읽기만 하는 명령은 괜찮지만, 같은 CSR에 쓰기를 “시도”하는 명령은 값이 0이어도 예외가 난다.
| 명령 | 조건 | CSR 읽기 | CSR 쓰기 | 흔한 의사 명령 |
|---|---|---|---|---|
csrrw | rd = x0 | 안 함 | 함 | csrw csr, rs |
| rd ≠ x0 | 함 | 함 | csrrw rd, csr, rs | |
csrrs · csrrc | rs1 = x0 | 함 | 안 함 | csrr rd, csr |
| rs1 ≠ x0 | 함 | 함 (레지스터 값이 0이어도) | csrs · csrc | |
csrrwi | rd = x0 | 안 함 | 함 | csrwi |
csrrsi · csrrci | uimm = 0 | 함 | 안 함 | — |
규격이 정한 CSR 접근 실패의 경우는 다음과 같다.
- 존재하지 않는 CSR에 접근하는 명령은 예약된 명령이다. 실제 구현은 대개 illegal-instruction 예외를 낸다.
- 특권이 부족한 접근은 illegal-instruction 예외를 일으킨다. (하이퍼바이저 확장이 있으면 일부 경우는 virtual-instruction 예외가 된다.)
- 읽기 전용 CSR에 쓰기를 시도하면 illegal-instruction 예외다.
- 읽기·쓰기 CSR 안의 일부 비트만 읽기 전용이라면, 그 비트에 대한 쓰기는 예외 없이 무시된다.
여기에 더해 주소 규칙만으로는 표현할 수 없는 “두 번째 문지기”가 있다. mstatus.TVM이 1이면 S 모드의 satp 접근이, mcounteren 비트가 0이면 S 모드의 카운터 읽기가, mstateen 비트가 0이면 새 확장의 상태 접근이 막힌다. RV64에서는 RV32 전용 상위 절반 CSR(cycleh 등)의 주소가 예약되어 있어 접근하면 보통 illegal-instruction 예외가 난다.
규격은 특권 명령 예외를 illegal-instruction 예외와 따로 두지 않았다. 아키텍처가 단순해지고, 어떤 상위 특권 명령이 구현되어 있는지도 숨길 수 있기 때문이다. 둘을 구별해야 한다면 트랩을 받은 상위 모드의 소프트웨어가 정책을 정한다.
하트의 신분증: misa와 ID 레지스터
부팅 직후의 펌웨어가 가장 먼저 알아야 할 것은 “나는 어떤 하트 위에서 돌고 있는가”다. 규격은 이를 위해 M 모드 CSR 몇 개를 반드시 읽을 수 있게 해 두었다. 그 중심에 있는 것이 misa(Machine ISA register, 0x301)다. misa는 WARL 읽기·쓰기 레지스터로, 맨 위 두 비트에 기본 정수 폭을, 맨 아래 26비트에 알파벳 한 글자짜리 확장의 유무를 담는다.
| MXL | XLEN | 비고 |
|---|---|---|
1 | 32 | MXLEN = 2MXL+4. misa가 0이 아니면 MXL은 M 모드의 실효 XLEN(MXLEN)이다. 덜 특권적인 모드의 XLEN은 MXLEN보다 작을 수는 있어도 클 수는 없다. |
2 | 64 | |
3 | 예약 |
MXL이 맨 위에 있는 데에는 이유가 있다. misa를 읽은 값의 부호(최상위 비트)로 한 번 분기하고, 왼쪽으로 한 칸 민 뒤 다시 부호로 분기하면 레지스터 폭을 모르는 코드로도 RV32인지 RV64인지 알아낼 수 있다. 규칙 몇 가지를 더 정리하면 다음과 같다.
- misa는 모든 구현에서 읽을 수 있어야 하지만, 0을 돌려주어 “misa를 구현하지 않았다”고 알릴 수 있다. 이때는 비표준 수단으로 기능을 알아내야 한다.
- Extensions 필드의 비트가 쓰기 가능하면 소프트웨어가 확장을 끌 수 있다. 끈 확장의 명령과 CSR은 구현되지 않은 것처럼 동작한다. 리셋 때에는 지원하는 최대 집합이 켜지고, I와 E가 모두 가능하면 I가 선택된다.
- E 비트는 읽기 전용이며 (misa 전체가 0이 아니라면) 항상 I 비트의 반대 값으로 읽힌다.
- 기능 x가 기능 y에 의존할 때 x만 켜고 y를 끄려 하면 둘 다 꺼진다. 규격의 예: F=0, D=1을 쓰면 F와 D가 모두 0, U=0, S=1을 쓰면 U와 S가 모두 0이 된다.
- C를 끄면 IALIGN이 32로 커진다. 그 쓰기 바로 다음 명령의 주소가 4바이트 정렬이 아니라면 misa 쓰기 자체가 취소된다.
- 비트가 1이면 그 기능이 있다는 뜻이지만, 비트가 0이라고 반드시 없는 것은 아니다. 없는 기능의 명령은 “예약”이 될 뿐 반드시 불법은 아니기 때문이다.
misa 만들기
01로 바뀐다는 점도 확인하자. I를 끄면 읽기 전용인 E가 저절로 켜진다.누가 만든 어떤 하트인가: 네 개의 ID 레지스터
| CSR | 주소 | 내용 |
|---|---|---|
mvendorid | 0xF11 | 32비트. JEDEC 제조사 ID를 Bank(연속 코드 0x7f의 개수, 비트 31:7)와 Offset(마지막 바이트에서 패리티를 뺀 7비트, 비트 6:0)으로 담는다. 0x7f를 열두 번 이어 쓴 뒤 0x8a로 끝나는 ID는 0x60a가 된다. 0이면 미구현 또는 비상업 구현. |
marchid | 0xF12 | MXLEN 비트. 마이크로아키텍처의 ID. 오픈 소스 프로젝트는 RISC-V International이 최상위 비트 0인 번호를 배정하고, 상업 ID는 최상위 비트가 1이다. mvendorid와 함께 하트의 종류를 유일하게 식별해야 한다. |
mimpid | 0xF13 | 구현(설계 버전)의 번호. 형식은 제공자가 정한다. |
mhartid | 0xF14 | 지금 코드를 실행하는 하트의 번호. 실행 환경 안에서 유일해야 하고, 연속일 필요는 없지만 하트 하나는 반드시 0번이어야 한다. 리셋 직후 “딱 한 하트만” 초기화를 하게 만들 때 쓴다. |
mconfigptr | 0xF15 | 하트와 플랫폼을 설명하는 설정 자료 구조가 있는 곳의 주소. |
misa는 M 모드 전용이다. 규격은 낮은 특권 수준이 CPU 레지스터를 직접 읽는 대신 환경 호출(ecall)로 실행 환경에 물어보라고 요구한다. 그래야 가상화 계층이 각 수준이 보는 ISA를 자유롭게 바꿀 수 있고, 하드웨어에 부담을 주지 않으면서 훨씬 풍부한 정보를 줄 수 있다. 리눅스는 부팅 때 펌웨어가 넘겨준 장치 트리의 riscv,isa 문자열을 읽는다.
mstatus: 하트의 조종판
mstatus(Machine Status, 0x300)는 하트의 현재 동작 상태를 기록하고 조절하는 MXLEN 비트 읽기·쓰기 레지스터다. 인터럽트를 켜고 끄는 스위치, 트랩 직전의 모드를 기억하는 칸, 부동소수점·벡터 상태가 더러워졌는지 알려 주는 표시등, 메모리 접근 권한을 비트는 레버가 한 레지스터에 모여 있다. RV32에서는 64비트 분량이 다 들어가지 않으므로, RV64 mstatus의 비트 62:36에 해당하는 필드를 mstatush(0x310)의 비트 30:4에 둔다. 단 SD, SXL, UXL은 mstatush에 없다.
| 무리 | 필드 (RV64 비트) | 역할 |
|---|---|---|
| 트랩 스택 | SIE(1) MIE(3) SPIE(5) MPIE(7) SPP(8) MPP(12:11) | 모드별 전역 인터럽트 허용, 트랩 직전의 허용 값과 모드를 한 단계 저장 |
| 확장 상태 | VS(10:9) FS(14:13) XS(16:15) SD(63) | 벡터·부동소수점·기타 확장 상태가 Off/Initial/Clean/Dirty 중 무엇인지 |
| 메모리 권한 | MPRV(17) SUM(18) MXR(19) | M 모드가 MPP의 권한으로 메모리를 읽게 하거나, 페이지 권한 해석을 바꿈 |
| 가상화 지원 | TVM(20) TW(21) TSR(22) | S 모드의 satp·SFENCE.VMA, WFI, SRET을 M 모드로 가로챔 |
| XLEN · 엔디언 | UXL(33:32) SXL(35:34) UBE(6) SBE(36) MBE(37) | U·S 모드의 XLEN과, 모드별 데이터 접근 바이트 순서 |
| H · CFI · 이중 트랩 | GVA(38) MPV(39) SPELP(23) MPELP(41) SDT(24) MDT(42) | 하이퍼바이저 확장, 랜딩 패드(Zicfilp), 이중 트랩(Ssdbltrp·Smdbltrp) 지원 |
mstatus 필드 탐색기 (RV64)
sstatus는 mstatus를 들여다보는 창이다
S 모드 소프트웨어는 sstatus(0x100)를 읽고 쓴다. sstatus는 별도의 레지스터가 아니라 mstatus의 제한된 보기다. 규격의 표현대로 단순한 구현에서는 sstatus의 어떤 필드를 읽거나 쓰는 것이 mstatus의 같은 이름 필드를 읽거나 쓰는 것과 같다. 같은 관계가 sie·sip와 mie·mip 사이에도 있다.
트랩 스택: MIE · MPIE · MPP
MIE와 SIE는 각 모드의 전역 인터럽트 허용 비트다. 하트가 모드 x에서 실행 중일 때 xIE=1이면 그 모드의 인터럽트가 전역으로 허용된다. 낮은 모드의 인터럽트는 그 모드의 wIE와 상관없이 항상 막혀 있고, 높은 모드의 인터럽트는 yIE와 상관없이 항상 허용된다. 그래서 U 모드에서 돌던 하트는 MIE가 0이어도 M 모드 타이머 인터럽트를 받는다.
트랩이 중첩될 수 있도록 각 모드는 두 칸짜리 스택을 가진다. 모드 y에서 모드 x로 트랩이 들어가면 xPIE ← xIE, xIE ← 0, xPP ← y가 된다. xRET은 반대로 xIE ← xPIE, 모드 ← xPP, xPIE ← 1, xPP ← 지원하는 가장 낮은 모드를 수행하고, 새 모드가 M이 아니면 MPRV도 0으로 만든다. MPP는 U·S·M을 모두 담아야 하므로 2비트, SPP는 U·S만 담으면 되므로 1비트다.
확장 상태: FS · VS · XS와 SD
부동소수점 레지스터 32개와 벡터 레지스터 32개를 문맥 전환 때마다 모두 저장하면 비싸다. 그래서 mstatus는 각 확장 상태가 Off(0), Initial(1), Clean(2), Dirty(3) 중 무엇인지 기록한다. 운영체제는 Dirty인 상태만 메모리에 저장하고 Clean으로 바꿔 두면 된다. SD는 FS·VS·XS 중 하나라도 Dirty이면 1이 되는 읽기 전용 요약 비트로, 최상위 비트에 있어서 부호 검사 한 번으로 “저장할 것이 있는가”를 판정할 수 있다.
나머지 필드들
| 필드 | 뜻 |
|---|---|
MPRV | 1이면 M 모드의 load·store가 MPP 모드인 것처럼 주소 변환·보호를 받는다. 명령어 인출은 영향받지 않는다. 오정렬 접근을 흉내 내는 M 모드 루틴이 소프트웨어 주소 변환 없이 사용자 메모리를 읽을 수 있게 한다. U 모드가 없으면 읽기 전용 0. |
SUM | 0이면 S 모드가 U 페이지(PTE의 U=1)에 접근할 때 폴트가 난다. 커널이 실수로 사용자 포인터를 따라가는 일을 막는다(19장). |
MXR | 1이면 실행 전용(X=1) 페이지도 load로 읽을 수 있다. 명령어를 읽어 흉내 내야 하는 처리기용이다. |
TVM | 1이면 S 모드의 satp 접근과 SFENCE.VMA·SINVAL.VMA가 illegal-instruction이 된다. 섀도 페이지 테이블로 게스트를 돌릴 때 쓴다. |
TW | 1이면 M보다 낮은 모드의 WFI가 구현이 정한 제한 시간 안에 끝나지 않을 때 illegal-instruction이 된다. 놀고 있는 게스트 대신 다른 게스트로 전환하는 데 쓴다. |
TSR | 1이면 S 모드의 SRET이 illegal-instruction이 된다. H 확장이 없는 하드웨어에서 하이퍼바이저를 흉내 낼 때 필요하다. |
UXL · SXL | RV64에서 U·S 모드의 XLEN을 고르는 WARL 필드. 인코딩은 misa.MXL과 같다. 읽기 전용으로 만들어 항상 64로 둘 수도 있다. |
UBE · SBE · MBE | 명령어 인출을 뺀 메모리 접근의 바이트 순서. 0이 리틀 엔디언, 1이 빅 엔디언이다. 명령어 인출은 언제나 리틀 엔디언이다. |
WPRI · WLRL · WARL: 필드에 쓰는 약속
CSR의 모든 비트가 모든 값을 받아 주지는 않는다. 어떤 필드는 미래를 위해 비워 두었고, 어떤 필드는 일부 값만 뜻이 있다. 규격은 이런 필드를 세 가지 이름으로 분류하고, 소프트웨어와 하드웨어가 각각 무엇을 지켜야 하는지 정했다.
| 규칙 | 소프트웨어가 지킬 것 | 하드웨어가 지킬 것 | 대표 예 |
|---|---|---|---|
| WPRI | 읽은 값을 무시하고, 다른 필드를 고칠 때 이 필드의 값을 그대로 다시 쓴다 | 필드를 갖추지 않았다면 읽기 전용 0 | mstatus 비트 0·2·4·31:25 |
| WLRL | 합법 값만 쓴다. 불법 값을 쓴 뒤 읽은 값을 믿지 않는다 | 지원 값만 구별할 만큼의 비트를 두되, 읽으면 전체 인코딩을 돌려준다. 불법 값 쓰기에 예외를 내도 된다(필수 아님) | mcause·scause의 Exception Code |
| WARL | 아무 값이나 쓸 수 있다 | 예외 없이 받아들이고, 읽으면 항상 합법 값. 불법 값을 쓴 뒤에는 쓴 값과 하트 상태에 따라 결정적으로 정해지는 합법 값 | misa, mtvec, MPP, SXL·UXL, pmpaddr, medeleg |
WARL 덕분에 펌웨어는 별도의 기능 목록 없이도 하드웨어를 탐지할 수 있다. 규격 비고에 나오는 예가 MPP다. MPP는 구현된 모드만 담을 수 있는 WARL 필드이므로, S(01)를 써 보고 다시 읽어서 01이 남아 있으면 S 모드가 있는 것이다. U와 M만 있는 하트라면 MPP를 담는 데 하드웨어 저장 비트가 하나면 충분하다(00 또는 11).
한 CSR에 쓴 값이 다른 CSR 필드의 합법 값 집합을 바꾸는 경우가 있다. 규격은 따로 정하지 않은 한, 두 번째 필드가 새 합법 값 중 UNSPECIFIED한 값을 즉시 갖는다고 정한다. 이전 값이 여전히 합법이어도 바뀔 수 있다. 이런 변화는 그 CSR에 대한 “쓰기”가 아니므로 쓰기에 딸린 부수 효과도 일으키지 않는다.
카운터와 접근 문: mcounteren · scounteren · mcountinhibit
성능 측정과 시간 측정을 위해 M 모드에는 64비트 카운터가 있다. mcycle(0xB00)은 하트가 돌고 있는 코어의 클럭 사이클을, minstret(0xB02)은 완료(retire)한 명령 수를 센다. 여기에 이벤트를 고를 수 있는 mhpmcounter3–mhpmcounter31(0xB03–0xB1F)과 그 이벤트 선택기 mhpmevent3–mhpmevent31(0x323–0x33F)이 있다. RV32에서는 위쪽 32비트를 …h CSR로 따로 읽는다.
낮은 모드는 같은 값을 읽기 전용 그림자로 본다. cycle(0xC00), instret(0xC02), hpmcounter3–31(0xC03–0xC1F)이 그것이고, time(0xC01)은 메모리 사상 레지스터 mtime의 그림자다. 그런데 카운터는 부채널 공격의 재료가 될 수도 있고, 가상화 환경에서는 값을 가공해 보여 줘야 할 수도 있다. 그래서 규격은 카운터마다 문을 달았다.
- mcounteren(0x306, 32비트): CY·TM·IR·HPMn 비트가 0이면 S·U 모드에서 해당 카운터를 읽을 때 illegal-instruction 예외가 난다. TM 비트는 S 모드의
stimecmp접근(Sstc)도 함께 관리한다. U 모드가 있는 하트에는 반드시 있지만 모든 비트가 읽기 전용 0이어도 된다. - scounteren(0x106, 32비트): 같은 배치로 U 모드의 읽기를 관리한다. U 모드는 scounteren과 mcounteren의 같은 비트가 모두 1일 때만 카운터를 읽을 수 있다.
- mcountinhibit(0x320, 32비트 WARL): CY·IR·HPMn 비트가 1이면 해당 카운터가 증가를 멈춘다. 접근 가능 여부에는 영향이 없다. 비트 1(TM)은 없다. mtime은 여러 코어가 함께 쓰는 실시간 시계라서 한 하트가 멈출 수 없기 때문이다. 쓰지 않는 카운터를 멈춰 전력을 아끼고, 모든 카운터를 한꺼번에 멈춰 원자적으로 읽는 데 쓴다.
카운터 문 열고 닫기
mcounteren (M이 설정)
scounteren (S가 설정)
mcountinhibit (M이 설정)
rdtime은 성공하지만 rdcycle은 실패한다. mcounteren.CY는 열려 있어도 scounteren.CY가 닫혀 있기 때문이다. 반대로 IR은 S가 열어 주었는데도 M이 막고 있어 U도 S도 읽지 못한다. 리눅스는 보안 때문에 사용자 공간의 cycle 직접 읽기를 막아 두는 경우가 있다.새 상태 지키기와 간접 접근: Smstateen · Smcsrind
Smstateen: 운영체제가 모르는 상태를 막는 문
새 확장이 하트에 레지스터를 추가했는데, 오래된 운영체제나 하이퍼바이저는 그 존재를 모른다고 하자. 문맥 전환 때 그 레지스터를 저장·복원하지 않으므로, 한 사용자 스레드나 게스트 OS가 써 둔 값을 다른 스레드나 게스트가 읽을 수 있다. 이것이 은닉 채널(covert channel)이다. 부동소수점과 벡터는 mstatus의 FS·VS 필드로 막을 수 있지만, 확장마다 sstatus에 필드를 하나씩 줄 수는 없다. 그래서 Smstateen/Ssstateen 확장은 “상태 허용” 비트를 모아 둘 전용 CSR을 만들었다.
mstateen0–3(0x30C–0x30F, RV32 상위 절반 0x31C–0x31F),sstateen0–3(0x10C–0x10F), H 확장이 있으면hstateen0–3(0x60C–0x60F, 상위 절반 0x61C–0x61F).- 각 수준의 stateen은 자기보다 낮은 모든 수준의 접근을 관리한다. counteren과 같은 방식이다. 막힌 상태에 접근하면 illegal-instruction(가상화 상황에서는 virtual-instruction) 예외다.
- mstateen의 어떤 비트가 0이면 hstateen과 sstateen의 같은 비트는 읽기 전용 0으로 보인다. 위에서 닫으면 아래에서 열 수 없다.
- sstateen의 비트는 아래(비트 0)부터, mstateen에만 있는 비트(U 수준에서는 원래 보이지 않는 상태)는 위(비트 63)부터 배정한다.
Smcsrind: 창 하나로 레지스터 배열 읽기
인터럽트 우선순위 표나 성능 카운터 설정처럼 비슷한 레지스터가 수십 개씩 늘어선 상태를 모두 CSR 주소에 하나씩 배정하면 4096칸이 금방 바닥난다. 게다가 번호로 순회하려면 CSR 번호가 명령어에 박혀 있으므로 switch 문이 필요하다. Smcsrind/Sscsrind는 AIA 확장에서 시작된 간접 접근 방식을 일반화했다. 선택 레지스터에 번호를 쓰고, 별칭 레지스터를 읽고 쓰면 그 번호의 레지스터에 접근한다.
| 수준 | 선택 CSR | 별칭 CSR |
|---|---|---|
| M | miselect 0x350 | mireg 0x351, mireg2 0x352, mireg3 0x353, mireg4–6 0x355–0x357 |
| S | siselect 0x150 | sireg 0x151, sireg2 0x152, sireg3 0x153, sireg4–6 0x155–0x157 |
| VS | vsiselect 0x250 | vsireg 0x251 … vsireg6 0x257 (0x254 제외) |
별칭 번호가 0x354를 건너뛰는 것은 그 자리가 AIA의 miph에 이미 배정되어 있기 때문이다. 선택 값의 최상위 비트(XLEN-1)가 1이면 custom 용도이고, 0이면 표준 확장에 배정된다. 지금 배정된 값으로는 AIA의 iprio0–15(0x30–0x3F), eidelivery(0x70), eithreshold(0x72), eip0–63(0x80–0xBF), eie0–63(0xC0–0xFF)가 있고, S 수준에서는 카운터 위임용 0x40–0x5F와 제어 전송 기록(Smctr)용 0x200–0x2FF가 더 있다.
csrw miselect, 0x72 다음 csrr t0, mireg를 실행하면 CSR 주소를 하나도 더 쓰지 않고 IMSIC의 eithreshold를 읽는다. 지원하지 않는 선택 값으로 mireg에 접근했을 때의 동작은 UNSPECIFIED이며, 구현은 보통 illegal-instruction 예외를 낼 것으로 기대된다.핵심 정리
- RISC-V의 특권 수준은 U(00), S(01), M(11) 셋이며 10은 예약이다. M 모드만 필수이고, 구현은 M / M·U / M·S·U 중 하나를 고른다.
- 하트는 언제나 한 모드에서만 실행되며, 모드는 트랩(같거나 더 높은 모드로)과 xRET(저장해 둔 모드로)으로만 바뀐다.
- CSR 주소 12비트 중
[11:10]=11이면 읽기 전용,[9:8]은 접근 가능한 최소 특권(00 U, 01 S, 10 HS, 11 M)이다. 특권 부족이나 읽기 전용 CSR 쓰기는 illegal-instruction 예외다. csrr(=csrrs rd, csr, x0)은 쓰기를 하지 않으므로 읽기 전용 CSR에도 안전하지만,csrw는 값과 상관없이 쓰기를 시도한다.- misa는 MXL(XLEN)과 A–Z 확장 비트를 담는 WARL 레지스터다. 의존 관계를 어기는 쓰기는 관련 기능을 모두 끈다.
- mstatus는 인터럽트 허용·트랩 스택(MIE·MPIE·MPP), 확장 상태(FS·VS·XS·SD), 메모리 권한(MPRV·SUM·MXR), 가상화 가로채기(TVM·TW·TSR)를 모은 조종판이며, sstatus는 그 제한된 보기다.
- WPRI는 보존·무시, WLRL은 합법 값만 쓰기, WARL은 무엇이든 쓰고 합법 값만 읽기다. WARL은 “써 보고 읽기”로 하드웨어 기능을 탐지하게 해 준다.
- 카운터 읽기는 mcounteren(→S)과 scounteren(→U)이 문을 열어야 가능하고, mcountinhibit은 증가만 멈춘다. Smstateen은 새 확장 상태를 막는 문, Smcsrind는 선택·별칭 CSR로 레지스터 배열에 접근하는 창이다.
확인 퀴즈
모든 RISC-V 하드웨어 구현이 반드시 갖춰야 하는 특권 모드는?
CSR 주소 0xC01(time)에 대해 주소만 보고 말할 수 있는 것은?
M 모드에서 csrw mhartid, t0를 실행하면?
M·S·U가 있는 하트에서 mcounteren.CY=1, scounteren.CY=0일 때 rdcycle의 결과는?
misa의 MXL 필드가 2라면?
WARL 필드에 대한 설명으로 옳은 것은?
S 모드 소프트웨어가 sstatus를 읽었을 때 볼 수 없는 필드는?
Smstateen 확장이 해결하려는 문제는?
더 읽을거리
- The RISC-V Instruction Set Manual, Volume II: Privileged Architecture — Introduction (v20260120): 특권 수준, 소프트웨어 스택 용어
- Chapter 1. Control and Status Registers (CSRs): 주소 규칙, 할당표, WPRI·WLRL·WARL
- Chapter 2. Machine-Level ISA, Version 1.13: misa, ID 레지스터, mstatus, 카운터
- Chapter 11. Supervisor-Level ISA, Version 1.13: sstatus, scounteren
- Chapter 3. "Smstateen/Ssstateen" Extensions, Version 1.0
- Chapter 4. "Smcsrind/Sscsrind" Indirect CSR Access, Version 1.0
- Volume I — "Zicsr" Extension for Control and Status Register Instructions
- RISC-V Advanced Interrupt Architecture (AIA) v1.0: miselect로 접근하는 IMSIC 레지스터