세그멘테이션 vs 페이징 — 메모리 관리 기법 비교
가상 메모리 구현 방식으로 세그멘테이션과 페이징 두 가지가 있습니다. 현대 OS는 페이징을 주로 사용하지만, 왜 세그멘테이션이 밀렸는지 이해하면 메모리 관리의 전체 그림이 보입니다.
세그멘테이션 (Segmentation)
프로세스의 메모리를 논리적 단위(세그먼트)로 나누는 방식 입니다.
프로세스의 논리적 구조:
┌──────────┐
│ Code 세그먼트 │ → 세그먼트 0
├──────────┤
│ Data 세그먼트 │ → 세그먼트 1
├──────────┤
│ Stack 세그먼트 │ → 세그먼트 2
├──────────┤
│ Heap 세그먼트 │ → 세그먼트 3
└──────────┘
각 세그먼트는 크기가 다를 수 있고, 의미 있는 단위로 나뉩니다.
세그먼트 테이블
논리 주소 = (세그먼트 번호, 오프셋)
세그먼트 테이블:
| 세그먼트 번호 | Base(시작 주소) | Limit(크기) |
|-------------|---------------|------------|
| 0 (Code) | 0x1000 | 0x400 |
| 1 (Data) | 0x2000 | 0x300 |
| 2 (Stack) | 0x5000 | 0x200 |
주소 변환:
논리 주소 (세그먼트 1, 오프셋 0x50)
→ 오프셋(0x50) < Limit(0x300)? Yes
→ 물리 주소 = Base + 오프셋 = 0x2000 + 0x50 = 0x2050
장점:
- 논리적 의미 단위로 메모리 관리 — Code는 읽기 전용, Stack은 성장 가능
- 세그먼트별 보호(읽기/쓰기/실행 권한) 설정 가능
- 공유가 쉬움 — 두 프로세스가 같은 Code 세그먼트를 공유 가능
단점:
- 외부 단편화 발생 — 세그먼트 크기가 다양하므로 빈 공간이 조각남
- 세그먼트가 커지면 재배치 필요
페이징 (Paging)
메모리를 고정 크기 블록(페이지)으로 나누는 방식 입니다. 논리적 의미와 무관하게 균일한 크기로 나눕니다.
가상 메모리: 물리 메모리:
┌────────┐ ┌────────┐
│ 페이지 0 │ ──────────────→ │ 프레임 3 │
├────────┤ ├────────┤
│ 페이지 1 │ ─────────→ │ 프레임 0 │ ← 페이지 2
├────────┤ │ ├────────┤
│ 페이지 2 │ ──→ │ │ 프레임 1 │ ← 페이지 3
├────────┤ │ │ ├────────┤
│ 페이지 3 │ ─ │ └──→ │ 프레임 2 │ ← 페이지 1
└────────┘ │ │ ├────────┤
│ └──────────→ │ 프레임 3 │ ← 페이지 0
└─────────────→ │ 프레임 4 │ ← 페이지 3 (X)
└────────┘
- 페이지: 가상 메모리의 고정 크기 블록 (보통 4KB)
- 프레임: 물리 메모리의 고정 크기 블록 (페이지와 같은 크기)
- 페이지 테이블: 페이지 번호 → 프레임 번호 매핑
주소 변환
가상 주소 = (페이지 번호, 오프셋)
페이지 크기가 4KB(2^12)일 때:
가상 주소 0x3A7C
= 페이지 번호: 0x3A7C / 4096 = 3
= 오프셋: 0x3A7C % 4096 = 0xA7C
페이지 테이블에서 페이지 3 → 프레임 7
물리 주소 = 프레임 7 × 4096 + 0xA7C = 0x7A7C
장점:
- 외부 단편화 없음 — 고정 크기이므로 빈 공간이 정확히 맞음
- 비연속 할당 가능 — 물리 메모리에서 연속일 필요 없음
- 구현이 하드웨어(MMU)로 효율적
단점:
- 내부 단편화 — 마지막 페이지에 낭비 공간 (최대 4KB - 1)
- 페이지 테이블 자체가 메모리를 차지함
- 논리적 의미 단위와 무관한 분할
세그멘테이션 vs 페이징 비교
| 항목 | 세그멘테이션 | 페이징 |
|---|---|---|
| 분할 단위 | 가변 크기 (논리적) | 고정 크기 (4KB 등) |
| 외부 단편화 | 있음 | 없음 |
| 내부 단편화 | 없음 | 있음 (마지막 페이지) |
| 주소 구조 | (세그먼트 번호, 오프셋) | (페이지 번호, 오프셋) |
| 공유/보호 | 세그먼트 단위로 쉬움 | 페이지 단위로 가능하지만 의미 경계와 안 맞을 수 있음 |
| 프로그래머 인식 | 논리적 구조 반영 | 프로그래머에게 투명 |
| 현대 OS | 거의 안 씀 | 주로 사용 |
세그먼티드 페이징 (Segmented Paging)
세그멘테이션과 페이징을 결합한 방식입니다. 세그먼트를 다시 페이지로 나눕니다.
논리 주소 = (세그먼트 번호, 페이지 번호, 오프셋)
1단계: 세그먼트 번호 → 해당 세그먼트의 페이지 테이블
2단계: 페이지 번호 → 물리 프레임
3단계: 프레임 + 오프셋 = 물리 주소
- 세그먼트의 논리적 분리 + 페이징의 외부 단편화 제거
- x86 아키텍처가 이 방식을 지원했지만, 현대 OS(리눅스 등)는 세그멘테이션을 최소화하고 사실상 순수 페이징만 사용
x86에서 세그멘테이션의 현재
x86-64 (64비트):
- 세그먼테이션은 사실상 비활성화
- CS, DS 세그먼트 레지스터는 존재하지만 base = 0, limit = 전체
- 메모리 보호는 페이지 테이블의 권한 비트로 처리
리눅스가 세그멘테이션을 안 쓰는 이유:
- 페이징만으로 메모리 보호/가상화 충분
- 세그멘테이션은 하드웨어 호환성을 복잡하게 만듦
- RISC 아키텍처(ARM 등)는 아예 세그멘테이션 미지원
페이지 테이블의 문제와 해결
문제: 페이지 테이블이 너무 크다
32비트 주소 공간, 4KB 페이지:
- 페이지 수 = 2^32 / 2^12 = 2^20 = 약 100만 개
- 각 엔트리 4바이트 → 페이지 테이블 크기 = 4MB (프로세스마다!)
해결 1: 다단계 페이지 테이블
2단계 페이지 테이블:
가상 주소: [디렉토리 인덱스 | 페이지 인덱스 | 오프셋]
1단계: 페이지 디렉토리 → 페이지 테이블의 위치
2단계: 페이지 테이블 → 프레임 번호
사용하지 않는 영역의 페이지 테이블은 만들지 않아도 됨 → 메모리 절약
해결 2: TLB (Translation Lookaside Buffer)
- 최근 변환 결과를 캐싱하는 하드웨어
- TLB 히트 시 페이지 테이블 접근 불필요 → 주소 변환 속도 향상
- 일반적으로 TLB 히트율 95% 이상
핵심 포인트
- 현대 OS가 세그멘테이션을 안 쓰는 이유: 외부 단편화 + RISC 미지원 + 페이징으로 충분
- 페이징의 내부 단편화가 심각하지 않은 이유: 최대 낭비가 4KB - 1 = 한 페이지 미만
- TLB와 컨텍스트 스위칭 관계: 프로세스 전환 시 TLB 플러시 → 성능 저하
정리
세그멘테이션은 논리적으로 깔끔하지만 외부 단편화가 치명적입니다. 페이징은 고정 크기로 외부 단편화를 제거했고, 다단계 페이지 테이블과 TLB로 오버헤드도 해결했습니다. 현대 OS는 사실상 페이징 일색이지만, 실제로는 두 방식을 비교하고 왜 페이징이 승리했는지를 설명할 수 있어야 합니다.