운영체제 아주 쉬운 세가지 이야기 6주차 (메모리 가상화 2)
2025. 10. 14. 19:25ㆍ개념 공부
주소 변환(Address Translation)과 세그멘테이션(Segmentation)
1) 배경: 왜 주소 변환이 필요한가
- 현대 OS는 여러 프로세스를 동시에 실행합니다. 각 프로세스는 “나만의 0번부터 시작하는 메모리”가 있다고 착각해야 합니다. 이를 가상 메모리의 환상이라고 합니다.
- 효율과 제어를 동시에 달성하기 위해, 하드웨어가 매 접근마다 주소를 번역하고, OS는 중요한 시점(시스템 콜, 타이머 인터럽트, 예외)에만 끼어듭니다(LDE: Limited Direct Execution).
2) 동적 재배치(Base & Bounds): 가장 간단한 주소 변환
2.1 핵심 아이디어
- Base(기저) 레지스터: 이 프로세스의 주소 공간이 물리 메모리 어디서 시작하는지.
- Bounds(경계) 레지스터: 이 프로세스의 주소 공간 크기(또는 끝 주소). 경계 바깥 접근은 예외!
// 동적 재배치의 번역 규칙(개념)
// (유저 프로그램이 낸) VirtualAddress 가 올 때,
// 먼저 합법 범위인지 검사하고, 물리 주소는 아래처럼 계산:
if (VirtualAddress < 0 || VirtualAddress ≥ Bounds) {
raise(PROTECTION_FAULT); // out-of-bounds → 예외(프로세스 종료 등)
} else {
PhysicalAddress = Base + VirtualAddress;
}
2.2 예제: 변환 결과 계산
가정: 주소 공간 크기 4KB, 물리 배치 시작점 Base=16KB
변환:
- VA 0 → PA 16KB (0 + 16KB) ✔️
- VA 1KB → PA 17KB ✔️
- VA 3000 → PA 19384 ✔️
- VA 4400 → Fault (경계 초과) ❌
변환:
- VA 0 → PA 16KB (0 + 16KB) ✔️
- VA 1KB → PA 17KB ✔️
- VA 3000 → PA 19384 ✔️
- VA 4400 → Fault (경계 초과) ❌
간단하지만 강력한 보호: 범위 밖 접근은 예외를 발생시켜 OS가 제어합니다.
- 프로세스 생성/종료 시 자유 리스트로 물리 메모리 할당/회수.
- 컨텍스트 스위치 때 Base/Bounds 저장·복원.
- 부트 시 예외 핸들러 등록, 실행 중 불법 접근 발생 시 종료 처리.
내부 단편화(Internal Fragmentation): 하나의 연속 슬롯에 통째로 배치하다 보니, 스택과 힙 사이의 큰 빈 공간도 통째로 차지해 낭비가 생깁니다. 이를 줄이려는 다음 단계가 세그멘테이션입니다.
3) 세그멘테이션(Segmentation): 논리 구간별로 따로 배치
3.1 왜 필요한가
- 일반적인 프로세스 주소 공간은 코드/힙/스택 세 구간이 큽니다. 이들을 따로 흩어 배치하면, 스택–힙 사이의 커다란 빈 가상 공간을 물리 메모리에 굳이 차지하지 않아도 됩니다(희소 주소 공간 지원).
3.2 하드웨어 관점: “세그먼트별 Base/Bounds”
세그먼트 레지스터 예
Code: Base=32KB, Size=2KB
Heap: Base=34KB, Size=3KB
Stack: Base=28KB, Size=2KB (역성장)
Code: Base=32KB, Size=2KB
Heap: Base=34KB, Size=3KB
Stack: Base=28KB, Size=2KB (역성장)
효과
쓰는 부분만 물리 메모리를 차지 → 큰 주소 공간을 작은 물리 메모리에도 수용 가능.
쓰는 부분만 물리 메모리를 차지 → 큰 주소 공간을 작은 물리 메모리에도 수용 가능.
세그먼트 단위 배치로 희소성(sparsity)을 효율적으로 처리.
3.3 “어느 세그먼트인가?”를 구분하는 두 방식
- 명시적(Explicit): 가상 주소 상위 비트로 세그먼트 선택(예: 상위 2비트로 code/heap/stack). 선택한 세그먼트의 오프셋을 추출해 Base에 더함.
- 암묵적(Implicit): 주소가 어떻게 생성되었는지로 구분(PC로 만든 주소면 코드, SP/BP 기반이면 스택, 그 외는 힙).
// 세그멘테이션 번역(명시적 방식) — 책의 개념을 코드로 옮김
// 상위 SEG_BITS 로 세그먼트 선택, 하위는 세그먼트 내부 오프셋
Segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT
Offset = VirtualAddress & OFFSET_MASK
if (Offset ≥ Bounds[Segment]) {
raise(PROTECTION_FAULT); // 오프셋이 세그먼트 크기를 넘으면 예외
} else {
Physical = Base[Segment] + Offset;
return load_or_store(Physical);
}
명시적 세그먼트 선택 + 경계 검사 → 물리 주소 계산 흐름.
3.4 역성장 스택 처리(중요!)
- 스택은 낮은 주소로 성장합니다. 하드웨어는 “성장 방향 플래그”를 세그먼트마다 기억하고, 스택처럼 음의 오프셋을 처리합니다(최대 크기에서 빼는 방식).
3.5 세그멘테이션 폴트의 의미
세그멘테이션 폴트는 “세그먼트 경계를 벗어난 주소 접근”을 뜻합니다. 오늘날 순수 하드웨어 세그멘트가 없어도, 이 용어는 계속 쓰입니다(불법 메모리 접근 예외).
3.6 보호/공유: 코드 공유와 권한 비트
- 세그먼트마다 읽기/쓰기/실행 권한 비트를 둬서 보호. 예: 코드 세그먼트를 읽기+실행으로만 표시하고 여러 프로세스가 공유 가능.
3.7 운영체제 이슈: 외부 단편화와 압축(compaction)
- 외부 단편화: 가변 크기 세그먼트들이 생겼다 없어지며 물리 메모리가 잘게 쪼개져 큰 연속 공간을 구하기 어려워짐.
- 해결책 1: 메모리 압축 — 실행 중인 세그먼트를 옮겨 큰 연속 free 블록 만들기(하지만 비쌈).
- 해결책 2: 프리 리스트 알고리즘(best-fit, first-fit, buddy 등)으로 큰 덩어리를 유지하려 노력(근본적 해결은 아님).
4) 주소 변환 & 세그멘테이션 — 그림으로 빠르게 복습
가상 주소 공간(논리)
┌──────────────────────┐ high
│ Stack (downward) │ ← 역성장
│ (여유) │
│ Heap │ → 정방향 성장
│ Program Code (RX) │
└──────────────────────┘ low
물리 메모리(예시 배치)
┌──────────────────────┐
│ OS │
├──────────────────────┤
│ Code(seg0) Base=32K│
├──────────────────────┤
│ Heap(seg1) Base=34K│
├──────────────────────┤
│ (free holes) │ <= 외부 단편화의 원인
├──────────────────────┤
│ Stack(seg2) Base=28K│
└──────────────────────┘
세그먼트 단위 배치로 쓰는 곳만 물리 메모리를 사용. 다만 외부 단편화가 생길 수 있음.
5) 실전 계산 연습: 직접 손으로 변환해보기
연습 A — Base/Bounds
조건: Base=32KB, Bounds=16KB.
질문: 다음 VA의 변환 결과?
1) 128 → 32KB+128 = 32896 ✔️
2) 15KB → 32KB+15KB = 47KB ✔️
3) 17KB → Fault (경계 초과) ❌
질문: 다음 VA의 변환 결과?
1) 128 → 32KB+128 = 32896 ✔️
2) 15KB → 32KB+15KB = 47KB ✔️
3) 17KB → Fault (경계 초과) ❌
경계 검사 → 합법이면 Base 더하기.
연습 B — 세그멘테이션(명시적)
조건: 14비트 VA, 상위 2비트로 세그먼트 선택(seg 0: code, 1: heap, 3: stack).
Heap 세그먼트: 가상 시작=4KB, Base=34KB, Size=3KB.
질문: VA=4200(10진) 변환?
- Heap 내부 오프셋 = 4200 - 4096 = 104
- PA = Base(34KB) + 104 = 34920 ✔️
Heap 세그먼트: 가상 시작=4KB, Base=34KB, Size=3KB.
질문: VA=4200(10진) 변환?
- Heap 내부 오프셋 = 4200 - 4096 = 104
- PA = Base(34KB) + 104 = 34920 ✔️
세그먼트 내부 오프셋을 구해 Base에 더합니다(경계 이내인지도 확인).
연습 C — 스택(역성장)
조건: Stack: Base=28KB, Size=2KB, 최대 세그 크기 4KB, 성장방향=음수.
질문: VA=15KB(=0x3C00) 변환?
- 상위 비트로 Stack 선택, 하위 오프셋 = 3KB.
- 음의 오프셋 = 3KB - 4KB = -1KB.
- PA = Base(28KB) + (-1KB) = 27KB ✔️
질문: VA=15KB(=0x3C00) 변환?
- 상위 비트로 Stack 선택, 하위 오프셋 = 3KB.
- 음의 오프셋 = 3KB - 4KB = -1KB.
- PA = Base(28KB) + (-1KB) = 27KB ✔️
역성장 세그먼트는 “최대치에서 빼기”로 음의 오프셋을 계산합니다.
6) (보너스) 세그폴트 디버깅 관점
- 세그폴트는 불법 주소 접근 신호입니다. 잘못된 포인터 산술, 해제 후 사용, 경계 밖 인덱싱 등에서 발생. 세그멘테이션 시대 용어지만, 오늘날 페이지 단위 보호에서도 그대로 씁니다.
7) 마무리 요약
- Base/Bounds: 빠르고 단순. 보호 가능. 하지만 내부 단편화·연속 배치 제약.
- Segmentation: 코드/힙/스택 단위로 따로 배치 → 희소 주소 공간을 효율적으로 수용. 보호/공유도 쉬움. 단, 외부 단편화라는 새 문제가 생김(압축·프리 리스트로 완화).
개념 체크리스트
- Base/Bounds의 경계 검사 → 더하기 순서를 말할 수 있는가? (두 정의 모두 가능)
- 세그먼트 선택(명시적 vs 암묵적)과 오프셋 계산 과정을 예제로 설명할 수 있는가?
- 스택 역성장 처리 방식을 수식/예시로 계산할 수 있는가?
- 내부/외부 단편화 차이와 각각의 원인·대응을 구분할 수 있는가?
- 권한 비트와 코드 공유의 관계를 설명할 수 있는가?
'개념 공부' 카테고리의 다른 글
| 운영체제 아주 쉬운 세가지 이야기 7주차 (빈 공간 관리) (0) | 2025.10.21 |
|---|---|
| 운영체제 아주 쉬운 세가지 이야기 6주차 (메모리 가상화 2-1) (0) | 2025.10.14 |
| 운영체제 아주 쉬운 세가지 이야기 5주차 (메모리 가상화 1) (0) | 2025.09.30 |
| 운영체제 아주 쉬운 세가지 이야기 4주차 (1-3) (0) | 2025.09.23 |
| 운영체제 아주 쉬운 세가지 이야기 4주차 (1-2) (0) | 2025.09.23 |