← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 03/21

Page-table format: PTE bit, level과 huge mapping

같은 Linux PTE API 아래에서 arm64 descriptor, x86-64 PTE와 RISC-V Sv39/Sv48 PTE가 어떤 bit와 level을 사용하는지 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 164 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

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으로 읽는다.

지연 시간 관점page walk level 수, huge mapping hit, page-walk cache와 TLB miss가 합쳐진다. 4KiB mapping의 최악 memory reference 수와 2MiB/1GiB leaf의 감소를 따로 계산한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptortable descriptor를 block으로 잘못 해석하거나 BBM을 어기면 stale translation 또는 architecture fault가 난다.raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다.
x86-64Present/RW/US/PWT/PCD/A/D/PS/NX와 software bitNXE, PAT, encryption C-bit 또는 software protnone encoding을 누락하면 permission과 cache type이 달라진다.CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다.
RISC-VV/R/W/X/U/G/A/D와 PPN fieldW=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

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

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 정렬을 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractmm core는 pgd/p4d/pud/pmd/pte API를 사용하지만 folded level, leaf 허용 level, accessed/dirty hardware 지원과 execute permission encoding은 architecture가 정한다.
arm64valid/table bit와 AttrIndx, AP, SH, AF, UXN/PXN descriptorbreak-before-make가 필요한 attribute/output-address 변경은 invalidation과 barrier 순서를 강제한다.
x86-64Present/RW/US/PWT/PCD/A/D/PS/NX와 software bitpermission 감소는 TLB shootdown 전까지 remote CPU에서 효력이 없으며 A/D bit clear는 hardware update와 경쟁한다.
RISC-VV/R/W/X/U/G/A/D와 PPN fieldinvalid PTE에서 valid leaf로 publish한 뒤 local/remote sfence.vma가 old translation을 제거해야 한다.
lifetime boundarypage-table page는 RCU/TLB grace period가 끝날 때까지 walker가 참조할 수 있다. leaf를 지웠다고 즉시 table page를 allocator에 돌려주면 remote walker가 재사용된 memory를 PTE로 읽는다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비break-before-make가 필요한 attribute/output-address 변경은 invalidation과 barrier 순서를 강제한다.관찰: raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다.
x86-64state 준비permission 감소는 TLB shootdown 전까지 remote CPU에서 효력이 없으며 A/D bit clear는 hardware update와 경쟁한다.관찰: CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다.
RISC-Vstate 준비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 정렬을 확인한다.

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개 줄에 각각 설명을 붙였습니다.

L156 * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L157 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L158#define PMD_ATTRINDX(t) (_AT(pmdval_t, (t)) << 2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L159#define PMD_ATTRINDX_MASK (_AT(pmdval_t, 7) << 2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L160(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L161/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L162 * Level 3 descriptor (PTE).

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L163 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L164#define PTE_VALID (_AT(pteval_t, 1) << 0)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L165#define PTE_TYPE_MASK (_AT(pteval_t, 3) << 0)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L166#define PTE_TYPE_PAGE (_AT(pteval_t, 3) << 0)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L167#define PTE_USER (_AT(pteval_t, 1) << 6) /* AP[1] */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L168#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* AP[2] */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L169#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L170#define PTE_AF (_AT(pteval_t, 1) << 10) /* Access Flag */

Access Flag가 clear인 valid mapping은 hardware access flag fault 또는 update 경로를 유발한다.

L171#define PTE_NG (_AT(pteval_t, 1) << 11) /* nG */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L172#define PTE_GP (_AT(pteval_t, 1) << 50) /* BTI guarded */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L173#define PTE_DBM (_AT(pteval_t, 1) << 51) /* Dirty Bit Management */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L174#define PTE_CONT (_AT(pteval_t, 1) << 52) /* Contiguous range */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L175#define PTE_PXN (_AT(pteval_t, 1) << 53) /* Privileged XN */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L176#define PTE_UXN (_AT(pteval_t, 1) << 54) /* User XN */

user execute-never permission bit로 kernel PXN과 독립적이다.

L177#define PTE_SWBITS_MASK _AT(pteval_t, (BIT(63) | GENMASK(58, 55)))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L178(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L179#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (50 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L180#ifdef CONFIG_ARM64_PA_BITS_52

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L181#ifdef CONFIG_ARM64_64K_PAGES

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L182#define PTE_ADDR_HIGH (_AT(pteval_t, 0xf) << 12)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L183#define PTE_ADDR_HIGH_SHIFT 36

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L184#define PHYS_TO_PTE_ADDR_MASK (PTE_ADDR_LOW | PTE_ADDR_HIGH)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L185#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L186#define PTE_ADDR_HIGH (_AT(pteval_t, 0x3) << 8)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L187#define PTE_ADDR_HIGH_SHIFT 42

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L188#define PHYS_TO_PTE_ADDR_MASK GENMASK_ULL(49, 8)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L189#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L190#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L191(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L192/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L193 * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L194 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L195#define PTE_ATTRINDX(t) (_AT(pteval_t, (t)) << 2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L196#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L197(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L198/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L199 * PIIndex[3:0] encoding (Permission Indirection Extension)

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L200 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L201#define PTE_PI_IDX_0 6 /* AP[1], USER */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L202#define PTE_PI_IDX_1 51 /* DBM */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L203#define PTE_PI_IDX_2 53 /* PXN */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L204#define PTE_PI_IDX_3 54 /* UXN */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L205(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L206/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L207 * POIndex[2:0] encoding (Permission Overlay Extension)

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L208 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L209#define PTE_PO_IDX_0 (_AT(pteval_t, 1) << 60)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L210#define PTE_PO_IDX_1 (_AT(pteval_t, 1) << 61)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L211#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 62)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L212(blank)

빈 줄은 arm64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L213#define PTE_PO_IDX_MASK GENMASK_ULL(62, 60)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L214(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개 줄에 각각 설명을 붙였습니다.

L43#define _PAGE_BIT_SAVED_DIRTY _PAGE_BIT_SOFTW2 /* Saved Dirty bit (leaf) */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L44#define _PAGE_BIT_NOPTISHADOW _PAGE_BIT_SOFTW2 /* No PTI shadow (root PGD) */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L45#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L46(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L47/* If _PAGE_BIT_PRESENT is clear, we use these: */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L48/* - if the user mapped it with PROT_NONE; pte_present gives true */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L49#define _PAGE_BIT_PROTNONE _PAGE_BIT_GLOBAL

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L50(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L51#define _PAGE_PRESENT (_AT(pteval_t, 1) << _PAGE_BIT_PRESENT)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L52#define _PAGE_RW (_AT(pteval_t, 1) << _PAGE_BIT_RW)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L53#define _PAGE_USER (_AT(pteval_t, 1) << _PAGE_BIT_USER)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L54#define _PAGE_PWT (_AT(pteval_t, 1) << _PAGE_BIT_PWT)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L55#define _PAGE_PCD (_AT(pteval_t, 1) << _PAGE_BIT_PCD)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L56#define _PAGE_ACCESSED (_AT(pteval_t, 1) << _PAGE_BIT_ACCESSED)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L57#define _PAGE_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_DIRTY)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L58#define _PAGE_PSE (_AT(pteval_t, 1) << _PAGE_BIT_PSE)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L59#define _PAGE_GLOBAL (_AT(pteval_t, 1) << _PAGE_BIT_GLOBAL)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L60#define _PAGE_SOFTW1 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW1)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L61#define _PAGE_SOFTW2 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L62#define _PAGE_SOFTW3 (_AT(pteval_t, 1) << _PAGE_BIT_SOFTW3)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L63#define _PAGE_PAT (_AT(pteval_t, 1) << _PAGE_BIT_PAT)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L64#define _PAGE_PAT_LARGE (_AT(pteval_t, 1) << _PAGE_BIT_PAT_LARGE)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L65#define _PAGE_SPECIAL (_AT(pteval_t, 1) << _PAGE_BIT_SPECIAL)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L66#define _PAGE_CPA_TEST (_AT(pteval_t, 1) << _PAGE_BIT_CPA_TEST)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L67#define _PAGE_KERNEL_4K (_AT(pteval_t, 1) << _PAGE_BIT_KERNEL_4K)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L68#ifdef CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L69#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT0)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L70#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT1)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L71#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT2)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L72#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 1) << _PAGE_BIT_PKEY_BIT3)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L73#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L74#define _PAGE_PKEY_BIT0 (_AT(pteval_t, 0))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L75#define _PAGE_PKEY_BIT1 (_AT(pteval_t, 0))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L76#define _PAGE_PKEY_BIT2 (_AT(pteval_t, 0))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L77#define _PAGE_PKEY_BIT3 (_AT(pteval_t, 0))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L78#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L79(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L80#define _PAGE_PKEY_MASK (_PAGE_PKEY_BIT0 | \

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L81 _PAGE_PKEY_BIT1 | \

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L82 _PAGE_PKEY_BIT2 | \

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L83 _PAGE_PKEY_BIT3)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Page-table format: PTE bit, level과 huge mapping의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L84(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L85#if defined(CONFIG_X86_64) || defined(CONFIG_X86_PAE)

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L86#define _PAGE_KNL_ERRATUM_MASK (_PAGE_DIRTY | _PAGE_ACCESSED)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L87#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L88#define _PAGE_KNL_ERRATUM_MASK 0

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L89#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L90(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L91#ifdef CONFIG_MEM_SOFT_DIRTY

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L92#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 1) << _PAGE_BIT_SOFT_DIRTY)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L93#else

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L94#define _PAGE_SOFT_DIRTY (_AT(pteval_t, 0))

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L95#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L96(blank)

빈 줄은 x86-64 Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L97/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L98 * Tracking soft dirty bit when a page goes to a swap is tricky.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L99 * We need a bit which can be stored in pte _and_ not conflict

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L100 * with swap entry format. On x86 bits 1-4 are *not* involved

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L101 * into swap entry computation, but bit 7 is used for thp migration,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L102 * so we borrow bit 1 for soft dirty tracking.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L103 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L104 * Please note that this bit must be treated as swap dirty page

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L105 * mark if and only if the PTE/PMD has present bit clear!

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L106 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L107#ifdef CONFIG_MEM_SOFT_DIRTY

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L108#define _PAGE_SWP_SOFT_DIRTY _PAGE_RW

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L109#else

Kconfig와 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개 줄에 각각 설명을 붙였습니다.

L5(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L6#ifndef _ASM_RISCV_PGTABLE_BITS_H

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L7#define _ASM_RISCV_PGTABLE_BITS_H

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L8(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L9#define _PAGE_ACCESSED_OFFSET 6

A bit가 clear이면 hardware 또는 software access-bit 처리 경로가 필요하다.

L10(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L11#define _PAGE_PRESENT (1 << 0)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L12#define _PAGE_READ (1 << 1) /* Readable */

R bit는 load 허용뿐 아니라 W와의 합법적인 encoding 조건에도 관여한다.

L13#define _PAGE_WRITE (1 << 2) /* Writable */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L14#define _PAGE_EXEC (1 << 3) /* Executable */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L15#define _PAGE_USER (1 << 4) /* User */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L16#define _PAGE_GLOBAL (1 << 5) /* Global */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L17#define _PAGE_ACCESSED (1 << 6) /* Set by hardware on any access */

A bit가 clear이면 hardware 또는 software access-bit 처리 경로가 필요하다.

L18#define _PAGE_DIRTY (1 << 7) /* Set by hardware on any write */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L19#define _PAGE_SOFT (3 << 8) /* Reserved for software */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L20(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L21#define _PAGE_SPECIAL (1 << 8) /* RSW: 0x1 */

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L22#define _PAGE_TABLE _PAGE_PRESENT

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L23(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L24/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L25 * _PAGE_PROT_NONE is set on not-present pages (and ignored by the hardware) to

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L26 * distinguish them from swapped out pages

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L27 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L28#define _PAGE_PROT_NONE _PAGE_GLOBAL

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L29(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L30/* Used for swap PTEs only. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L31#define _PAGE_SWP_EXCLUSIVE _PAGE_ACCESSED

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L32(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L33#define _PAGE_PFN_SHIFT 10

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L34(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L35/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L36 * when all of R/W/X are zero, the PTE is a pointer to the next level

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L37 * of the page table; otherwise, it is a leaf PTE.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L38 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L39#define _PAGE_LEAF (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)

compile-time 이름, constant 또는 architecture helper를 가져오는 줄이다. macro라면 최종 instruction과 memory-order 의미까지 펼쳐서 확인한다.

L40(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L41#endif /* _ASM_RISCV_PGTABLE_BITS_H */

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L42(blank)

빈 줄은 RISC-V Page-table format: PTE bit, level과 huge mapping 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

VA 하나를 세 architecture의 page-table index로 분해

4KiB page와 48-bit VA에서 가상주소 0xffff800012345678을 예로 든다.

  1. offset하위 12bit는 0x678이다.
  2. PTE indexbit20:12는 (VA>>12)&0x1ff = 0x145다.
  3. PMD indexbit29:21은 0x091, PUD index bit38:30과 PGD index bit47:39도 같은 방식으로 구한다.
  4. 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

경계별 상세 분석

01

공통 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으로 읽는다.

02

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을 출력한다.

03

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를 확인한다.

04

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 정렬을 확인한다.

05

객체 수명과 소유권을 먼저 고정한다

page-table page는 RCU/TLB grace period가 끝날 때까지 walker가 참조할 수 있다. leaf를 지웠다고 즉시 table page를 allocator에 돌려주면 remote walker가 재사용된 memory를 PTE로 읽는다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

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

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64table descriptor를 block으로 잘못 해석하거나 BBM을 어기면 stale translation 또는 architecture fault가 난다.raw PTE, level, output PA, AttrIndx, AP, SH, AF, UXN/PXN과 granule을 출력한다.
x86-64NXE, PAT, encryption C-bit 또는 software protnone encoding을 누락하면 permission과 cache type이 달라진다.CR4.LA57, EFER.NXE, raw PTE, PS/G/NX와 physical-address mask를 확인한다.
RISC-VW=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

재현과 계측 절차

  1. 동일한 VA에 대해 page-table walker를 작성해 raw entry와 최종 PA를 세 architecture에서 출력한다.
  2. 4KiB mapping과 huge mapping의 TLB miss latency를 perf counter로 비교한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.