세그멘테이션 ⭐⭐⭐
세그멘테이션 ⭐⭐⭐¶
개요¶
세그멘테이션(Segmentation)은 프로그램을 논리적 단위인 세그먼트로 나누어 관리하는 메모리 기법입니다. 코드, 데이터, 스택 등 의미 있는 단위로 분리하여 보호와 공유가 용이합니다.
목차¶
1. 세그먼트의 개념¶
1.1 프로그래머 관점의 메모리¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 프로그래머가 보는 메모리 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 프로그래머는 메모리를 연속된 바이트 배열이 아닌, │
│ 논리적 단위의 집합으로 인식합니다. │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 프로그램 │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ main() │ │ 함수 A() │ │ 함수 B() │ │ │
│ │ │ 코드 │ │ 코드 │ │ 코드 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 전역 변수 │ │ 상수 │ │ 스택 │ │ │
│ │ │ (데이터) │ │ (읽기전용) │ │ (지역변수) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 심볼 테이블 │ │ 힙 │ │ │
│ │ │ (디버그용) │ │ (동적할당) │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 각각의 논리 단위 = 세그먼트 │
│ 세그먼트마다 다른 크기, 다른 보호 속성 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
1.2 세그먼트의 특징¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 특징 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 가변 크기 │
│ - 페이지와 달리 고정 크기가 아님 │
│ - 논리적 단위에 따라 크기가 다름 │
│ │
│ 2. 논리적 분리 │
│ ┌──────────────┐ │
│ │ 코드 세그먼트 │ → 실행 가능, 읽기 전용 │
│ ├──────────────┤ │
│ │ 데이터 세그먼트│ → 읽기/쓰기 가능 │
│ ├──────────────┤ │
│ │ 스택 세그먼트 │ → 자동 확장, 아래로 성장 │
│ ├──────────────┤ │
│ │ 힙 세그먼트 │ → 동적 할당, 위로 성장 │
│ └──────────────┘ │
│ │
│ 3. 보호 용이 │
│ - 각 세그먼트에 다른 접근 권한 부여 │
│ - 코드 수정 방지, 스택 실행 방지 등 │
│ │
│ 4. 공유 용이 │
│ - 코드 세그먼트를 여러 프로세스가 공유 │
│ - 공유 라이브러리 구현에 적합 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2. 세그먼트 테이블¶
2.1 세그먼트 테이블 구조¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 테이블 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 세그먼트 테이블 기준 레지스터 (STBR): 0x5000 │
│ 세그먼트 테이블 길이 레지스터 (STLR): 5 │
│ │
│ ┌────────────┬────────────────┬────────────┬───────────────────────┐ │
│ │ 세그먼트 │ 기준 주소 │ 한계 │ 보호 │ │
│ │ 번호 │ (Base) │ (Limit) │ │ │
│ ├────────────┼────────────────┼────────────┼───────────────────────┤ │
│ │ 0 │ 0x00000 │ 1400 │ R-X (코드) │ │
│ │ 1 │ 0x06300 │ 400 │ R-- (상수) │ │
│ │ 2 │ 0x04300 │ 1100 │ RW- (데이터) │ │
│ │ 3 │ 0x03200 │ 1000 │ RW- (스택) │ │
│ │ 4 │ 0x04700 │ 2000 │ RW- (힙) │ │
│ └────────────┴────────────────┴────────────┴───────────────────────┘ │
│ │
│ Base: 세그먼트의 물리 메모리 시작 주소 │
│ Limit: 세그먼트의 최대 크기 (바이트) │
│ 보호: R(읽기), W(쓰기), X(실행) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
2.2 물리 메모리 배치¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 물리 메모리에서의 세그먼트 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 물리 메모리 │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 0x00000 ┌────────────────────────────────────┐ │ │
│ │ │ 세그먼트 0 (코드) │ │ │
│ │ │ 1400 바이트 │ │ │
│ │ 0x00578 └────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ 빈 공간 │ │ │
│ │ 0x03200 └────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ 세그먼트 3 (스택) │ │ │
│ │ │ 1000 바이트 │ │ │
│ │ 0x035E8 └────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ 0x04300 │ 세그먼트 2 (데이터) │ │ │
│ │ │ 1100 바이트 │ │ │
│ │ 0x0474C └────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ 0x04700 │ 세그먼트 4 (힙) │ │ │
│ │ │ 2000 바이트 │ │ │
│ │ 0x04ED0 └────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ 0x06300 │ 세그먼트 1 (상수) │ │ │
│ │ │ 400 바이트 │ │ │
│ │ 0x06490 └────────────────────────────────────┘ │ │
│ │ ... │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ 주의: 세그먼트가 물리 메모리에서 연속이지만, 순서대로 배치되지 않음 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3. 주소 변환¶
3.1 논리 주소 구조¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 논리 주소 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 논리 주소 = <세그먼트 번호, 오프셋> │
│ │
│ 예: <2, 400> = 세그먼트 2의 400번째 바이트 │
│ │
│ ┌─────────────────┬────────────────────────────────┐ │
│ │ 세그먼트 번호(s)│ 오프셋(d) │ │
│ │ 4비트 │ 12비트 │ │
│ └─────────────────┴────────────────────────────────┘ │
│ │
│ 이 예에서: │
│ - 최대 16개 세그먼트 (2^4) │
│ - 세그먼트당 최대 4KB (2^12) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.2 주소 변환 과정¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 주소 변환 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 논리 주소: <2, 400> │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 세그먼트 번호 추출: s = 2 │ │
│ │ │ │
│ │ 2. 범위 검사: s < STLR? │ │
│ │ 2 < 5 → OK │ │
│ │ │ │
│ │ 3. 세그먼트 테이블 조회 │ │
│ │ segment[2] = {base: 0x04300, limit: 1100} │ │
│ │ │ │
│ │ 4. 한계 검사: d < limit? │ │
│ │ 400 < 1100 → OK │ │
│ │ (만약 d >= limit이면 TRAP 발생!) │ │
│ │ │ │
│ │ 5. 물리 주소 계산 │ │
│ │ 물리 주소 = base + d │ │
│ │ = 0x04300 + 400 │ │
│ │ = 0x04300 + 0x190 │ │
│ │ = 0x04490 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 결과: <2, 400> → 0x04490 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.3 하드웨어 구현¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그멘테이션 하드웨어 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 논리 주소 │
│ ┌───────┬────────┐ │
│ │ s │ d │ │
│ └───┬───┴────┬───┘ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────────┐ │ │
│ │ 세그먼트 테이블 │ │ │
│ │ (STBR 기준) │ │ │
│ │ │ │ │
│ │ ┌───────┬─────────┐ │ │ │
│ │ │ limit │ base │◀──┘ │ │
│ │ └───┬───┴────┬────┘ │ │
│ └───────┼────────┼────────────┘ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌────────────┐ ┌────────┐ │ │
│ │ 비교기 │ │ 가산기│◀────┘ │
│ │ d < limit │ │ base+d │ │
│ └─────┬──────┘ └───┬────┘ │
│ │ │ │
│ ┌───┴───┐ │ │
│ │ yes │ no │ │
│ │ │ │ │ │ │
│ ▼ │ ▼ │ ▼ │
│ OK │ TRAP │ 물리 주소 │
│ │ │ │
│ └───────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.4 C언어 구현¶
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#define MAX_SEGMENTS 16
typedef struct {
uint32_t base; // 기준 주소
uint32_t limit; // 한계 (크기)
bool valid; // 유효 비트
uint8_t protection; // 보호 비트 (R=1, W=2, X=4)
} SegmentTableEntry;
SegmentTableEntry segment_table[MAX_SEGMENTS];
int stlr = 0; // Segment Table Length Register
#define PROT_READ 1
#define PROT_WRITE 2
#define PROT_EXEC 4
// 논리 주소 → 물리 주소 변환
int translate_segment_address(uint16_t segment, uint32_t offset,
uint8_t access_type, uint32_t* physical) {
// 1. 세그먼트 번호 범위 검사
if (segment >= stlr) {
printf("ERROR: Invalid segment number %d (STLR=%d)\n", segment, stlr);
return -1; // Segmentation Fault
}
// 2. 유효성 검사
SegmentTableEntry* entry = &segment_table[segment];
if (!entry->valid) {
printf("ERROR: Segment %d is not valid\n", segment);
return -1; // Segmentation Fault
}
// 3. 한계 검사
if (offset >= entry->limit) {
printf("ERROR: Offset %u >= Limit %u in segment %d\n",
offset, entry->limit, segment);
return -1; // Segmentation Fault
}
// 4. 보호 검사
if ((entry->protection & access_type) != access_type) {
printf("ERROR: Protection violation in segment %d\n", segment);
printf(" Required: 0x%x, Allowed: 0x%x\n",
access_type, entry->protection);
return -1; // Protection Fault
}
// 5. 물리 주소 계산
*physical = entry->base + offset;
return 0;
}
int main() {
// 세그먼트 테이블 초기화
segment_table[0] = (SegmentTableEntry){
.base = 0x00000, .limit = 1400, .valid = true,
.protection = PROT_READ | PROT_EXEC // 코드 세그먼트
};
segment_table[1] = (SegmentTableEntry){
.base = 0x06300, .limit = 400, .valid = true,
.protection = PROT_READ // 상수 세그먼트
};
segment_table[2] = (SegmentTableEntry){
.base = 0x04300, .limit = 1100, .valid = true,
.protection = PROT_READ | PROT_WRITE // 데이터 세그먼트
};
stlr = 3;
uint32_t physical;
// 테스트 1: 정상 접근
if (translate_segment_address(2, 400, PROT_READ, &physical) == 0) {
printf("Segment 2, Offset 400 -> Physical 0x%X\n", physical);
}
// 테스트 2: 한계 초과
translate_segment_address(2, 1200, PROT_READ, &physical);
// 테스트 3: 보호 위반 (코드 세그먼트에 쓰기 시도)
translate_segment_address(0, 100, PROT_WRITE, &physical);
return 0;
}
4. 보호와 공유¶
4.1 세그먼트 보호¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 보호 비트 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 세그먼트 디스크립터의 보호 필드: │
│ │
│ ┌─────┬─────┬─────┬─────────────────────────────┐ │
│ │ R │ W │ X │ 설명 │ │
│ ├─────┼─────┼─────┼─────────────────────────────┤ │
│ │ 1 │ 0 │ 1 │ 코드 세그먼트 (실행/읽기) │ │
│ │ 1 │ 0 │ 0 │ 상수 세그먼트 (읽기 전용) │ │
│ │ 1 │ 1 │ 0 │ 데이터 세그먼트 (읽기/쓰기)│ │
│ │ 0 │ 0 │ 0 │ 접근 불가 │ │
│ └─────┴─────┴─────┴─────────────────────────────┘ │
│ │
│ 추가 보호 속성: │
│ - DPL (Descriptor Privilege Level): 0-3, 접근 가능한 특권 수준 │
│ - Present: 세그먼트가 메모리에 있는지 │
│ - Accessed: 접근 여부 (페이지 교체에 사용) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.2 세그먼트 공유¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 공유 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 프로세스 A 프로세스 B │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 세그먼트 테이블 │ │ 세그먼트 테이블 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ 0: 코드 │────────┐ │ 0: 코드 │────────┐ │
│ │ base=0x1000 │ │ │ base=0x1000 │ │ │
│ │ limit=5000 │ │ │ limit=5000 │ │ │
│ ├─────────────────┤ │ ├─────────────────┤ │ │
│ │ 1: 데이터 │───┐ │ │ 1: 데이터 │───┐ │ │
│ │ base=0x8000 │ │ │ │ base=0xC000 │ │ │ │
│ │ limit=3000 │ │ │ │ limit=4000 │ │ │ │
│ └─────────────────┘ │ │ └─────────────────┘ │ │ │
│ │ │ │ │ │
│ │ ▼ │ ▼ │
│ 물리 메모리 │ ┌─────────────────────────────┼────┐ │
│ ┌─────────────────────┼────│ 공유 코드 세그먼트 │────┤ │
│ │ 0x1000 │ │ (libc.so 등) │ │ │
│ │ │ │ 5000 바이트 │ │ │
│ │ │ └─────────────────────────────┼────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ 0x8000 ┌──────────────┐ ┌──────────────┐ │
│ │ │ A의 데이터 │ 0xC000 │ B의 데이터 │ │
│ │ │ (개별) │ │ (개별) │ │
│ │ └──────────────┘ └──────────────┘ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ 코드 세그먼트: 공유 (같은 물리 주소 참조) │
│ 데이터 세그먼트: 개별 (각 프로세스 고유) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
5. 페이징과 세그멘테이션 비교¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 페이징 vs 세그멘테이션 비교 │
├───────────────────────┬─────────────────────┬───────────────────────────┤
│ 항목 │ 페이징 │ 세그멘테이션 │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 분할 단위 │ 고정 크기 (페이지) │ 가변 크기 (세그먼트) │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 프로그래머 인식 │ 투명함 (모름) │ 인식함 (논리적 단위) │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 외부 단편화 │ 없음 │ 있음 │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 내부 단편화 │ 있음 (마지막 페이지)│ 없음 │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 테이블 크기 │ 클 수 있음 │ 세그먼트 수에 비례 │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 메모리 할당 │ 간단 │ 복잡 (First-Fit 등) │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 보호 단위 │ 페이지 단위 │ 논리적 단위 (유연함) │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 공유 │ 가능하나 복잡 │ 논리적 단위로 쉬움 │
├───────────────────────┼─────────────────────┼───────────────────────────┤
│ 현대 사용 │ 주로 사용 │ 페이징과 결합 │
└───────────────────────┴─────────────────────┴───────────────────────────┘
6. 세그멘테이션 + 페이징 결합¶
6.1 세그먼트 페이징 (Segmentation with Paging)¶
┌─────────────────────────────────────────────────────────────────────────┐
│ 세그먼트 + 페이징 결합 주소 변환 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 논리 주소 │
│ ┌────────────┬────────────┬──────────────┐ │
│ │ 세그먼트(s) │ 페이지(p) │ 오프셋(d) │ │
│ └─────┬──────┴─────┬──────┴──────┬───────┘ │
│ │ │ │ │
│ ▼ │ │ │
│ ┌──────────────┐ │ │ │
│ │ 세그먼트 │ │ │ │
│ │ 테이블 │ │ │ │
│ ├──────────────┤ │ │ │
│ │ 세그먼트 s: │ │ │ │
│ │ 페이지 테이블│───┘ │ │
│ │ 시작 주소 │ │ │
│ └──────┬───────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ 페이지 테이블 │ │ │
│ │ (세그먼트별) │ │ │
│ ├──────────────┤ │ │
│ │ 페이지 p: │ │ │
│ │ 프레임 번호 │───────────┐ │ │
│ └──────────────┘ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ │
│ │물리 주소 │ │
│ │= 프레임×크기│ │
│ │ + 오프셋 │ │
│ └──────────────┘ │
│ │
│ 장점: 세그먼트의 논리적 분리 + 페이징의 외부 단편화 제거 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6.2 MULTICS 시스템¶
┌─────────────────────────────────────────────────────────────────────────┐
│ MULTICS 메모리 관리 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 34비트 가상 주소: │
│ ┌──────────────┬──────────────┬──────────────┐ │
│ │ 세그먼트(18) │ 페이지(6) │ 오프셋(10) │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ │
│ - 세그먼트 수: 2^18 = 256K 세그먼트 │
│ - 세그먼트당 페이지: 2^6 = 64 페이지 │
│ - 페이지 크기: 2^10 = 1KB │
│ - 세그먼트 최대 크기: 64 × 1KB = 64KB │
│ │
│ 주소 변환: │
│ 1. 세그먼트 번호로 세그먼트 테이블 조회 │
│ 2. 해당 세그먼트의 페이지 테이블 위치 획득 │
│ 3. 페이지 번호로 페이지 테이블 조회 │
│ 4. 프레임 번호 + 오프셋 = 물리 주소 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7. Intel x86 세그멘테이션¶
7.1 보호 모드 세그먼트 레지스터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 세그먼트 레지스터 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 16비트 세그먼트 선택자 (Segment Selector) │
│ ┌────────────────────────────┬─────┬───────┐ │
│ │ 인덱스 (13비트) │ TI │ RPL │ │
│ └────────────────────────────┴─────┴───────┘ │
│ │
│ TI: Table Indicator (0=GDT, 1=LDT) │
│ RPL: Requested Privilege Level (0-3) │
│ │
│ 세그먼트 레지스터들: │
│ ┌──────┬────────────────────────────────────────┐ │
│ │ CS │ Code Segment - 현재 실행 중인 코드 │ │
│ │ DS │ Data Segment - 기본 데이터 │ │
│ │ SS │ Stack Segment - 스택 │ │
│ │ ES │ Extra Segment - 추가 데이터 │ │
│ │ FS │ 추가 세그먼트 (스레드 로컬 스토리지) │ │
│ │ GS │ 추가 세그먼트 (커널에서 사용) │ │
│ └──────┴────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.2 세그먼트 디스크립터¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 세그먼트 디스크립터 (64비트) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 비트: 63 56 55 52 51 48 47 40 39 32 │
│ ┌─────────┬───────┬───────┬──────────────┬───────┐ │
│ 상위 │Base[31:24]│Flags │Limit │ Access Byte │Base │ │
│ 32비트│ (8비트) │(4비트)│[19:16]│ (8비트) │[23:16]│ │
│ └─────────┴───────┴───────┴──────────────┴───────┘ │
│ │
│ 비트: 31 16 15 0 │
│ ┌─────────────────┬─────────────────────┐ │
│ 하위 │ Base[15:0] │ Limit[15:0] │ │
│ 32비트│ (16비트) │ (16비트) │ │
│ └─────────────────┴─────────────────────┘ │
│ │
│ Base: 32비트 세그먼트 시작 주소 (분산 저장) │
│ Limit: 20비트 세그먼트 크기 │
│ │
│ Flags (4비트): │
│ ┌────┬────┬────┬────┐ │
│ │ G │D/B │ L │AVL │ │
│ └────┴────┴────┴────┘ │
│ G: Granularity (0=바이트, 1=4KB 단위) │
│ D/B: Default operation size (0=16bit, 1=32bit) │
│ L: 64-bit code segment │
│ AVL: Available for system software │
│ │
│ Access Byte: │
│ ┌────┬─────┬────┬────┬────┬────┬────┬────┐ │
│ │ P │ DPL │ S │ E │DC │RW │ A │ │ │
│ └────┴─────┴────┴────┴────┴────┴────┴────┘ │
│ P: Present bit │
│ DPL: Descriptor Privilege Level (0-3) │
│ S: Descriptor type (0=system, 1=code/data) │
│ E: Executable (0=data, 1=code) │
│ DC: Direction/Conforming │
│ RW: Readable(code)/Writable(data) │
│ A: Accessed │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.3 x86 주소 변환 (세그멘테이션 + 페이징)¶
┌─────────────────────────────────────────────────────────────────────────┐
│ x86 전체 주소 변환 과정 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 논리 주소 │
│ ┌──────────────────┬────────────────────────────────┐ │
│ │ 세그먼트 선택자 │ 오프셋 │ │
│ │ (16비트) │ (32비트) │ │
│ └────────┬─────────┴───────────────┬────────────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ │ │
│ │ 세그멘테이션 단계 │ │ │
│ │ │ │ │
│ │ GDT/LDT 조회 │ │ │
│ │ Base + Offset │◀───────────┘ │
│ │ = 선형 주소 │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ 선형 주소 (Linear Address) │
│ ┌─────────────────────┐ │
│ │ 페이징 단계 │ │
│ │ │ │
│ │ CR3 → 페이지 │ │
│ │ 디렉토리 → 테이블 │ │
│ │ → 프레임 │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ 물리 주소 │
│ │
│ 현대 운영체제 (Linux, Windows): │
│ - 세그멘테이션 단순화 (Base=0, Limit=최대) │
│ - 실질적으로 페이징만 사용 │
│ - 선형 주소 = 논리 주소 (플랫 메모리 모델) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.4 Linux의 세그멘테이션 사용¶
// Linux x86에서의 세그먼트 설정 (arch/x86/include/asm/segment.h)
// GDT 엔트리 인덱스 (Linux 커널)
#define GDT_ENTRY_KERNEL_CS 1
#define GDT_ENTRY_KERNEL_DS 2
#define GDT_ENTRY_DEFAULT_USER_CS 3
#define GDT_ENTRY_DEFAULT_USER_DS 4
// 세그먼트 선택자 계산
// 인덱스 << 3 | TI(0=GDT) | RPL
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8) // 0x08
#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS * 8) // 0x10
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3) // 0x1B (RPL=3)
#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3) // 0x23 (RPL=3)
// 플랫 메모리 모델: 모든 세그먼트가 0에서 시작, 4GB까지
// Base = 0x00000000
// Limit = 0xFFFFFFFF (G=1이면 4GB)
/*
* Linux는 세그멘테이션을 최소한으로 사용:
* - 커널/사용자 권한 분리 (CS, DS 세그먼트)
* - 스레드 로컬 스토리지 (FS, GS 세그먼트)
* - 실제 메모리 보호는 페이징으로 수행
*/
연습 문제¶
문제 1: 세그먼트 주소 변환¶
다음 세그먼트 테이블로 논리 주소 <1, 500>을 물리 주소로 변환하시오.
| 세그먼트 | Base | Limit |
|---|---|---|
| 0 | 1000 | 600 |
| 1 | 2000 | 400 |
| 2 | 3000 | 800 |
정답 보기
1. 세그먼트 번호: 1
2. 오프셋: 500
3. 세그먼트 1의 Limit: 400
오프셋(500) >= Limit(400) 이므로 Segmentation Fault!
주소 변환 실패 - 세그먼트 범위 초과
문제 2: 세그먼트 보호¶
다음 시나리오에서 보호 위반이 발생하는지 판단하시오.
세그먼트 테이블: | 세그먼트 | Base | Limit | 보호 | |---------|------|-------|------| | 0 (코드) | 0x1000 | 2000 | R-X | | 1 (데이터)| 0x5000 | 3000 | RW- |
- 세그먼트 0의 주소 500에서 명령어 페치
- 세그먼트 0의 주소 100에 데이터 쓰기
- 세그먼트 1의 주소 2500에서 읽기
정답 보기
1. 세그먼트 0 (코드), 주소 500, 실행(X)
- 500 < 2000: 범위 OK
- X 권한 있음: 보호 OK
→ 정상 실행
2. 세그먼트 0 (코드), 주소 100, 쓰기(W)
- 100 < 2000: 범위 OK
- W 권한 없음 (R-X만): 보호 위반!
→ Protection Fault
3. 세그먼트 1 (데이터), 주소 2500, 읽기(R)
- 2500 < 3000: 범위 OK
- R 권한 있음 (RW-): 보호 OK
→ 정상 실행
문제 3: 세그먼트 공유¶
프로세스 A와 B가 있고, 둘 다 같은 공유 라이브러리(1000바이트)를 사용합니다. 이 라이브러리가 물리 주소 0x10000에 로드되어 있을 때, 각 프로세스의 세그먼트 테이블을 작성하시오.
(A는 세그먼트 2에, B는 세그먼트 3에 라이브러리 매핑)
정답 보기
프로세스 A 세그먼트 테이블:
| 세그먼트 | Base | Limit | 보호 | 설명 |
|---------|---------|-------|------|----------|
| 0 | 0x5000 | 2000 | R-X | A의 코드 |
| 1 | 0x8000 | 1500 | RW- | A의 데이터|
| 2 | 0x10000 | 1000 | R-X | 공유 라이브러리|
프로세스 B 세그먼트 테이블:
| 세그먼트 | Base | Limit | 보호 | 설명 |
|---------|---------|-------|------|----------|
| 0 | 0x20000 | 3000 | R-X | B의 코드 |
| 1 | 0x25000 | 2000 | RW- | B의 데이터|
| 2 | ... | ... | ... | 기타 |
| 3 | 0x10000 | 1000 | R-X | 공유 라이브러리|
→ 세그먼트 A:2와 B:3가 같은 물리 주소(0x10000)를 가리킴
→ 라이브러리 코드가 메모리에 한 번만 적재됨
문제 4: 외부 단편화¶
세그먼트 방식에서 외부 단편화가 발생하는 이유와 해결 방법을 설명하시오.
정답 보기
외부 단편화 발생 이유:
1. 세그먼트는 가변 크기
2. 세그먼트 할당/해제 반복으로 메모리에 작은 홀들 생성
3. 총 자유 공간은 충분하지만 연속 공간 부족
예:
[사용][500KB 홀][사용][300KB 홀][사용][200KB 홀]
총 자유공간: 1000KB, 하지만 600KB 세그먼트 할당 불가
해결 방법:
1. 압축 (Compaction)
- 세그먼트를 이동하여 홀 통합
- 비용이 높음 (프로세스 중단 필요)
2. 페이징 결합
- 세그먼트를 페이지로 분할
- 외부 단편화 제거
- 현대 시스템의 표준 방식
3. 버디 시스템 (Buddy System)
- 2의 거듭제곱 크기로 메모리 분할
- 단편화 감소, 빠른 병합
문제 5: x86 세그먼트 선택자¶
Linux에서 사용자 프로세스의 CS 레지스터 값이 0x23입니다. 이를 해석하시오.
정답 보기
0x23 = 0b00100011
세그먼트 선택자 구조: [Index(13)|TI(1)|RPL(2)]
0x23 = 0000 0000 0010 0011
Index (비트 15-3): 0000 0000 0010 0 = 4
TI (비트 2): 0 = GDT
RPL (비트 1-0): 11 = 3 (사용자 모드)
해석:
- GDT의 4번째 엔트리 (GDT_ENTRY_DEFAULT_USER_CS)
- 사용자 모드 (Ring 3)
- 이것은 사용자 코드 세그먼트를 가리킴
Linux 커널 코드와 일치:
#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
= (4 * 8 + 3)
= 32 + 3
= 35
= 0x23 ✓
다음 단계¶
14_Virtual_Memory.md에서 요구 페이징과 가상 메모리 시스템을 배워봅시다!
참고 자료¶
- Silberschatz, "Operating System Concepts" Chapter 8
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3
- Linux kernel source:
arch/x86/kernel/cpu/common.c(GDT 초기화) - Tanenbaum, "Modern Operating Systems" Chapter 3