H 확장: 하이퍼바이저
클라우드 서버 한 대에는 수십 개의 운영체제가 동시에 돈다. 그 운영체제들은 저마다 자기가 컴퓨터의 주인이라고 믿고 페이지 테이블을 바꾸고, 인터럽트를 끄고, 특권 명령을 실행한다. H 확장은 이런 게스트 운영체제를 대부분 트랩 없이 하드웨어에서 직접 돌리게 해 주는 특권 구조의 확장이다. 핵심은 두 가지다. 게스트 전용의 가짜 S 모드(VS 모드)와 그 모드가 쓰는 복제 CSR, 그리고 게스트 물리 주소를 한 번 더 바꾸는 2단계 주소 변환이다.
- 트랩하고 흉내 내는 고전적 가상화의 비용과, H 확장이 그 비용을 없애는 방식을 설명한다.
- V 비트와 HS·VS·VU 모드의 관계를 그림으로 그린다.
- V=1일 때
sstatus·sepc·satp접근이vsstatus·vsepc·vsatp로 바뀌는 규칙을 안다. - VS-stage(vsatp)와 G-stage(hgatp)의 2단계 변환, Sv39x4의 16 KiB 루트 테이블을 이해한다.
- 중첩 페이지 워크의 최악 메모리 접근 횟수
n·m + n + m을 직접 유도한다. - 게스트 페이지 폴트·가상 명령 예외와 M→HS→VS 트랩 위임 경로를 따라간다.
- HLV·HLVX·HSV, HFENCE.VVMA·HFENCE.GVMA, 월드 스위치 순서, 게스트 인터럽트 주입을 안다.
왜 하드웨어 가상화가 필요한가
가상 머신을 만드는 가장 오래된 방법은 트랩하고 흉내 내기(trap-and-emulate)다. 게스트 운영체제를 권한이 낮은 U 모드에서 돌리면, 게스트가 특권 명령(csrw satp, sret, sfence.vma …)을 실행할 때마다 불법 명령 예외가 나서 하이퍼바이저로 넘어온다. 하이퍼바이저는 그 명령을 해독하고, 게스트 몫의 가짜 레지스터에 결과를 반영한 뒤 게스트로 돌아간다. 1974년 Popek과 Goldberg가 정리한 “가상화 가능한 구조”의 조건이 바로 이 방식이 성립하는 조건이다.
RISC-V의 기본 특권 구조는 이 방식을 쓰기 쉽게 설계되었다. 특권 명령이 몇 개 없고 모두 확실히 트랩되기 때문이다. 하지만 트랩 한 번에 수백 사이클이 들고, 게스트 커널은 생각보다 훨씬 자주 특권 동작을 한다. 특히 가상 메모리가 문제다. 게스트가 고친 페이지 테이블을 하드웨어가 그대로 쓸 수 없으니, 하이퍼바이저는 게스트 페이지 테이블을 감시하며 진짜 주소를 넣은 섀도 페이지 테이블(shadow page table)을 따로 만들어 둘을 계속 맞춰야 한다.
vsatp 등)을 트랩 없이 직접 고치고, 하이퍼바이저는 정말 필요할 때만 개입한다.| 방식 | 게스트 OS가 도는 곳 | 메모리 가상화 | 대표적인 비용 |
|---|---|---|---|
| 트랩하고 흉내 내기 | U 모드 (강등) | 섀도 페이지 테이블 | 특권 명령·페이지 테이블 수정마다 트랩 |
| 반가상화 (para-virtualization) | U 모드 + 수정된 커널 | 하이퍼콜로 페이지 테이블 요청 | 게스트 커널을 고쳐야 한다 |
| H 확장 (하드웨어 지원) | VS 모드 | 2단계 주소 변환 (vsatp + hgatp) | TLB 미스 때 페이지 워크가 길어진다 |
H 확장은 게스트 운영체제를 수정 없이 돌리면서 트랩 횟수를 크게 줄인다. 대가는 하드웨어 복잡도와 2단계 페이지 워크의 비용이다. 이 장의 뒤쪽에서 그 비용을 정확히 세어 본다. H 확장이 켜지려면 정수 레지스터가 32개인 RV32I·RV64I 베이스여야 하고(E 베이스 불가), mtval이 읽기 전용 0이면 안 되며, 페이지 기반 변환(RV32는 Sv32, RV64는 최소 Sv39)이 있어야 한다. misa의 비트 7(문자 H)이 이 확장을 나타낸다.
H 확장 v1.0은 2021년 말 다른 여러 확장과 함께 비준되었다. 그 뒤 RVA23 프로파일이 응용 프로세서용 S 모드 프로파일(RVA23S64)에서 H를 필수로 정하면서, 리눅스를 돌리는 범용 RISC-V 칩이라면 하이퍼바이저 지원을 갖추는 것이 기본이 되었다(23장).
V 비트와 다섯 가지 모드
H 확장은 새 특권 수준을 끼워 넣지 않는다. 대신 하트에 가상화 모드(virtualization mode) 비트 V를 하나 더한다. V는 CSR로 보이는 값이 아니라 하트 내부의 상태이고, 트랩과 트랩 복귀 때만 바뀐다. V=1이면 하트는 지금 게스트를 실행하고 있으며 2단계 주소 변환이 켜진다. 원래의 S 모드는 HS 모드(hypervisor-extended supervisor)로 이름이 바뀌고, 하이퍼바이저나 KVM 같은 호스팅 기능이 있는 OS가 여기서 돈다.
hstatus.SPV=1로 둔 채 sret을 실행하면 V가 1이 되어 게스트로 돌아간다.| V | 명목 권한 | 약칭 | 이름 | 2단계 변환 |
|---|---|---|---|---|
| 0 | U | U | User mode | 꺼짐 |
| 0 | S | HS | Hypervisor-extended supervisor mode | 꺼짐 |
| 0 | M | M | Machine mode | 꺼짐 |
| 1 | U | VU | Virtual user mode | 켜짐 |
| 1 | S | VS | Virtual supervisor mode | 켜짐 |
명목 권한(nominal privilege)이라는 말에 주목하자. VS 모드의 명목 권한은 S다. 그래서 게스트 커널 안에서는 sret도, csrr a0, sstatus도 그대로 실행된다. 권한의 크기는 M > HS > VS > VU 순서이며, HS는 VS보다 높다. 이 순서가 뒤에서 볼 트랩 위임 규칙의 바탕이다.
규격은 두 가지를 모두 염두에 둔다. Xen 같은 타입 1 하이퍼바이저는 HS 모드 자체가 하이퍼바이저이고, 리눅스 KVM 같은 타입 2는 일반 커널이 HS에서 돌면서 가상 머신도 함께 돌린다. KVM은 U 모드에 QEMU 같은 가상 머신 관리 프로그램을 두고, 게스트를 실행할 때만 sret으로 V=1 세계에 들어간다. HS 모드는 S 모드와 똑같이 동작하므로 일반 S 모드 OS는 고치지 않고 HS에서도, VS 게스트로도 돈다.
하이퍼바이저 CSR과 VS CSR 바꿔치기
H 확장이 더하는 CSR은 두 무리다. 하나는 HS 모드만 쓰는 하이퍼바이저 CSR(h로 시작)으로, 게스트를 어떻게 다룰지 정한다. 다른 하나는 VS CSR(vs로 시작)로, 보통의 S CSR을 그대로 복제한 게스트 전용 사본이다. 주소를 보면 규칙이 보인다. S CSR이 0x1xx이면 짝이 되는 VS CSR은 0x2xx, 하이퍼바이저 CSR은 대부분 0x6xx다.
| CSR | 주소 | 하는 일 |
|---|---|---|
hstatus | 0x600 | 게스트 실행을 제어·기록하는 상태 레지스터 (SPV, SPVP, GVA, VTSR …) |
hedeleg | 0x602 | HS로 온 예외를 VS로 한 번 더 위임 |
hideleg | 0x603 | HS로 온 인터럽트를 VS로 한 번 더 위임 |
hie | 0x604 | VS 수준 인터럽트와 게스트 외부 인터럽트(SGEI)의 허용 비트 |
hip | 0x644 | 같은 인터럽트들의 대기 비트 |
hvip | 0x645 | 하이퍼바이저가 게스트에 가상 인터럽트를 주입 |
hcounteren | 0x606 | 게스트가 cycle·time·instret 등을 읽을 수 있는지 |
htimedelta | 0x605 | 게스트가 읽는 time = 실제 time + htimedelta |
hgeie | 0x607 | 게스트 외부 인터럽트 허용 |
hgeip | 0xE12 | 게스트 외부 인터럽트 대기 (읽기 전용) |
henvcfg | 0x60A | V=1일 때의 실행 환경 설정 (PBMTE, ADUE, STCE, CBZE …) |
htval | 0x643 | 트랩 때 기록: 게스트 물리 주소 ≫ 2 |
htinst | 0x64A | 트랩 때 기록: 변환된 명령어 또는 의사 명령 값 |
hgatp | 0x680 | G-stage 변환의 MODE·VMID·루트 PPN |
| VS CSR | 주소 | 대신하는 S CSR | S CSR 주소 |
|---|---|---|---|
vsstatus | 0x200 | sstatus | 0x100 |
vsie | 0x204 | sie | 0x104 |
vstvec | 0x205 | stvec | 0x105 |
vsscratch | 0x240 | sscratch | 0x140 |
vsepc | 0x241 | sepc | 0x141 |
vscause | 0x242 | scause | 0x142 |
vstval | 0x243 | stval | 0x143 |
vsip | 0x244 | sip | 0x144 |
vstimecmp (Sstc) | 0x24D | stimecmp | 0x14D |
vsatp | 0x280 | satp | 0x180 |
M 모드에도 두 CSR이 더해진다. 게스트 물리 주소를 담는 mtval2(0x34B)와 변환된 명령어를 담는 mtinst(0x34A)로, HS의 htval·htinst와 같은 역할을 M 쪽에서 한다.
바꿔치기 규칙
H 확장의 가장 영리한 부분이 여기다. V=1일 때 게스트 커널이 csrr a0, sepc(주소 0x141)를 실행하면, 하트는 sepc가 아니라 vsepc를 읽는다. 게스트 커널은 자기가 진짜 S 모드에 있다고 믿고 평소의 주소를 쓰지만, 하드웨어가 조용히 게스트 사본으로 연결해 준다. 그동안 HS의 진짜 sepc는 값을 그대로 지키며 기계 동작에 영향을 주지 않는다.
csrr a0, sepc라도 V=1이면 vsepc(금색)로 연결된다. 게스트는 VS CSR을 자기 주소(0x241)로 직접 건드릴 수 없고, 그렇게 하면 가상 명령 예외가 난다. VS CSR을 이름 그대로 다룰 수 있는 것은 M과 HS뿐이다.“When V=1, the VS CSRs substitute for the corresponding supervisor CSRs, taking over all functions of the usual supervisor CSRs except as specified otherwise. Instructions that normally read or modify a supervisor CSR shall instead access the corresponding VS CSR.”
Volume II, “H” Extension — Hypervisor and Virtual Supervisor CSRs — VS 사본은 트랩이 자동으로 쓰거나 트랩 직후·SRET 직전에 곧바로 쓰이는 CSR에만 있다. 소프트웨어가 정확한 순간에 바꿔 끼울 수 없는 것들이기 때문이다.
그 이유 때문에 모든 S CSR에 짝이 있는 것은 아니다. senvcfg, scounteren, scontext는 VS 사본이 없어서 V=1에서도 같은 레지스터가 그대로 쓰인다. 하이퍼바이저는 게스트를 바꿀 때 이런 CSR을 손으로 저장·복원해야 한다.
CSR 바꿔치기 탐색기
HS 수준 S CSR
VS CSR
하이퍼바이저 CSR
satp를 고르고 hstatus.VTVM을 켜 보자. 하이퍼바이저가 게스트의 페이지 테이블 교체를 가로채고 싶을 때 쓰는 스위치다. 또 V=1에서 hgatp나 vsstatus를 이름 그대로 읽으면 무슨 일이 생기는지, senvcfg는 왜 바뀌지 않는지도 확인하자.hstatus 한눈에 보기
hstatus는 mstatus가 M 모드를 위해 하는 일을 게스트에 대해 한다. 트랩이 들어올 때 하드웨어가 기록하는 필드(SPV·SPVP·GVA)와, 게스트의 특정 동작을 가로채는 필드(VTSR·VTW·VTVM)가 핵심이다.
hstatus 비트 배치(규격 Figure 2). HSXLEN=32이면 VSXL과 HUPMM이 없고 나머지 위치는 같다.| 필드 | 비트 | 뜻 |
|---|---|---|
| SPV | 7 | HS로 트랩할 때 직전의 V를 기록. V=0에서 SRET하면 V ← SPV. |
| SPVP | 8 | V=1에서 트랩하면 직전의 명목 권한(VS=1, VU=0)을 기록. HLV/HSV가 어떤 권한으로 게스트 메모리를 읽을지 정한다. |
| GVA | 6 | 트랩이 stval에 게스트 가상 주소를 썼으면 1. |
| VSBE | 5 | VS 모드 메모리 접근의 엔디언 (0 = 리틀). |
| HU | 9 | 1이면 U 모드에서도 HLV·HLVX·HSV를 쓸 수 있다. |
| VGEIN | 17:12 | VS 외부 인터럽트로 연결할 게스트 외부 인터럽트 번호(0 = 없음). |
| VTVM · VTW · VTSR | 20 · 21 · 22 | VS 모드의 satp 접근·SFENCE.VMA / WFI / SRET을 가상 명령 예외로 가로챈다. mstatus의 TVM·TW·TSR과 같은 구실. |
| VSXL | 33:32 | VS 모드의 XLEN (misa.MXL과 같은 인코딩). |
| HUPMM | 49:48 | U 모드 HLV/HSV의 포인터 마스킹 (Ssnpm). |
2단계 주소 변환: VS-stage와 G-stage
메모리 가상화가 H 확장의 두 번째 기둥이다. V=1이면 모든 메모리 접근이 두 번 변환된다. 첫 단계 VS-stage는 게스트 커널이 vsatp로 관리하는 평범한 Sv39/Sv48 페이지 테이블이고, 게스트 가상 주소(GVA)를 게스트 물리 주소(GPA)로 바꾼다. 둘째 단계 G-stage는 하이퍼바이저가 hgatp로 관리하며, GPA를 진짜 슈퍼바이저 물리 주소(SPA)로 바꾼다. 게스트는 G-stage가 있다는 사실조차 모른다. 섀도 페이지 테이블이 필요 없어지는 이유다.
hgatp와 Sv39x4
hgatp는 satp와 비슷하게 생겼다. MODE, 가상 머신 식별자 VMID, G-stage 루트 테이블의 PPN이 들어간다. ASID가 프로세스를 구별하듯 VMID는 가상 머신을 구별해, 게스트를 바꿀 때 TLB를 통째로 비우지 않아도 되게 한다. HSXLEN=64에서 MODE 값은 0(Bare), 8(Sv39x4), 9(Sv48x4), 10(Sv57x4)이고 나머지는 예약이다. HSXLEN=32이면 0(Bare)과 1(Sv32x4)뿐이다.
hgatp. 아래: Sv39x4가 나누는 41비트 게스트 물리 주소. Sv39 가상 주소와 똑같고 VPN[2]만 2비트 넓다. 그래서 루트 테이블만 4배(16 KiB)로 커진다. 비트 63:41 중 하나라도 1이면 게스트 페이지 폴트다.“In each case, the size of the incoming address is widened by 2 bits (to 34, 41, 50, or 59 bits). To accommodate the 2 extra bits, the root page table (only) is expanded by a factor of four to be 16 KiB instead of the usual 4 KiB.”
Volume II, “H” Extension — Guest Physical Address Translation — 규격은 게스트에게 가상 주소 공간의 4배까지 물리 주소 공간을 줄 수 있게 하려고 이렇게 했다고 설명한다. 예를 들어 Sv39만 지원하는 칩도 41비트 게스트 물리 주소를 만들 수 있다.
G-stage만의 규칙
- 모든 G-stage 접근은 U 모드 접근으로 본다. 그래서 G-stage의 리프 PTE는 U=1이어야 한다. U=0이면 게스트 페이지 폴트가 난다. PTE 형식이 같아서 하이퍼바이저가 자기 페이지 테이블 일부를 G-stage와 함께 쓸 수도 있다.
- VS 페이지 테이블을 읽는 접근은 G-stage에서 원래 접근 종류(명령 인출·읽기·쓰기)와 상관없이 암묵적 읽기(A/D 갱신이면 쓰기)로 권한을 검사한다. 다만 예외가 나면 원래 접근 종류로 보고한다. 명령 인출 중이었다면 명령 게스트 페이지 폴트(20)가 된다.
- G-stage PTE의 G 비트는 아직 쓰지 않는다. 소프트웨어는 0으로 둔다.
vsstatus.MXR은 VS-stage에만 작용하고, HS 수준sstatus.MXR은 두 단계 모두에 작용한다.
G-stage에서 문제가 생기면 보통의 페이지 폴트(12·13·15)가 아니라 게스트 페이지 폴트(20·21·23)가 난다. 이 예외는 게스트에게 위임할 수 없고 항상 HS(또는 M)가 받는다. 게스트는 G-stage를 모르니 고칠 수도 없기 때문이다. 하이퍼바이저는 보통 이 폴트를 받아 게스트에게 메모리를 실제로 할당하거나(지연 할당), MMIO 장치 접근을 흉내 낸다.
중첩 페이지 워크의 비용
TLB가 모두 빗나간 최악의 경우를 세어 보자. 게스트가 n단계 페이지 테이블(Sv39이면 3)을, 하이퍼바이저가 m단계 G-stage(Sv39x4이면 3)를 쓴다고 하자.
- 게스트 페이지 워크는
vsatp.PPN에서 시작한다. 이 값은 GPA다. 루트 테이블의 PTE 주소vsatp.PPN × 4096 + VPN[n−1] × 8도 GPA이므로, 그 PTE를 읽으려면 먼저 G-stage 워크(m번 읽기)로 SPA를 구해야 한다. 그다음에야 PTE 자체를 읽는다(1번). 한 게스트 단계에 m + 1번이다. - 게스트 PTE 안의 PPN이 가리키는 다음 테이블도 GPA다. 그래서 n개의 게스트 단계 모두 같은 일을 반복한다: n × (m + 1)번.
- 마지막 리프 PTE가 준 것은 데이터의 GPA다. 이를 SPA로 바꾸려면 G-stage 워크가 한 번 더 필요하다: + m번.
합치면 페이지 테이블 읽기는 n(m+1) + m = nm + n + m = (n+1)(m+1) − 1번이고, 실제 데이터 접근 1번이 따로 붙는다. Sv39 게스트를 Sv39x4 위에서 돌리면 3·3 + 3 + 3 = 15번, Sv48 위의 Sv48x4라면 4·4 + 4 + 4 = 24번이다. 가상화가 없을 때 각각 3번, 4번이던 것에 비하면 5–6배다. 24라는 숫자는 x86-64의 4단계 × 4단계 중첩 페이징을 분석한 Bhargava 등(ASPLOS 2008)의 결과와도 같다.
2차원 페이지 워크 계수기
| 게스트 \ G-stage | Bare (m=0) | Sv39x4 (m=3) | Sv48x4 (m=4) | Sv57x4 (m=5) |
|---|---|---|---|---|
| Sv39 (n=3) | 3 | 15 | 19 | 23 |
| Sv48 (n=4) | 4 | 19 | 24 | 29 |
| Sv57 (n=5) | 5 | 23 | 29 | 35 |
실제 칩은 이 비용을 거의 치르지 않는다. TLB가 GVA → SPA를 곧바로 기억하고, 페이지 워크 캐시가 중간 단계의 결과를 붙잡아 두기 때문이다. 규격은 hgatp.VMID와 VS 수준 ASID를 합친 것을 실질적인 ASID로 보라고 설명한다. 덕분에 여러 가상 머신의 변환이 TLB에 섞여 있어도 서로를 구별할 수 있고, 월드 스위치 때 TLB를 비울 필요가 없다. VMID 비트 수는 구현마다 다르며 Sv39x4·Sv48x4·Sv57x4에서 최대 14비트, Sv32x4에서 최대 7비트다.
게스트 페이지 폴트와 트랩 위임
H 확장은 원인 코드도 늘린다. 게스트를 위한 예외 넷과 인터럽트 넷이다. VS 모드의 ecall은 HS 모드의 ecall(9)과 다른 코드 10을 받는다. 두 ecall을 따로 위임할 수 있게 하려는 것이다.
| 종류 | 코드 | 이름 | 언제 |
|---|---|---|---|
| 예외 | 8 | Environment call from U-mode or VU-mode | 사용자·게스트 앱의 시스템 호출 |
| 예외 | 10 | Environment call from VS-mode | 게스트 커널의 SBI 호출 |
| 예외 | 20 | Instruction guest-page fault | 명령 인출 중 G-stage 실패 |
| 예외 | 21 | Load guest-page fault | 읽기 중 G-stage 실패 |
| 예외 | 22 | Virtual instruction | V=1에서 HS라면 허용될 명령이 막힘 |
| 예외 | 23 | Store/AMO guest-page fault | 쓰기·AMO 중 G-stage 실패 |
| 인터럽트 | 2 · 6 · 10 | Virtual supervisor software / timer / external | VS 수준 인터럽트 (VSSI·VSTI·VSEI) |
| 인터럽트 | 12 | Supervisor guest external interrupt | 게스트 외부 인터럽트가 HS에 알림 (SGEI) |
가상 명령 예외
V=1에서 어떤 명령이 막혔는데, 그 명령이 HS 모드였다면 실행될 수 있었던(HS-qualified) 경우에는 불법 명령 예외(2) 대신 가상 명령 예외(virtual instruction, 22)가 난다. 예를 들어 게스트 커널이 hgatp를 읽거나, VTSR=1일 때 sret을, VTVM=1일 때 satp에 쓰거나, HLV·HFENCE 같은 하이퍼바이저 명령을 실행하는 경우다. 규격은 M이 가상 명령 예외를 HS로 곧장 위임하고, 불법 명령 예외는 먼저 M에서 살펴보는 것이 보통이라 가상 명령 예외가 더 빨리 처리될 것이라고 설명한다.
위임은 두 단계로
예외와 인터럽트는 기본적으로 모두 M으로 간다. medeleg/mideleg이 HS로 내려 보내고, V=1에서 일어난 트랩이라면 hedeleg/hideleg가 한 번 더 VS로 내려 보낸다. 트랩은 자기보다 권한이 낮은 모드로는 가지 않으므로, HS에서 일어난 트랩이 VS로 가는 일은 없다.
mideleg·hideleg로 판단한다. VS 수준 인터럽트(2·6·10)는 H 확장이 있으면 mideleg의 해당 비트가 읽기 전용 1이라 언제나 M을 건너뛴다.| 받는 모드 | V 변화 | 하드웨어가 기록하는 것 |
|---|---|---|
| M | V ← 0 | mepc, mcause, mtval, mtval2, mtinst, mstatus의 MPV(RV64 비트 39)·MPP·GVA(비트 38)·MPIE·MIE. RV32는 MPV·GVA가 mstatush 비트 7·6. |
| HS | V ← 0 | sepc, scause, stval, htval, htinst, hstatus의 SPV·SPVP(V=1에서 왔을 때만)·GVA, sstatus의 SPP·SPIE·SIE |
| VS | V는 1 유지 | vsepc, vscause, vstval, vsstatus의 SPP·SPIE·SIE. hstatus와 HS의 sstatus는 그대로 |
게스트 페이지 폴트가 HS로 오면 stval에는 평소처럼 게스트 가상 주소가, htval에는 실패한 게스트 물리 주소를 오른쪽으로 2비트 민 값(또는 0)이 들어간다. 2비트를 미는 이유는 RV32에서도 34비트 GPA를 담기 위해서이고, PMP 주소 레지스터와 PTE가 물리 주소를 담는 방식과 같다. 폴트가 VS 페이지 테이블을 읽다가 났다면 htval은 그 PTE의 GPA이고, htinst에는 특별한 의사 명령 값 0x00003000(RV64, VS-stage 변환용 64비트 읽기)이나 0x00003020(A/D 갱신용 쓰기)이 들어가 이 상황을 알려 준다. RV32 게스트라면 0x00002000·0x00002020이다.
트랩 경로 시뮬레이터
hedeleg를 켜려 해 보자. 왜 막혀 있을까? 타이머 인터럽트에서 hideleg[6]을 켜고 끄며 원인 코드가 6에서 5로 바뀌는 것도 확인하자.하이퍼바이저 명령과 월드 스위치
HLV · HLVX · HSV: 게스트의 눈으로 메모리 읽기
하이퍼바이저는 종종 게스트의 가상 주소로 메모리를 읽어야 한다. 게스트가 SBI 호출로 넘긴 버퍼 주소를 따라가거나, MMIO 폴트를 일으킨 게스트 명령어를 가져와 해독할 때다. 소프트웨어로 두 단계 페이지 테이블을 걸어가는 대신 가상 머신 로드·스토어(virtual-machine load/store) 명령을 쓴다. 이 명령들은 V=0인 HS(또는 M, HU=1이면 U)에서 실행되지만, 메모리 접근만은 V=1이고 권한이 hstatus.SPVP인 것처럼 2단계 변환을 거친다.
| 명령 | 인코딩 (funct7 · rs2 · funct3=100 · opcode=1110011) | 설명 |
|---|---|---|
| HLV.B / HLV.BU | 0110000 · 00000 / 00001 | 게스트 주소에서 바이트 읽기 (부호 / 0 확장) |
| HLV.H / HLV.HU | 0110010 · 00000 / 00001 | 하프워드 읽기 |
| HLVX.HU | 0110010 · 00011 | 읽기 권한 대신 실행 권한으로 검사하는 하프워드 읽기 |
| HLV.W / HLV.WU | 0110100 · 00000 / 00001 | 워드 읽기 (HLV.WU는 RV64 전용) |
| HLVX.WU | 0110100 · 00011 | 실행 권한으로 검사하는 워드 읽기 (RV32에서도 유효) |
| HLV.D | 0110110 · 00000 | 더블워드 읽기 (RV64 전용) |
| HSV.B/H/W/D | 0110001/0110011/0110101/0110111 · rs2 | 게스트 주소에 쓰기 (rd 자리는 0, HSV.D는 RV64 전용) |
HLVX는 게스트의 명령어를 가져오는 일을 흉내 내려고 만들었다. 실행만 되고 읽기는 안 되는 페이지에서도 명령어를 읽을 수 있어야 하기 때문이다. 그래도 예외는 명령 인출 예외가 아니라 일반 로드 예외로 보고된다. V=1에서 이 명령들을 실행하면 가상 명령 예외, HU=0인 U 모드에서 실행하면 불법 명령 예외다.
HFENCE.VVMA와 HFENCE.GVMA
| 명령 | 인코딩 | 무엇을 비우나 |
|---|---|---|
| HFENCE.VVMA rs1, rs2 | 0010001 rs2 rs1 000 00000 1110011 | 지금 hgatp.VMID 가상 머신의 VS-stage 변환. rs1 = 게스트 가상 주소, rs2 = 게스트 ASID. “잠깐 VS 모드에 들어가 SFENCE.VMA를 한 것”과 같다. |
| HFENCE.GVMA rs1, rs2 | 0110001 rs2 rs1 000 00000 1110011 | G-stage 변환. rs1 = 게스트 물리 주소 ≫ 2, rs2 = VMID. HS에서는 mstatus.TVM=0일 때만 실행된다. |
V=1에서 sfence.vma를 실행하면 그 인자는 게스트 가상 주소와 게스트 ASID로 해석되고, 현재 VMID의 VS-stage 변환에만 적용된다. 게스트 커널은 평소대로 sfence.vma를 쓰면 되고, G-stage는 건드릴 수 없다. G-stage 테이블을 고친 하이퍼바이저는 hfence.gvma로 정리해야 한다.
월드 스위치
하이퍼바이저가 게스트 A를 멈추고 게스트 B를 돌리는 일을 월드 스위치(world switch)라 한다. VS CSR 덕분에 저장할 것이 분명하다. 게스트의 정수 레지스터와 함께 vs* CSR을 메모리에 저장하고 B의 값을 넣으면 된다. 주의할 점은 vsatp와 hgatp를 한꺼번에 바꿀 방법이 없다는 것이다.
vsatp를 0으로 → hgatp 교체 → 새 vsatp)는 규격이 직접 권하는 순서다. 그래야 추측 실행이 A의 VS 변환을 B의 VMID로 TLB에 넣는 일이 없다.“No mechanism is provided to atomically change vsatp and hgatp together. Hence, to prevent speculative execution causing one guest’s VS-stage translations to be cached under another guest’s VMID, world-switch code should zero vsatp, then swap hgatp, then finally write the new vsatp value.”
시간도 가상화해야 한다. 게스트가 time을 읽으면 하드웨어가 htimedelta를 더한 값을 돌려준다. 게스트를 다른 하트로 옮기거나 잠시 멈췄다 다시 돌릴 때 게스트가 보는 시간이 매끄럽게 이어지게 하려는 것이다. Sstc가 있으면 게스트 커널은 stimecmp(실제로는 vstimecmp)에 직접 써서 타이머를 맞출 수 있고, 비교 기준은 time + htimedelta다.
게스트에게 인터럽트 전달하기
게스트 커널이 보는 인터럽트는 S 수준 인터럽트(소프트웨어 1, 타이머 5, 외부 9)다. 하지만 하드웨어에서 이들은 VS 수준 인터럽트(2, 6, 10)로 존재한다. hideleg의 비트 2·6·10을 켜 두면, VS 수준 인터럽트가 게스트에게 위임되면서 자동으로 S 수준 번호로 번역된다. 게스트의 vscause에는 9·5·1이 기록된다. 수정하지 않은 커널이 자기 인터럽트를 그대로 알아보게 하려는 장치다.
VS 수준 인터럽트가 걸리는 길은 세 가지다.
- 소프트웨어 주입: 하이퍼바이저가
hvip의 VSEIP(10)·VSTIP(6)·VSSIP(2)를 쓴다. 에뮬레이션한 장치의 인터럽트를 게스트에 넣을 때 쓴다. - 게스트 타이머: Sstc의
vstimecmp가 만료되면hip.VSTIP가 켜진다. - 게스트 외부 인터럽트: 장치를 게스트에 직접 붙여 준(pass-through) 경우, 인터럽트 컨트롤러가 가상 하트 몫의 인터럽트를 따로 모아
hgeip의 비트로 알린다.hstatus.VGEIN이 고른 비트가 곧 게스트의 외부 인터럽트(VSEIP)가 된다.
hgeip 비트 2가 켜지고, hstatus.VGEIN=2이므로 그대로 hip.VSEIP가 된다. hideleg[10]=1이면 게스트 커널은 하이퍼바이저의 개입 없이 외부 인터럽트(코드 9)를 받는다.AIA 규격(22장)의 IMSIC는 H 확장을 가진 하트에 대해 게스트 인터럽트 파일을 둘 수 있고, 그 개수는 정확히 GEILEN개다. 게스트 커널이 vsiselect·vsireg로 접근하는 인터럽트 파일은 hstatus.VGEIN이 고른 게스트 파일이다. 그래서 게스트는 자기 인터럽트 파일을 진짜 S 수준 파일처럼 다루고, 장치는 MSI를 그 파일에 바로 쓴다. 하이퍼바이저는 가상 하트를 다른 물리 하트로 옮길 때만 VGEIN과 IOMMU의 MSI 변환을 고치면 된다. GEILEN은 구현마다 다르며 RV64에서 최대 63, RV32에서 최대 31이다. 그보다 많은 가상 하트는 hvip로 흉내 내면 된다.
HS로 향하는 여러 인터럽트가 동시에 걸리면 SEI, SSI, STI, SGEI, VSEI, VSSI, VSTI, LCOFI 순으로 처리한다. VS 수준 인터럽트는 V=0(HS 실행 중)일 때도 hip에 대기 상태로 남아 있다가, 위임되어 있으면 게스트로 돌아간 뒤에 게스트에게 전달된다.
핵심 정리
- H 확장 v1.0은 가상화 모드 비트 V를 더해 M·HS·U(V=0)와 VS·VU(V=1)의 다섯 모드를 만든다. 일반 S 모드 OS는 고치지 않고 HS에서도, VS 게스트로도 돈다.
- V=1이면 S CSR 접근이 VS CSR(
0x2xx)로 바꿔치기된다. 하이퍼바이저 CSR(0x6xx)과 VS CSR을 이름 그대로 쓰려 하면 가상 명령 예외(22)가 난다. - 메모리는 VS-stage(vsatp)와 G-stage(hgatp) 두 번 변환된다. Sv39x4·Sv48x4·Sv57x4는 주소를 2비트 넓히고 루트 테이블만 16 KiB로 키운다. G-stage 접근은 U 모드 접근으로 검사한다.
- TLB가 모두 빗나가면 페이지 테이블 읽기는 n·m + n + m = (n+1)(m+1) − 1번이다. Sv39/Sv39x4는 15번, Sv48/Sv48x4는 24번. TLB와 VMID가 이 비용을 숨긴다.
- 게스트 페이지 폴트(20·21·23)와 가상 명령 예외(22), VS ecall(10)은 VS로 위임할 수 없다. 트랩은
medeleg→hedeleg순으로 M → HS → VS로 내려간다.htval은 GPA ≫ 2를 담는다. - HLV·HLVX·HSV는 하이퍼바이저가 게스트 주소로 메모리에 접근하게 하고, HFENCE.VVMA·HFENCE.GVMA는 각 단계의 변환 캐시를 정리한다. 월드 스위치는 vsatp=0 → hgatp → vsatp 순서로 한다.
- VS 수준 인터럽트(2·6·10)는 위임되면 S 수준 번호(1·5·9)로 번역된다.
hvip주입,vstimecmp, IMSIC 게스트 인터럽트 파일 +VGEIN이 게스트에 인터럽트를 전한다.
확인 퀴즈
V=1인 게스트 커널이 csrr a0, sepc를 실행하면 무엇을 읽는가?
vsepc를 자기 주소(0x241)로 직접 읽으려 할 때 난다.게스트 커널이 읽은 주소의 GPA가 G-stage에 매핑되어 있지 않을 때 나는 예외와 코드는?
Sv39 게스트가 Sv39x4 G-stage 위에서 돌 때, TLB가 모두 빗나가면 데이터 접근 전에 페이지 테이블을 최대 몇 번 읽는가?
Sv39x4의 G-stage 루트 페이지 테이블이 16 KiB인 이유는?
게스트 커널의 ecall(VS 모드, 코드 10)을 hedeleg로 게스트 자신에게 위임할 수 있는가?
hideleg[6]=1일 때 VS 타이머 인터럽트를 받은 게스트의 vscause에 기록되는 인터럽트 코드는?
월드 스위치에서 규격이 권하는 CSR 쓰기 순서는?
HLVX.WU가 일반 HLV.WU와 다른 점은?
더 읽을거리
- The RISC-V Instruction Set Manual, Volume II — “H” Extension for Hypervisor Support, Version 1.0 (v20260120)
- Volume II — Control and Status Registers (CSR 주소 목록)
- Volume II — RISC-V Privileged Instruction Set Listings (HLV·HSV·HFENCE 인코딩)
- RISC-V Advanced Interrupt Architecture v1.0 — IMSIC (게스트 인터럽트 파일)
- Gerald J. Popek, Robert P. Goldberg, “Formal Requirements for Virtualizable Third Generation Architectures”, Communications of the ACM 17(7), 1974
- Ravi Bhargava, Benjamin Serebrin, Francesco Spadini, Srilatha Manne, “Accelerating Two-Dimensional Page Walks for Virtualized Systems”, ASPLOS 2008 — 중첩 페이지 워크 24회 분석
- 이 책의 18장(트랩), 19장(가상 메모리), 22장(SBI·AIA)