kernel crash log에서 ffff로 시작하는 주소를 보았다고 direct-map pointer라고 판단할 수는 없습니다. 같은 높은 주소 공간에 RAM alias, page metadata, module, entry stack과 진단용 shadow가 함께 있습니다. 영역의 이름뿐 아니라 시작과 끝을 알아야 잘못된 주소 변환을 피할 수 있습니다.
여기서는 upstream Linux v6.18의 기본 x86-64 layout를 end-exclusive 범위로 읽습니다. 숫자는 실행 중인 machine에서 측정한 값이 아니며, KASLR과 configuration에 따라 실제 base·예약 용도가 달라집니다. 주소를 분류하는 지도이지 그 안의 모든 byte에 접근해도 된다는 허가가 아닙니다.
끝 주소를 먼저 통일합니다
문서가 마지막으로 포함하는 주소를 ffffc87fffffffff라고 쓰면 다음 영역의 시작은 그보다 1 큰 ffffc88000000000입니다. 내부 계산을 [start, end)로 통일하면 크기는 end - start이고, 한 영역의 end와 다음 영역의 start가 같아도 overlap하지 않습니다. 아래 표의 hexadecimal 값에는 0x prefix를 생략했습니다.
4-level의 일반 canonical lower half는 [0, 0000800000000000), upper half는 [ffff800000000000, 2^64)입니다. 5-level에서는 각각 [0, 0100000000000000), [ff00000000000000, 2^64)로 넓어집니다. 이는 LAM 계열의 예외를 제외한 주소 형식입니다. lower half 끝의 한 page를 guard로 남기는 kernel policy도 있으므로 형식상 canonical이라는 사실과 사용자 mapping 가능 여부는 다릅니다.
2^64는 수학적 exclusive endpoint입니다. 64-bit unsigned 변수에 저장하면 0으로 wrap하므로 실제 C pointer나 정수 상수처럼 쓰지 않습니다. 높은 주소에서 뺄셈과 정렬을 할 때는 이 endpoint 표현의 차이를 계속 의식해야 합니다.
크게 확장되는 영역
먼저 실제 allocator·metadata와 관련된 세 영역을 비교하겠습니다. 다음은 4-level 기본값입니다.
| 영역 | start | exclusive end |
|---|---|---|
| direct map | ffff888000000000 |
ffffc88000000000 |
| vmalloc·ioremap | ffffc90000000000 |
ffffe90000000000 |
| vmemmap | ffffea0000000000 |
ffffeb0000000000 |
길이는 각각 64 TiB, 32 TiB, 1 TiB입니다. direct map은 physical memory에 offset 관계를 주는 주소 공간이고, vmemmap은 그 RAM의 data 자체가 아니라 struct page를 담는 metadata 주소입니다. 따라서 vmemmap pointer에서 direct-map base를 빼어 원래 data의 physical address를 얻을 수는 없습니다.
5-level 기본값은 다음과 같습니다.
| 영역 | start | exclusive end |
|---|---|---|
| direct map | ff11000000000000 |
ff91000000000000 |
| vmalloc·ioremap | ffa0000000000000 |
ffd2000000000000 |
| vmemmap | ffd4000000000000 |
ffd6000000000000 |
길이는 각각 32 PiB, 12.5 PiB, 0.5 PiB입니다. 세 영역의 크기 비율이 모두 같은 것은 아닙니다. virtual reservation의 크기로 실제 RAM 용량이나 page metadata의 현재 allocation 크기를 역산해서도 안 됩니다. KMSAN은 vmalloc 영역을 shadow·origin 용도로 더 나누며, CONFIG_RANDOMIZE_MEMORY는 direct map·vmalloc·vmemmap의 순서를 유지하면서 base를 이동시킵니다. 실제 pointer의 분류에는 build와 boot 상태를 확인해야 합니다. 기본 layout와 randomization 조건, configuration별 vmalloc 범위가 기준입니다.
guard와 shadow 사이의 간격
kernel half의 처음부터 direct map이 시작하지는 않습니다. 4-level에서는 [ffff800000000000, ffff880000000000)의 8 TiB가 guard·hypervisor reservation이고, 다음 [ffff880000000000, ffff888000000000)의 0.5 TiB가 PTI LDT remap입니다. 5-level에서는 각각 [ff00000000000000, ff10000000000000)의 4 PiB와 [ff10000000000000, ff11000000000000)의 0.25 PiB입니다. LDT 영역은 사용자 data를 vmalloc처럼 요청할 pool이 아닙니다.
큰 영역 사이의 unused interval도 숫자로 구별할 수 있습니다. 4-level의 간격은 direct map 뒤 [ffffc88000000000, ffffc90000000000) 0.5 TiB, vmalloc 뒤 [ffffe90000000000, ffffea0000000000) 1 TiB, vmemmap 뒤 [ffffeb0000000000, ffffec0000000000) 1 TiB입니다. 이 뒤 [ffffec0000000000, fffffc0000000000) 16 TiB가 KASAN shadow reservation입니다.
5-level의 같은 간격은 [ff91000000000000, ffa0000000000000) 3.75 PiB, [ffd2000000000000, ffd4000000000000) 0.5 PiB, [ffd6000000000000, ffdf000000000000) 2.25 PiB입니다. KASAN shadow는 [ffdf000000000000, fffffc0000000000)입니다. 문서의 약 8 PiB라는 표시는 근삿값이며 정확히 8 PiB인 범위라는 뜻은 아닙니다.
shadow가 예약된다고 같은 양의 RAM을 소비한다는 뜻도 아닙니다. 반대로 unused라고 표시된 virtual interval을 일반 allocator가 임의로 가져다 쓸 수 있는 것도 아닙니다. 주소 배치, 기능 활성화와 실제 page-table backing은 서로 다른 층입니다.
높은 쪽에서 같아지는 영역
4-level과 5-level은 fffffc0000000000부터 같은 높은 배치를 공유합니다. 처음 2 TiB인 [fffffc0000000000, fffffe0000000000)는 unused interval이며 KASLR의 상한과 관계가 있습니다. 그 위의 특수 영역은 다음과 같습니다.
| 용도 | start | exclusive end |
|---|---|---|
| cpu entry area | fffffe0000000000 |
fffffe8000000000 |
| ESPFIX stack | ffffff0000000000 |
ffffff8000000000 |
| EFI runtime window | ffffffef00000000 |
ffffffff00000000 |
cpu entry area와 ESPFIX는 각각 0.5 TiB reservation입니다. 둘 사이 [fffffe8000000000, ffffff0000000000)도 0.5 TiB hole입니다. ESPFIX 위에서 EFI까지 [ffffff8000000000, ffffffef00000000)는 444 GiB, EFI 위에서 kernel image 기준까지 [ffffffff00000000, ffffffff80000000)는 2 GiB hole입니다.
EFI의 64 GiB window는 특히 접근 문맥이 다릅니다. 별도 efi_pgd에 마련하며 EFI runtime 호출 동안 이용합니다. 그 주소가 layout에 있다는 이유로 일반 kernel PGD에 항상 같은 mapping이 존재한다고 추론할 수 없습니다. cpu entry area도 보통 data buffer와 달리 entry·exception 처리를 위한 영역입니다.
image와 module 사이의 가변 경계
kernel image 기준인 __START_KERNEL_map은 ffffffff80000000입니다. v6.18은 CONFIG_RANDOMIZE_BASE가 없으면 KERNEL_IMAGE_SIZE를 512 MiB, 있으면 1 GiB로 둡니다. 따라서 module 시작식 __START_KERNEL_map + KERNEL_IMAGE_SIZE는 각각 ffffffffa0000000 또는 ffffffffc0000000이 됩니다. image binary가 그만큼 실제 RAM을 차지한다는 뜻은 아닙니다.
module의 exclusive end인 MODULES_END는 보통 ffffffffff000000이지만 CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP에서는 fffffffffe000000입니다. 이 때문에 기본 문서의 512 MiB image·1520 MiB module 예시를 모든 build의 고정 경계로 사용하면 틀립니다. image 크기 정의와 module 끝 정의를 함께 읽어야 합니다.
fixmap은 FIXADDR_START가 configuration에 따라 달라지는 별도 영역이며 기본 layout의 위 경계는 ffffffffff600000입니다. 바로 이 주소에서 legacy vsyscall ABI의 4 KiB page인 [ffffffffff600000, ffffffffff601000)가 시작합니다. 그 위의 넓은 간격까지 전부 vsyscall code가 아닙니다. 최상단 [ffffffffffe00000, 2^64)의 2 MiB는 unused hole입니다.
이 숫자들이 실제 진단에 유용한 순간은 주소가 경계와 맞지 않을 때입니다. vmalloc pointer를 direct map으로 해석했는지, metadata와 data를 혼동했는지, 정적 base를 randomization된 machine에 적용했는지부터 확인할 수 있습니다. 그 뒤에야 해당 mapping의 PTE와 객체 lifetime을 조사할 순서가 됩니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 3 §3.2의 kernel virtual layout 설명.
- Linux v6.18 x86-64 memory layout.
- Linux v6.18 page_64_types.h, pgtable_64_types.h.