🧠 메모리 교재 · 05_가상메모리_페이징

가상 메모리 ① 페이징과 주소 변환

목표 학습 시간: 105분 + 복습 15분 · 전공 수준 정독용

이 챕터를 마치면: (1) 가상 메모리가 푸는 세 문제(격리·추상화·효율)를 설명하고, (2) 가상 주소를 VPN/offset으로 비트 단위로 쪼개 변환하고, (3) 단일 페이지 테이블의 크기 문제를 수식으로 보이고, (4) x86-64 4단계 페이지 테이블이 그 문제를 어떻게 푸는지 따라갈 수 있다.


0. 학습 지도 (105분)

구간 내용
1 18 가상 메모리가 푸는 세 문제
2 15 페이지·프레임과 페이지 크기의 트레이드오프
3 20 가상 주소 분해와 변환(비트 계산)
4 12 페이지 테이블 항목(PTE)의 내용
5 15 단일 테이블의 크기 문제(수식)
6 20 다단계 페이지 테이블 (x86-64 4단계)
7 5 공유·보호, 그리고 다음 세션 예고
복습 15 인출 + 변환 계산

1. 가상 메모리가 푸는 세 문제

옛날엔 프로그램이 물리 주소를 직접 썼다. 그러면 곤란한 일들이 생긴다 — 두 프로그램이 같은 주소를 쓰면 충돌하고, 한 프로그램이 다른 프로그램(또는 OS)의 메모리를 마음대로 읽고 쓰며, 메모리에 프로그램을 올리려면 연속된 빈 공간이 필요하다. 가상 메모리는 이 셋을 한꺼번에 푼다.

각 프로세스는 자기만의 가상 주소 공간(0부터 거대한 범위)을 보고, OS+하드웨어(MMU)가 이 가상 주소를 실제 물리 주소로 번역한다. 이 한 겹의 간접층(indirection)이 세 가지를 준다.

1.1 격리(isolation)와 보호(protection)

프로세스 A의 가상 0x4000과 B의 가상 0x4000서로 다른 물리 프레임으로 번역된다. 그래서 둘은 같은 주소를 써도 충돌하지 않고(세션 2의 가설 정답), 서로의 메모리를 건드릴 수도 없다. 각 페이지에 읽기/쓰기/실행 권한을 걸어 코드 영역을 읽기 전용으로 보호하는 것도 여기서 나온다.

1.2 추상화(abstraction)

프로세스는 "메모리는 0부터 쭉 이어진 거대한 내 공간"이라는 단순하고 균일한 모델을 본다. 물리 메모리가 실제로 얼마든, 어디에 조각나 있든 신경 쌀 필요가 없다. 컴파일러·링커도 이 균일한 가상 공간을 가정해 코드를 만든다.

1.3 물리 메모리의 효율적 사용

핵심 한 줄: 가상 메모리 = "물리 주소를 직접 쓰는 대신, 프로세스마다 번역되는 가상 주소를 쓰게 한 한 겹의 간접층." 이 간접층이 격리·추상화·효율을 동시에 산다.


2. 페이지와 프레임

2.1 왜 바이트가 아니라 덩어리 단위인가

가상→물리 번역을 매 바이트마다 기록하면 번역표가 메모리보다 커진다. 그래서 메모리를 고정 크기 덩어리로 잘라 덩어리 단위로 번역한다.

2.2 페이지 크기의 트레이드오프

왜 하필 4KB? 크기 선택엔 양면이 있다.

4KB는 이 둘의 오랜 절충점이다. 대용량 메모리를 다루는 DB·가상화에서는 huge page로 TLB 압박을 줄인다.


3. 가상 주소의 분해와 변환

3.1 offset과 VPN으로 쪼개기

페이지가 4KB = 2¹² 바이트이므로, 가상 주소의 하위 12비트는 페이지 안 위치(offset), 나머지 상위 비트는 VPN이다.

가상 주소 (48비트 예)
 ┌───────────────────────────────┬──────────────┐
 │        VPN (상위 36비트)        │ offset(12비트)│
 └───────────────────────────────┴──────────────┘
            │                            │
            │  페이지 테이블에서 VPN→PFN    │  그대로 복사
            ▼                            ▼
 ┌───────────────────────────────┬──────────────┐
 │        PFN (물리 프레임 번호)    │ offset(12비트)│  = 물리 주소
 └───────────────────────────────┴──────────────┘

핵심 규칙: offset은 절대 번역하지 않는다. 페이지 안에서의 상대 위치는 가상이든 물리든 동일하니까. 번역되는 건 오직 페이지 번호(VPN→PFN)다.

3.2 비트 계산 워크드 예제

연습으로 페이지 크기를 8KB(offset 13비트)로 바꿔 같은 절차를 해보면, "offset 비트 수 = log₂(페이지 크기)"라는 규칙이 손에 붙는다.


4. 페이지 테이블 항목(PTE)의 내용

페이지 테이블은 "VPN→PFN" 매핑을 담은 표로 프로세스마다 하나씩 있고, 보통 물리 메모리에 산다. MMU가 이 표를 읽어 하드웨어로 번역한다. 각 항목(PTE)에는 PFN 말고도 중요한 비트들이 있다.

비트/필드 의미
PFN 이 가상 페이지가 매핑된 물리 프레임 번호
Present(유효) 이 페이지가 지금 물리 메모리에 있나? (없으면 페이지 폴트 — 세션 6)
R/W 읽기 전용인가 쓰기 가능인가
U/S 사용자 모드 접근 허용인가, 커널 전용인가
NX 실행 금지(코드 주입 방어, W^X)
Accessed 최근에 접근됐나 (교체 정책이 참고 — 세션 6)
Dirty 수정됐나 (스왑 아웃 시 디스크에 다시 써야 하나)

이 비트들이 가상 메모리를 단순 번역기 이상으로 만든다 — 보호(R/W/NX/US), 디맨드 페이징(Present), 교체 정책(Accessed/Dirty) 이 전부 PTE 비트에서 나온다.


5. 단일 페이지 테이블의 크기 문제

가장 단순한 설계는 "VPN을 인덱스로 하는 거대한 1차원 배열"이다. 그런데 이게 왜 망하는지 수식으로 보자.

가정: 48비트 가상 주소, 4KB(2^12) 페이지
가상 페이지 수 = 2^48 / 2^12 = 2^36 개
PTE 하나 = 8바이트 라 하면
표 하나의 크기 = 2^36 × 8 = 2^39 = 512 GB

프로세스마다 512GB짜리 페이지 테이블을 둬야 한다? 물리 메모리가 그보다 작을 텐데 불가능하다.

게다가 더 큰 낭비가 있다. 대부분의 프로세스는 이 거대한 주소 공간 중 극히 일부만 쓴다(코드 약간, 힙 약간, 스택 약간, 가운데는 텅 빔). 그런데 단일 1차원 표는 안 쓰는 영역의 항목까지 전부 자리를 차지한다. "쓰지도 않는 페이지들을 위한 표"가 메모리를 다 먹는 것이다.


6. 다단계 페이지 테이블 — 트리로 푼다

6.1 아이디어: 표를 트리로

해법은 표를 계층(트리) 으로 만드는 것이다. 상위 표가 하위 표를 가리키고, 실제로 쓰는 영역에 해당하는 하위 표만 만든다. 안 쓰는 영역은 상위 항목을 "없음(invalid)"으로 비워, 그 아래 거대한 하위 표를 아예 만들지 않는다. 거대한 빈 공간의 표를 생략하는 것 — 이게 공간 절약의 핵심이다.

6.2 x86-64의 4단계 (구체적으로)

x86-64는 48비트 가상 주소를 9 + 9 + 9 + 9 + 12 로 쪼갠다.

가상 주소 48비트
 ┌──────┬──────┬──────┬──────┬───────────┐
 │ 9비트│ 9비트│ 9비트│ 9비트│ offset 12 │
 │ PML4 │ PDPT │  PD  │  PT  │           │
 └──┬───┴──┬───┴──┬───┴──┬───┴───────────┘
    │      │      │      │
   L1표 ──┘      │      │      │     ← CR3 레지스터가 L1(PML4) 시작 주소를 가리킴
    └─> L2표 ───┘      │      │
           └─> L3표 ───┘      │
                  └─> L4표(PT)─┘ → PFN
                                  PFN ‖ offset = 물리 주소
가상 주소 48b = 9 + 9 + 9 + 9 + 12 PML4 9 PDPT 9 PD 9 PT 9 offset 12 CR3 PML4 PDPT PD PT [PML4] [PDPT] [PD] [PT] → PFN 각 표 = 512항목 × 8B = 4KB · PFN ‖ offset = 물리 주소 · 번역 1회 = 메모리 4접근
4단계 페이지 워크 — CR3에서 시작해 9비트씩 인덱스로 PML4→PDPT→PD→PT를 타고 내려가 PFN을 얻는다(쓰는 가지의 표만 존재).

6.3 대가: 번역이 비싸졌다

트리를 타고 내려가려면 단계마다 메모리에서 표를 읽어야 한다. 4단계면 번역 한 번에 메모리 접근 4번 + 실제 데이터 1번 = 5번. 모든 메모리 접근이 5배 느려지는 셈이다. 이건 받아들일 수 없다 → 다음 세션의 TLB가 정확히 이 비용을 캐싱으로 없앤다.

6.4 대안 한 줄: 역 페이지 테이블

프레임 수에 비례하는 역 페이지 테이블(inverted page table) 도 있다(물리 프레임마다 "어느 프로세스의 어느 VPN인가"를 기록). 표가 물리 메모리 크기에만 비례해 작지만, 검색이 복잡해 보통 해시와 함께 쓴다. 주류는 다단계지만 설계 대안으로 알아두자.


7. 공유·보호, 그리고 다음 세션 예고

페이지 테이블은 번역기일 뿐 아니라 공유와 보호의 도구다.

다음 세션은 (1) 번역 비용을 없애는 TLB, (2) Present 비트가 만드는 페이지 폴트와 디맨드 페이징, (3) mmap·스왑·COW 로 이어진다.


8. 흔한 오해 바로잡기


9. 한 장 정리


10. 복습 (15분) — 답을 가리고

Q1. 가상 메모리가 푸는 세 문제를 한 줄씩.

격리/보호(프로세스끼리 충돌·간섭 차단), 추상화(균일하고 큰 주소 공간이라는 단순 모델), 물리 메모리 효율(유연 배치·공유·디맨드 페이징).

Q2. 페이지 4KB일 때 가상 주소 0x00012F40의 VPN과 offset, 그리고 PFN=0x88이면 물리 주소는?

offset = 하위 12비트 = 0xF40. VPN = 0x12. 물리 주소 = PFN ‖ offset = 0x88F40 (= 0x00088F40).

Q3. 페이지 크기를 8KB로 바꾸면 offset 비트 수는? 이유는?

13비트. offset 비트 = log₂(페이지 크기) = log₂(8192) = 13.

Q4. 48비트 주소, 4KB 페이지, PTE 8B에서 단일 페이지 테이블의 크기를 계산하라.

페이지 수 = 2^48/2^12 = 2^36. 크기 = 2^36 × 8 = 2^39 = 512GB (프로세스마다!).

Q5. 다단계 페이지 테이블이 공간을 아끼는 핵심 원리는?

표를 트리로 만들어, 실제 쓰는 영역에 해당하는 하위 표만 생성하고 안 쓰는 영역은 상위 항목을 invalid로 비워 거대한 하위 표 자체를 만들지 않는다. 쓰는 만큼만 표가 생긴다.

Q6. x86-64에서 48비트 주소는 어떻게 쪼개지며, 번역은 어디서 시작하나? 번역 한 번의 메모리 접근 수는?

9+9+9+9+12 (PML4/PDPT/PD/PT/offset). CR3가 가리키는 PML4에서 시작해 9비트씩 타고 내려간다(page walk). 4단계라 번역에 메모리 접근 4번 + 데이터 1번 = 총 5번.

Q7. PTE의 Present·Accessed·Dirty 비트는 각각 이후 어느 메커니즘에서 쓰이나?

Present → 페이지 폴트/디맨드 페이징(세션 6). Accessed → 교체 정책(LRU/클럭 근사). Dirty → 스왑 아웃 시 디스크에 다시 써야 하는지 판단.


다음: 세션 6 — 가상 메모리 ② TLB·페이지 폴트·mmap (이어지는 파일)