Ryotta's Basic

System
🖥️ System 검증완료

title: Virtual Memory
date: 2025-06-21
category: system
tags: [virtual memory, page table, TLB, page fault, address translation, paging]


Virtual Memory

개요

Virtual Memory는 각 프로세스에 독립된 가상 주소 공간(Virtual Address Space)을 제공하여, 물리 메모리의 제약으로부터 프로세스를 격리하는 메모리 관리 기법이다. 이를 통해 여러 프로세스가 물리 메모리를 안전하게 공유할 수 있으며, 프로그래머는 메모리의 실제 크기와 위치에 구애받지 않고 프로그래밍할 수 있다.

가상 메모리는 현대 운영체제의 근간을 이루는 기술로, 프로세스 격리, 메모리 효율적 사용, 동적 할당, 메모리 맵핑 파일 등 핵심 기능을 제공한다. CPU가 생성하는 모든 주소는 가상 주소이며, MMU(Memory Management Unit) 하드웨어에 의해 물리 주소로 변환된다. 이 과정에서 TLB, page-walk cache, huge page 같은 보조 메커니즘이 함께 동작해 주소 변환 지연을 줄인다.

핵심 개념

가상 주소 공간 (Virtual Address Space)

각 프로세스는 독립적인 가상 주소 공간을 가지며, 일반적으로 64비트 시스템에서는 매우 큰 주소 범위를 다룰 수 있다. x86-64에서는 오랫동안 48비트 canonical address를 사용했지만, 최근 CPU와 커널은 5-level paging(LA57)을 통해 57비트 가상 주소 공간까지 확장할 수 있다. 상위 비트는 sign extension 규칙을 따라 canonical form을 이뤄야 하므로, 하드웨어가 잘못된 주소를 초기에 걸러낼 수 있다.

  • Kernel Space: OS 커널이 사용하는 상위 영역 (일반적으로 0xFFFF800000000000~)
  • User Space: 사용자 프로세스가 사용하는 하위 영역 (0x0000000000000000~0x7FFFFFFFFFFF)

페이지 테이블 (Page Table)

가상 주소를 물리 주소로 변환하는 자료구조이다. 현대 CPU는 4단계 계층적 페이지 테이블(Hierarchical Page Table)을 사용하여 메모리를 효율적으로 관리한다. Linux 커널은 소프트웨어 계층 이름으로 PGD/P4D/PUD/PMD/PTE를 사용하고, 실제 하드웨어 단계 수가 적으면 중간 레벨을 folding해 같은 코드 경로를 유지한다.

x86-64 4단계 페이지 테이블 구조:

레벨 이름 비트 수 엔트리 수 역할
4 PML4 (Page Map Level 4) 9비트 512 최상위 디렉토리
3 PDPT (Page Directory Pointer Table) 9비트 512 중간 디렉토리
2 PD (Page Directory) 9비트 512 페이지 디렉토리
1 PT (Page Table) 9비트 512 최종 페이지 테이블
Virtual Address Translation

Linux는 이를 PGD/P4D/PUD/PMD/PTE 계층으로 추상화하며, 하드웨어에 따라 중간 레벨이 folding될 수 있다. 5-level paging이 활성화된 시스템에서는 더 얕은 구조 대신 한 단계가 추가되어 더 큰 가상 주소 공간을 다룬다.

TLB (Translation Lookaside Buffer)

페이지 테이블 변환을 가속화하기 위한 캐시이다. MMU 내부에 위치하며, 최근 사용된 가상→물리 주소 매핑을 저장한다.

  • TLB 구조: 일반적으로 fully associative 또는 set-associative
  • TLB 크기: CPU 세대와 코어 설계에 따라 달라진다
  • 지연 시간: TLB hit은 매우 빠르고, miss 시에는 page walk가 필요해 훨씬 느려진다
  • ASID/PCID: 프로세스 전환 시 TLB flush를 줄이기 위한 식별자
  • Page-walk cache: 상위 페이지 테이블 항목을 다시 읽는 비용을 줄여준다
  • TLB shootdown: 다른 코어의 오래된 변환 캐시를 무효화하기 위한 코어 간 동기화

페이지 폴트 (Page Fault)

가상 주소에 해당하는 페이지가 물리 메모리에 없을 때 발생하는 트랩이다. 하드웨어 트랩 후 OS 커널의 페이지 폴트 핸들러가 처리한다.

페이지 폴트 처리 과정:
1. MMU가 가상 주소 변환 시 present 비트가 0인 엔트리 발견
2. CPU가 Trap #14 (Page Fault) 발생
3. 핸들러가 CR2 레지스터에서 faulting 주소 획득
4. VMA(Virtual Memory Area)에서 해당 주소의 유효성 검사
5. 유효한 경우: 물리 프레임 할당 → 페이지 로드 → TLB 갱신
6. 유효하지 않은 경우: SEGFAULT 시그널 전달

페이지 교체 알고리즘

물리 메모리가 가득 찼을 때 어떤 페이지를 교체할지를 결정하는 알고리즘이다:

  • LRU (Least Recently Used): 가장 오래전에 접근된 페이지를 교체. 이상적이나 구현 비용이 높다.
  • CLOCK (Second Chance): reference bit를 활용한 근사 LRU 계열 기법. 여러 운영체제의 기본 아이디어로 널리 쓰인다.
  • NFU (Not Frequently Used): 접근 빈도를 추적하여 교체 대상 선택
  • Optimal: 미래 접근 패턴을 알 수 있는 이상적인 알고리즘 (연구용)

Linux는 전통적인 active/inactive LRU와 최근의 Multi-Gen LRU(MGLRU)처럼 LRU 근사 계열 정책을 조합해 reclaim 효율과 스캔 비용 사이를 절충한다. 따라서 교체 정책은 단일 알고리즘 하나라기보다, 참조 이력과 reclaim 비용을 함께 고려하는 운영체제 정책 집합으로 이해하는 편이 정확하다.

비교/분석

4단계 vs 5단계 페이지 테이블

구분 일반적 x86-64 5-level paging
주소 범위 48-bit canonical 57-bit canonical
레벨 PML4 → PDPT → PD → PT PML5 → PML4 → PDPT → PD → PT
목적 일반적인 서버/데스크톱 매우 큰 가상 주소 공간 지원
적용 널리 사용 지원 CPU와 커널에서 선택적 사용

페이지 테이블 엔트리 비트 필드 (x86-64)

비트 이름 설명
0 Present 페이지가 물리 메모리에 존재하는지
1 Read/Write 쓰기 허용 여부 (0: 읽기 전용)
2 User/Supervisor 사용자 모드 접근 허용 여부
3 Page-Level Write-Through 직렬 쓰기 동작
4 Page-Level Cache Disable 캐싱 비활성화
5 Accessed 해당 페이지에 접근 이력
6 Dirty 해당 페이지에 쓰기 이력
7 Page Size 0: 4KB 페이지, 1: huge page
8~11 Global TLB에서 전역 유효 (프로세스 전환 시 flush 안 함)
12~51 Physical Address 물리 프레임 번호 (PFN)
52~62 Reserved/Software OS 임의 사용 가능
63 XD (Execute Disable) 실행 불가 비트 (NX 비트)

페이지 크기 비교

페이지 크기 사용처 장점 단점
4KB (표준) 일반 메모리 세밀한 메모리 관리 TLB 커버리지 작음
2MB (Huge) 대용량 메모리 TLB 미스 감소, 변환 오버헤드 감소 내부 단편화
1GB (Giga) 매우 큰 할당 최소 오버헤드 내부 단편화 심각

페이지 폴트 유형

유형 원인 처리 방법
Minor (Soft) 페이지가 메모리에 있으나 매핑 안 됨 매핑만 추가 (I/O 불필요)
Major (Hard) 페이지가 디스크에 있음 디스크에서 로드 (I/O 필요, 느림)
Invalid 잘못된 주소 접근 프로세스 종료 (SIGSEGV)

TLB 미스 처리 경로 비교

경로 핵심 동작 성능 영향 자주 쓰는 완화 방법
TLB Hit TLB에서 즉시 PFN 획득 가장 빠름 locality 유지
TLB Miss + Page-walk cache hit 상위 엔트리 일부를 캐시에서 재사용 중간 수준 지연 huge page, 정렬된 접근
TLB Miss + Full page walk 페이지 테이블을 단계별로 모두 탐색 지연 증가 THP, page-walk cache, PCID
TLB Miss + Page Fault 커널 진입 후 매핑 생성/복구 가장 큼 prefetch, 충분한 메모리, 스와핑 억제

동작 원리

1단계: 가상 주소 생성

CPU가 메모리 접근 명령을 실행하면, 레지스터에 저장된 가상 주소가 MMU에 전달된다. 가상 주소는 48비트(일반적)이며, 각 9비트씩 4개의 인덱스와 12비트 오프셋으로 분할된다.

2단계: TLB 조회

MMU는 먼저 TLB에서 가상 주소의 매핑을 찾는다. 4단계 페이지 테이블의 전체 탐색 없이 TLB hit이면 즉시 물리 주소를 얻는다. 현대 프로세서의 TLB hit률은 99% 이상이다.

3단계: 페이지 테이블 탐색 (TLB Miss)

TLB miss가 발생하면 하드웨어(또는 OS 소프트웨어)가 4단계 페이지 테이블을 순차적으로 탐색한다:

  1. CR3 레지스터 → PML4 테이블 기반 주소 획득
  2. PML4[VPN[47:39]] → PDPT 기반 주소
  3. PDPT[VPN[38:30]] → PD 기반 주소
  4. PD[VPN[29:21]] → PT 기반 주소 또는 2MB huge page 직접 매핑
  5. PT[VPN[20:12]] → 물리 프레임 번호 (PFN) + 플래그
  6. 물리 주소 = PFN × 페이지 크기 + 오프셋[11:0]

상위 엔트리의 huge page 비트가 설정돼 있으면 page walk는 PMD(2MB) 또는 PUD(1GB) 레벨에서 조기에 종료될 수 있다. 이 경우 하위 PT를 생략하므로 TLB reach와 page walk 비용 측면에서 유리하지만, 단편화와 할당 제약이 커진다.

4단계: 페이지 폴트 처리

present 비트가 0이면 페이지 폴트가 발생한다. OS 커널의 do_page_fault() 함수가 호출되어:

  1. faulting 주소와 접근 유형(읽기/쓰기/실행) 확인
  2. 해당 프로세스의 VMA 목록에서 주소 범위 검색
  3. 유효한 VMA인 경우: 물리 프레임 할당 → 디스크 I/O → 매핑 갱신
  4. 유효하지 않은 경우: SIGSEGV 시그널 발생

5단계: Copy-on-Write (COW)

fork() 시 자식 프로세스는 부모의 페이지 테이블을 공유하지만, 쓰기 접근 시에만 복사가 수행된다:

  1. fork() 호출 시 모든 페이지를 읽기 전용으로 설정
  2. 자식 또는 부모가 쓰기 접근 시 페이지 폴트 발생
  3. 핸들러가 페이지를 복사하고 양쪽 프로세스의 매핑을 독립적으로 갱신
  4. 이를 통해 fork()의 시간/메모리 비용을 대폭 절감

6단계: 메모리 맵핑 파일 (mmap)

파일의 내용을 가상 주소 공간에 직접 매핑하여, 파일 I/O를 메모리 접근으로 처리할 수 있다:

  • mmap() 호출 시 파일의 가상 주소 영역에 대한 VMA 생성
  • 실제 물리 프레임 할당은 접근 시점( demand paging )에 수행
  • msync()로 변경사항을 디스크에 반영
  • munmap()으로 매핑 해제

파일 기반 매핑은 page cache와 직접 연결되므로, 같은 파일 페이지를 여러 프로세스가 공유할 수 있다. 반면 anonymous memory는 파일 백엔드 없이 swap 또는 reclaim 정책의 직접적인 영향을 받기 때문에, page fault와 reclaim 경로가 다르게 전개된다.

장단점

장점 단점
프로세스 간 메모리 격리 및 보안 페이지 폴트 오버헤드 (특히 major fault)
물리 메모리보다 큰 가상 주소 공간 사용 가능 TLB miss 시 4단계 변환 비용
효율적인 메모리 공유 (shared memory, COW) 페이지 테이블 자체의 메모리 오버헤드
동적 할당 및 메모리 압축 하드웨어 지원 필요 (MMU, TLB)
메모리 맵핑 파일로 I/O 효율화 실시간 시스템에서 예측 불가능한 지연
스와핑으로 큰 프로세스 실행 가능 스와핑 과다 시 thrashing 위험

관련 기술

  • CPU Cache Architecture: 캐시 계층과 TLB miss의 체감 비용
  • Memory Hierarchy: locality와 계층형 저장 장치의 배경
  • Linux Memory Tiering Deep: NUMA와 메모리 계층 이동
  • VFS FS PageCache BlockIO: page cache와 file-backed mmap 경로
  • Intel SDM Vol. 3A: Paging 구조 및 TLB 관리의 공식 사양
  • ARMv8 Translation: ARM 아키텍처의 2~4단계 가변 페이지 테이블
  • Linux Kernel mm/: 가상 메모리 서브시스템 구현 (mmap, page fault, swap)
  • Linux Kernel Page Tables 문서: 5-level hierarchy, folding, huge page 매핑 설명
  • Huge Pages / Transparent Huge Pages (THP): TLB 효율성을 위한 대형 페이지 기법
  • KSM (Kernel Same-page Merging): 동일한 페이지를 공유하여 메모리 중복 제거
  • NUMA-aware Paging: 비균일 메모리 접근 시스템에서의 페이지 할당 최적화
  • ARM CCA (Confidential Compute Architecture): 하드웨어 기반 메모리 격리 기술

핵심 정리

Virtual Memory는 프로세스별 독립된 가상 주소 공간을 통해 메모리 격리와 효율적 공유를 가능케 하는 OS의 핵심 메커니즘이다. 계층적 페이지 테이블, TLB, page-walk cache의 조합은 변환 지연을 줄이면서 대규모 주소 공간을 실용적으로 만든다. 페이지 폴트 기반의 demand paging은 초기 메모리 사용량을 줄이고, Copy-on-Write는 fork() 비용을 크게 낮춘다. 현대 시스템에서는 Huge Pages, THP, PCID, NUMA-aware paging, MGLRU 같은 기법이 함께 동작해 성능과 메모리 효율을 동시에 최적화한다.