Chapter 19

가상 메모리: Sv32부터 Sv57까지

프로그램이 쓰는 주소는 진짜 메모리 칩의 주소가 아니다. 하트는 load·store·명령어 인출을 할 때마다 가상 주소를 물리 주소로 바꾸는데, 그 변환표가 메모리에 놓인 페이지 테이블이다. 이 장에서는 RISC-V 특권 규격이 정한 페이지 기반 가상 메모리 Sv32·Sv39·Sv48·Sv57을 비트 단위로 해부하고, 규격의 변환 알고리즘을 시뮬레이터로 한 단계씩 따라간다.

왜 가상 메모리인가

운영체제 위에서 도는 프로그램은 저마다 “0번지부터 시작하는 넓은 메모리를 혼자 쓴다”고 믿는다. 실제로는 수십 개의 프로세스가 한 개의 DRAM을 나눠 쓰고 있다. 이 착각을 만들어 주는 장치가 가상 메모리(virtual memory)다. 하트가 내놓는 모든 주소는 가상 주소(VA)이고, 하드웨어가 이를 물리 주소(PA)로 바꿔서 메모리에 접근한다.

변환은 바이트 단위가 아니라 페이지(page) 단위로 한다. RISC-V의 기본 페이지는 4 KiB다. 가상 주소 공간을 4 KiB 조각(페이지)으로, 물리 메모리를 같은 크기의 조각(프레임(frame))으로 자르고, “가상 페이지 몇 번은 물리 프레임 몇 번”이라는 대응표를 프로세스마다 하나씩 둔다.

프로세스 A (ASID 1) 물리 메모리 (DRAM) 프로세스 B (ASID 2) 코드 데이터 힙 공유 라이브러리 매핑 없음 (V=0) 스택 (아직 디스크) 코드 데이터 힙 공유 라이브러리 매핑 없음 (V=0) 스택 (아직 디스크) 프레임 0 · A 힙 프레임 1 · B 데이터 프레임 2 · A 코드 프레임 3 · 공유 코드 프레임 4 · B 코드 프레임 5 · A 데이터 프레임 6 · B 힙 디스크 (스왑)폴트가 나면 불러온다
그림 19-1. 두 프로세스가 같은 가상 주소를 써도 서로 다른 물리 프레임으로 간다(격리). 공유 라이브러리는 두 프로세스의 페이지가 같은 프레임 3을 가리킨다(공유). 스택처럼 아직 메모리에 없는 페이지는 접근할 때 페이지 폴트가 나고, 운영체제가 디스크에서 불러온 뒤 매핑을 채운다(요구 페이징).
얻는 것어떻게예
격리프로세스마다 다른 페이지 테이블. 남의 프레임은 아예 표에 없다.버그 난 프로그램이 커널이나 다른 프로그램을 덮어쓰지 못한다
요구 페이징V=0인 항목에 접근하면 페이지 폴트 → OS가 그때 프레임을 채운다실행 파일을 다 읽지 않고 시작, 스왑, 메모리 초과 할당
공유여러 표가 같은 프레임을 가리킨다공유 라이브러리, 공유 메모리, fork 뒤 copy-on-write
보호페이지마다 읽기·쓰기·실행·사용자 권한 비트코드 영역 쓰기 금지, 데이터 영역 실행 금지(W^X)
연속된 착각흩어진 프레임을 연속된 가상 주소로 보여 준다큰 배열을 할당할 때 물리적으로 붙은 메모리가 필요 없다
4 KiB를 고른 이유

규격의 해설은 “오랜 숙고 끝에 RV32와 RV64 모두 관례적인 4 KiB 페이지로 정했다”고 적는다. 큰 페이지는 TLB 한 칸이 덮는 범위를 넓히지만 내부 단편화로 메모리를 낭비한다. 기존 운영체제와 장치 드라이버를 옮기기 쉽게 4 KiB를 기본으로 두고, 큰 페이지가 필요할 때는 슈퍼페이지(이 장 뒤쪽)로 해결한다.

satp: 변환을 켜는 스위치

가상 메모리를 켜고 끄는 것은 S 모드 CSR 하나, satp(Supervisor Address Translation and Protection), CSR 주소 0x180다. 이 레지스터에는 세 가지가 들어 있다. 어떤 변환 방식을 쓸지(MODE), 지금 주소 공간의 이름표(ASID), 그리고 루트 페이지 테이블의 물리 주소를 4 KiB로 나눈 값(PPN)이다.

SXLEN=32RV32MODE311ASID30229PPN루트 페이지 테이블의 물리 페이지 번호21022SXLEN=64RV64MODE63604ASID594416PPN루트 페이지 테이블의 물리 페이지 번호43044
그림 19-2. satp의 비트 배치. 아래 숫자는 필드 폭(비트)이다. 세 필드 모두 WARL이라 아무 값이나 쓸 수 있지만 구현이 지원하는 값만 남는다. PPN에 주소가 아니라 “페이지 번호”를 넣기 때문에 RV32도 22비트 PPN으로 34비트(16 GiB) 물리 주소 공간을 가리킬 수 있다.
SXLENMODE 값이름뜻
320Bare변환·보호 없음 (VA = PA)
321Sv3232비트 가상 주소, 2단계 페이지 테이블
640Bare변환·보호 없음
641–7—표준용 예약
648Sv3939비트 가상 주소, 3단계
649Sv4848비트 가상 주소, 4단계
6410Sv5757비트 가상 주소, 5단계
6411Sv64예약 (앞으로 정의할 64비트 방식)
6412–13—표준용 예약
6414–15—사용자 정의(custom)용

satp에는 소프트웨어가 알아 두면 편리한 성질이 몇 가지 있다.

ASID와 PPN을 한 레지스터에 둔 이유

문맥 전환 때 ASID와 루트 테이블을 따로따로 바꾸면, 그 사이의 짧은 순간에 “새 ASID + 옛 테이블”이나 “옛 ASID + 새 테이블” 조합으로 변환이 일어나 남의 주소 공간 항목이 TLB에 섞일 수 있다. 한 CSR에 두면 csrw satp 한 번으로 둘이 원자적으로 바뀐다.

# a0 = 새 루트 페이지 테이블의 물리 주소, a1 = 새 ASID srli t0, a0, 12 # PPN = 물리 주소 / 4096 slli t1, a1, 44 # ASID를 비트 59:44 자리로 or t0, t0, t1 li t1, 8 # MODE = 8 (Sv39) slli t1, t1, 60 # MODE를 비트 63:60 자리로 or t0, t0, t1 csrw satp, t0 # 세 필드가 한꺼번에 바뀐다

가상 주소의 모양: Sv32·Sv39·Sv48·Sv57

4 KiB 페이지에서 주소의 아래 12비트는 페이지 안의 위치, 즉 페이지 오프셋(page offset)이고 변환 없이 그대로 물리 주소로 넘어간다. 나머지 위쪽 비트가 가상 페이지 번호(VPN)다. VPN을 한 번에 큰 표로 찾지 않고 여러 조각으로 나눠, 조각 하나가 페이지 테이블 한 단계의 색인이 되도록 한다. 이것이 다단계(radix-tree) 페이지 테이블이다.

조각의 크기는 “페이지 테이블 하나는 정확히 한 페이지”라는 원칙에서 나온다. Sv39의 PTE는 8바이트이므로 4096 ÷ 8 = 512개 = 29, 그래서 VPN 조각은 9비트다. Sv32의 PTE는 4바이트라서 한 표에 1024개 = 210, 조각이 10비트다.

Sv32VA 32비트VPN[1]3122VPN[0]2112page offset110Sv39VA 39비트부호 확장= 비트 386339VPN[2]3830VPN[1]2921VPN[0]2012offset110Sv48VA 48비트부호 확장= 비트 476348VPN[3]4739VPN[2]3830VPN[1]2921VPN[0]2012offset110Sv57VA 57비트부호 확장= 비트 566357VPN[4]5648VPN[3]4739VPN[2]3830VPN[1]2921VPN[0]2012offset110Sv39 PA물리 56비트PPN[2]5530PPN[1]2921PPN[0]2012page offset110
그림 19-3. 가상 주소의 분할. 64비트 방식(Sv39·Sv48·Sv57)은 VPN 조각이 모두 9비트이고 단계만 하나씩 늘어난다. 위쪽 회색 점선 부분은 쓰지 않는 비트로, 반드시 맨 위 VPN 비트를 복사한 값(부호 확장)이어야 한다. 맨 아래는 Sv39의 물리 주소 56비트(PPN 44비트 + 오프셋 12비트)다.
방식VA 비트단계VPN 조각PTEPA 비트지원 페이지 크기주소 공간
Sv3232210 + 104 B344 KiB, 4 MiB4 GiB
Sv393939 × 38 B564 KiB, 2 MiB, 1 GiB512 GiB
Sv484849 × 48 B56+ 512 GiB256 TiB
Sv575759 × 58 B56+ 256 TiB128 PiB

정규형 주소와 가운데 “구멍”

RV64의 레지스터는 64비트지만 Sv39가 실제로 쓰는 가상 주소는 39비트뿐이다. 규격은 명령어 인출 주소와 load/store 유효 주소의 비트 63–39가 모두 비트 38과 같아야 하며, 그렇지 않으면 페이지 폴트가 난다고 정한다(Sv48은 63–48이 비트 47과, Sv57은 63–57이 비트 56과 같아야 한다). 이런 주소를 흔히 정규형 주소(canonical address)라 부른다.

결과적으로 쓸 수 있는 주소는 64비트 공간의 맨 아래 절반 조각과 맨 위 절반 조각이 되고, 그 사이는 거대한 구멍이다. 운영체제는 보통 아래쪽을 사용자 공간, 위쪽(최상위 비트가 1)을 커널 공간으로 쓴다. 최상위 비트 하나만 보고 사용자 주소인지 커널 주소인지 바로 가를 수 있어 편리하다. 규격 해설도 이것이 0 확장이 아니라 “굳어진 관례”를 따른 선택이라고 설명한다.

더 넓은 방식은 좁은 방식을 포함한다

Sv48을 구현하면 Sv39도 반드시 구현해야 하고, Sv57을 구현하면 Sv48도 구현해야 한다. 더 넓은 하드웨어는 사실상 공짜로 좁은 방식을 지원할 수 있기 때문이다. 리눅스는 부팅할 때 하드웨어가 지원하는 가장 넓은 방식을 고르고, 커널 인자 no5lvl·no4lvl로 낮출 수 있다.

EXPLORER

가상 주소 형식 탐색기

변환 방식
예시 주소
satp.MODE8
단계 수3
주소 공간512 GiB
가장 큰 페이지1 GiB
주소 검사—
방식을 Sv39 → Sv48 → Sv57로 바꾸며 회색 부호 확장 영역이 줄고 VPN 조각이 하나씩 늘어나는 것을 보자. 그다음 0x00007fffffffffff(Sv48 아래쪽 절반의 끝)를 넣고 Sv39와 Sv48에서 판정이 어떻게 달라지는지 확인해 보자. 거꾸로 Sv39에서 정규형인 주소는 Sv48·Sv57에서도 언제나 정규형이다.

페이지 테이블 항목(PTE)

페이지 테이블의 한 칸이 페이지 테이블 항목(PTE)이다. 하위 10비트는 상태와 권한 비트이고, 그 위가 물리 페이지 번호(PPN)다. Sv39·Sv48·Sv57의 PTE는 아래 10비트와 위쪽 10비트(63–54)의 뜻이 모두 같고, 가운데 PPN을 몇 조각으로 나눠 보느냐만 다르다.

Sv39PTE 64비트N631PBMT62612예약60547PPN[2]532826PPN[1]27199PPN[0]18109RSW982D71A61G51U41X31W21R11V01Sv32PTE 32비트PPN[1]312012PPN[0]191010RSW982D71A61G51U41X31W21R11V01 유효 권한 R·W·X 사용자 U 확장용: N(Svnapot) · PBMT(Svpbmt) 소프트웨어용 RSW · 예약
그림 19-4. Sv39(위)와 Sv32(아래)의 PTE. 아래쪽 8비트 D·A·G·U·X·W·R·V와 소프트웨어용 RSW 2비트는 두 형식이 똑같다. Sv39의 비트 63은 Svnapot의 N, 62–61은 Svpbmt의 PBMT, 60–54는 예약이다. 해당 확장이 없거나 꺼져 있으면 이 비트들은 0이어야 하고, 0이 아니면 페이지 폴트가 난다.
비트이름뜻
0V Valid1이면 유효. 0이면 나머지 비트는 하드웨어가 보지 않으므로 OS가 자유롭게 쓴다(예: 스왑 위치 기록).
1–3R W X읽기·쓰기·실행 권한. 셋 다 0이면 이 PTE는 잎이 아니라 다음 단계 표를 가리키는 포인터다.
4U UserU 모드가 접근할 수 있는 페이지. S 모드는 SUM=1일 때만 load/store할 수 있고, 실행은 언제나 금지.
5G Global모든 주소 공간에 똑같이 있는 매핑(커널 영역 등). TLB가 ASID와 상관없이 공유할 수 있다. 포인터 PTE에 G가 있으면 그 아래 매핑이 모두 전역이다.
6A Accessed마지막으로 지운 뒤 읽기·쓰기·인출이 있었음. 페이지 교체 정책의 힌트.
7D Dirty마지막으로 지운 뒤 쓰기가 있었음. 내보낼 때 디스크에 다시 써야 하는지 알려 준다.
8–9RSW감독자 소프트웨어용. 하드웨어는 무시한다.
10–53PPN잎이면 데이터 페이지의, 포인터면 다음 단계 표의 물리 페이지 번호(44비트).
54–60예약0이어야 한다. Svrsw60t59b 확장이 있으면 60–59는 소프트웨어용이 된다.
61–62PBMTSvpbmt: 페이지 단위 메모리 유형(PMA·NC·IO). 잎 PTE에서만 의미가 있다.
63NSvnapot: 연속된 64 KiB 영역의 일부임을 표시.

R·W·X 조합과 잎/포인터

XWR뜻
000다음 단계 페이지 테이블을 가리키는 포인터
001읽기 전용 페이지
010예약 쓰기만 되는 페이지는 없다
011읽기·쓰기 페이지
100실행 전용 페이지
101읽기·실행 페이지
110예약
111읽기·쓰기·실행 페이지
규격 원문에서
“The permission bits, R, W, and X, indicate whether the page is readable, writable, and executable, respectively. When all three are zero, the PTE is a pointer to the next level of the page table; otherwise, it is a leaf PTE. Writable pages must also be marked readable; the contrary combinations are reserved for future use.”

Volume II, Sv32: Addressing and Memory Protection — Sv39 이후의 방식도 “비트 9–0은 Sv32와 같은 뜻”이라며 이 정의를 그대로 물려받는다.

포인터 PTE (비잎) PPN = 0x80401 X W R0 0 0 다음 단계 페이지 테이블 주소 = PPN × 4096 (512개 PTE) 잎 PTE (leaf) PPN = 0x81001 X W R1 0 1 데이터 페이지 (여기서 변환 끝) 크기는 잎이 나온 단계가 정한다: 4 KiB · 2 MiB · 1 GiB …
그림 19-5. 같은 PPN 필드라도 R·W·X가 모두 0이면 “다음 표가 있는 곳”, 하나라도 1이면 “데이터가 있는 곳”이다. 포인터 PTE의 D·A·U 비트는 앞으로의 표준 용도로 예약되어 있어 소프트웨어가 0으로 두어야 한다.

페이지 테이블 워크: 규격의 변환 알고리즘

TLB에 답이 없으면 하드웨어(페이지 워커)가 메모리의 페이지 테이블을 직접 따라 내려간다. 이를 페이지 테이블 워크(page-table walk)라 한다. Sv39에서는 satp가 가리키는 루트 표에서 VPN[2]로 한 칸을 고르고, 그 칸이 가리키는 표에서 VPN[1]로, 다시 VPN[0]으로 고른다.

VA VPN[2] VPN[1] VPN[0] offset 63–39 satpPPN × 4096 [0][511] [0][511] [0][511] 포인터 PTE 포인터 PTE 잎 PTE 4 KiB 페이지 찾는 바이트 레벨 2 (루트) 레벨 1 레벨 0 데이터 PTE 주소 = 표의 시작 주소 + VPN[i] × 8
그림 19-6. Sv39 워크. 점선은 VA의 각 조각이 각 표의 몇 번째 칸인지 고르는 색인이고, 실선 화살표는 PTE의 PPN이 다음 표(또는 데이터 페이지)의 위치를 알려 주는 포인터다. 금색 점이 satp에서 출발해 표 세 개를 거쳐 찾는 바이트에 이르는 길을 보여 준다. TLB가 없다면 load 하나에 메모리 접근이 네 번(PTE 셋 + 데이터 하나) 필요하다.

규격은 이 과정을 알고리즘으로 정확히 적어 두었다. Sv32를 기준으로 쓰고, Sv39·Sv48·Sv57은 LEVELS와 PTESIZE만 바꾸면 된다(Sv39: LEVELS=3, PTESIZE=8). 아래는 Sv39에 맞춰 옮긴 것이다.

  1. a = satp.ppn × 4096, i = 2로 시작한다. (실효 특권 모드가 S나 U여야 한다.)
  2. pte = 메모리[a + va.vpn[i] × 8]를 읽는다. 이 읽기가 PMA·PMP 검사에 걸리면 원래 접근 종류의 접근 폴트(access fault)다.
  3. V=0이거나, R=0인데 W=1이거나, 예약 비트·예약 인코딩이 켜져 있으면 페이지 폴트.
  4. R=1 또는 X=1이면 잎이므로 5단계로. 아니면 포인터다: i = i − 1. i < 0이면 페이지 폴트. 아니면 a = pte.ppn × 4096으로 놓고 2단계로 돌아간다.
  5. 잎에 도착했다. i > 0인데 pte.ppn[i−1:0] ≠ 0이면 정렬되지 않은 슈퍼페이지 → 페이지 폴트.
  6. 현재 특권 모드와 SUM·MXR을 고려해 U 비트가 이 접근을 허락하는지 본다. 아니면 페이지 폴트.
  7. 그림자 스택(Zicfiss) 규칙상 R·W·X 조합이 허락되지 않으면 접근 폴트.
  8. R·W·X 비트가 이 접근을 허락하는지 본다. 아니면 페이지 폴트.
  9. A=0이거나, 쓰기인데 D=0이면: Svade라면 페이지 폴트, 아니면 PTE를 원자적으로 비교·갱신해 A(쓰기면 D도)를 1로 만든다.
  10. 성공. pa.pgoff = va.pgoff. i > 0이면 슈퍼페이지이므로 pa.ppn[i−1:0] = va.vpn[i−1:0], 그리고 pa.ppn[2:i] = pte.ppn[2:i].
페이지 폴트와 접근 폴트는 다르다

“페이지 테이블이 이 접근을 허락하지 않는다”면 페이지 폴트(12·13·15)다. 반면 PTE를 읽으러 간 물리 주소나 최종 물리 주소가 PMP·PMA 검사에 걸리면 접근 폴트(1·5·7)다. 운영체제는 앞의 것만 고칠 수 있다. 뒤의 것은 M 모드가 친 울타리(20장)에 부딪힌 것이다.

SIMULATOR

Sv39 페이지 테이블 워크

접근
특권 모드
A/D 갱신
sstatus
실행
가상 주소—
결과—
PTE 읽기0
PTE 쓰기 (A/D)0
시나리오를 하나씩 골라 한 단계를 눌러 보자. 2 MiB·1 GiB 시나리오에서는 워크가 레벨 1·2에서 일찍 끝나고 VPN 조각이 오프셋으로 바뀌는 것을, ⑦·⑧에서는 SUM·MXR 상자를 켜고 다시 돌렸을 때 결과가 바뀌는 것을 확인하자. 가상 주소 칸에 직접 주소를 넣어도 된다(이 메모리에는 시나리오에 나온 매핑만 있다).

슈퍼페이지와 Svnapot

알고리즘 4단계를 다시 보자. 잎은 꼭 레벨 0에서만 나오는 것이 아니다. 어느 단계의 PTE든 잎이 될 수 있다. Sv39에서 레벨 1에서 잎이 나오면 VPN[0]과 오프셋을 합친 21비트가 통째로 오프셋이 되어 2 MiB짜리 메가페이지(megapage)가 되고, 레벨 2에서 나오면 30비트 오프셋의 1 GiB 기가페이지(gigapage)가 된다. Sv48은 512 GiB 테라페이지, Sv57은 256 TiB 페타페이지까지 지원한다.

VA 비트 38 ← → 0 4 KiB 페이지레벨 0에서 잎 VPN[2] VPN[1] VPN[0] offset 12비트 PTE 읽기 3번TLB 1칸 = 4 KiB 2 MiB 메가페이지레벨 1에서 잎 VPN[2] VPN[1] offset 21비트 (VPN[0] 포함) PTE 읽기 2번TLB 1칸 = 2 MiB 1 GiB 기가페이지레벨 2에서 잎 VPN[2] offset 30비트 (VPN[1]·VPN[0] 포함) PTE 읽기 1번TLB 1칸 = 1 GiB 정렬 조건 메가페이지 잎은 pte.ppn[0] = 0, 기가페이지 잎은 pte.ppn[1:0] = 0 이어야 한다. 아니면 “정렬되지 않은 슈퍼페이지” 페이지 폴트.
그림 19-7. 잎이 위쪽 단계에서 나올수록 페이지가 커지고, 아래쪽 VPN 조각은 변환되지 않는 오프셋이 된다. 슈퍼페이지는 가상 주소와 물리 주소가 모두 자기 크기의 배수에 정렬되어 있어야 한다. 물리 쪽 정렬은 PTE의 아래쪽 PPN 조각이 0인지로 검사한다.

슈퍼페이지의 가장 큰 이득은 TLB 도달 범위다. TLB 한 칸이 4 KiB 대신 2 MiB나 1 GiB를 덮으므로, 커다란 데이터를 다루는 프로그램이나 커널의 물리 메모리 직접 매핑(linear map)에서 TLB 미스가 크게 준다. 워크도 짧아진다. 리눅스의 transparent huge page와 hugetlbfs가 이 기능을 쓴다.

Svnapot: 64 KiB 연속 페이지

2 MiB는 너무 크고 4 KiB는 너무 작을 때가 있다. Svnapot(NAPOT Translation Contiguity, v1.0)은 레벨 0의 PTE 16개가 64 KiB의 연속된 영역을 함께 매핑한다고 표시하는 방법이다. PTE의 N 비트(63)를 1로 하고, pte.ppn[0]의 아래 4비트를 1000으로 둔다. 현재 표준이 정한 크기는 64 KiB(napot_bits = 4) 하나뿐이고, 레벨 0이 아니거나 다른 패턴이면 예약 인코딩이라 페이지 폴트가 난다.

레벨 0 표: 정렬된 16칸 16개 모두 N=1, ppn[0] = …x 1000 (보통 같은 값을 16번 복사해 둔다) TLB 한 칸 VPN 하위 4비트 무시 → 64 KiB를 덮는다 물리 메모리 64 KiB 4 KiB × 16, 64 KiB 정렬 pa.ppn[0][3:0] ← va.vpn[0][3:0]
그림 19-8. Svnapot. 64 KiB로 정렬된 16개 PTE가 한 연속 영역을 이룬다. 하드웨어는 NAPOT PTE를 읽을 때 ppn[0]의 아래 4비트를 VA의 vpn[0] 아래 4비트로 바꿔 끼운 값을 쓰므로, 16칸 중 어느 칸을 읽어도 같은 결과가 나오고 TLB는 한 칸으로 64 KiB를 기억할 수 있다.
NAPOT 영역의 A·D 비트

구현은 NAPOT 영역을 한 TLB 칸으로 처리하면서 16개 PTE 중 아무것이나 참고할 수 있다. 그래서 A·D 비트가 16개 사이에서 서로 다를 수 있다. 운영체제는 한 페이지가 접근되었는지(또는 수정되었는지) 알려면 16개 별칭을 모두 확인해야 한다. 영역을 바꿀 때는 먼저 16개를 모두 무효로 만들고 SFENCE.VMA를 한 다음 새 값을 쓴다.

권한 검사와 페이지 폴트

잎을 찾았다고 끝이 아니다. 알고리즘 5–9단계는 이 접근이 허락되는지 차례로 검사한다. 어느 하나라도 통과하지 못하면 원래 접근 종류에 맞는 페이지 폴트가 나고, stval(또는 mtval)에 문제의 가상 주소가 담긴다.

① 정렬 검사i>0 이면ppn[i−1:0] = 0 ? ② U 비트특권 모드와SUM을 함께 본다 ③ R·W·X읽기는 MXR=1이면X 페이지도 허용 ④ A·D 비트A=0 또는 (쓰기·D=0)Svadu 갱신 / Svade 폴트 아니오거부거부Svade 통과 페이지 폴트 · scause = 12 / 13 / 15 인출이면 12, load면 13, store·AMO면 15 · stval = 가상 주소 물리 주소 완성 이어서 PMP·PMA 검사 · S 모드는 U=1 페이지의 명령어를 SUM과 상관없이 실행할 수 없다. · U 모드는 U=0 페이지에 전혀 접근할 수 없다. · AMO는 load 페이지 폴트를 내지 않는다(항상 15).
그림 19-9. 잎 PTE를 찾은 뒤의 검사 순서(알고리즘 5–9단계). 어느 검사에서 걸리든 결과는 같은 종류의 페이지 폴트이고, 예외 코드는 실패한 검사가 아니라 원래 접근 종류가 정한다.
접근 종류페이지 폴트 (scause)접근 폴트이 페이지 폴트를 내는 명령
명령어 인출12 Instruction page fault1X=0 페이지에서 실행, S 모드가 U=1 페이지 실행
load13 Load page fault5load, LR, 캐시 블록 관리(CMO) 명령
store·AMO15 Store/AMO page fault7store, SC, AMO, cbo.zero

SUM과 MXR: sstatus의 두 스위치

sstatus의 두 비트가 권한 검사를 바꾼다. SUM(permit Supervisor User Memory access, 비트 18)이 0이면 S 모드가 U=1 페이지에 load/store하는 순간 폴트가 난다. 커널은 평소에 SUM=0으로 두고, copy_from_user처럼 사용자 메모리를 일부러 만지는 짧은 구간에서만 SUM을 켠다. 실수로 사용자 포인터를 따라가는 버그를 잡아 주는 안전장치다. MXR(Make eXecutable Readable, 비트 19)이 1이면 X=1 페이지도 읽을 수 있다. 실행 전용 페이지에서 명령어를 읽어 에뮬레이션해야 하는 트랩 처리기에 쓴다.

모드페이지 U읽기·쓰기실행
U1R/W 비트대로X 비트대로
U0폴트폴트
S0R/W 비트대로X 비트대로
S1SUM=1이면 R/W 비트대로, SUM=0이면 폴트항상 폴트

SUM·MXR을 바꾸는 것은 SFENCE.VMA 없이 즉시 효력이 있다. TLB 항목에는 PTE의 권한 비트가 그대로 들어 있고, 검사는 매 접근마다 현재 SUM·MXR 값으로 하기 때문이다.

A·D 비트는 누가 켜나: Svade와 Svadu

운영체제는 A 비트로 “최근에 쓰인 페이지”를, D 비트로 “디스크에 다시 써야 할 페이지”를 가려낸다. 이 비트를 켜는 방법은 두 가지다.

방식A=0 또는 (쓰기·D=0)을 만나면장단점
Svade페이지 폴트. OS 처리기가 비트를 켜고 명령을 다시 실행한다.하드웨어가 단순. 처음 접근·처음 쓰기마다 트랩 비용
Svadu (v1.0)하드웨어가 PTE를 원자적으로 비교·갱신해 A(와 D)를 켠다.트랩 없음. 페이지 워커가 메모리에 원자적으로 써야 한다

Svadu가 있으면 menvcfg.ADUE(비트 61)로 하드웨어 갱신을 켜고 끈다. ADUE=0이면 Svade처럼 동작한다. A 비트는 추측 실행 때문에 켜져도 되지만, D 비트는 실제로 실행된 store 때문에만, 그리고 프로그램 순서대로 켜져야 한다. 하드웨어가 A·D를 끄는 일은 없다. 스왑이나 메모리 매핑 입출력처럼 A·D가 필요 없는 페이지라면 처음부터 둘 다 1로 두는 것이 빠르다.

TLB, ASID, 그리고 SFENCE.VMA

load 하나마다 메모리를 세 번 더 읽는다면 컴퓨터는 몇 배 느려질 것이다. 그래서 하트는 최근 변환 결과를 TLB(Translation Lookaside Buffer)에 저장한다. 규격은 TLB라는 말 대신 주소 변환 캐시(address-translation cache)라는 추상적 이름을 쓰고, 그 크기와 구조는 정하지 않는다. 대신 이 캐시가 메모리의 페이지 테이블과 자동으로 일치하지 않는다(incoherent)는 점, 그리고 소프트웨어가 SFENCE.VMA로 동기화해야 한다는 점을 정한다.

VA의 VPN0x00010 satp.ASID2 V ASID G VPN PPN 권한 1100x000100x81010RW-U 1200x000100x82010RW-U 1–1커널0x80200R-X- 0––(빈 칸)–– 같은 VPN 0x00010이 ASID 1과 2에 따로 들어 있어도 섞이지 않는다 적중 조건 V = 1 VPN 같음 ASID 같음 또는 G = 1 → PA = PPN ‖ offset TLB의 구조(칸 수, 연관도, 단계 수)는 규격이 정하지 않는다. 하트마다 따로 있다고 보이기만 하면 된다.
그림 19-10. ASID를 쓰는 TLB 조회. 각 칸에 ASID가 붙어 있으므로 문맥 전환 때 TLB를 비울 필요가 없다. G=1(전역) 항목은 ASID를 따지지 않고 모든 주소 공간에서 적중한다. 커널 매핑에 G를 붙이면 프로세스 수만큼 같은 항목을 중복 저장하지 않아도 된다.

SFENCE.VMA의 네 가지 모양

sfence.vma rs1, rs2(인코딩 0001001 rs2 rs1 000 00000 1110011)는 “앞서 이 하트에 보인 페이지 테이블 쓰기”를 “뒤따르는 변환을 위한 암묵적 읽기”보다 앞에 오도록 순서를 맞추고, 관련된 변환 캐시 항목을 무효화한다. rs1은 가상 주소, rs2는 ASID를 지정하며 레지스터 번호가 x0인지에 따라 범위가 달라진다.

rs1rs2범위전역(G) 항목쓰는 때
x0x0모든 주소 공간의 모든 단계포함전역 매핑을 바꾼 경우, 대규모 변경
x0ASID그 ASID의 모든 단계제외ASID 재사용, 포인터(비잎) PTE 변경
VAx0그 VA의 잎 PTE, 모든 주소 공간포함전역 매핑의 잎 하나 변경
VAASID그 VA의 잎 PTE, 그 ASID만제외사용자 페이지 하나의 잎 변경 (가장 흔함)
# a0 = PTE의 주소, a1 = 그 페이지의 가상 주소, a2 = ASID sd zero, 0(a0) # PTE를 무효로 (8바이트 한 번에 써야 한다) sfence.vma a1, a2 # 이 VA, 이 ASID의 잎 항목만 무효화 # 다른 하트도 이 주소 공간을 쓰고 있었다면 IPI를 보내 # 그쪽에서도 sfence.vma를 실행하게 해야 한다 (TLB shootdown)

규격이 꼽는 대표적인 규칙은 다음과 같다.

SIMULATOR

TLB와 ASID: 두 프로세스가 번갈아 달릴 때

칸VASIDG가상 페이지PPN최근 사용
ASID
실행
지금 실행 중인 하트에서 SFENCE.VMA (a0 = 0x10000, a1 = 현재 ASID)
실행 중 · ASID—
적중0
미스0
적중률—
워크 PTE 읽기0
ASID 사용으로 끝까지 돌린 결과와 ASID 없음으로 돌린 결과의 적중률을 비교해 보자. ASID가 없으면 프로세스를 바꿀 때마다 비전역 항목을 비워야 하므로 A로 돌아온 뒤 다시 미스가 난다. 커널 페이지(G=1)는 어느 경우든 살아남는다. 중간에 네 가지 sfence.vma 버튼을 눌러 어떤 칸이 지워지는지(빨간 줄) 비교해 보자.

Sv 확장들: Svpbmt·Svinval·Svvptc와 친구들

특권 규격의 감독자 장에는 기본 Sv 방식 외에도 작은 확장들이 모여 있다. 응용 프로세서 프로파일 RVA23S64는 이 중 상당수를 필수로 요구한다(23장).

확장버전하는 일RVA23S64
Sv39—39비트 3단계 변환필수
Sv48 · Sv57—48·57비트 변환선택
Svnapot1.064 KiB NAPOT 연속 매핑 (N 비트)필수
Svpbmt1.0PTE로 PMA 메모리 유형 덮어쓰기 (PBMT)필수
Svinval1.0SFENCE.VMA를 무효화와 순서 맞추기로 쪼갬필수
Svade—A/D가 꺼져 있으면 페이지 폴트필수
Svadu1.0하드웨어가 A/D를 원자적으로 갱신선택
Svvptc1.0무효→유효 PTE 변경 뒤 SFENCE.VMA 생략 가능선택
Svrsw60t59b1.0PTE 비트 60–59를 소프트웨어용으로—

Svpbmt: 페이지마다 메모리 유형 정하기

메모리 영역이 캐시 가능한지, 입출력 장치인지 같은 성질(PMA)은 원래 물리 주소마다 고정이다. Svpbmt은 잎 PTE의 비트 62–61(PBMT)로 이를 덮어쓴다. 예를 들어 그래픽 프레임 버퍼를 캐시하지 않되 쓰기 결합은 허용하는 NC로 매핑할 수 있다. menvcfg.PBMTE(비트 62)가 1일 때만 쓸 수 있고, 0이면 Svpbmt가 없는 것처럼 동작한다.

이름PBMT요청하는 메모리 속성
PMA0덮어쓰지 않음. 물리 주소의 PMA를 그대로
NC1캐시 불가, 멱등(idempotent), 약한 순서(RVWMO), 주 메모리
IO2캐시 불가, 비멱등, 강한 순서(I/O 순서), 입출력
—3예약 (잎에 쓰면 페이지 폴트)

포인터 PTE의 비트 62–61은 예약이다. 같은 물리 페이지를 캐시 가능 속성과 불가 속성으로 동시에 매핑하면 일관성이 깨질 수 있으므로, 속성을 바꿔 접근하기 전에는 fence iorw,iorw → cbo.flush → fence iorw,iorw 같은 순서가 필요하다고 규격은 설명한다.

Svinval: 무효화를 묶어서 빠르게

범위가 넓은 매핑을 해제할 때 SFENCE.VMA를 페이지마다 실행하면, 매번 “앞선 쓰기 정렬 + 무효화 + 뒤따르는 변환 정렬”을 다 하느라 파이프라인이 거듭 멈춘다. Svinval은 이것을 세 명령으로 쪼갠다.

명령인코딩하는 일
sfence.w.inval0001100 00000 00000 000 00000 1110011앞선 store를 뒤따르는 SINVAL.VMA보다 앞에 정렬
sinval.vma rs1, rs20001011 rs2 rs1 000 00000 1110011같은 rs1·rs2의 SFENCE.VMA가 지울 항목을 무효화 (순서 보장은 없음)
sfence.inval.ir0001100 00001 00000 000 00000 1110011앞선 SINVAL.VMA를 뒤따르는 암묵적 변환 읽기보다 앞에 정렬
sfence.vma × 4 순서 맞추기가 8번 Svinval 묶음 w.inval inval.ir sinval.vma × 4 (겹쳐서 처리) 순서 맞추기 2번이면 끝 시간 → 순서 맞추기(멈춤) 무효화 작업
그림 19-11. 네 페이지를 무효화할 때의 개념도. SFENCE.VMA는 매번 앞뒤 순서 맞추기(빨강)를 동반하지만, Svinval은 SFENCE.W.INVAL과 SFENCE.INVAL.IR 사이에 SINVAL.VMA를 몰아넣어 무효화(파랑)를 겹쳐 처리할 수 있다. 단순한 구현은 SINVAL.VMA를 SFENCE.VMA처럼, 나머지 둘을 no-op로 구현해도 된다.

Svinval은 하이퍼바이저 확장이 있으면 hinval.vvma·hinval.gvma도 함께 제공한다(21장). SINVAL.VMA는 SFENCE.VMA와 같은 권한을 요구하므로 mstatus.TVM=1이면 S 모드에서 불법 명령 예외가 나지만, 두 순서 명령은 부작용이 없어 TVM과 상관없이 허용된다.

Svvptc와 Svrsw60t59b

Svvptc가 있으면 PTE의 V 비트를 0→1로 바꾼 store는 “유한한 시간 안에” 이 하트의 변환에 보이게 된다. 그래서 새 매핑을 만들 때마다 하던 SFENCE.VMA를 생략할 수 있다. 가끔 쓸데없는 페이지 폴트가 한 번 더 날 수 있지만, 명령을 아끼는 이득이 더 크다는 판단이다. 유효→무효, 권한 축소 같은 다른 변경에는 여전히 펜스가 필요하다. Svrsw60t59b는 원래 예약이라 0이어야 했던 PTE 비트 60–59를 운영체제가 메타데이터 저장용으로 쓸 수 있게 풀어 준다.

핵심 정리

  1. 가상 메모리는 페이지(기본 4 KiB) 단위로 VA를 PA로 바꾸며 격리·요구 페이징·공유·보호를 제공한다.
  2. satp(0x180)는 MODE(RV64: Bare 0, Sv39 8, Sv48 9, Sv57 10 / RV32: Sv32 1), ASID, 루트 표의 PPN을 담는다. 지원하지 않는 MODE를 쓰면 쓰기 전체가 무시된다.
  3. 페이지 테이블 하나는 정확히 한 페이지다. Sv39는 8바이트 PTE 512개이므로 VPN 조각이 9비트이고, Sv32는 4바이트 PTE 1024개라서 10비트다. Sv39 이상은 VA의 위쪽 비트가 부호 확장된 정규형이어야 한다.
  4. PTE의 R=W=X=0은 포인터, 하나라도 1이면 잎이다. W=1·R=0은 예약 조합이다. 어느 단계든 잎이 될 수 있어 메가·기가·테라·페타페이지가 생기며, 물리 쪽 정렬이 안 맞으면 페이지 폴트다.
  5. 권한 검사는 U 비트(SUM 고려), R·W·X(MXR 고려), A·D(Svade는 폴트, Svadu는 하드웨어 갱신) 순이며, 실패하면 접근 종류에 따라 12·13·15번 페이지 폴트가 난다.
  6. TLB는 ASID와 G 비트로 주소 공간을 구별한다. 페이지 테이블을 고친 뒤에는 rs1(VA)·rs2(ASID)로 범위를 고른 sfence.vma로 자기 하트를 동기화하고, 다른 하트에는 IPI로 알린다.
  7. Svnapot(64 KiB 연속), Svpbmt(PMA·NC·IO), Svinval(무효화 묶음), Svvptc(무효→유효 펜스 생략)는 성능과 장치 매핑을 돕는 작은 확장이다.

확인 퀴즈

Sv39의 페이지 테이블 하나에 들어 있는 PTE 수와 PTE 하나의 크기는?

표 하나가 정확히 4 KiB 페이지 하나이고 PTE가 8바이트이므로 512개다. 그래서 VPN 조각이 9비트다. 1024개 × 4바이트는 Sv32다.

V=1이고 R=W=X=0인 PTE는 무엇을 뜻하나?

R·W·X가 모두 0이면 잎이 아니라 비잎(포인터) PTE다. PPN × 4096이 다음 표의 주소가 된다. 예약 조합은 W=1·R=0인 경우다.

Sv39에서 가상 주소 0x0000004000000000에 load하면?

비트 38이 1인데 그 위 비트는 0이라 정규형이 아니다. 규격은 이 경우 페이지 폴트를 낸다고 정한다.

Sv39 워크가 레벨 1에서 잎 PTE를 만났는데 pte.ppn[0] ≠ 0이다. 결과는?

메가페이지는 물리적으로도 2 MiB에 정렬되어야 한다. 알고리즘 5단계에서 pte.ppn[i−1:0] ≠ 0이면 페이지 폴트다.

S 모드에서 SUM=1로 두고 U=1 페이지의 명령어를 실행하려 하면?

SUM은 S 모드의 load/store만 허락한다. 사용자 페이지에서 실행하는 것은 SUM과 상관없이 금지된다. 공격자가 심은 코드를 커널이 실행하지 못하게 하려는 것이다.

쓰기 권한(W=0)이 없는 페이지에 AMO 명령을 실행했다. scause는?

AMO는 읽고 쓰는 명령이지만 load 페이지 폴트는 내지 않는다. 권한 문제는 항상 store/AMO 페이지 폴트(15)로 보고된다. 7은 PMP·PMA에 걸렸을 때다.

ASID 3인 프로세스의 사용자 페이지 하나(전역 아님)의 잎 PTE를 고쳤다. 가장 알맞은 명령은?

잎 하나를 바꿨으면 rs1에 VA를, 전역 매핑이 아니면 rs2에 ASID를 준다. 앞의 두 답도 틀리지는 않지만 쓸데없이 많은 항목을 지운다(과잉 펜스).

Svnapot으로 64 KiB 영역을 표시할 때 레벨 0 PTE의 ppn[0] 아래 4비트는?

N=1이고 ppn[0]이 x xxxx 1000이면 64 KiB 연속 영역이다(napot_bits = 4). 다른 패턴은 지금은 예약이라 페이지 폴트가 난다.

더 읽을거리