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 구현
| 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 | Present/RW/US/PWT/PCD/A/D/PS/NX와 software bit | 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
- Present/RW/US/PWT/PCD/A/D/PS/NX와 software bit
- 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
- R/W/X가 모두 0이면 next-level table이고 하나라도 설정되면 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 정렬을 확인한다.
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에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
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개 줄에 각각 설명을 붙였습니다.
* AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define PMD_ATTRINDX(t) (_AT(pmdval_t, (t)) << 2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PMD_ATTRINDX_MASK (_AT(pmdval_t, 7) << 2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Level 3 descriptor (PTE).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define PTE_VALID (_AT(pteval_t, 1) << 0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_TYPE_MASK (_AT(pteval_t, 3) << 0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_TYPE_PAGE (_AT(pteval_t, 3) << 0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_USER (_AT(pteval_t, 1) << 6) /* AP[1] */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* AP[2] */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#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 */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_GP (_AT(pteval_t, 1) << 50) /* BTI guarded */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_DBM (_AT(pteval_t, 1) << 51) /* Dirty Bit Management */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_CONT (_AT(pteval_t, 1) << 52) /* Contiguous range */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PXN (_AT(pteval_t, 1) << 53) /* Privileged XN */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#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)))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (50 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#ifdef CONFIG_ARM64_PA_BITS_52Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#ifdef CONFIG_ARM64_64K_PAGESKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define PTE_ADDR_HIGH (_AT(pteval_t, 0xf) << 12)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_ADDR_HIGH_SHIFT 36compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PHYS_TO_PTE_ADDR_MASK (PTE_ADDR_LOW | PTE_ADDR_HIGH)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define PTE_ADDR_HIGH (_AT(pteval_t, 0x3) << 8)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_ADDR_HIGH_SHIFT 42compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PHYS_TO_PTE_ADDR_MASK GENMASK_ULL(49, 8)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define PTE_ATTRINDX(t) (_AT(pteval_t, (t)) << 2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* PIIndex[3:0] encoding (Permission Indirection Extension)Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define PTE_PI_IDX_0 6 /* AP[1], USER */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PI_IDX_1 51 /* DBM */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PI_IDX_2 53 /* PXN */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PI_IDX_3 54 /* UXN */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* POIndex[2:0] encoding (Permission Overlay Extension)Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define PTE_PO_IDX_0 (_AT(pteval_t, 1) << 60)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PO_IDX_1 (_AT(pteval_t, 1) << 61)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 62)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define PTE_PO_IDX_MASK GENMASK_ULL(62, 60)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86-64 · Linux 6.18.37
Present/RW/US/PWT/PCD/A/D/PS/NX와 software bit
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개 줄에 각각 설명을 붙였습니다.
#define _PAGE_BIT_SAVED_DIRTY _PAGE_BIT_SOFTW2 /* Saved Dirty bit (leaf) */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_BIT_NOPTISHADOW _PAGE_BIT_SOFTW2 /* No PTI shadow (root PGD) */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* If _PAGE_BIT_PRESENT is clear, we use these: */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
/* - if the user mapped it with PROT_NONE; pte_present gives true */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define _PAGE_BIT_PROTNONE _PAGE_BIT_GLOBALcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_PRESENT (_AT(pteval_t, 1) << _PAGE_BIT_PRESENT)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_RW (_AT(pteval_t, 1) << _PAGE_BIT_RW)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_USER (_AT(pteval_t, 1) << _PAGE_BIT_USER)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PWT (_AT(pteval_t, 1) << _PAGE_BIT_PWT)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PCD (_AT(pteval_t, 1) << _PAGE_BIT_PCD)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_ACCESSED (_AT(pteval_t, 1) << _PAGE_BIT_ACCESSED)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_DIRTY)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PSE (_AT(pteval_t, 1) << _PAGE_BIT_PSE)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_GLOBAL (_AT(pteval_t, 1) << _PAGE_BIT_GLOBAL)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_SOFTW1 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW1)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_SOFTW2 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_SOFTW3 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW3)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PAT (_AT(pteval_t, 1) << _PAGE_BIT_PAT)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PAT_LARGE (_AT(pteval_t, 1) << _PAGE_BIT_PAT_LARGE)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_SPECIAL (_AT(pteval_t, 1) << _PAGE_BIT_SPECIAL)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_CPA_TEST (_AT(pteval_t, 1) << _PAGE_BIT_CPA_TEST)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_KERNEL_4K (_AT(pteval_t, 1) << _PAGE_BIT_KERNEL_4K)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#ifdef CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYSKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT1)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT2)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT3)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 0))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 0))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 0))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 0))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_PKEY_MASK (_PAGE_PKEY_BIT0 | \compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
_PAGE_PKEY_BIT1 | \이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
_PAGE_PKEY_BIT2 | \이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
_PAGE_PKEY_BIT3)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_PAE)Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_KNL_ERRATUM_MASK (_PAGE_DIRTY | _PAGE_ACCESSED)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_KNL_ERRATUM_MASK 0compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_MEM_SOFT_DIRTYKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_SOFT_DIRTY)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 0))compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Tracking soft dirty bit when a page goes to a swap is tricky.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* We need a bit which can be stored in pte _and_ not conflictLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* with swap entry format. On x86 bits 1-4 are *not* involvedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* into swap entry computation, but bit 7 is used for thp migration,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* so we borrow bit 1 for soft dirty tracking.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Please note that this bit must be treated as swap dirty pageLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* mark if and only if the PTE/PMD has present bit clear!Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#ifdef CONFIG_MEM_SOFT_DIRTYKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _PAGE_SWP_SOFT_DIRTY _PAGE_RWcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
RISC-V · Linux 6.18.37
V/R/W/X/U/G/A/D와 PPN field
R/W/X가 모두 0이면 next-level table이고 하나라도 설정되면 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개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifndef _ASM_RISCV_PGTABLE_BITS_HKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
#define _ASM_RISCV_PGTABLE_BITS_Hcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_ACCESSED_OFFSET 6A bit가 clear이면 hardware 또는 software access-bit 처리 경로가 필요하다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_PRESENT (1 << 0)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_READ (1 << 1) /* Readable */R bit는 load 허용뿐 아니라 W와의 합법적인 encoding 조건에도 관여한다.
#define _PAGE_WRITE (1 << 2) /* Writable */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_EXEC (1 << 3) /* Executable */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_USER (1 << 4) /* User */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_GLOBAL (1 << 5) /* Global */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#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 */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_SOFT (3 << 8) /* Reserved for software */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_SPECIAL (1 << 8) /* RSW: 0x1 */compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
#define _PAGE_TABLE _PAGE_PRESENTcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* _PAGE_PROT_NONE is set on not-present pages (and ignored by the hardware) toLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* distinguish them from swapped out pagesLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define _PAGE_PROT_NONE _PAGE_GLOBALcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Used for swap PTEs only. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define _PAGE_SWP_EXCLUSIVE _PAGE_ACCESSEDcompile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#define _PAGE_PFN_SHIFT 10compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* when all of R/W/X are zero, the PTE is a pointer to the next levelLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* of the page table; otherwise, it is a leaf PTE.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#define _PAGE_LEAF (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#endif /* _ASM_RISCV_PGTABLE_BITS_H */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
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
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
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: Present/RW/US/PWT/PCD/A/D/PS/NX와 software bit
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
R/W/X가 모두 0이면 next-level table이고 하나라도 설정되면 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로 읽는다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
page walk level 수, huge mapping hit, page-walk cache와 TLB miss가 합쳐진다. 4KiB mapping의 최악 memory reference 수와 2MiB/1GiB leaf의 감소를 따로 계산한다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
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 배치를 확인한다.
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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.