PML4: 왜 큰 페이지 테이블 하나로는 안 될까?
“그냥 큰 페이지 테이블과 그 안에 포인터를 쓰면 되는 거 아닌가?”라는 의문에서 시작해 64비트 시스템의 다단계 페이지 테이블(PML4)을 살펴본다.
1. PML4란?
PML4는 64비트 시스템에서 사용하는 다단계 페이지 테이블의 최상위 단계다. 여기서 “그냥 큰 페이지 테이블 하나에 포인터를 쓰면 되는 것 아닌가?”라는 의문이 들 수 있는데, 그게 왜 비효율적인지부터 살펴보자.
2. 왜 PML4(다단계)가 필요한가?
64비트 시스템은 이론상 2⁶⁴ = 16엑사바이트(EB)의 주소 공간을 표현할 수 있다. 하지만 현실적으로 x86-64 아키텍처는 48비트만 사용한다 (2⁴⁸ = 256TB).
이를 4KB 페이지로 나누면 필요한 페이지 수는 다음과 같다.
| 항목 | 계산 | 결과 |
|---|---|---|
| 주소 공간 | 2⁴⁸ | 256 TB |
| 페이지 크기 | 2¹² | 4 KB |
| 총 페이지 수 | 2⁴⁸ / 2¹² = 2³⁶ | 약 687억 개 |
단일 테이블로 쓴다면?
엔트리 하나가 8바이트(64bit)라면, 2³⁶ × 8B = 512GB짜리 단일 테이블이 필요하다. 게다가 이걸 프로세스마다 따로 관리해야 한다. 메모리 낭비가 엄청나다.
다단계 페이징의 핵심 아이디어
전체 주소 공간 중 실제로 쓰는 부분만 테이블로 만들자. 테이블을 트리 구조로 나눠 필요한 만큼만 동적으로 할당하는 방식이다.
| 단계 | 엔트리 수 | 다음 단계 |
|---|---|---|
| PML4 | 512 | → PDP |
| PDP | 512 | → PD |
| PD | 512 | → PT |
| PT | 512 | → 실제 물리 페이지 |
이 구조의 장점은 다음과 같다.
| 장점 | 설명 |
|---|---|
| 메모리 절약 | 안 쓰는 주소 공간은 테이블 자체를 만들지 않음 |
| 유연성 | 프로세스마다 필요한 주소 영역만 테이블 구성 |
| 격리·보호 | 계층 구조에서 각 테이블을 독립적으로 관리 |
3. PML4 개념과 주소 변환
3-1. 가상 주소 구조 (48비트)
| PML4 (9bit) | PDP (9bit) | PD (9bit) | PT (9bit) | Offset (12bit) |
- 각 테이블 인덱스는 9비트 = 512개 엔트리 (2⁹)
- Offset 12비트는 4KB 페이지 내부의 주소
- 총 페이지 수: 2⁽⁴⁸⁻¹²⁾ = 2³⁶개
3-2. 4단계 주소 변환 흐름
가상 주소를 따라 4단계 테이블을 타고 내려가 물리 주소를 얻는다.
PML4 ──(9bit)──▶ PDP ──(9bit)──▶ PD ──(9bit)──▶ PT ──(9bit)──▶ 물리 페이지(4KB)
+ Offset(12bit)
각 단계에서 9비트로 512개 엔트리 중 하나를 골라 다음 테이블의 주소를 얻고, 마지막 PT에서 물리 페이지를 찾은 뒤 Offset을 더해 최종 물리 주소를 완성한다.
3-3. 필요한 만큼만 만든다
핵심은 필요한 테이블만 동적으로 생성한다는 점이다. 예를 들어 4KB 페이지 단 하나만 쓴다면, PML4 + PDP + PD + PT 테이블 4개와 물리 페이지 1개만 있으면 된다. 512GB짜리 단일 테이블과 비교하면 압도적으로 효율적이다.
4. PintOS와 다단계 페이징
PintOS는 x86-64 기반의 4단계 페이징 구조를 사용한다. 관련 함수는 다음과 같다.
| 함수 | 역할 |
|---|---|
pml4_create() |
새 PML4 테이블 생성 |
pml4_set_page(pml4, va, pa, writable) |
가상주소 va를 물리주소 pa에 매핑 |
pml4_get_page(pml4, va) |
va에 매핑된 pa 조회 |
pml4_activate(pml4) |
현재 CPU가 사용할 페이지 테이블로 설정 |
pml4와 va가 함께 쓰이는데, pml4는 지도, va는 좌표라고 생각하면 이해하기 쉽다.
프로세스마다 다른 pml4
struct thread안에pml4멤버가 있다- 프로세스 전환 시, 커널은
cr3레지스터에 해당 프로세스의pml4물리 주소를 설정해 주소 공간을 전환한다