Skip to content

필사 모드: [운영체제] 09. 메인 메모리 관리

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

배경

기본 개념

CPU가 직접 접근할 수 있는 저장소는 레지스터와 메인 메모리뿐이다. 디스크의 데이터는 반드시 메모리에 올라와야 CPU가 처리할 수 있다.

메모리 접근에는 시간이 걸리므로, CPU와 메모리 사이에 캐시를 두어 속도 차이를 완화한다.

CPU <-> 레지스터 (~1ns) <-> 캐시 (~10ns) <-> 메인 메모리 (~100ns)

주소 바인딩 (Address Binding)

프로그램에서 사용하는 주소를 실제 메모리 주소로 변환하는 과정이다. 바인딩 시점에 따라 세 가지로 나뉜다.

[주소 바인딩 시점]

1. 컴파일 시간 바인딩

- 프로세스가 메모리의 어디에 올라갈지 컴파일 시 결정

- 위치가 바뀌면 재컴파일 필요

- 예: MS-DOS의 .COM 프로그램

2. 로드 시간 바인딩

- 프로그램을 메모리에 올릴 때 주소 결정

- 재배치 가능 코드(relocatable code) 사용

- 로드 후 주소 변경 불가

3. 실행 시간 바인딩 (현대 OS)

- 실행 중에 주소를 동적으로 변환

- MMU(Memory Management Unit) 하드웨어 필요

- 프로세스를 메모리 내에서 이동 가능

논리적 주소와 물리적 주소

- **논리적 주소(Logical Address)**: CPU가 생성하는 주소. 가상 주소라고도 함

- **물리적 주소(Physical Address)**: 실제 메모리 하드웨어의 주소

[MMU를 통한 주소 변환]

CPU --[논리적 주소: 346]--> MMU --[물리적 주소: 14346]--> 메모리

|

재배치 레지스터

(기준값: 14000)

물리적 주소 = 논리적 주소 + 재배치 레지스터 값

346 + 14000 = 14346

사용자 프로그램은 논리적 주소만 다루며, 물리적 주소를 직접 볼 수 없다.

동적 로딩과 동적 링킹

**동적 로딩**: 루틴이 호출될 때만 메모리에 적재한다. 사용하지 않는 루틴은 메모리를 차지하지 않으므로 메모리 이용률이 향상된다.

**동적 링킹**: 라이브러리를 실행 시간에 링크한다.

[정적 링킹 vs 동적 링킹]

정적 링킹:

프로그램 A: [코드 + libc 복사본] -- 50MB

프로그램 B: [코드 + libc 복사본] -- 50MB

총 메모리: 100MB

동적 링킹 (공유 라이브러리):

프로그램 A: [코드 + libc 참조] -- 10MB

프로그램 B: [코드 + libc 참조] -- 10MB

libc.so: [공유 라이브러리] -- 40MB

총 메모리: 60MB

Linux: .so 파일 (Shared Object)

Windows: .dll 파일 (Dynamic-Link Library)

연속 메모리 할당

가장 단순한 메모리 관리 방식으로, 각 프로세스를 연속된 메모리 영역에 배치한다.

메모리 보호

[기준 레지스터와 한계 레지스터]

기준(base) 한계(limit)

| |

v v

+-------+=======================+-------+

| OS | 프로세스 P의 영역 | 기타 |

+-------+=======================+-------+

0 300040 420940

CPU가 생성한 주소 addr에 대해:

if (addr >= base && addr < base + limit)

접근 허용 -> 물리 메모리 접근

else

트랩 발생 -> OS가 오류 처리

메모리 할당 전략

빈 공간(hole) 목록에서 프로세스에게 메모리를 할당하는 방법이다.

[메모리 할당 예시]

초기 상태:

|---OS---|--P1--|-------빈공간-------|--P3--|---빈공간---|

할당 전략:

1. 최초 적합(First Fit): 첫 번째로 충분한 빈 공간에 할당

장점: 빠름

2. 최적 적합(Best Fit): 가장 작은 충분한 빈 공간에 할당

장점: 작은 남은 공간 생성

단점: 전체 탐색 필요, 매우 작은 조각 생성

3. 최악 적합(Worst Fit): 가장 큰 빈 공간에 할당

장점: 남은 공간이 커서 재활용 가능

단점: 전체 탐색 필요

단편화 (Fragmentation)

[외부 단편화 vs 내부 단편화]

외부 단편화:

|P1| 빈 |P2| 빈 |P3| 빈 |P4|

100KB 50KB 80KB

총 빈 공간: 230KB인데, 200KB 프로세스를 올릴 수 없음!

(연속 공간이 아니므로)

해결: 압축(Compaction) - 프로세스를 한쪽으로 이동

|P1|P2|P3|P4|-----230KB 빈 공간-----|

내부 단편화:

메모리를 고정 크기 블록으로 할당할 때

프로세스가 블록보다 작으면 내부에 남는 공간 발생

프로세스 크기: 18,462 바이트

할당 블록 크기: 20,000 바이트

내부 단편화: 1,538 바이트 낭비

페이징 (Paging)

페이징은 논리적 주소 공간을 비연속적으로 할당하여 외부 단편화를 완전히 제거하는 기법이다.

기본 개념

- **페이지(Page)**: 논리적 메모리를 고정 크기 블록으로 나눈 것

- **프레임(Frame)**: 물리적 메모리를 같은 크기의 블록으로 나눈 것

- **페이지 테이블(Page Table)**: 페이지를 프레임에 매핑하는 테이블

[페이징 주소 변환]

논리적 주소 = 페이지 번호(p) + 페이지 오프셋(d)

예: 페이지 크기 4KB (2^12), 논리 주소 32비트

상위 20비트 = 페이지 번호 (최대 2^20 = 1M 페이지)

하위 12비트 = 오프셋 (0 ~ 4095)

+--------+--------+

| p (20) | d (12) | 논리적 주소

+--------+--------+

|

v

[페이지 테이블]

p -> f (프레임 번호)

|

v

+--------+--------+

| f (20) | d (12) | 물리적 주소

+--------+--------+

[페이징 예시]

논리 메모리 (4페이지): 물리 메모리 (8프레임):

+------+ +------+

|페이지0| ----+ | | 프레임0

+------+ | +------+

|페이지1| --+ | |페이지2| 프레임1

+------+ | | +------+

|페이지2| -+| | | | 프레임2

+------+ || | +------+

|페이지3| || +------------>|페이지0| 프레임3

+------+ || +------+

|+--------------->|페이지1| 프레임4

| +------+

+---------------->|페이지2| 프레임5 (X)

+------+

|페이지3| 프레임6

+------+

| | 프레임7

+------+

페이지 테이블:

페이지0 -> 프레임3

페이지1 -> 프레임4

페이지2 -> 프레임1

페이지3 -> 프레임6

TLB (Translation Lookaside Buffer)

매번 페이지 테이블을 메모리에서 조회하면 메모리 접근이 두 배로 늘어난다. TLB는 페이지 테이블의 캐시 역할을 한다.

[TLB를 통한 주소 변환]

CPU --논리 주소(p, d)--> TLB 검색

|

+-----------+-----------+

| |

TLB 히트 TLB 미스

(빠름, ~1ns) (느림)

| |

프레임 f 획득 페이지 테이블 조회

| 프레임 f 획득

| TLB에 등록

| |

+----------+------------+

|

물리 주소 (f, d)로

메모리 접근

유효 접근 시간(EAT) 계산:

TLB 히트율 = 99% (일반적)

메모리 접근 시간 = 100ns

TLB 접근 시간 = 10ns

EAT = 0.99 * (10 + 100) + 0.01 * (10 + 100 + 100)

= 0.99 * 110 + 0.01 * 210

= 108.9 + 2.1

= 111ns

TLB 없이: 200ns (메모리 2번 접근)

TLB 있으면: 111ns (44.5% 개선)

페이지 테이블 구조

프로세스의 주소 공간이 크면(예: 64비트) 페이지 테이블 자체가 매우 커진다.

계층적 페이지 테이블 (Multi-level Page Table)

[2단계 페이지 테이블]

32비트 주소, 4KB 페이지:

+--------+--------+--------+

| p1(10) | p2(10) | d(12) |

+--------+--------+--------+

외부 페이지 테이블 (1단계)

+---+

| 0 |---> 2단계 테이블 A

+---+ +---+

| 1 |--+ | 0 |--> 프레임 번호

+---+ | +---+

| 2 | | | 1 |--> 프레임 번호

+---+ | +---+

| | ...|

| +---+

|

+-> 2단계 테이블 B

+---+

| 0 |--> 프레임 번호

+---+

| ...|

+---+

장점: 사용하지 않는 영역의 2단계 테이블은 생성하지 않음

-> 메모리 절약

해시 페이지 테이블

[해시 페이지 테이블]

논리 페이지 번호 p를 해시 함수에 입력

-> 해시 테이블의 슬롯으로 매핑

-> 체인(연결 리스트)에서 p를 검색

-> 대응하는 프레임 번호 반환

64비트 주소 공간처럼 매우 큰 경우에 유용

역 페이지 테이블 (Inverted Page Table)

[역 페이지 테이블]

일반 페이지 테이블: 프로세스당 하나 (논리 -> 물리)

역 페이지 테이블: 시스템에 하나 (물리 프레임 -> 프로세스, 페이지)

물리 메모리의 각 프레임에 대해 하나의 항목:

프레임 0: (PID=5, 페이지=3)

프레임 1: (PID=2, 페이지=7)

프레임 2: (비어 있음)

프레임 3: (PID=5, 페이지=0)

...

장점: 물리 메모리 크기에 비례하는 테이블 크기

단점: 주소 변환 시 전체 테이블 검색 필요 (해시로 해결)

스와핑 (Swapping)

프로세스 전체 또는 일부를 디스크로 내보내 메모리를 확보하는 기법이다.

[표준 스와핑]

메인 메모리 백킹 스토어(디스크)

+---------+ +---------+

| OS | | |

+---------+ | P2의 |

| P1 | <-- swap in --- | 이미지 |

+---------+ | |

| P3 | --- swap out --> | |

+---------+ +---------+

| 빈공간 |

+---------+

현대 시스템에서는 프로세스 전체가 아닌

페이지 단위로 스와핑 (페이지 스와핑)

[페이지 단위 스와핑]

프로세스의 특정 페이지만 디스크로 이동:

- 오랫동안 사용하지 않은 페이지를 swap out

- 필요할 때 다시 swap in

- 가상 메모리의 기반 기술

메모리 보호

페이징 환경에서의 메모리 보호는 페이지 테이블에 보호 비트를 추가하여 구현한다.

[페이지 테이블 항목의 구조]

+-------+-----+-----+-----+-------+

| 프레임 | 유효 | 읽기 | 쓰기 | 실행 |

| 번호 | 비트 | 가능 | 가능 | 가능 |

+-------+-----+-----+-----+-------+

유효 비트(Valid bit):

1 = 이 페이지가 프로세스의 논리 주소 공간에 속함

0 = 유효하지 않은 페이지 (접근 시 트랩 발생)

보호 비트:

읽기 전용 페이지에 쓰기 시도 -> 하드웨어 트랩

코드 페이지에 대해 실행만 허용, 쓰기 불허

// 메모리 보호 예시: mprotect 시스템 콜

#include <sys/mman.h>

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

void segfault_handler(int sig) {

printf("세그먼트 폴트 발생! 보호된 메모리 접근 시도\n");

exit(1);

}

int main() {

signal(SIGSEGV, segfault_handler);

// 페이지 크기 단위로 메모리 할당

size_t page_size = getpagesize(); // 보통 4096

void *ptr = aligned_alloc(page_size, page_size);

// 데이터 쓰기

*(int *)ptr = 42;

printf("값: %d\n", *(int *)ptr);

// 메모리를 읽기 전용으로 보호

mprotect(ptr, page_size, PROT_READ);

// 쓰기 시도 -> 세그먼트 폴트!

*(int *)ptr = 100;

free(ptr);

return 0;

}

정리

메인 메모리 관리는 프로세스에게 효율적이고 안전한 메모리 공간을 제공하기 위한 핵심 기능이다. MMU가 논리적 주소를 물리적 주소로 변환하고, 페이징은 외부 단편화를 제거하며, TLB가 주소 변환 성능을 보장한다. 계층적 페이지 테이블과 역 페이지 테이블은 대용량 주소 공간을 효율적으로 관리하고, 보호 비트를 통해 프로세스 간 메모리 격리를 보장한다.

현재 단락 (1/248)

CPU가 직접 접근할 수 있는 저장소는 레지스터와 메인 메모리뿐이다. 디스크의 데이터는 반드시 메모리에 올라와야 CPU가 처리할 수 있다.

작성 글자: 0원문 글자: 4,936작성 단락: 0/248