01 · QUESTION
무엇을 확인할 것인가
pte_present()가 참이라는 공통 결과 뒤에서 hardware page walker가 실제로 검사하는 bit와 permission은 어떻게 다른가?
mm core는 pgd/p4d/pud/pmd/pte API를 사용하지만 folded level, leaf 허용 level, accessed/dirty hardware 지원과 execute permission encoding은 architecture가 정한다.
software PTE helper가 만든 raw value가 memory에 publish되고 TLB invalidate가 끝난 뒤에야 hardware permission이 바뀐다. PTE write와 shootdown을 하나의 transaction으로 읽는다.
02 · CONTRACT
공통 규약과 아키텍처별 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptor | table descriptor를 block으로 잘못 해석하거나 BBM을 어기면 stale translation 또는 architecture fault가 난다. | raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다. |
| x86-64 | x86 PTE의 권한·상태 비트와 소프트웨어 비트 | NXE, PAT, encryption C-bit 또는 software protnone encoding을 누락하면 permission과 cache type이 달라진다. | CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다. |
| RISC-V | V/R/W/X/U/G/A/D와 PPN field | W=1,R=0 같은 reserved encoding이나 misaligned superpage PPN은 page fault를 만든다. | SATP.MODE, ASID, level index, V/R/W/X/U/G/A/D와 PPN 정렬을 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptor
- state
- 4KiB granule 기준 최대 4/5 level을 사용하며 block descriptor가 PUD/PMD leaf를 만든다. UXN과 PXN이 user/kernel execute를 분리하고 AF/DBM이 access/dirty 관리에 관여한다.
- checkpoint
- raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다.
x86-64
- mechanism
- x86 PTE의 권한·상태 비트와 소프트웨어 비트
- state
- 4-level 또는 LA57 5-level이며 PMD/PUD의 PS bit가 large page leaf를 표시한다. PAT bit 위치는 4KiB와 huge PTE에서 달라지고 global bit는 CR3 switch 생존 범위에 영향을 준다.
- checkpoint
- CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다.
RISC-V
- mechanism
- V/R/W/X/U/G/A/D와 PPN field
- state
- 기본 Sv39/Sv48/Sv57에서는 V=0 또는 W=1,R=0일 때 먼저 page fault다. 그 외 V=1에서 R/W/X=000이면 다음 단계 table 후보, R=1 또는 X=1이면 leaf 후보이다. Sv39, Sv48, Sv57 mode에 따라 root level이 달라지며 superpage leaf는 낮은 PPN field 정렬을 요구한다.
- checkpoint
- SATP.MODE, ASID, level index, V/R/W/X/U/G/A/D와 PPN 정렬을 확인한다.
| 판독 순서 | 비트 조건 | 기본 Sv39/Sv48/Sv57 결과 |
|---|---|---|
| 1 · 유효성 | V=0 또는 R=0,W=1 | page fault. _PAGE_LEAF 마스크가 W 비트를 감지해도 유효한 leaf가 아니다. |
| 2 · 다음 단계 | V=1,R/W/X=000 | 다음 레벨 table 후보. 마지막 레벨이면 page fault. |
| 3 · leaf | V=1, 앞의 오류가 없고 R=1 또는 X=1 | leaf 후보. 접근 권한·A/D 및 superpage PPN 정렬을 더 검사한다. |
pgd/p4d/pud/pmd/pte API를 사용하지만 folded level, leaf 허용 level, accessed/dirty hardware 지원과 execute permission encoding은 architecture가 정한다.sfence.vma가 old translation을 제거해야 한다.sfence.vma가 old translation을 제거해야 한다.→관찰: SATP.MODE, ASID, level index, V/R/W/X/U/G/A/D와 PPN 정렬을 확인한다.04 · SOURCE
Linux 6.18.37 원본 코드와 주요 구문 해설
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.
arm64 · Linux 6.18.37
valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptor
4KiB granule 기준 최대 4/5 level을 사용하며 block descriptor가 PUD/PMD leaf를 만든다. UXN과 PXN이 user/kernel execute를 분리하고 AF/DBM이 access/dirty 관리에 관여한다.
원본 코드: arch/arm64/include/asm/pgtable-hwdef.h:156-214
156 * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).
157 */
158#define PMD_ATTRINDX(t) (_AT(pmdval_t, (t)) << 2)
159#define PMD_ATTRINDX_MASK (_AT(pmdval_t, 7) << 2)
160
161/*
162 * Level 3 descriptor (PTE).
163 */
164#define PTE_VALID (_AT(pteval_t, 1) << 0)
165#define PTE_TYPE_MASK (_AT(pteval_t, 3) << 0)
166#define PTE_TYPE_PAGE (_AT(pteval_t, 3) << 0)
167#define PTE_USER (_AT(pteval_t, 1) << 6) /* AP[1] */
168#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* AP[2] */
169#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */
170#define PTE_AF (_AT(pteval_t, 1) << 10) /* Access Flag */
171#define PTE_NG (_AT(pteval_t, 1) << 11) /* nG */
172#define PTE_GP (_AT(pteval_t, 1) << 50) /* BTI guarded */
173#define PTE_DBM (_AT(pteval_t, 1) << 51) /* Dirty Bit Management */
174#define PTE_CONT (_AT(pteval_t, 1) << 52) /* Contiguous range */
175#define PTE_PXN (_AT(pteval_t, 1) << 53) /* Privileged XN */
176#define PTE_UXN (_AT(pteval_t, 1) << 54) /* User XN */
177#define PTE_SWBITS_MASK _AT(pteval_t, (BIT(63) | GENMASK(58, 55)))
178
179#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (50 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)
180#ifdef CONFIG_ARM64_PA_BITS_52
181#ifdef CONFIG_ARM64_64K_PAGES
182#define PTE_ADDR_HIGH (_AT(pteval_t, 0xf) << 12)
183#define PTE_ADDR_HIGH_SHIFT 36
184#define PHYS_TO_PTE_ADDR_MASK (PTE_ADDR_LOW | PTE_ADDR_HIGH)
185#else
186#define PTE_ADDR_HIGH (_AT(pteval_t, 0x3) << 8)
187#define PTE_ADDR_HIGH_SHIFT 42
188#define PHYS_TO_PTE_ADDR_MASK GENMASK_ULL(49, 8)
189#endif
190#endif
191
192/*
193 * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).
194 */
195#define PTE_ATTRINDX(t) (_AT(pteval_t, (t)) << 2)
196#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)
197
198/*
199 * PIIndex[3:0] encoding (Permission Indirection Extension)
200 */
201#define PTE_PI_IDX_0 6 /* AP[1], USER */
202#define PTE_PI_IDX_1 51 /* DBM */
203#define PTE_PI_IDX_2 53 /* PXN */
204#define PTE_PI_IDX_3 54 /* UXN */
205
206/*
207 * POIndex[2:0] encoding (Permission Overlay Extension)
208 */
209#define PTE_PO_IDX_0 (_AT(pteval_t, 1) << 60)
210#define PTE_PO_IDX_1 (_AT(pteval_t, 1) << 61)
211#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 62)
212
213#define PTE_PO_IDX_MASK GENMASK_ULL(62, 60)
214 주요 구문 해설
원본 59줄은 그대로 표시하며, 검토한 38개 구문에 설명을 붙였습니다.
#define PMD_ATTRINDX(t) (_AT(pmdval_t, (t)) << 2)메모리 속성 번호 t를 PMD의 비트 [4:2]에 넣습니다. 이 값은 캐시 속성 자체가 아니라 MAIR 레지스터에 저장된 속성을 선택하는 3비트 인덱스입니다. _AT로 PMD 값의 자료형에 맞춘 뒤 시프트합니다.
#define PMD_ATTRINDX_MASK (_AT(pmdval_t, 7) << 2)PMD의 AttrIndx에 해당하는 비트 [4:2]만 선택하는 마스크입니다. 7을 왼쪽으로 2비트 이동한 값은 0x1c입니다. 기존 속성 번호를 읽거나 해당 부분만 지우고 새 번호를 넣을 때 사용합니다.
#define PTE_VALID (_AT(pteval_t, 1) << 0)PTE의 유효 여부를 나타내는 비트 0의 마스크입니다. 하드웨어는 이 비트가 설정된 항목을 유효한 주소 변환 정보로 해석합니다. 비트를 지운 항목은 유효한 매핑으로 사용할 수 없습니다.
#define PTE_TYPE_MASK (_AT(pteval_t, 3) << 0)PTE의 하위 2비트를 선택하는 마스크 3입니다. 유효 여부와 항목 형식을 함께 검사할 때 사용합니다. 주소나 접근 권한 비트는 포함하지 않습니다.
#define PTE_TYPE_PAGE (_AT(pteval_t, 3) << 0)레벨 3에서 페이지 매핑을 나타내는 하위 2비트 값 0b11입니다. PTE_VALID만 설정하는 것이 아니라 레벨 3 페이지 디스크립터의 형식에 맞춰 두 비트를 설정합니다.
#define PTE_USER (_AT(pteval_t, 1) << 6) /* AP[1] */일반 권한 형식에서 EL0 접근을 허용하는 AP[1], 즉 비트 6의 마스크입니다. 최종 읽기·쓰기 허용 여부는 읽기 전용 비트와 다른 제어 설정에도 영향을 받습니다. Permission Indirection을 사용하면 같은 위치를 권한 표의 인덱스로 해석합니다.
#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* AP[2] */일반 권한 형식의 AP[2], 즉 비트 7입니다. 설정하면 하드웨어는 읽기 전용으로 처리합니다. Dirty Bit Management를 사용할 때는 나중에 쓸 수 있는 매핑도 처음에는 이 비트가 설정될 수 있으므로, 소프트웨어가 허용한 쓰기 권한과 구분해야 합니다.
#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */SH[1:0]에 0b11을 넣어 일반 형식의 페이지를 내부 공유 영역(inner-shareable)에 속하도록 지정합니다. LPA2가 활성화된 형식에서는 이 위치에 물리 주소의 상위 비트가 들어가므로, 실제 보호 속성을 구성할 때 PTE_MAYBE_SHARED로 적용 여부를 구분합니다.
#define PTE_AF (_AT(pteval_t, 1) << 10) /* Access Flag */Access Flag가 clear인 valid mapping은 hardware access flag fault 또는 update 경로를 유발한다.
#define PTE_NG (_AT(pteval_t, 1) << 11) /* nG */비트 11의 nG(non-global) 속성입니다. TLB에서 ASID로 주소 공간을 구분해야 하는 매핑에 사용합니다. 서로 다른 프로세스가 같은 가상 주소를 사용해도 주소 변환을 혼동하지 않게 합니다.
#define PTE_GP (_AT(pteval_t, 1) << 50) /* BTI guarded */비트 50의 Guarded Page 속성입니다. BTI를 사용하는 실행 페이지에 설정하며, 간접 분기가 허용된 진입 위치로 향하는지 검사할 때 사용합니다. 이 비트만 설정한다고 BTI를 지원하지 않는 CPU에 기능이 생기는 것은 아닙니다.
#define PTE_DBM (_AT(pteval_t, 1) << 51) /* Dirty Bit Management */비트 51의 Dirty Bit Management 속성입니다. 쓰기가 발생할 때 하드웨어가 읽기 전용 상태를 갱신할 수 있는 매핑을 표시하며, 리눅스에서는 PTE_WRITE를 나타내는 데도 사용합니다. 이 비트 자체가 쓰기 완료 기록을 뜻하지는 않습니다.
#define PTE_CONT (_AT(pteval_t, 1) << 52) /* Contiguous range */비트 52의 Contiguous 힌트입니다. 정렬, 주소 연속성, 공통 속성 조건을 만족하는 인접 항목들을 TLB에서 묶어 처리할 수 있음을 알립니다. 이 비트를 설정하는 것만으로 물리 페이지가 연속 배치되지는 않습니다.
#define PTE_PXN (_AT(pteval_t, 1) << 53) /* Privileged XN */비트 53의 PXN(Privileged Execute Never) 속성입니다. 일반 권한 형식에서 EL1이 이 페이지의 명령을 실행하지 못하게 합니다. EL0의 실행을 제어하는 UXN과는 별개입니다.
#define PTE_UXN (_AT(pteval_t, 1) << 54) /* User XN */user execute-never permission bit로 kernel PXN과 독립적이다.
#define PTE_SWBITS_MASK _AT(pteval_t, (BIT(63) | GENMASK(58, 55)))리눅스가 소프트웨어 용도로 사용하는 비트 63과 [58:55]를 묶은 마스크입니다. BIT는 한 비트를, GENMASK는 연속된 범위를 선택합니다. 하드웨어 주소·권한 비트와 소프트웨어 상태를 구분해 다룰 때 사용합니다.
#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (50 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)PAGE_SHIFT부터 49까지의 비트를 선택하는 주소 마스크입니다. 페이지 내부 오프셋을 제외하고 PTE의 일반적인 위치에 저장된 물리 주소를 추출합니다. 52비트 물리 주소의 나머지 상위 비트는 PTE_ADDR_HIGH로 따로 처리합니다.
#ifdef CONFIG_ARM64_PA_BITS_5252비트 물리 주소를 지원하는 빌드에서는 PTE의 일반 주소 필드에 들어가지 않는 상위 주소 비트의 별도 인코딩을 정의합니다. 가상 주소 폭을 선택하는 CONFIG_ARM64_VA_BITS_52와 다른 설정입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#ifdef CONFIG_ARM64_64K_PAGES64KiB 페이지의 52비트 물리 주소 형식은 상위 네 비트를 PTE의 15:12에 담습니다. 다른 페이지 크기의 LPA2 형식에서는 상위 두 비트를 9:8에 담으므로 마스크와 이동량을 별도로 정의합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define PTE_ADDR_HIGH (_AT(pteval_t, 0xf) << 12)52비트 물리 주소와 64KiB 페이지를 사용할 때 PTE의 비트 [15:12]를 선택합니다. 이곳에 물리 주소의 [51:48]을 옮겨 저장하므로, 연속된 주소 마스크만으로는 원래 주소를 복원할 수 없습니다.
#define PTE_ADDR_HIGH_SHIFT 3664KiB 페이지에서 52비트 물리 주소를 복원할 때 쓰는 시프트 값 36입니다. PTE[15:12]를 왼쪽으로 36비트 이동해 물리 주소 [51:48]로 되돌립니다. PTE를 만들 때는 반대 방향으로 이동합니다.
#define PHYS_TO_PTE_ADDR_MASK (PTE_ADDR_LOW | PTE_ADDR_HIGH)64KiB 페이지의 52비트 물리 주소를 PTE 형식으로 변환한 뒤 남길 비트 범위입니다. 일반 주소 부분인 PTE_ADDR_LOW와 상위 주소를 옮겨 놓은 PTE_ADDR_HIGH를 OR로 합칩니다.
#else64KiB 페이지가 아닌 52비트 물리 주소 구성입니다. LPA2 형식에 맞춰 상위 두 주소 비트를 PTE의 9:8에서 처리하고 이동량과 추출 마스크도 그 형식으로 바꿉니다.
#define PTE_ADDR_HIGH (_AT(pteval_t, 0x3) << 8)64KiB가 아닌 페이지에서 52비트 물리 주소를 사용하는 형식은 주소의 [51:50]을 PTE[9:8]에 저장합니다. 0x3을 왼쪽으로 8비트 이동한 마스크로 두 비트를 선택합니다. LPA2에서는 기존 공유 속성 위치를 주소에 사용한다는 점이 64KiB 형식과 다릅니다.
#define PTE_ADDR_HIGH_SHIFT 42PTE[9:8]에 저장한 물리 주소 [51:50]을 복원하는 시프트 값 42입니다. 64KiB 형식의 값 36을 쓰면 다른 주소가 되므로 페이지 크기에 따라 정의를 구분합니다.
#define PHYS_TO_PTE_ADDR_MASK GENMASK_ULL(49, 8)64KiB가 아닌 페이지의 52비트 주소 형식에서 변환된 값의 비트 [49:8]을 남깁니다. __phys_to_pte_val이 phys와 phys를 오른쪽으로 42비트 이동한 값을 합친 뒤 사용하며, 낮은 위치로 옮긴 상위 주소 2비트도 보존합니다.
#endif181행에서 시작한 선택 구간을 마칩니다. 64KiB 페이지의 52비트 물리 주소 형식은 상위 네 비트를 PTE의 15:12에 담습니다. 다른 페이지 크기의 LPA2 형식에서는 상위 두 비트를 9:8에 담으므로 마스크와 이동량을 별도로 정의합니다.
#endif180행에서 시작한 선택 구간을 마칩니다. 52비트 물리 주소를 지원하는 빌드에서는 PTE의 일반 주소 필드에 들어가지 않는 상위 주소 비트의 별도 인코딩을 정의합니다. 가상 주소 폭을 선택하는 CONFIG_ARM64_VA_BITS_52와 다른 설정입니다.
#define PTE_ATTRINDX(t) (_AT(pteval_t, (t)) << 2)메모리 속성 번호 t를 PTE의 AttrIndx, 즉 비트 [4:2]에 넣습니다. 이 번호로 선택한 MAIR 속성에 따라 Normal·Device 메모리와 캐시 동작이 결정됩니다. 속성 전체를 PTE에 직접 넣는 것은 아닙니다.
#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)PTE의 AttrIndx 비트 [4:2]를 선택하는 마스크 0x1c입니다. 물리 주소와 권한은 그대로 두고 메모리 속성 번호만 추출하거나 바꿀 때 사용합니다.
#define PTE_PI_IDX_0 6 /* AP[1], USER */Permission Indirection의 4비트 인덱스 중 비트 0이 PTE의 비트 6에 있음을 나타냅니다. 값 6은 마스크가 아니라 위치 번호입니다. pte_pi_index는 BIT(6)으로 해당 비트를 추출해 인덱스의 비트 0으로 옮깁니다.
#define PTE_PI_IDX_1 51 /* DBM */Permission Indirection 인덱스의 비트 1을 PTE의 비트 51에서 추출하기 위한 위치 번호입니다. 일반 형식에서 DBM으로 쓰는 위치를 인덱스로 해석합니다. 값 51 자체를 권한 마스크처럼 OR로 합쳐서는 안 됩니다.
#define PTE_PI_IDX_2 53 /* PXN */Permission Indirection 인덱스의 비트 2가 PTE의 비트 53에 있음을 나타냅니다. 일반 형식의 PXN 위치에서 한 비트를 추출해 권한 표를 선택하는 4비트 인덱스에 넣습니다.
#define PTE_PI_IDX_3 54 /* UXN */Permission Indirection 인덱스의 비트 3이 PTE의 비트 54에 있음을 나타냅니다. 일반 형식의 UXN 위치를 사용하며 다른 세 비트와 합쳐 권한 표의 인덱스를 만듭니다.
#define PTE_PO_IDX_0 (_AT(pteval_t, 1) << 60)Permission Overlay 인덱스의 비트 0에 해당하는 PTE 비트 60의 마스크입니다. POR_EL0 등의 설정에서 추가 접근 제한을 선택하기 위한 정보이며, 직접 읽기·쓰기 권한을 부여하는 비트는 아닙니다.
#define PTE_PO_IDX_1 (_AT(pteval_t, 1) << 61)Permission Overlay 인덱스의 비트 1에 해당하는 PTE 비트 61의 마스크입니다. 비트 60·62와 함께 3비트 보호 키 인덱스를 구성해 추가 접근 제한을 선택합니다.
#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 62)Permission Overlay 인덱스의 비트 2에 해당하는 PTE 비트 62의 마스크입니다. 세 비트로 최대 8개 값을 표현하며, 실제 접근 허용·금지는 선택한 오버레이 설정에 따릅니다.
#define PTE_PO_IDX_MASK GENMASK_ULL(62, 60)Permission Overlay 인덱스 전체인 PTE[62:60]을 선택하는 마스크입니다. FIELD_GET 등으로 3비트 키를 추출하거나 기존 인덱스를 지우고 새 키를 넣을 때 사용합니다.
x86-64 · Linux 6.18.37
x86 PTE의 권한·상태 비트와 소프트웨어 비트
4-level 또는 LA57 5-level이며 PMD/PUD의 PS bit가 large page leaf를 표시한다. PAT bit 위치는 4KiB와 huge PTE에서 달라지고 global bit는 CR3 switch 생존 범위에 영향을 준다.
원본 코드: arch/x86/include/asm/pgtable_types.h:43-109
43#define _PAGE_BIT_SAVED_DIRTY _PAGE_BIT_SOFTW2 /* Saved Dirty bit (leaf) */
44#define _PAGE_BIT_NOPTISHADOW _PAGE_BIT_SOFTW2 /* No PTI shadow (root PGD) */
45#endif
46
47/* If _PAGE_BIT_PRESENT is clear, we use these: */
48/* - if the user mapped it with PROT_NONE; pte_present gives true */
49#define _PAGE_BIT_PROTNONE _PAGE_BIT_GLOBAL
50
51#define _PAGE_PRESENT (_AT(pteval_t, 1) << _PAGE_BIT_PRESENT)
52#define _PAGE_RW (_AT(pteval_t, 1) << _PAGE_BIT_RW)
53#define _PAGE_USER (_AT(pteval_t, 1) << _PAGE_BIT_USER)
54#define _PAGE_PWT (_AT(pteval_t, 1) << _PAGE_BIT_PWT)
55#define _PAGE_PCD (_AT(pteval_t, 1) << _PAGE_BIT_PCD)
56#define _PAGE_ACCESSED (_AT(pteval_t, 1) << _PAGE_BIT_ACCESSED)
57#define _PAGE_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_DIRTY)
58#define _PAGE_PSE (_AT(pteval_t, 1) << _PAGE_BIT_PSE)
59#define _PAGE_GLOBAL (_AT(pteval_t, 1) << _PAGE_BIT_GLOBAL)
60#define _PAGE_SOFTW1 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW1)
61#define _PAGE_SOFTW2 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW2)
62#define _PAGE_SOFTW3 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW3)
63#define _PAGE_PAT (_AT(pteval_t, 1) << _PAGE_BIT_PAT)
64#define _PAGE_PAT_LARGE (_AT(pteval_t, 1) << _PAGE_BIT_PAT_LARGE)
65#define _PAGE_SPECIAL (_AT(pteval_t, 1) << _PAGE_BIT_SPECIAL)
66#define _PAGE_CPA_TEST (_AT(pteval_t, 1) << _PAGE_BIT_CPA_TEST)
67#define _PAGE_KERNEL_4K (_AT(pteval_t, 1) << _PAGE_BIT_KERNEL_4K)
68#ifdef CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS
69#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT0)
70#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT1)
71#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT2)
72#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT3)
73#else
74#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 0))
75#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 0))
76#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 0))
77#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 0))
78#endif
79
80#define _PAGE_PKEY_MASK (_PAGE_PKEY_BIT0 | \
81 _PAGE_PKEY_BIT1 | \
82 _PAGE_PKEY_BIT2 | \
83 _PAGE_PKEY_BIT3)
84
85#if defined(CONFIG_X86_64) || defined(CONFIG_X86_PAE)
86#define _PAGE_KNL_ERRATUM_MASK (_PAGE_DIRTY | _PAGE_ACCESSED)
87#else
88#define _PAGE_KNL_ERRATUM_MASK 0
89#endif
90
91#ifdef CONFIG_MEM_SOFT_DIRTY
92#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_SOFT_DIRTY)
93#else
94#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 0))
95#endif
96
97/*
98 * Tracking soft dirty bit when a page goes to a swap is tricky.
99 * We need a bit which can be stored in pte _and_ not conflict
100 * with swap entry format. On x86 bits 1-4 are *not* involved
101 * into swap entry computation, but bit 7 is used for thp migration,
102 * so we borrow bit 1 for soft dirty tracking.
103 *
104 * Please note that this bit must be treated as swap dirty page
105 * mark if and only if the PTE/PMD has present bit clear!
106 */
107#ifdef CONFIG_MEM_SOFT_DIRTY
108#define _PAGE_SWP_SOFT_DIRTY _PAGE_RW
109#else주요 구문 해설
원본 67줄은 그대로 표시하며, 검토한 49개 구문에 설명을 붙였습니다.
#define _PAGE_BIT_SAVED_DIRTY _PAGE_BIT_SOFTW2 /* Saved Dirty bit (leaf) */32비트 x86에서는 저장용 Dirty 상태에 소프트웨어 비트 10을 할당합니다. 쓰기 금지로 바꿀 때도 변경 이력을 다른 위치에 남기기 위한 위치 번호입니다. 64비트 구성에서 비트 58을 사용하는 정의와는 적용 조건이 다릅니다.
#define _PAGE_BIT_NOPTISHADOW _PAGE_BIT_SOFTW2 /* No PTI shadow (root PGD) */32비트 x86의 최상위 PGD에서 PTI용 사용자 페이지 테이블로 복제하지 않을 항목을 비트 10으로 표시합니다. SavedDirty와 같은 번호지만, 이 값은 최상위 PGD에 쓰고 SavedDirty는 최하위 항목에 쓴다는 차이가 있습니다.
#endif38행에서 시작한 선택 구간을 마칩니다. 64비트와 32비트 PTE는 소프트웨어용 여유 비트의 배치가 다릅니다. saved-dirty와 PTI shadow 제외 표식은 64비트에서는 SOFTW5를, 32비트에서는 지원하지 않는 userfaultfd WP와 겹치는 SOFTW2 자리를 사용합니다.
#define _PAGE_BIT_PROTNONE _PAGE_BIT_GLOBALPresent가 0일 때 비트 8을 리눅스의 PROT_NONE 표시로 재사용합니다. Present가 1이면 같은 위치가 Global을 뜻합니다. Present가 0인 항목은 유효한 하드웨어 변환이 아니므로, 리눅스는 이 비트로 접근을 금지한 상주 페이지와 스왑 항목을 구분할 수 있습니다.
#define _PAGE_PRESENT (_AT(pteval_t, 1) << _PAGE_BIT_PRESENT)PTE의 비트 0인 Present를 설정하는 마스크입니다. 하드웨어가 유효한 주소 변환에 사용할 수 있는지를 나타냅니다. 값이 0인 항목은 리눅스가 스왑이나 PROT_NONE 등의 상태로 해석할 수 있습니다.
#define _PAGE_RW (_AT(pteval_t, 1) << _PAGE_BIT_RW)비트 1의 쓰기 허용 마스크입니다. 읽기 전용 페이지를 쓰기 가능으로 바꿀 때 사용하지만, 최종 접근 허용 여부는 상위 페이지 테이블과 CPU의 보호 설정에도 영향을 받습니다.
#define _PAGE_USER (_AT(pteval_t, 1) << _PAGE_BIT_USER)비트 2의 User/Supervisor 마스크입니다. 사용자 모드에서 접근 가능한 페이지를 지정할 때 사용합니다. 최상위부터 최하위까지의 권한을 함께 확인해야 최종 접근 허용 여부를 알 수 있습니다.
#define _PAGE_PWT (_AT(pteval_t, 1) << _PAGE_BIT_PWT)비트 3의 Page Write Through 마스크입니다. PAT·PCD와 함께 메모리의 캐시 방식을 선택합니다. 쓰기를 허용하는 RW 비트와는 역할이 다릅니다.
#define _PAGE_PCD (_AT(pteval_t, 1) << _PAGE_BIT_PCD)비트 4의 Page Cache Disable 마스크입니다. PAT·PWT와 함께 캐시 방식을 선택합니다. 이 비트 하나만 설정한다고 임의의 메모리를 안전한 MMIO 영역으로 만들 수 있는 것은 아닙니다.
#define _PAGE_ACCESSED (_AT(pteval_t, 1) << _PAGE_BIT_ACCESSED)비트 5의 Accessed 마스크입니다. CPU가 주소 변환에 사용한 기록이며, 커널은 페이지의 최근 참조 여부 등을 판단할 때 사용합니다. 쓰기 여부를 기록하는 Dirty 비트와는 다릅니다.
#define _PAGE_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_DIRTY)비트 6의 Dirty 마스크입니다. 매핑을 통한 쓰기로 내용이 바뀌었음을 기록하며, 커널이 페이지 회수나 저장 장치로의 쓰기 여부를 판단할 때 참고합니다. 소프트웨어의 soft-dirty 추적 비트와는 별개입니다.
#define _PAGE_PSE (_AT(pteval_t, 1) << _PAGE_BIT_PSE)비트 7의 Page Size 속성입니다. 지원되는 상위 항목에 설정하면 다음 페이지 테이블이 아니라 대형 페이지를 가리킵니다. 같은 위치가 4KiB 최하위 PTE에서는 PAT에 쓰이므로 항목의 레벨을 구분해야 합니다.
#define _PAGE_GLOBAL (_AT(pteval_t, 1) << _PAGE_BIT_GLOBAL)비트 8의 Global 속성입니다. 유효한 변환에 설정하면 해당 CR3 전환 시 일반적인 비전역 변환과 같은 방식으로 TLB에서 제거되지 않습니다. 명시적인 무효화까지 막는 속성은 아닙니다.
#define _PAGE_SOFTW1 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW1)소프트웨어용 비트 9를 선택하는 마스크입니다. CPU의 읽기·쓰기 권한이 아니라 리눅스 내부에서 SPECIAL이나 CPA 시험용 표시 등으로 사용합니다.
#define _PAGE_SOFTW2 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW2)소프트웨어용 비트 10의 마스크입니다. 설정과 항목의 종류에 따라 userfaultfd 쓰기 보호 추적이나 32비트 구성의 SavedDirty 등 내부 상태를 저장합니다.
#define _PAGE_SOFTW3 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW3)소프트웨어용 비트 11의 마스크입니다. 용도에 따라 사용자 페이지의 soft-dirty 추적이나 커널 매핑을 4KiB로 유지하는 표시에 사용합니다. 하드웨어 Dirty 비트 6과는 다릅니다.
#define _PAGE_PAT (_AT(pteval_t, 1) << _PAGE_BIT_PAT)4KiB 최하위 PTE에서 PAT 인덱스에 쓰는 비트 7의 마스크입니다. PWT·PCD와 함께 메모리 종류를 선택합니다. 대형 페이지 항목에서는 비트 7이 페이지 크기를 지정하므로 PAT 위치도 달라집니다.
#define _PAGE_PAT_LARGE (_AT(pteval_t, 1) << _PAGE_BIT_PAT_LARGE)대형 페이지 항목에서 PAT 인덱스에 쓰는 비트 12의 마스크입니다. 4KiB PTE의 PAT 비트 7과 구분해 페이지 크기와 캐시 속성을 올바르게 처리합니다.
#define _PAGE_SPECIAL (_AT(pteval_t, 1) << _PAGE_BIT_SPECIAL)일반적인 struct page와 그대로 대응시키지 않는 특수 PTE를 소프트웨어 비트 9로 표시합니다. 하드웨어 접근 권한을 추가하는 비트가 아닙니다.
#define _PAGE_CPA_TEST (_AT(pteval_t, 1) << _PAGE_BIT_CPA_TEST)페이지 속성 변경(CPA) 시험에 쓰는 소프트웨어 비트 9의 마스크입니다. 시험 대상을 표시하고 변경 결과를 확인할 때 사용하며 CPU의 실행·쓰기 권한 자체를 뜻하지는 않습니다.
#define _PAGE_KERNEL_4K (_AT(pteval_t, 1) << _PAGE_BIT_KERNEL_4K)커널 매핑을 4KiB 페이지로 유지하고 대형 페이지로 합치지 않도록 하는 소프트웨어 표시입니다. 비트 11에 기록해 하위 테이블을 합칠 수 있는지 판단할 때 사용합니다.
#ifdef CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYSIntel protection keys는 같은 사용자 주소 공간 안에서 페이지 묶음별 읽기·쓰기를 PKRU로 제어하는 기능입니다. 켜지면 PTE의 네 비트 키 필드를 정의하고, 꺼지면 이 필드 마스크를 0으로 만들어 키 비트를 사용하지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT0)Intel 메모리 보호 키를 빌드하는 구성에서 인덱스의 비트 0에 해당하는 PTE 비트 59의 마스크입니다. 4비트 인덱스로 최대 16개 키를 선택하며, 실제 읽기·쓰기 제한은 해당 보호 키 레지스터의 설정과 함께 결정됩니다.
#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT1)Intel 메모리 보호 키를 빌드하는 구성에서 인덱스의 비트 1에 해당하는 PTE 비트 60의 마스크입니다. 4비트 인덱스로 최대 16개 키를 선택하며, 실제 읽기·쓰기 제한은 해당 보호 키 레지스터의 설정과 함께 결정됩니다.
#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT2)Intel 메모리 보호 키를 빌드하는 구성에서 인덱스의 비트 2에 해당하는 PTE 비트 61의 마스크입니다. 4비트 인덱스로 최대 16개 키를 선택하며, 실제 읽기·쓰기 제한은 해당 보호 키 레지스터의 설정과 함께 결정됩니다.
#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT3)Intel 메모리 보호 키를 빌드하는 구성에서 인덱스의 비트 3에 해당하는 PTE 비트 62의 마스크입니다. 4비트 인덱스로 최대 16개 키를 선택하며, 실제 읽기·쓰기 제한은 해당 보호 키 레지스터의 설정과 함께 결정됩니다.
#elseprotection key 지원이 없으므로 PTE의 네 키 필드 마스크를 모두 0으로 둡니다. 공통 페이지 속성 코드가 키 비트를 설정하지 않도록 하는 정의입니다.
#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 0))메모리 보호 키를 빌드하지 않으면 인덱스 비트 0의 마스크를 0으로 정의합니다. 공통 속성 계산에 포함하더라도 PTE 비트 59가 설정되지 않아 사용하지 않는 키 정보가 생기지 않습니다.
#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 0))메모리 보호 키를 빌드하지 않으면 인덱스 비트 1의 마스크를 0으로 정의합니다. 공통 속성 계산에 포함하더라도 PTE 비트 60가 설정되지 않아 사용하지 않는 키 정보가 생기지 않습니다.
#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 0))메모리 보호 키를 빌드하지 않으면 인덱스 비트 2의 마스크를 0으로 정의합니다. 공통 속성 계산에 포함하더라도 PTE 비트 61가 설정되지 않아 사용하지 않는 키 정보가 생기지 않습니다.
#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 0))메모리 보호 키를 빌드하지 않으면 인덱스 비트 3의 마스크를 0으로 정의합니다. 공통 속성 계산에 포함하더라도 PTE 비트 62가 설정되지 않아 사용하지 않는 키 정보가 생기지 않습니다.
#endif68행에서 시작한 선택 구간을 마칩니다. Intel protection keys는 같은 사용자 주소 공간 안에서 페이지 묶음별 읽기·쓰기를 PKRU로 제어하는 기능입니다. 켜지면 PTE의 네 비트 키 필드를 정의하고, 꺼지면 이 필드 마스크를 0으로 만들어 키 비트를 사용하지 않습니다.
#define _PAGE_PKEY_MASK (_PAGE_PKEY_BIT0 | \보호 키 인덱스를 구성하는 네 비트를 OR로 합친 마스크입니다. 기능이 활성화된 구성에서는 PTE[62:59]를 선택해 키를 추출하거나 바꿀 때 사용합니다. 보호 키 기능을 빌드하지 않으면 각 항이 0이므로 마스크 전체도 0입니다.
_PAGE_PKEY_BIT1 | \PTE에 담는 4비트 보호 키 번호 중 bit 1의 마스크를 합칩니다. 이 값 자체가 접근 허용 여부는 아니며, 키 번호가 PKRU의 권한 항목을 선택합니다.
_PAGE_PKEY_BIT2 | \보호 키 번호의 bit 2까지 OR하여 페이지 표에서 키 필드만 골라낼 마스크를 만듭니다. 나머지 PTE 속성 비트는 포함하지 않습니다.
_PAGE_PKEY_BIT3)마지막 bit 3을 합쳐 4비트 보호 키 필드 전체의 마스크를 완성합니다. 보호 키 기능을 빌드하지 않으면 각 구성 비트가 0이므로 전체 마스크도 0입니다.
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_PAE)64비트 또는 PAE 페이지 표에서는 Knights Landing의 접근·dirty 비트 관련 우회를 위한 마스크를 제공합니다. 비PAE 32비트 형식은 해당 마스크를 0으로 둡니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define _PAGE_KNL_ERRATUM_MASK (_PAGE_DIRTY | _PAGE_ACCESSED)x86-64 또는 PAE 구성에서 빈 페이지 테이블 항목인지 판단할 때 제외할 Accessed·Dirty 마스크입니다. pte_none 등에서 이 비트들을 무시해 KNL 하드웨어 오류와 관련된 잔여 비트를 유효한 매핑으로 잘못 판단하지 않도록 합니다.
#else비PAE 32비트 페이지 표는 위의 Knights Landing 우회 비트 마스크를 사용하지 않아 0으로 정의합니다. 페이지의 일반 접근·dirty 비트 자체를 삭제하는 것은 아닙니다.
#define _PAGE_KNL_ERRATUM_MASK 0x86-64도 PAE도 아닌 구성에서는 KNL 대응용 제외 마스크를 0으로 정의합니다. 공통 판정식에서 이 마스크를 반전해 적용해도 원래 값은 바뀌지 않습니다.
#endif85행에서 시작한 선택 구간을 마칩니다. 64비트 또는 PAE 페이지 표에서는 Knights Landing의 접근·dirty 비트 관련 우회를 위한 마스크를 제공합니다. 비PAE 32비트 형식은 해당 마스크를 0으로 둡니다.
#ifdef CONFIG_MEM_SOFT_DIRTYsoft-dirty는 사용자 페이지가 기준 시점 이후 쓰였는지 소프트웨어가 추적하는 기능입니다. 켜지면 PTE에 추적 비트 마스크를 정의하고, 꺼지면 0으로 두어 해당 표식을 쓰지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_SOFT_DIRTY)CONFIG_MEM_SOFT_DIRTY를 사용할 때 소프트웨어 변경 이력을 추적하는 비트 11입니다. 사용자 공간의 변경 추적에 쓰며 CPU가 설정하는 Dirty 비트 6과는 별개의 기록입니다.
#elsesoft-dirty 추적을 빌드하지 않아 해당 PTE 표식 마스크를 0으로 만듭니다. 하드웨어의 실제 dirty 비트는 별도로 유지됩니다.
#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 0))soft-dirty 추적 기능을 빌드하지 않으면 이 속성을 자료형이 지정된 상수 0으로 정의합니다. 공통 코드에서 보호 속성에 OR로 합쳐도 새로운 비트가 추가되지 않습니다.
#endif91행에서 시작한 선택 구간을 마칩니다. soft-dirty는 사용자 페이지가 기준 시점 이후 쓰였는지 소프트웨어가 추적하는 기능입니다. 켜지면 PTE에 추적 비트 마스크를 정의하고, 꺼지면 0으로 두어 해당 표식을 쓰지 않습니다.
#ifdef CONFIG_MEM_SOFT_DIRTYsoft-dirty 추적을 켜면 swap entry에도 쓰기 흔적을 남길 수 있도록 비트 위치를 정합니다. 여기서는 present가 꺼진 entry의 _PAGE_RW 자리를 사용하며 실제 present PTE의 쓰기 허용 비트와 문맥을 구분해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define _PAGE_SWP_SOFT_DIRTY _PAGE_RWPresent가 0인 스왑 항목에서는 일반적인 RW 비트 1을 soft-dirty 정보 저장에 사용합니다. 유효한 하드웨어 변환이 아니므로, 이때 비트 1은 쓰기 허용이 아니라 스왑된 페이지의 변경 이력을 뜻합니다.
#elsesoft-dirty 추적이 없으므로 swap entry용 soft-dirty 마스크도 0으로 둡니다. swap 기능 자체를 끄는 조건은 아닙니다.
RISC-V · Linux 6.18.37
V/R/W/X/U/G/A/D와 PPN field
기본 Sv39/Sv48/Sv57에서는 V=0 또는 W=1,R=0일 때 먼저 page fault다. 그 외 V=1에서 R/W/X=000이면 다음 단계 table 후보, R=1 또는 X=1이면 leaf 후보이다. Sv39, Sv48, Sv57 mode에 따라 root level이 달라지며 superpage leaf는 낮은 PPN field 정렬을 요구한다.
원본 코드: arch/riscv/include/asm/pgtable-bits.h:5-42
5
6#ifndef _ASM_RISCV_PGTABLE_BITS_H
7#define _ASM_RISCV_PGTABLE_BITS_H
8
9#define _PAGE_ACCESSED_OFFSET 6
10
11#define _PAGE_PRESENT (1 << 0)
12#define _PAGE_READ (1 << 1) /* Readable */
13#define _PAGE_WRITE (1 << 2) /* Writable */
14#define _PAGE_EXEC (1 << 3) /* Executable */
15#define _PAGE_USER (1 << 4) /* User */
16#define _PAGE_GLOBAL (1 << 5) /* Global */
17#define _PAGE_ACCESSED (1 << 6) /* Set by hardware on any access */
18#define _PAGE_DIRTY (1 << 7) /* Set by hardware on any write */
19#define _PAGE_SOFT (3 << 8) /* Reserved for software */
20
21#define _PAGE_SPECIAL (1 << 8) /* RSW: 0x1 */
22#define _PAGE_TABLE _PAGE_PRESENT
23
24/*
25 * _PAGE_PROT_NONE is set on not-present pages (and ignored by the hardware) to
26 * distinguish them from swapped out pages
27 */
28#define _PAGE_PROT_NONE _PAGE_GLOBAL
29
30/* Used for swap PTEs only. */
31#define _PAGE_SWP_EXCLUSIVE _PAGE_ACCESSED
32
33#define _PAGE_PFN_SHIFT 10
34
35/*
36 * when all of R/W/X are zero, the PTE is a pointer to the next level
37 * of the page table; otherwise, it is a leaf PTE.
38 */
39#define _PAGE_LEAF (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)
40
41#endif /* _ASM_RISCV_PGTABLE_BITS_H */
42 주요 구문 해설
원본 38줄은 그대로 표시하며, 검토한 19개 구문에 설명을 붙였습니다.
#ifndef _ASM_RISCV_PGTABLE_BITS_HRISC-V PTE 비트 정의 헤더가 중복 처리되지 않도록 하는 포함 보호 조건입니다. 페이지 변환 기능을 켜는 런타임 검사나 Kconfig 옵션은 아닙니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#define _ASM_RISCV_PGTABLE_BITS_H헤더가 중복으로 포함되는 것을 막는 표시입니다. 앞의 #ifndef와 함께 사용해 한 번 읽은 헤더의 본문을 다시 처리하지 않도록 합니다. 숫자값이 아니라 정의 여부만 검사하므로 치환할 값은 비워 둡니다.
#define _PAGE_ACCESSED_OFFSET 6A bit가 clear이면 hardware 또는 software access-bit 처리 경로가 필요하다.
#define _PAGE_PRESENT (1 << 0)PTE의 Valid에 해당하는 비트 0의 마스크 1입니다. 유효한 페이지나 다음 페이지 테이블을 가리키는 항목에 사용합니다. 최종 페이지 매핑인지 구분하려면 R/W/X 조합도 함께 확인해야 합니다.
#define _PAGE_READ (1 << 1) /* Readable */R bit는 load 허용뿐 아니라 W와의 합법적인 encoding 조건에도 관여한다.
#define _PAGE_WRITE (1 << 2) /* Writable */쓰기 허용 W를 나타내는 비트 2의 마스크 4입니다. 일반적인 쓰기 가능 페이지는 R도 설정해야 합니다. 기본 Sv39/Sv48/Sv57에서 W=1,R=0은 유효한 읽기·쓰기 leaf가 아니라 page fault를 일으킵니다.
#define _PAGE_EXEC (1 << 3) /* Executable */명령 실행 허용 X를 나타내는 비트 3의 마스크 8입니다. 데이터 전용 페이지에서는 이 비트를 지우고, 실행할 코드를 매핑할 때 필요한 권한과 함께 설정합니다.
#define _PAGE_USER (1 << 4) /* User */사용자 모드에서 접근 가능한 페이지를 나타내는 U, 비트 4의 마스크입니다. Supervisor 모드의 접근은 SUM 등 다른 제어에도 영향을 받으므로 이 비트 하나가 모든 모드의 접근을 허용하지는 않습니다.
#define _PAGE_GLOBAL (1 << 5) /* Global */비트 5의 G 마스크입니다. 특정 ASID에만 속하지 않는 전역 매핑을 나타내며, 주소 공간 전환과 TLB 무효화 범위를 결정할 때 사용합니다.
#define _PAGE_ACCESSED (1 << 6) /* Set by hardware on any access */A bit가 clear이면 hardware 또는 software access-bit 처리 경로가 필요하다.
#define _PAGE_DIRTY (1 << 7) /* Set by hardware on any write */비트 7의 Dirty 상태를 나타내는 D 마스크입니다. 매핑을 통한 쓰기로 내용이 바뀌었음을 기록하며, 커널이 페이지 회수나 저장 장치로의 쓰기 여부를 판단할 때 사용합니다.
#define _PAGE_SOFT (3 << 8) /* Reserved for software */소프트웨어용 RSW 비트 [9:8]을 함께 선택하는 마스크 0x300입니다. 하드웨어의 R/W/X 권한과 별개로 리눅스가 PTE 종류 등 보조 상태를 저장하는 데 사용합니다.
#define _PAGE_SPECIAL (1 << 8) /* RSW: 0x1 */RSW의 하위 비트인 비트 8을 특수 PTE 표시로 사용합니다. 일반적인 struct page에 대응하는 페이지로 처리할 수 없는 매핑을 리눅스가 구분하기 위한 소프트웨어 정보입니다.
#define _PAGE_TABLE _PAGE_PRESENT다음 레벨의 페이지 테이블을 가리키는 항목의 기본값으로 Valid 비트만 사용합니다. R/W/X가 모두 0이면 최종 페이지가 아니라 다음 테이블을 가리키므로 페이지 접근 권한과 구분합니다.
#define _PAGE_PROT_NONE _PAGE_GLOBALValid가 0인 항목에서 Global 위치인 비트 5를 PROT_NONE 표시로 재사용합니다. CPU에는 무효한 항목이지만, 리눅스는 접근을 금지한 상주 페이지와 스왑 항목을 구분할 수 있습니다.
#define _PAGE_SWP_EXCLUSIVE _PAGE_ACCESSED스왑용 PTE에서는 Accessed 위치인 비트 6을 스왑 항목의 배타적 사용 표시로 사용합니다. 유효한 페이지의 접근 이력과는 의미가 다릅니다. 익명 페이지의 공유·복사 처리에 필요한 상태를 스왑 항목에 저장합니다.
#define _PAGE_PFN_SHIFT 10물리 페이지 번호 PFN을 PTE의 비트 10부터 저장하기 위한 시프트 값입니다. 하위 10비트는 속성 등에 남겨 두고 PFN을 왼쪽으로 10비트 이동해 주소 부분을 만듭니다. 페이지 크기가 1024바이트라는 뜻은 아닙니다.
#define _PAGE_LEAF (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)R·W·X 중 하나라도 설정되었는지 검사하는 마스크 0xe입니다. 이 마스크가 0이 아니어도 하드웨어에서 유효한 leaf라는 보장은 없습니다. 기본 Sv39/Sv48/Sv57의 page walker는 V=0 또는 W=1,R=0을 먼저 page fault로 처리한 뒤 R=1 또는 X=1인지 검사합니다. 그다음 접근 권한, A/D 상태와 superpage PPN 정렬도 확인합니다.
#endif /* _ASM_RISCV_PGTABLE_BITS_H */6행에서 시작한 선택 구간을 마칩니다. RISC-V PTE 비트 정의 헤더가 중복 처리되지 않도록 하는 포함 보호 조건입니다. 페이지 변환 기능을 켜는 런타임 검사나 Kconfig 옵션은 아닙니다.
05 · WORKED EXAMPLE
숫자로 검산하기
VA 하나를 세 architecture의 page-table index로 분해
4KiB page와 48-bit VA에서 가상주소 0xffff800012345678을 예로 든다.
- offset하위 12bit는 0x678이다.
- PTE indexbit20:12는 (VA>>12)&0x1ff = 0x145다.
- PMD indexbit29:21은 0x091, PUD index bit38:30과 PGD index bit47:39도 같은 방식으로 구한다.
- architecture checkx86 canonical sign extension, arm64 TCR VA size, RISC-V 선택 SATP mode가 이 VA를 실제로 허용하는지 별도로 확인한다.
결론index 산술이 같아 보이는 4KiB/9-bit level 구성에서도 descriptor bit, top-level 수와 canonical address 규칙은 같지 않다.
06 · DEEP DIVE
주요 동작 상세 분석
공통 커널 코드와 아키텍처별 함수의 역할 구분
mm core는 pgd/p4d/pud/pmd/pte API를 사용하지만 folded level, leaf 허용 level, accessed/dirty hardware 지원과 execute permission encoding은 architecture가 정한다.
software PTE helper가 만든 raw value가 memory에 publish되고 TLB invalidate가 끝난 뒤에야 hardware permission이 바뀐다. PTE write와 shootdown을 하나의 transaction으로 읽는다.
arm64: valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptor
4KiB granule 기준 최대 4/5 level을 사용하며 block descriptor가 PUD/PMD leaf를 만든다. UXN과 PXN이 user/kernel execute를 분리하고 AF/DBM이 access/dirty 관리에 관여한다.
break-before-make가 필요한 attribute/output-address 변경은 invalidation과 barrier 순서를 강제한다. 디버깅할 때는 raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다.
x86-64: x86 PTE의 권한·상태 비트와 소프트웨어 비트
4-level 또는 LA57 5-level이며 PMD/PUD의 PS bit가 large page leaf를 표시한다. PAT bit 위치는 4KiB와 huge PTE에서 달라지고 global bit는 CR3 switch 생존 범위에 영향을 준다.
permission 감소는 TLB shootdown 전까지 remote CPU에서 효력이 없으며 A/D bit clear는 hardware update와 경쟁한다. 디버깅할 때는 CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다.
RISC-V: V/R/W/X/U/G/A/D와 PPN field
기본 Sv39/Sv48/Sv57에서는 V=0 또는 W=1,R=0일 때 먼저 page fault다. 그 외 V=1에서 R/W/X=000이면 다음 단계 table 후보, R=1 또는 X=1이면 leaf 후보이다. Sv39, Sv48, Sv57 mode에 따라 root level이 달라지며 superpage leaf는 낮은 PPN field 정렬을 요구한다.
invalid PTE에서 valid leaf로 publish한 뒤 local/remote sfence.vma가 old translation을 제거해야 한다. 디버깅할 때는 SATP.MODE, ASID, level index, V/R/W/X/U/G/A/D와 PPN 정렬을 확인한다.
객체를 사용하고 해제하는 주체와 시점을 확인한다
page-table page는 RCU/TLB grace period가 끝날 때까지 walker가 참조할 수 있다. leaf를 지웠다고 즉시 table page를 allocator에 돌려주면 remote walker가 재사용된 memory를 PTE로 읽는다.
주소나 레지스터 값만 확인하면 갱신되지 않은 상태를 놓칠 수 있다. 상태를 만드는 코드, 다른 코드에 전달하는 시점, 사용하는 코드, 해제하는 시점을 함께 기록한다.
최대 지연 시간은 명령 하나의 실행 시간만으로 결정되지 않는다
page walk level 수, huge mapping hit, page-walk cache와 TLB miss가 합쳐진다. 4KiB mapping의 최악 memory reference 수와 2MiB/1GiB leaf의 감소를 따로 계산한다.
평균값뿐 아니라 인터럽트를 비활성화한 시간, 다른 CPU의 응답 시간, 펌웨어 호출 시간, 재시도 횟수를 따로 확인해야 최대 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | table descriptor를 block으로 잘못 해석하거나 BBM을 어기면 stale translation 또는 architecture fault가 난다. | raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다. |
| x86-64 | NXE, PAT, encryption C-bit 또는 software protnone encoding을 누락하면 permission과 cache type이 달라진다. | CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다. |
| RISC-V | W=1,R=0 같은 reserved encoding이나 misaligned superpage PPN은 page fault를 만든다. | SATP.MODE, ASID, level index, V/R/W/X/U/G/A/D와 PPN 정렬을 확인한다. |
08 · LAB
재현과 계측 절차
- 동일한 VA에 대해 page-table walker를 작성해 raw entry와 최종 PA를 세 architecture에서 출력한다.
- 4KiB mapping과 huge mapping의 TLB miss latency를 perf counter로 비교한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
하드웨어 판독 규칙: RISC-V Supervisor ISA 1.13, Virtual Address Translation Process. 기본 Sv39/Sv48/Sv57를 설명하며, 예약 비트와 확장 기능의 추가 조건도 확인해야 한다.
09 · REFERENCES
원문 위치
- arm64arch/arm64/include/asm/pgtable-hwdef.h:156-214
- x86-64arch/x86/include/asm/pgtable_types.h:43-109
- RISC-Varch/riscv/include/asm/pgtable-bits.h:5-42
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.