개요
운영체제가 메모리를 관리하는 방식은 단순히 "RAM에 데이터를 올린다"는 것 이상으로 복잡한 구조를 가집니다. 프로세스는 실제 물리 메모리보다 훨씬 넓은 주소 공간을 사용할 수 있고, 운영체제는 이를 투명하게 관리합니다. 이 글에서는 가상 메모리, 물리 메모리, 페이징, 커밋 등 메모리 관리의 핵심 개념들을 정리해봤습니다.
1. 물리 메모리 (Physical Memory)
물리 메모리는 시스템에 실제로 장착된 RAM을 의미합니다. CPU가 직접 접근할 수 있는 실제 저장 공간으로, 용량은 하드웨어에 의해 고정됩니다.
이 물리 주소 공간은 개념적으로 0번지부터 시작하는 선형 주소 공간입니다. CPU는 메모리 버스를 통해 이 물리 주소에 직접 접근합니다. 32비트 시스템에서는 주소 공간이 최대 2³² = 4GB로 제한되고, 64비트 시스템에서는 이론상 수 EB까지 표현 가능합니다.
이 물리 주소를 프로세스가 직접 사용하면 여러 문제가 생기는데, 이를 해결하기 위해 다음 섹션에서 설명하는 가상 메모리 개념이 등장합니다.
2. 가상 메모리 (Virtual Memory)
가상 메모리는 프로세스에게 실제 물리 메모리보다 넓은 주소 공간을 제공하는 메모리 추상화 기법입니다. 각 프로세스는 자신만의 독립적인 가상 주소 공간을 가지며, 운영체제와 CPU(MMU)가 가상 주소를 물리 주소로 변환합니다.
가상 메모리가 필요한 이유
- 프로세스 간 보호: 프로세스 A가 실수로 프로세스 B의 메모리를 침범하는 것을 방지합니다. 각 프로세스는 자신만의 "고립된 섬" 같은 주소 공간을 가집니다.
- 주소 추상화: 프로그램은 자신이 물리 메모리 몇 번지에 올라가는지 알 필요가 없습니다. 덕분에 프로그램을 특정 위치에 종속되지 않게 짤 수 있습니다.
- 효율적 메모리 사용: 실제 사용되는 부분만 물리 메모리에 올림으로써, 물리 메모리보다 큰 프로그램도 실행할 수 있습니다.
가상 메모리는 프로세스마다 독립된 논리 주소 공간을 부여해 이 두 가지 문제를 모두 해결합니다.
3. 페이징 (Paging)
페이징은 가상 메모리와 물리 메모리를 고정 크기의 블록으로 나누어 관리하는 메모리 관리 기법입니다.
페이징이 필요한 이유
가상 메모리는 프로세스마다 독립된 주소 공간을 제공합니다. 그런데 그 가상 주소를 실제로 물리 메모리에 어떻게 올릴 것인가의 문제가 있었습니다.
이 문제를 해결하기 위해, 처음에는 프로세스를 통째로 또는 큰 덩어리째 물리 메모리에 올리는 세그멘테이션(Segmentation) 방식을 썼는데, 외부 단편화 문제가 심각했습니다. 페이징은 이 한계를 극복하기 위한 대안으로 등장했고, 두 기법은 한동안 병존했습니다. x86 아키텍처처럼 세그멘테이션과 페이징을 혼합해서 쓰는 경우도 있었지만, 현대 OS에서는 세그멘테이션을 거의 걷어내고 페이징 방식을 많이 사용하고 있습니다.
구성 요소
- 페이지(Page): 가상 주소 공간을 나눈 고정 크기 블록 (일반적으로 4KB)
- 페이지 프레임(Page Frame): 물리 메모리를 나눈 고정 크기 블록 (페이지와 동일한 크기)
- 페이지 테이블(Page Table): 가상 주소(페이지 번호)를 물리 주소(프레임 번호)로 변환하는 매핑 테이블
페이지 폴트 (Page Fault)
페이지 폴트는 프로세스가 접근하려는 페이지가 현재 물리 메모리에 없을 때 발생합니다.
- CPU가 가상 주소 접근 시도
- 페이지 테이블 조회 → 해당 페이지가 물리 메모리에 없음 확인
- 페이지 폴트 인터럽트 발생
- OS가 디스크(스왑 영역)에서 해당 페이지를 물리 메모리로 로드
- 페이지 테이블 업데이트 후 프로세스 재개
페이지 폴트가 빈번하게 발생하면 OS가 페이지 교체 작업에 너무 많은 시간을 소비하게 되고, 결국 CPU가 실제 작업은 거의 처리하지 못하는 상태에 빠집니다. 이를 쓰레싱(Thrashing) 이라고 합니다.
4. 스왑 (Swap)
스왑은 물리 메모리가 부족할 때 사용하는 디스크 공간입니다.
| 항목 | 설명 |
|---|---|
| Swap Out | 물리 메모리의 페이지를 디스크로 내보냄 |
| Swap In | 디스크의 페이지를 물리 메모리로 가져옴 |
| Swap 공간 | Linux에서는 swap 파티션 또는 swap 파일 / Windows에서는 pagefile.sys |
스왑은 물리 메모리 부족을 보완하지만, 디스크 속도(HDD 기준 수ms)는 RAM(수십ns)보다 수만 배 느리기 때문에 스왑이 빈번하면 시스템 전체가 느려집니다.
5. 가상 메모리 할당
프로세스가 메모리를 요청하면 운영체제는 이를 두 단계로 나누어 처리합니다.
| 단계 | 설명 | 물리 메모리 사용 |
|---|---|---|
| Reserve (예약) | 가상 주소 공간의 범위만 예약. 실제 메모리는 아직 사용 안 함 | ❌ |
| Commit (커밋) | 실제로 메모리를 사용하겠다고 약속. 물리 메모리 또는 스왑 공간이 backing store로 할당됨 | ✅ |
메모리 커밋(Memory Commit)은 OS가 프로세스에게 "네가 요청한 이만큼의 메모리는 나중에 네가 실제로 쓰려고 할 때 내가 반드시 물리적인 공간(RAM 또는 Swap)을 마련해 주겠다"라고 확정적인 약속을 하는 행위입니다.
6. Memory Commit
커밋이 필요한 이유
malloc(1GB) 를 호출했을 때 성공을 리턴받으면, 프로세스는 "이 1GB는 내 거다"라고 믿고 이후 코드를 작성합니다. 나중에 실제로 쓰려고 했더니 갑자기 실패하면 프로그램이 예측 불가능한 시점에 터집니다.
그래서 OS는 malloc 시점에 "나중에 실제로 써도 내가 책임질게"라는 약속을 합니다. 이 약속의 총합이 Commit Charge이고, OS가 책임질 수 있는 한도가 Commit Limit입니다. Commit Limit을 초과한 약속은 지킬 수 없기 때문에 OS가 처음부터 거절합니다.
malloc(1GB) 호출
↓
OS: "내가 나중에 줄 수 있어" 확인 (Commit Limit 여유 체크)
↓
여유 있음 → 커밋 성공, 주소 반환
여유 없음 → NULL 반환 (할당 실패)
Commit Limit
Commit Limit은 RAM 크기와 pagefile 크기의 합입니다. OS가 커밋 요청에 대해 "내가 책임질 수 있다"고 보장할 수 있는 최대 한도이며, 물리 메모리(RAM)와 보조 저장소(pagefile)를 합산한 값이 됩니다.
참고로 주의해야 할 상황은 커밋 누수(Commit Leak) 입니다. 메모리를 할당만 하고 해제하지 않는 버그가 있는 프로세스가 있으면 Commit Charge가 계속 쌓입니다. 이 경우 물리 메모리는 아직 여유가 있어도 Commit Charge가 Commit Limit에 도달하는 순간 OS가 "메모리가 부족합니다" 경고를 띄우거나 새로운 할당 요청을 거절합니다. 물리 메모리 사용량만 보고 "메모리 여유 있는데 왜 이러지?"라고 혼란스러운 상황이 올 수 있는데 바로 이 경우입니다.
Demand Paging
커밋이 완료됐다고 해서 바로 물리 메모리가 할당되는 것은 아닙니다. 실제로 그 주소에 접근(읽기/쓰기)하는 순간 페이지 폴트가 발생하고, 그때 물리 메모리 페이지가 할당됩니다. 이를 Demand Paging이라고 합니다.
malloc(1GB)
↓
커밋 완료 - 가상 주소만 존재, 물리 메모리엔 아직 없음
↓
실제로 그 주소에 데이터를 씀 (write)
↓
페이지 폴트 발생
↓
OS가 물리 메모리 페이지 할당
↓
이제 물리 메모리에 올라옴 (RSS 증가)
단순히 메모리를 할당만 하면 물리 메모리에 올라오지 않기 때문에, 실제로 물리 메모리를 점유하려면 할당한 공간에 실제로 써야 합니다.
char *p = malloc(1GB); // 커밋만 됨
memset(p, 0, 1GB); // 이 시점에 물리 메모리로 올라옴
7. TLB (Translation Lookaside Buffer)
페이지 테이블은 메모리에 저장되어 있어, 주소 변환을 위해 매번 메모리에 접근하면 성능이 느려집니다. TLB는 최근에 사용한 주소 변환 정보를 저장하는 CPU 내부의 고속 캐시로, 가상 주소를 물리 주소로 변환하는 과정을 빠르게 처리할 수 있도록 도와줍니다.
동작방식
CPU가 가상 주소에 접근할 때마다 먼저 TLB를 확인합니다.
- TLB Hit: TLB에 해당 가상 주소의 매핑이 있으면 바로 물리 주소로 변환합니다. 메모리 접근 없이 처리되므로 매우 빠릅니다.
- TLB Miss: TLB에 매핑이 없으면 페이지 테이블을 직접 조회(Page Table Walk)해서 물리 주소를 찾고, 이후를 위해 해당 매핑을 TLB에 저장합니다.
일반적인 프로그램은 한번 접근한 메모리 근처를 반복해서 접근하는 경향(지역성, Locality)이 있기 때문에, TLB 히트율은 보통 99% 이상으로 매우 높습니다.
참고 - 캐시, 페이징 풀, 비페이징 풀
Windows 작업관리자 메모리 탭에서 볼 수 있는 항목들입니다. 세 가지 모두 프로세스가 아닌 OS가 내부적으로 사용하는 메모리입니다.
| 항목 | 사용 주체 | 스왑 가능 | 설명 |
| 캐시 (Cached) | OS | ✅ | 디스크에서 읽은 파일 데이터 저장. 메모리 부족 시 즉시 회수 가능. |
| 페이징 풀 (Paged Pool) | 커널 | ✅ | 커널 영역 메모리 중 디스크로 스왑 아웃될 수 있는 영역. |
| 비페이징 풀 (Non-Paged) | 커널 | ❌ | 인터럽트 처리 등 절대 RAM에 고정되어야 하는 커널 메모리. |