PMA·PMP·ePMP: 물리 메모리 보호
19장의 가상 메모리는 운영체제가 응용 프로그램끼리를 떼어 놓는 장치다. 그런데 운영체제 자신이 건드려서는 안 되는 메모리, 이를테면 M 모드 펌웨어나 보안 모니터는 누가 지킬까? 답은 M 모드가 물리 주소 공간에 직접 치는 울타리, 곧 물리 메모리 보호(PMP)다. 이 장에서는 하드웨어가 정해 둔 주소별 성질인 PMA, M 모드가 설정하는 PMP, M 모드 자신까지 묶는 Smepmp(ePMP), 그리고 주소 윗비트를 태그로 쓰게 해 주는 포인터 마스킹을 차례로 살펴본다.
- PMA와 PMP, 페이지 기반 가상 메모리가 각각 무엇을 검사하는지 구별한다.
- pmpcfg·pmpaddr CSR의 비트 배치와 RV32·RV64에서의 차이를 안다.
- TOR·NA4·NAPOT 주소 일치 방식을 이해하고 NAPOT 값을 직접 계산한다.
- “가장 낮은 번호의 엔트리가 이긴다”, “모든 바이트가 일치해야 한다”, 기본 거부 규칙과 L 비트 잠금을 설명한다.
- Smepmp의 mseccfg.MML·MMWP·RLB가 M 모드를 어떻게 제한하는지 규칙표로 읽는다.
- 포인터 마스킹(Smmpm·Smnpm·Ssnpm)의 PMLEN과 “무시” 변환을 비트 단위로 따라간다.
왜 물리 메모리에 울타리가 필요한가
17장에서 보았듯이 RISC-V 하트는 M·S·U 세 특권 모드 가운데 하나에서 돈다. 리눅스 같은 운영체제는 S 모드에서, 그 아래 펌웨어(OpenSBI 같은 SBI 구현)는 M 모드에서 돈다. S 모드 운영체제는 페이지 테이블로 어떤 물리 페이지든 자기 주소 공간에 매핑할 수 있다. 그렇다면 운영체제가 실수로, 혹은 공격을 받아 펌웨어의 코드나 비밀 키가 있는 물리 주소를 매핑해 버리면 어떻게 막을까? 페이지 테이블은 운영체제가 직접 쓰는 것이므로 이 문제를 풀 수 없다. 운영체제보다 더 높은 곳에서, 물리 주소를 기준으로 검사하는 장치가 필요하다.
그 장치가 물리 메모리 보호(PMP, Physical Memory Protection)다. PMP는 하트마다 있는 M 모드 전용 CSR 묶음으로, 물리 주소 영역별로 읽기·쓰기·실행 권한을 정한다. 이와 나란히, 하드웨어가 처음부터 정해 둔 주소별 성질을 검사하는 물리 메모리 속성(PMA, Physical Memory Attributes) 검사기가 있다. 두 검사기는 주소 변환이 끝난 물리 주소를 두고 동시에 판정한다.
세 가지 검사 장치는 질문이 서로 다르다. 표로 정리하면 다음과 같다.
| PMA | PMP | 페이지 기반 가상 메모리 | |
|---|---|---|---|
| 질문 | 이 물리 주소에 그런 접근이 가능한가 | 지금 모드가 이 물리 주소에 접근해도 되는가 | 이 가상 주소는 어느 물리 주소이며 권한은 무엇인가 |
| 누가 정하나 | 칩·보드 설계 (대부분 고정) | M 모드 소프트웨어 (pmpcfg·pmpaddr) | S 모드 운영체제 (페이지 테이블, satp) |
| 모드에 따라 | 달라지지 않는다 | S·U 모드에 적용, M 모드는 선택적 | S·U 모드 (M 모드는 변환 없음) |
| 위반 시 | 접근 오류 (정밀 트랩 권장) | 접근 오류 (항상 정밀 트랩) | 페이지 폴트 (코드 12·13·15) |
특권 규격은 M·U 두 모드만 있는 “보안 임베디드 시스템” 구성을 따로 꼽는다. 가상 메모리가 없는 마이크로컨트롤러에서는 PMP가 U 모드 응용 프로그램을 가두는 유일한 장치다. M 모드만 있는 칩에서도 PMP의 잠금 기능으로 약간의 보호(예: 부트 ROM을 실수로 덮어쓰지 않기)를 얻을 수 있다.
PMA: 주소마다 타고난 성질
물리 주소 공간은 균일하지 않다. 어떤 구간은 DRAM이고, 어떤 구간은 UART 같은 장치의 제어 레지스터이며, 어떤 구간은 비어 있다. 장치 레지스터는 읽기만 해도 값이 바뀌기도 하고(수신 버퍼에서 한 글자를 꺼내는 읽기), 원자적 연산을 지원하지 않기도 한다. 규격은 주소 구간마다 가진 이런 성질을 PMA(Physical Memory Attributes)라 부르고, 그것을 검사하는 하드웨어를 PMA 검사기라 부른다.
다른 ISA는 이런 속성 일부를 페이지 테이블 항목에 넣기도 한다. RISC-V는 플랫폼 고유 정보가 가상화 계층(페이지 테이블)으로 새어 들어가지 않도록 PMA를 별도의 하드웨어 구조로 분리했다. 대부분은 설계 때 고정되어 PMA 검사기에 배선되고, 실행 중 바꿀 수 있는 속성은 플랫폼 고유의 메모리 맵 레지스터로 설정한다.
| PMA 종류 | 규격이 정하는 내용 |
|---|---|
| 주 메모리 vs I/O | 가장 중요한 구분. 주 메모리는 정해진 성질을 모두 갖춰야 하고, 장치 스크래치패드처럼 그 성질을 못 갖춘 구간은 I/O로 분류한다. 접근할 수 없는 구간도 “R·W·X가 없는 I/O”로 표현한다. |
| 접근 종류 | 지원하는 접근 폭(바이트 ~ 버스트), 정렬 안 된 접근 지원 여부, 명령어 가져오기 가능 여부, 하드웨어 페이지 테이블 읽기·쓰기 가능 여부. |
| 원자성 (AMO) | AMONone < AMOSwap < AMOLogical(and·or·xor 추가) < AMOArithmetic(A 확장 전부). Zacas가 AMOCASW·AMOCASD·AMOCASQ 단계를 더한다. |
| 원자성 (예약) | LR/SC 지원 정도: RsrvNone, RsrvNonEventual(되긴 하지만 결국 성공 보장 없음), RsrvEventual. |
| 정렬 안 된 원자성 단위 | MAGnn: 접근한 바이트가 모두 같은 nn바이트 정렬 구간 안에 있으면 정렬 예외 없이 원자적으로 실행된다. |
| 메모리 순서 | 주 메모리는 RVWMO 또는 RVTSO. I/O는 느슨한 순서 또는 강한 순서이며, 강한 순서 구간은 순서 채널 번호를 가진다(채널 0: 그 구간 하나만, 채널 1: 모든 I/O 구간에 걸친 전역 순서). |
| 일관성 · 캐시 가능 | 캐시 가능 여부는 소프트웨어에 보이는 동작을 바꾸지 않아야 하므로 M 모드만 관리하는 플랫폼 설정으로 다룬다. 캐시 불가 구간의 접근은 캐시가 아니라 메모리 자체가 답해야 한다. |
| 멱등성 | 주 메모리는 멱등이라고 가정한다. I/O는 읽기·쓰기 멱등성을 따로 정한다. 비멱등 구간에는 투기적·중복 접근을 해서는 안 된다. |
규격은 PMA 위반을 가능하면 정밀하게 트랩하라고 강하게 권한다. 그러면 명령어·load·store 접근 오류 예외로 나타나며, 페이지 폴트와는 구별된다. 다만 오류 응답으로 장치를 찾아내는 옛 버스처럼 정밀 트랩이 불가능한 경우에는 부정확한 버스 오류 인터럽트로 보고될 수도 있다. 또 정렬 안 된 접근을 비멱등 구간에 하면 정렬 예외 대신 접근 오류를 내는 것이 좋다고 규격은 말한다. 정렬 예외를 받은 트랩 처리기가 작은 접근 여러 번으로 흉내 내면 장치에 부작용이 생기기 때문이다.
페이지 단위로 메모리 종류를 덮어쓰는 Svpbmt 확장은 19장(가상 메모리)에서 다룬다. PMA가 “이 물리 주소의 원래 성질”이라면, Svpbmt는 운영체제가 페이지마다 “이 매핑은 캐시하지 않는 메모리 또는 I/O처럼 다뤄라”라고 덧붙이는 장치다.
PMP 엔트리와 CSR 배치
PMP는 엔트리 여러 개로 이루어진다. 엔트리 하나는 8비트 설정 레지스터 pmpNcfg와 주소 레지스터 pmpaddrN의 짝이다. 구현은 엔트리를 0개, 16개, 64개 중 하나만큼 가질 수 있고, 번호가 낮은 엔트리부터 구현해야 한다. 모든 필드는 WARL이라 읽기 전용 0일 수도 있으며, PMP CSR은 M 모드에서만 접근할 수 있다.
pmpNcfg와 그 묶음. RV32에서는 CSR 하나에 4개, RV64에서는 8개씩 빽빽하게 담아 문맥 전환 비용을 줄인다. RV64가 pmpcfg1 대신 pmpcfg2를 쓰는 까닭은, 엔트리 8–11의 설정이 RV32와 RV64 모두에서 pmpcfg2[31:0]에 놓이게 하기 위해서다.| CSR | 주소 | 내용 |
|---|---|---|
pmpcfg0 – pmpcfg15 | 0x3A0 – 0x3AF | 엔트리 설정 바이트 묶음 (RV64는 짝수 번호만) |
pmpaddr0 – pmpaddr63 | 0x3B0 – 0x3EF | 엔트리 주소 (물리 주소를 4로 나눈 값) |
mseccfg / mseccfgh | 0x747 / 0x757 | Smepmp·포인터 마스킹 등의 보안 설정 (뒤에서) |
pmpaddr: 물리 주소를 4로 나눈 값
주소 레지스터는 물리 주소 그 자체가 아니라 비트 2부터 위쪽을 담는다. RV32에서는 32비트 레지스터가 34비트 물리 주소의 비트 33–2를, RV64에서는 비트 55–2를 담는다(레지스터의 63–54번 비트는 0). 왜 RV32가 34비트일까? Sv32 페이지 테이블이 34비트 물리 주소를 지원하므로 PMP도 XLEN보다 넓은 주소를 다뤄야 하기 때문이다. Sv39·Sv48이 지원하는 56비트 물리 주소가 RV64의 한계가 된 것도 같은 이유다.
pmpaddr × 4로 읽어야 한다. 구현된 물리 주소 비트가 더 적으면 남는 비트는 WARL 규칙에 따라 고정된다.설정 바이트의 R·W·X는 이 엔트리가 각각 읽기·쓰기·명령어 실행을 허용하는지를 뜻한다. R=0, W=1인 조합은 예약되어 있다(쓰기만 되고 읽기는 안 되는 메모리는 의미가 없다). 권한이 없는 접근은 종류에 따라 다른 예외가 된다.
| 거부된 접근 | 예외 | mcause 코드 |
|---|---|---|
| 명령어 가져오기 (X 없음) | Instruction access fault | 1 |
| load, LR, 캐시 블록 관리 명령 (R 없음) | Load access fault | 5 |
store, SC, AMO, cbo.zero (W 없음) | Store/AMO access fault | 7 |
주소 일치 방식: OFF · TOR · NA4 · NAPOT
설정 바이트의 2비트 A 필드가 pmpaddr를 어떻게 해석할지 정한다.
| A | 이름 | 뜻 |
|---|---|---|
| 0 | OFF | 엔트리를 끈다. 아무 주소와도 일치하지 않는다. |
| 1 | TOR (Top Of Range) | 바로 앞 엔트리의 주소를 바닥, 자기 주소를 꼭대기로 쓰는 임의 구간 pmpaddr[i-1] ≤ y < pmpaddr[i] |
| 2 | NA4 | 자연 정렬된 4바이트 구간 |
| 3 | NAPOT | 자연 정렬된 2의 거듭제곱 크기(8바이트 이상) 구간. 크기는 pmpaddr 하위 비트로 표현 |
pmpaddr[i-1] ≥ pmpaddr[i]) 그 TOR 엔트리는 아무 주소와도 일치하지 않는다.NAPOT: 꼬리의 1로 크기를 적는다
NAPOT는 기준 주소와 크기를 레지스터 하나에 함께 담는 영리한 방법이다. 크기가 2n이고 기준 주소가 그 크기의 배수라면, 기준 주소의 하위 n비트는 모두 0이라 정보가 없다. 그 자리에 크기를 적는다. pmpaddr의 오른쪽 끝에서부터 연속된 1의 개수 k가 크기를 나타내고, 그 바로 위의 0이 경계 표시다.
NAPOT 인코더 · 디코더
0x8000_1000처럼 크기의 배수가 아닌 값으로 바꾸면 무슨 일이 생기는지, 디코드 방향에서 0xFFFF_FFFF를 넣으면 몇 바이트가 되는지도 확인해 보자.우선순위, 일치 규칙, 잠금
엔트리 여럿이 겹칠 수 있으므로 누가 이기는지 정해야 한다. 규칙은 단순하고 정적이다. 접근의 어느 한 바이트라도 일치하는 엔트리 중 번호가 가장 낮은 것이 결과를 정한다. 그런데 그 엔트리가 접근의 모든 바이트를 덮지 못하면, 권한 비트와 상관없이 접근은 실패한다. 영역 경계를 걸친 접근이 두 엔트리의 권한을 반씩 섞어 쓰는 일을 막기 위해서다.
“PMP entries are statically prioritized. The lowest-numbered PMP entry that matches any byte of a memory operation determines whether that operation succeeds or fails. The matching PMP entry must match all bytes of a memory operation, or the operation fails, irrespective of the L, R, W, and X bits.”
Machine-Level ISA, Priority and Matching Logic — 규격은 예로 0xC–0xF를 덮는 엔트리가 가장 높은 우선순위일 때 0x8–0xF 8바이트 접근은 실패한다고 든다.
L 비트: 잠금이자 “M 모드에도 적용”
L 비트는 두 가지 일을 한다. 첫째, 엔트리를 잠근다. 잠긴 엔트리의 pmpcfg와 pmpaddr에 대한 쓰기는 무시되며, 엔트리가 TOR이면 바닥으로 쓰이는 앞 엔트리의 pmpaddr도 잠긴다. 잠금은 하트가 리셋될 때까지 풀리지 않는다(A가 OFF여도 잠긴다). 둘째, 그 엔트리의 R·W·X를 M 모드에도 강제한다. L=0인 엔트리는 S·U 모드만 제한하고, M 모드 접근은 그 엔트리와 일치해도 무조건 성공한다.
규격의 표현을 빌리면 PMP는 기본적으로 아무 권한도 없는 S·U 모드에 권한을 주는 장치이고, 잠금을 통해 기본적으로 모든 권한을 가진 M 모드에서 권한을 빼앗는 장치다. M 모드 접근 판정에는 한 가지 예외가 더 있다. mstatus.MPRV=1이고 MPP가 S나 U이면 M 모드의 데이터 접근은 MPP 모드의 접근처럼 검사된다(17장). 또 페이지 테이블 워크 같은 암묵적 접근은 S 모드 접근으로 검사된다.
PMP 검사기 (RV32, 엔트리 8개)
| # | A | pmpaddr (16진수) | R | W | X | L | 바이트 범위 [시작, 끝) |
|---|
0x8에 크기 8로 접근해 보자. L을 켠 엔트리는 편집할 수 없게 되는데, 이것을 푸는 유일한 방법은 “하트 리셋”이다(리셋은 A와 L 필드를 0으로 만든다).펌웨어가 PMP를 설정하는 모습
전형적인 부팅 펌웨어는 S 모드 운영체제로 넘어가기 직전에 PMP를 설정한다. 목표는 두 가지다. 자기 자신(코드·데이터·스택)은 S·U 모드에서 보이지 않게 하고, 나머지 물리 메모리는 운영체제가 쓰게 열어 주는 것이다. 엔트리 0이 우선순위가 가장 높다는 점을 이용해, 숨길 영역을 먼저 두고 “나머지 전부”를 마지막 엔트리로 둔다.
몇 가지를 짚어 두자.
- 설정 바이트는 마지막에 쓴다. 주소를 먼저 써 두고 A 필드를 켜야 중간 상태에서 엉뚱한 구간이 잠깐 일치하는 일을 피한다.
- SFENCE.VMA가 필요하다. 가상 메모리가 있는 구현은 주소 변환을 미리, 투기적으로 해서 캐시해 둘 수 있고, 그 물리 주소의 PMP 판정도 변환과 접근 사이 아무 때나 할 수 있다. 그래서 PMP CSR을 바꾼 뒤
sfence.vma x0, x0로 동기화해야 한다. 가상 메모리가 없는 구현은 PMP를 동기적으로 검사하므로 필요 없다. 하이퍼바이저 확장이 있을 때의 추가 요구는 21장(하이퍼바이저)에서 다룬다. - 입도(granularity)를 확인한다. 플랫폼은 4바이트보다 굵은 PMP 입도 2G+2바이트를 정할 수 있다. G ≥ 1이면 NA4를 고를 수 없다. 입도는 pmp0cfg에 0을 쓰고 pmpaddr0에 모든 비트 1을 쓴 뒤 다시 읽어, 가장 낮은 1의 위치 G로 알아낸다.
- 엔트리 수도 탐색한다. 모든 필드가 WARL이므로, 써 보고 다시 읽어 남아 있는지로 구현 여부를 알 수 있다.
부트 ROM이 L 비트를 켜서 엔트리를 잠그면 이후 펌웨어는 그 엔트리를 고칠 수 없다. M 모드에도 권한이 강제되므로, 잠근 영역에 실행 권한을 빠뜨리면 M 모드 자신도 그 코드를 실행하지 못한다. 잠금은 “리셋 전까지 아무도 이 규칙을 바꿀 수 없다”는 보증이므로, 펌웨어 갱신 경로까지 고려해 신중히 설계해야 한다.
TOR의 꼭대기는 pmpaddr × 4이므로 2XLEN+2번지는 꼭대기로 쓸 수 없다. 예컨대 34비트 물리 주소의 RV32에서 TOR로는 맨 꼭대기 워드를 덜 특권적인 모드에 열어 줄 수 없다. 규격의 비고는 이럴 때 NAPOT를 쓰거나 그 워드를 포기하라고 하면서, 페이지 단위로 메모리를 관리하면 16 GiB 가운데 0.00002%만 잃는다고 덧붙인다.
Smepmp: M 모드도 함부로 실행하지 못하게
기본 PMP에서 잠기지 않은 규칙은 M 모드에 아무 영향이 없다. 즉 M 모드는 S·U 모드의 메모리를 마음대로 읽고 실행할 수 있다. 공격자가 U 모드 메모리에 악성 코드를 심고 M 모드가 그것을 실행하도록 속인다면? 이것이 Smepmp(“enhanced PMP”, 흔히 ePMP) 1.0이 막으려는 위협이다. S 모드에는 이미 sstatus.SUM과 “U 페이지는 S 모드가 실행할 수 없다”는 규칙이 있지만, M 모드에는 그런 장치가 없었다. M과 U만 있는 칩에서는 운영체제 전체가 M 모드에서 돌기 때문에 이 빈틈이 더 크다.
| mseccfg 필드 | 비트 | 뜻 |
|---|---|---|
| MML Machine Mode Lockdown | 0 | L 비트의 뜻을 “M 전용(L=1) / S·U 전용(L=0)”으로 바꾸고, RW=01과 LRWX=1111을 공유 영역 인코딩으로 쓴다. 한 번 켜면 PMP 리셋 전까지 끌 수 없다(sticky). |
| MMWP Machine-Mode Allowlist Policy | 1 | 일치하는 규칙이 없을 때 M 모드의 기본 정책을 “무시(허용)”에서 “거부”로 바꾼다. 역시 sticky. |
| RLB Rule Locking Bypass | 2 | 1이면 잠긴 규칙도 지우거나 고칠 수 있다(부팅 중 임시 우회, 디버그용). RLB=0인 상태에서 L=1인 엔트리가 하나라도 있으면 RLB는 0으로 고정된다. |
| USEED · SSEED · MLPE · PMM | 8, 9, 10, 33:32 | Zkr의 seed CSR 접근 허용, Zicfilp의 M 모드 착지 패드, Smmpm 포인터 마스킹(다음 절). Smepmp와는 별개의 필드다. |
MML=1일 때 규격은 규칙 하나하나가 M 모드와 S·U 모드에 무엇을 허용하는지를 16칸짜리 규칙표로 정리한다. 아래 탐색기에서 각 칸을 눌러 보자. MML을 0으로 바꾸면 같은 비트가 기본 PMP에서 무엇을 뜻하는지 비교할 수 있다.
Smepmp 규칙표 탐색기
MML=1이면 실행 권한이 있는 M 전용 규칙(1001, 1101)이나 잠긴 공유 코드 규칙(1010, 1011)을 새로 쓰는 일은 무시되고 pmpcfg가 바뀌지 않는다. RLB=1일 때만(예: 부팅 과정) 이 제한이 풀린다. 그렇지 않으면 공격자가 M 모드 코드 실행 권한을 슬쩍 추가할 수 있기 때문이다.
MML·MMWP는 켜면 잠기고 RLB는 끄면 잠기므로, 써 보고 읽는 방식으로 존재를 확인하기 어렵다. 그래서 규격은 부트 ROM이 일찍 MMWP나 MML을 켜고 펌웨어로 넘어가면, 펌웨어가 mseccfg를 읽어 확인하는 방식을 기대한다고 적는다.
포인터 마스킹: 주소 윗비트를 태그로
RV64의 64비트 주소 가운데 실제로 메모리를 가리키는 데 쓰는 비트는 Sv48이면 48개, Sv57이면 57개뿐이다. 남는 윗비트에 태그를 넣을 수 있다면 HWASAN 같은 메모리 오류 검출기가 포인터마다 “이 포인터가 가리켜야 할 메모리의 색깔”을 적어 둘 수 있다. 문제는 태그가 붙은 주소로 load하면 주소가 유효하지 않아 예외가 난다는 점이다. 소프트웨어로 매번 태그를 떼어 내면 느리다. 포인터 마스킹(Pointer Masking) 1.0.0은 하드웨어가 명시적 메모리 접근의 주소 윗비트 PMLEN개를 무시하게 해 준다.
| 확장 | 무엇을 제어하나 | 설정 위치 |
|---|---|---|
| Smmpm | M 모드 자신의 포인터 마스킹 | mseccfg.PMM |
| Smnpm | 바로 아래 모드 (S 모드가 있으면 S/HS, 없으면 U) | menvcfg.PMM |
| Ssnpm | U 모드 (H 확장이 있으면 VS·VU 포함) | senvcfg.PMM 등 |
| Sspm · Supm | “S 모드 / U 모드에서 포인터 마스킹을 쓸 수 있다”는 실행 환경의 약속 (하드웨어 요구 없음, 프로파일용) | 실행 환경이 제공 |
PMM 필드는 모두 같은 2비트 WARL 인코딩을 쓴다: 00 끔(PMLEN=0), 01 예약, 10 PMLEN = XLEN−57 (RV64에서 7), 11 PMLEN = XLEN−48 (RV64에서 16). 한 모드의 설정은 바로 위 모드의 CSR에 있다. 포인터 마스킹은 RV64 전용이며 RV32에서 켜려는 쓰기는 반영되지 않는다.
- 적용 대상: load·store·AMO·부동소수점 load/store·벡터 load/store·CMO처럼 명시적 메모리 접근만. 명령어 가져오기, 페이지 테이블 워크, IOMMU·DMA 장치의 접근에는 적용되지 않는다.
- MPRV: MPRV가 켜지면 유효 특권 모드의 설정이 쓰인다. MXR이 유효한 접근에는 마스킹이 적용되지 않는다.
- CSR에 쓰는 값은 그대로: 소프트웨어가 stvec 등에 태그 붙은 주소를 써도 마스킹하지 않는다. 반면 하드웨어가 stval에 주소를 기록할 때는 마스킹된 주소를 쓴다.
“무시” 변환 한 비트씩 보기
0xFFFF_FFFF_1234_5678인지, 물리 주소로 바꾸면 0x01FF_FFFF_1234_5678인지 확인하자. 태그 0x2A 포인터에서 PMLEN을 16으로 올리면 비트 47이 0이라 위쪽이 모두 0으로 채워진다.포인터 마스킹이 하는 일은 태그를 무시하는 것까지다. 태그가 메모리 쪽 태그와 맞는지 확인하는 일은 소프트웨어(또는 앞으로 나올 확장)의 몫이다. 그래도 검사하지 않는 일반 접근에서 매번 태그를 떼어 내지 않아도 되므로 큰 이득이다. 또 리눅스처럼 최상위 비트로 커널·사용자 주소를 구별하던 코드는, 마스킹이 켜지면 비트 XLEN−PMLEN−1을 보아야 한다는 점에 주의해야 한다.
핵심 정리
- PMA는 주소 구간마다 하드웨어가 정한 성질(주 메모리/I/O, 접근 폭, 원자성, 순서, 캐시, 멱등성)이고, PMP는 M 모드가 정하는 모드별 접근 권한이다. 둘 다 물리 주소로 나란히 검사하며 위반은 접근 오류(1·5·7)가 된다.
- PMP 엔트리는
pmpNcfg(L·A·X·W·R) +pmpaddrN(물리 주소 ≫ 2)이다. 엔트리는 0·16·64개이고 CSR은 M 모드 전용이다. RV64는 짝수 pmpcfg만 쓴다. - A = OFF·TOR·NA4·NAPOT. TOR은
pmpaddr[i-1] ≤ y < pmpaddr[i], NAPOT은 꼬리의 1 k개로 크기 2k+3을 적는다. - 가장 낮은 번호의 일치 엔트리가 결정하고, 그 엔트리가 접근의 모든 바이트를 덮지 못하면 실패한다. 일치가 없으면 M은 통과, S·U는 실패한다.
- L 비트는 리셋 전까지 엔트리를 잠그고 권한을 M 모드에도 강제한다. PMP를 바꾼 뒤에는
sfence.vma x0, x0로 동기화한다. - Smepmp의 MML은 L을 “M 전용/S·U 전용”으로 바꿔 M 모드가 S·U 메모리를 실행하지 못하게 하고, MMWP는 일치 없는 M 접근을 거부, RLB는 부팅 중 잠금 우회를 허용한다.
- 포인터 마스킹은 RV64에서 명시적 접근 주소의 위 PMLEN(7 또는 16)비트를 무시해 HWASAN 같은 태그 기법을 빠르게 한다.
확인 퀴즈
A=NAPOT인 엔트리의 pmpaddr 하위 비트가 …10111로 끝난다. 이 엔트리가 덮는 구간의 크기는?
엔트리 2가 TOR일 때 일치하는 바이트 주소 y의 범위는?
PMP 엔트리 16개가 구현되어 있고 모두 A=OFF다. S 모드에서 load를 하면?
엔트리 0이 NA4로 0xC–0xF를 덮고 R·W가 켜져 있다. 이것이 가장 높은 우선순위로 일치할 때, 0x8에서 시작하는 8바이트 load는?
L=0, RWX=000인 엔트리와 일치하는 M 모드 store의 결과는? (Smepmp의 MML은 0)
mseccfg.MML=1일 때 LRWX=0111 규칙이 덮는 영역에서 M 모드가 명령어를 가져오면?
RV64에서 menvcfg.PMM=11로 S 모드 포인터 마스킹을 켰다. Sv48 가상 주소의 변환에서 위쪽 16비트는 무엇으로 채워지나?
다음 중 모든 특권 모드에서 같은 값이며 대개 칩·보드 설계 때 고정되는 것은?
더 읽을거리
- Volume II: Machine-Level ISA — Physical Memory Attributes, Physical Memory Protection, mseccfg (v20260120)
- "Smepmp" Extension for PMP Enhancements for memory access and execution prevention in Machine mode, Version 1.0
- Pointer Masking Extensions, Version 1.0.0
- Control and Status Registers — CSR 주소 배정표 (pmpcfg·pmpaddr·mseccfg 주소)
- 이 책의 관련 장: 17장 특권 모드와 CSR · 18장 트랩 · 19장 가상 메모리 (Svpbmt) · 21장 하이퍼바이저