
멀티프로그래밍(multiprogramming)이란 여러 프로세스가 동시에 물리 메모리에 적재되어 실행되는 것을 말한다. 이를 위해 다음 두 가지가 반드시 필요하다:
가장 기본적인 메모리 보호 구현 방법은 두 개의 레지스터를 사용하는 것이다:
| 레지스터 | 역할 | 예시 |
|---|---|---|
| Base Register (기저 레지스터) | 해당 프로세스에 할당된 가장 작은 합법적 물리 주소 | 30004 |
| Limit Register (한계 레지스터) | 해당 프로세스의 주소 범위(크기) | 12090 |

CPU가 user mode에서 생성하는 모든 주소에 대해 하드웨어가 자동으로 다음을 검사한다:
CPU가 생성한 주소
│
▼
주소 ≥ base?
├── NO → trap (주소 오류)
└── YES
│
▼
주소 < base + limit?
├── NO → trap (주소 오류)
└── YES → 메모리 접근 허용
핵심 포인트:

| 용어 | 영어 | 설명 |
|---|---|---|
| 논리 주소 (= 가상 주소) | Logical address / Virtual address | CPU가 생성하는 주소 |
| 가상 주소 공간 | Virtual address space | 실행 중인 프로그램이 생성하는 모든 주소의 집합 |
| 물리 주소 | Physical address | 실제 DRAM 상의 주소 |
| 물리 주소 공간 | Physical address space | 논리 주소에 대응되는 모든 물리 주소의 집합 |
CPU가 생성하는 주소 = 논리(가상) 주소
│
[ MMU 변환 ]
│
▼
메모리에 보내는 주소 = 물리 주소

만약 사용자 프로그램이 실제 물리 주소를 직접 다룬다면 다음과 같은 문제가 생긴다:
이것은 매우 비현실적이고 고통스러운 일이다.

가장 간단한 MMU 구현은 relocation register(재배치 레지스터, base register와 동일)를 사용하는 것이다:
relocation register
┌───────┐
│ 14000 │
└───┬───┘
│
CPU ──► 논리주소 346 ──►[+]──► 물리주소 14346 ──► 메모리
MMU
물리 주소 = relocation register 값 + CPU가 생성한 논리 주소

| 레지스터 | 저장하는 값 |
|---|---|
| Relocation Register | 가장 작은 물리 주소 (= base address) |
| Limit Register | 논리 주소의 범위 (= 프로세스 크기) |
CPU가 생성한
논리 주소(d)
│
▼
d < limit register?
├── NO → trap (주소 오류)
└── YES
│
▼
d + relocation register = 물리 주소 → 메모리 접근
Process 1이 논리 주소 5000을 생성하면:
1. 5000 < 12090 (limit)? → YES, 범위 내
2. 물리 주소 = 30004 + 5000 = 35004
3. 메모리 주소 35004에 접근
Process 1이 논리 주소 15000을 생성하면:
1. 15000 < 12090 (limit)? → NO, 범위 밖
2. trap 발생 → 운영체제가 불법 접근 처리

멀티프로그래밍을 위해 여러 프로세스가 물리 메모리에 동시에 존재해야 하며, 각 프로세스의 메모리를 보호하기 위해 relocation/limit 레지스터가 필요하다.
연속 메모리 할당(Contiguous Memory Allocation)이란 각 프로세스가 물리 메모리의 하나의 연속된 영역에 적재되는 방식이다.
┌──────────────┐ 0
│ Process 0 │
├──────────────┤ 10000
│ Process 1 │
├──────────────┤ 15000
│ Process 2 │
├──────────────┤ 25000
│ Process 3 │
├──────────────┤ 30000
│ (빈 공간) │
└──────────────┘

[초기 상태] [P5 종료] [P9 도착]
┌────────┐ ┌────────┐ ┌────────┐
│ OS │ │ OS │ │ OS │
├────────┤ ├────────┤ ├────────┤
│ P5 │ │ hole │ │ P9 │
├────────┤ ├────────┤ ├────────┤
│ P8 │ → │ P8 │ → │ P8 │ → ...
├────────┤ ├────────┤ ├────────┤
│ P2 │ │ P2 │ │ P2 │
└────────┘ └────────┘ └────────┘

프로세스가 반복적으로 메모리에 적재되고 제거되면, 빈 공간이 작은 조각들로 분산된다. 전체 빈 공간을 합치면 요청을 만족할 수 있지만, 연속된 공간이 없어서 새 프로세스를 할당할 수 없는 상황을 외부 단편화(External Fragmentation)라 한다.
┌────────┐
│ OS │
├────────┤
│ hole │ ← 작은 빈 공간
├────────┤
│ P9 │
├────────┤
│ hole │ ← 작은 빈 공간
├────────┤
│ P2 │
├────────┤
│ hole │ ← 작은 빈 공간
└────────┘
→ hole들을 합치면 충분하지만, 연속되지 않아서 큰 프로세스를 할당 불가!

메모리 내용을 재배치(shuffle)하여 모든 빈 공간을 하나의 큰 블록으로 모으는 것.
조건: 재배치가 동적(dynamic)이고 실행 시점(execution time)에 이루어져야만 가능하다.
[압축 전] [압축 후]
┌────────┐ ┌────────┐
│ OS │ │ OS │
├────────┤ ├────────┤
│ hole │ │ P9 │
├────────┤ → ├────────┤
│ P9 │ │ P2 │
├────────┤ ├────────┤
│ hole │ │ P10 │
├────────┤ ├────────┤
│ P2 │ │ 큰 hole │
├────────┤ │ │
│ hole │ │ │
└────────┘ └────────┘
프로세스에게 물리 메모리가 어디든 사용 가능한 곳에 할당될 수 있도록 한다. 즉, 한 프로세스가 물리적으로 떨어진 여러 조각에 흩어져 적재될 수 있다.
→ 이것이 바로 페이징(Paging)의 핵심 아이디어!

페이징(Paging)은 프로세스의 물리 주소 공간이 비연속적(noncontiguous)일 수 있도록 허용하는 메모리 관리 기법이다.
| 용어 | 영어 | 설명 |
|---|---|---|
| 페이지 (Page) | Page | 가상 메모리를 고정 크기로 나눈 논리적 블록 |
| 프레임 (Frame) | Page Frame | 물리 메모리를 같은 크기로 나눈 물리적 블록 |
| 페이지 테이블 | Page Table | 페이지와 프레임 간의 매핑 정보를 담는 테이블 |
가상 주소 공간 물리 주소 공간
┌────────┐ ┌────────┐ Frame 0
│ Page 0 │╲ │ │
├────────┤ ╲ ├────────┤ Frame 1
│ Page 1 │ ╲─────────│ Page 0 │ ← Page 0은 Frame 1에
├────────┤ ╱─────────├────────┤ Frame 2
│ Page 2 │╱ │ │
├────────┤ ┌───────├────────┤ Frame 3
│ Page 3 │────┘ │ Page 2 │ ← Page 2는 Frame 3에
└────────┘ ├────────┤ Frame 4
│ Page 1 │ ← Page 1은 Frame 4에
├────────┤ ...
│ Page 3 │ ← Page 3은 Frame 7에
└────────┘


논리 메모리 페이지 테이블 물리 메모리
┌────────┐ ┌───┬───┐ ┌────────┐ Frame 0
│ Page 0 │──────►│ 0 │ 1 │──────►│ │
├────────┤ ├───┼───┤ ├────────┤ Frame 1
│ Page 1 │──────►│ 1 │ 4 │──┐ │ Page 0 │ ← PTE[0]=1
├────────┤ ├───┼───┤ │ ├────────┤ Frame 2
│ Page 2 │──────►│ 2 │ 3 │──┼──► │ │
├────────┤ ├───┼───┤ │ ├────────┤ Frame 3
│ Page 3 │──────►│ 3 │ 7 │ │ │ Page 2 │ ← PTE[2]=3
└────────┘ └───┴───┘ │ ├────────┤ Frame 4
└──► │ Page 1 │ ← PTE[1]=4
├────────┤ ...
├────────┤ Frame 7
│ Page 3 │ ← PTE[3]=7
└────────┘

CPU가 생성하는 논리 주소는 두 부분으로 나뉜다:
┌─────────────────┬──────────────┐
│ Page Number(p) │ Page Offset(d)│
└─────────────────┴──────────────┘
m - n 비트 n 비트
| 필드 | 역할 |
|---|---|
| Page Number (p) | 페이지 테이블의 인덱스. 해당 페이지가 매핑된 프레임 번호를 찾는다. |
| Page Offset (d) | 해당 페이지(= 프레임) 내에서의 바이트 위치 |
물리 주소 = Frame번호 × Page크기 + Offset
또는 비트 수준에서:
논리 주소: [ p (페이지 번호) | d (오프셋) ]
│ │
페이지 테이블 조회 그대로 복사
│ │
▼ │
물리 주소: [ f (프레임 번호) | d (오프셋) ]


가상 주소 0 계산:
가상 주소 13 계산:

슬라이드의 상세 예시 (가상 주소 8196 → 물리 주소 24580):
Incoming virtual address: 8196 (= 0x2004)
16비트 주소에서 12비트 오프셋 (페이지 크기 4KB = 2^12):
→ 상위 4비트 = page number, 하위 12비트 = offset
8196 = 0010 0000 0000 0100
──── ────────────────
p=2 d=4 (offset)
페이지 테이블에서 page 2 조회 → frame number = 110 (이진수) = 6
물리 주소 = 110 | 000000000100
= 1100 0000 0000 0100
= 24580 (10진수)
즉, 물리 주소 = 6 × 4096 + 4 = 24580

내부 단편화(Internal Fragmentation)란 프로세스에 할당된 메모리가 실제 필요한 메모리보다 약간 더 큰 경우 발생한다. 프레임 단위로 할당하므로, 마지막 페이지는 프레임을 가득 채우지 못할 수 있다.
┌──────────┐ ┌──────────┐
│ Page 1 │────────► │ Frame 1 │ (가득 참)
├──────────┤ ├──────────┤
│ Page 2 │────────► │ Frame 2 │ (가득 참)
├──────────┤ ├──────────┤
│ ... │ │ ... │
├──────────┤ ├──────────┤
│ Page 35 │────────► │ Frame 35 │ (가득 참)
├──────────┤ ├──────────┤
│ Page 36 │────────► │ Frame 36 │ ← 1,086 bytes만 사용
│(1,086 B) │ │ │ 962 bytes 낭비!
└──────────┘ └──────────┘ (내부 단편화)
| 구분 | 외부 단편화 | 내부 단편화 |
|---|---|---|
| 발생 원인 | 가변 크기 할당 | 고정 크기(프레임) 할당 |
| 문제 | 빈 공간이 흩어져서 연속 할당 불가 | 프레임 내부에 안 쓰는 공간 발생 |
| 해당 방식 | 연속 메모리 할당 | 페이징 |
| 해결책 | 압축 / 페이징 | 페이지 크기를 줄임 (trade-off 있음) |
(trade-off)
| page size | internal fragmentation | PTE 개수 | page table 크기 |
|---|---|---|---|
| 크게 | 커짐 (낭비 많음) | 적음 | 작음 |
| 작게 | 줄어듦 (낭비 적음) | 많아짐 | 커짐 |

운영체제와 다른 프로세스의 코드/데이터에 대한 불법 접근을 방지해야 한다.
각 PTE에 보호 비트(protection bit)를 둔다:
| 비트 값 | 의미 | 설명 |
|---|---|---|
r-x | Read + Execute | 읽기 및 실행만 가능 (코드 영역) |
r-- | Read Only | 읽기만 가능 (상수 데이터) |
rw- | Read + Write | 읽기/쓰기 가능 (일반 데이터, 스택, 힙) |
페이지 번호로 PTE를 조회할 때, 프레임 번호와 함께 보호 비트도 확인한다. 예를 들어, read-only 페이지에 쓰기를 시도하면 trap이 발생한다.
| 영역 | 보호 수준 |
|---|---|
| 커널 코드/데이터 | 사용자 프로세스에 대해 읽기 전용 |
| 프로세스 텍스트(코드) | 자기 자신 및 다른 프로세스에 대해 읽기 전용 |
| 프로세스 데이터/스택/힙 | 해당 프로세스에 읽기/쓰기 |

프로세스의 가상 주소 공간 중 일부 페이지만 물리 메모리에 매핑되어 있을 수 있다. 매핑되지 않은 페이지에 접근하면 적절한 조치가 필요하다.
각 PTE에 valid-invalid bit을 추가한다:
| 비트 값 | 의미 |
|---|---|
| v (valid) | 해당 페이지가 물리 프레임에 매핑되어 있음 |
| i (invalid) | 해당 페이지가 어떤 프레임에도 매핑되지 않음 |

가상 주소 공간 페이지 테이블 물리 메모리
(00000 시작)
┌───┬───────┬────────┐
┌──────────┐ │ 0 │ 2 │ v │ Frame 2: page 0
│ page 0 │ ├───┼───────┼────────┤
├──────────┤ │ 1 │ 3 │ v │ Frame 3: page 1
│ page 1 │ ├───┼───────┼────────┤
├──────────┤ │ 2 │ 4 │ v │ Frame 4: page 2
│ page 2 │ ├───┼───────┼────────┤
├──────────┤ │ 3 │ 7 │ v │ Frame 7: page 3
│ page 3 │ ├───┼───────┼────────┤
├──────────┤ │ 4 │ 8 │ v │ Frame 8: page 4
│ page 4 │ ├───┼───────┼────────┤
├──────────┤ │ 5 │ 9 │ v │ Frame 9: page 5
│ page 5 │ ├───┼───────┼────────┤
└──────────┘ │ 6 │ 0 │ i │ ← invalid! 매핑 없음
├───┼───────┼────────┤
│ 7 │ 0 │ i │ ← invalid! 매핑 없음
└───┴───────┴────────┘

시나리오: 40개의 프로세스가 동일한 텍스트 에디터를 사용한다고 하자.
하지만 텍스트 코드는 읽기 전용(read-only)이고 실행 중 변하지 않는다(pure code, reentrant code). 같은 코드를 40번 복사하는 것은 메모리 낭비이다.

P1의 페이지 테이블 물리 메모리
┌──────┬──────┐ ┌────────────┐
│ ed1 │ 3 │──┐ │ │ Frame 0
│ ed2 │ 4 │──┼──┐ ├────────────┤
│ ed3 │ 6 │──┼──┼──┐ │ data 1 │ Frame 1 ← P1 전용 데이터
│data1 │ 1 │ │ │ │ ├────────────┤
└──────┴──────┘ │ │ │ │ data 3 │ Frame 2 ← P3 전용 데이터
│ │ │ ├────────────┤
P2의 페이지 테이블 │ │ │ │ ed 1 │ Frame 3← 코드 공유!
┌──────┬──────┐ │ │ │ ├────────────┤
│ ed1 │ 3 │──┘ │ │ │ ed 2 │ Frame 4 ← 코드 공유!
│ ed2 │ 4 │─────┘ │ ├────────────┤
│ ed3 │ 6 │────────┘ │ │ Frame 5
│data2 │ 7 │ ├────────────┤
└──────┴──────┘ │ ed 3 │ Frame 6 ← 코드 공유!
├────────────┤
P3의 페이지 테이블 │ data 2 │ Frame 7 ← P2 전용 데이터
┌──────┬──────┐ └────────────┘
│ ed1 │ 3 │──────────► Frame 3 (같은 프레임!)
│ ed2 │ 4 │──────────► Frame 4 (같은 프레임!)
│ ed3 │ 6 │──────────► Frame 6 (같은 프레임!)
│data3 │ 2 │──────────► Frame 2 (P3 전용)
└──────┴──────┘
핵심: 세 프로세스의 ed1, ed2, ed3 페이지가 모두 같은 물리 프레임(3, 4, 6)을 가리킨다. 데이터 페이지만 프로세스별로 다른 프레임에 매핑된다.

시나리오: 32비트 논리 주소 공간, 4 KB 페이지, PTE 크기 4 bytes
현대 시스템은 32~64비트 주소를 지원하므로, 페이지 테이블 자체가 매우 커진다.

페이지 테이블 자체도 비연속적으로 물리 메모리에 저장하자! 즉, 페이지 테이블을 또 페이징하는 것이다.

32비트 주소, 4 KB 페이지 기준:
논리 주소 (32비트):
┌────────────┬────────────┬──────────────┐
│ p1 (10) │ p2 (10) │ d (12) │
└────────────┴────────────┴──────────────┘
1단계 인덱스 2단계 인덱스 페이지 오프셋
| 필드 | 비트 수 | 역할 |
|---|---|---|
| p1 | 10 | 1단계(outer) 페이지 테이블의 인덱스 |
| p2 | 10 | 2단계(inner) 페이지 테이블 페이지의 인덱스 |
| d | 12 | 프레임 내 오프셋 |
논리 주소: [p1 | p2 | d]
Step 1: p1 → outer page table에서 조회
→ 2단계 페이지 테이블의 프레임 번호를 얻음
Step 2: p2 → 해당 2단계 페이지 테이블에서 조회
→ 최종 데이터 프레임 번호를 얻음
Step 3: 프레임 번호 + d(오프셋) → 물리 주소
┌──────────┐
│ p1 = 3 │
│ p2 = 7 │
│ d = 100 │
└──┬───────┘
│
▼ p1=3
┌──────────┐ outer page table
│ ... │ (1단계, 2^10 = 1024개 엔트리)
│ entry[3] │──────►┌──────────┐
│ ... │ │ ... │ page of page table
└──────────┘ │ entry[7] │──────►┌──────────┐
│ ... │ │ Frame F │
└──────────┘ └──┬───────┘
(2단계 테이블 중 하나) │
│ + offset d=100
▼
물리 주소 = F × 4096 + 100

프로그래머는 메모리를 하나의 선형 주소 공간으로 보지 않는다. 대신 다음과 같은 논리적 단위(세그먼트)로 인식한다:
각 세그먼트가 얼마나 커질지, 어디에 배치될지 프로그래머는 모르며, 가상 메모리에서 어디로 가는지 관리하고 싶지도 않다.


세그멘테이션에서 가상 주소는 2차원이다:
< segment number, offset >
예시 코드에서:
display 함수: <seg 0, offset 500>malloc 함수 (C 라이브러리): <seg 2, offset 0>
| 필드 | 역할 |
|---|---|
| base | 해당 세그먼트가 물리 메모리에서 시작하는 주소 |
| limit | 해당 세그먼트의 길이 |

CPU가 생성한 가상 주소: <s, d>
│ │
│ ▼
│ d < limit?
│ ├── NO → trap (주소 오류)
│ └── YES
│ │
▼ ▼
세그먼트 테이블[s]의 물리 주소 = base + d
base/limit 조회

세그먼트 테이블:
┌─────┬───────┬───────┐
│ seg │ limit │ base │
├─────┼───────┼───────┤
│ 0 │ 1000 │ 1400 │
│ 1 │ 400 │ 6300 │
│ 2 │ 400 │ 4300 │
│ 3 │ 1100 │ 3200 │
│ 4 │ 1000 │ 4700 │
└─────┴───────┴───────┘
주소 변환 예시:
• <segment 2, byte 53>:
- 53 < 400 (limit)? YES
- 물리 주소 = 4300 + 53 = 4353
• <segment 3, byte 852>:
- 852 < 1100 (limit)? YES
- 물리 주소 = 3200 + 852 = 4052
| 특성 | 세그멘테이션 | 페이징 |
|---|---|---|
| 분할 크기 | 가변 (세그먼트마다 다름) | 고정 (모든 페이지 동일) |
| 프로그래머 인식 | 있음 (논리적 단위) | 없음 (투명) |
| 주소 형식 | <세그먼트 번호, 오프셋> | <페이지 번호, 오프셋> |
| 외부 단편화 | 있음 | 없음 |
| 내부 단편화 | 없음 | 있음 (마지막 페이지) |

Intel Pentium 아키텍처는 순수 세그멘테이션과 세그멘테이션 + 페이징을 모두 지원한다.
CPU ──► Segmentation Unit ──► Paging Unit ──► Physical Memory
(가상 주소) (선형 주소) (물리 주소)
MMU
| 항목 | 값 |
|---|---|
| 세그먼트 최대 크기 | 4 GB |
| 프로세스당 최대 세그먼트 수 | 2^14 (16,384개) |
| Private segment | 최대 2^13개 (프로세스 전용) |
| Shared segment | 최대 2^13개 (프로세스 간 공유) |

| 테이블 | 역할 |
|---|---|
| LDT (Local Descriptor Table) | 프라이빗 세그먼트의 매핑 정보 |
| GDT (Global Descriptor Table) | 공유 세그먼트의 매핑 정보 |
┌─────────────┬───┬─────┬──────────────────┐
│ s (13비트) │ g │ p │ offset │
└─────────────┴───┴─────┘──────────────────┘
세그먼트 번호 GDT 보호 세그먼트 내 오프셋
/LDT
◄──── selector ────►
- s: 세그먼트 번호 (13비트)
- g: GDT(1) 또는 LDT(0) 선택
- p: 보호 수준 (2비트)
앞에서 Proccess당 Segment수가 라 했는데 gdt ldt를 구분하는 것이 각각 1, 0 이기 때문에 + 임으로 맞다.
강의 교안에는 없지만
이걸 Intel에서는 RPL (Requested Privilege Level)이라고 불러. CPU가 세그먼트에 접근할 때 이 값을 확인해서, 현재 실행 중인 코드의 권한 수준보다 높은 권한이 필요한 세그먼트에 접근하려 하면 protection fault를 발생시켜.
실제로 Linux에서는 Ring 1, 2를 안 쓰고 Ring 0(커널)과 Ring 3(유저)만 사용해. 이것도 이식성 때문인데, ARM 같은 아키텍처는 2단계(커널/유저)만 지원한다.


32비트 선형 주소:
┌────────────┬────────────┬──────────────┐
│ p1 (10) │ p2 (10) │ d (12) │
└────────────┴────────────┴──────────────┘
CR3 레지스터 → Page Directory (1단계)
│
▼ p1으로 인덱싱
Page Table (2단계)
│
▼ p2로 인덱싱
Frame + d(오프셋) → 물리 주소

Linux는 Intel의 세그멘테이션 하드웨어를 사실상 사용하지 않는다.
방법: 모든 세그먼트의 base = 0x0000으로 설정
Intel 원칙:
CPU → [세그먼트 테이블: base 더함 (유효)] → [페이지 테이블: frame 변환] → 물리 메모리
Linux:
CPU → [세그먼트 테이블: base=0 (무변화)] → [페이지 테이블: frame 변환] → 물리 메모리
↓
VA → seg 테이블 통과
→ 0x0 + VA = VA
주소값 변화 없음!
결과: 가상 주소(VA) = 선형 주소. 세그먼트 테이블을 거쳐도 주소가 바뀌지 않는다.

[Intel 원칙] [Linux]
세그먼트마다 0x0000 시작 단일 가상 주소 공간
CPU 주소 = <seg#, offset> CPU 주소 = 단일 숫자
┌───────────────┐ 0x0000 ┌───────────────┐ 0x1000
│ seg 0 Text │ │ Text │ 0x1000~0x1FFF
│ 0x0000-0x0FFF │ ├───────────────┤ 0x2000
├───────────────┤ 0x0000 │ Data │ 0x2000~0x2FFF
│ seg 1 Data │ ├───────────────┤ (미사용)
│ 0x0000-0x0FFF │ ├───────────────┤ 0x4000
├───────────────┤ 0x0000 │ Heap │ 0x4000~0x5FFF
│ seg 2 Heap │ ├───────────────┤ (미사용)
│ 0x0000-0x1FFF │ ├───────────────┤ 0x7000
├───────────────┤ 0x0000 │ Stack │ 0x7000~0x7FFF
│ seg 3 Stack │ └───────────────┘
│ 0x0000-0x0FFF │
└───────────────┘
Intel: seg=0의 0x0200 ≠ seg=2의 0x0200 Linux: 0x4200 → Heap만 해당
(오프셋 같아도 다른 위치) (단일 숫자로 구분 가능)
→ seg# 필수! → seg# 불필요!
| seg # | 이름 | base | limit | 결과 |
|---|---|---|---|---|
| 0 Text | hello, main | 0x0000 | 0xFFFF... | VA = 선형 (변화 없음) |
| 1 Data | global | 0x0000 | 0xFFFF... | VA = 선형 (변화 없음) |
| 2 Heap | *heap | 0x0000 | 0xFFFF... | VA = 선형 (변화 없음) |
| 3 Stack | local | 0x0000 | 0xFFFF... | VA = 선형 (변화 없음) |
모든 base가 0x0000이므로, 선형주소 = 0x0 + VA = VA 그대로.


예제 코드의 변수들:
hello() 함수: VA = 0x1000global = 42: VA = 0x2000*heap = 99 (malloc): VA = 0x4200local = 1: VA = 0x7800


page# = 선형주소 ÷ 페이지크기
offset = 선형주소 % 페이지크기
물리주소 = frame# × 페이지크기 + offset
1) hello() — VA 0x1000
세그먼트: seg 0, base=0x0 → 선형주소 = 0x1000
페이징: page#=1, offset=0x000 → page 1 → frame 3
물리주소: 0x3000
2) global=42 — VA 0x2000
세그먼트: seg 1, base=0x0 → 선형주소 = 0x2000
페이징: page#=2, offset=0x000 → page 2 → frame 5
물리주소: 0x5000
3) *heap=99 — VA 0x4200 ★
세그먼트: seg 2, base=0x0 → 선형주소 = 0x4200
페이징: page#=4, offset=0x200 → page 4 → frame 7
물리주소: 0x7200
4) local=1 — VA 0x7800
세그먼트: seg 3, base=0x0 → 선형주소 = 0x7800
페이징: page#=7, offset=0x800 → page 7 → frame 1
물리주소: 0x1800


Appendix의 예제 코드 기반 페이지 테이블:
| Page # | 내용 | Frame # | Protection | Valid |
|---|---|---|---|---|
| Page 0 | Code (main 기계어) | Frame 3 | r-x (읽기+실행) | ✓ valid |
| Page 1 | Read-only data (read_only=42) | Frame 6 | r-- (읽기 전용) | ✓ valid |
| Page 2 | Stack (data, valid*, invalid*) | Frame 1 | rw- (읽기/쓰기) | ✓ valid |
| Page 3 | Heap (malloc → *valid=777 → free) | Frame 7 | rw- | ✓→✗ (free 후 invalid) |
| Page 4 | NULL (invalid = NULL) | — | — | ✗ invalid (매핑 없음 → page fault!) |
메모리 접근 순서:
1. valid bit 확인 → invalid이면 page fault
2. protection bit 확인 → 위반 시 segfault
3. 물리 프레임 접근
유효한 주소인데 메모리에 없을 뿐 → page fault 처리 (디스크에서 로딩 후 재시작)
유효하지 않은 주소 → segmentation fault (SIGSEGV 시그널 → 프로세스 종료)
Pagefault가 trap(발생방식)이고 segmentation fault는 결과!


3개 프로세스가 editor() 함수를 공유하는 경우:
가상 주소 공간 페이지 테이블 엔트리 물리 메모리
[Process P0]
editor() 0x55e... → page 0 → frame 0 (공유) ──► Frame 0: editor() 코드
my_data=0 → page 1 → frame 1 ──► Frame 1: my_data(P0)=0
[Process P1]
editor() 0x55e... → page 0 → frame 0 (공유) ──► Frame 0: (같은 프레임!)
my_data=1 → page 1 → frame 2 ──► Frame 2: my_data(P1)=1
[Process P2]
editor() 0x55e... → page 0 → frame 0 (공유) ──► Frame 0: (같은 프레임!)
my_data=2 → page 1 → frame 3 ──► Frame 3: my_data(P2)=2
핵심 관찰:
editor() 함수의 가상 주소는 세 프로세스 모두 동일 (0x55e7e9b8b1a9) → 같은 코드 페이지 공유my_data의 가상 주소도 세 프로세스 모두 동일 (0x7ffebf567de0) → 하지만 페이지 테이블이 다른 물리 프레임으로 매핑| 개념 | 핵심 요약 |
|---|---|
| Base/Limit Register | 연속 할당에서 프로세스 메모리 보호를 위한 하드웨어 레지스터 |
| 논리(가상) 주소 | CPU가 생성하는 주소. 프로그램이 보는 주소 |
| 물리 주소 | 실제 DRAM의 주소. 메모리 버스에 나가는 주소 |
| MMU | 가상→물리 주소 변환을 수행하는 하드웨어 |
| Relocation Register | 가장 간단한 MMU. 모든 주소에 base값을 더함 |
| 연속 메모리 할당 | 프로세스를 하나의 연속 영역에 배치. 외부 단편화 발생 |
| 외부 단편화 | 전체 빈 공간은 충분하지만 연속 공간이 부족한 문제 |
| 내부 단편화 | 고정 크기 프레임 할당 시 마지막 프레임의 낭비 |
| 페이징 | 고정 크기 page/frame으로 비연속 할당. 외부 단편화 해결 |
| 페이지 테이블 | page→frame 매핑 정보 저장. 프로세스마다 하나 |
| Protection Bit | PTE에 r/w/x 권한 정보 저장 |
| Valid-Invalid Bit | PTE에 해당 페이지의 물리 매핑 유무 표시 |
| 공유 페이지 | 읽기 전용 코드를 여러 프로세스가 같은 프레임으로 공유 |
| 계층적 페이지 테이블 | 페이지 테이블 자체를 페이징하여 메모리 절약 |
| 세그멘테이션 | 가변 크기 논리 단위로 분할. <seg#, offset> 주소 |
| Linux 세그멘테이션 | base=0으로 무력화, 실질적으로 paging만 사용 |
| Intel Pentium | Segmentation + Paging 모두 지원. 2단계 변환 |