'무한하다'는 착각
우리가 작성하는 프로그램은 자신이 컴퓨터의 모든 메모리를 독점하고 있다고 믿는다. 하지만 실제 물리적 하드웨어는 제한적이고 혼잡하다. 이 간극을 메우기 위해 운영체제는 각 프로세스에게 가상 메모리라는 완벽한 환상(illusion)을 제공한다. 오늘은 이 환상이 어떻게 기술적으로 구현되는지, 그리고 그 기저에 깔린 하드웨어적 원리를 정리해보고자 한다.
1. 하드웨어의 현실: 계층 구조와 지역성
가상화를 논하기 전에, 우리가 다루는 물리적 자원의 실체를 먼저 이해해야 했다. 현대 컴퓨터 시스템은 초기 모델처럼 단순한 평면 메모리 구조가 아니다. CPU, 캐시, DRAM, 스토리지 등이 복잡하게 얽혀 있다.
RAM의 두 얼굴: SRAM vs DRAM
메모리의 핵심인 RAM은 크게 두 가지로 나뉜다. 이 둘의 차이를 이해하는 것이 시스템 성능 최적화의 첫걸음이다.
SRAM (Static RAM): 트랜지스터 6개로 구성되어 빠르고 전원만 공급되면 데이터가 유지된다. 하지만 비싸다. (약 $860/GB) 주로 캐시(Cache)에 쓰인다.
DRAM (Dynamic RAM): 커패시터와 트랜지스터 1개로 구성되어 작게 만들 수 있다. 하지만 전자가 누설되므로 10~100ms마다 데이터를 새로고침(Refresh) 해야 한다. 비교적 저렴하다. (약 $8.6/GB) 이것이 우리가 쓰는 메인 메모리다.
데이터가 날아가는 휘발성 메모리만으로는 시스템을 유지할 수 없다. 전원이 꺼져도 데이터를 지키는 비휘발성 메모리(Nonvolatile Memory)의 진화 과정도 흥미로웠다.
ROM: 생산 시점에 아예 박혀서 나옴.
PROM/EPROM: 한 번 쓰거나 자외선으로 지울 수 있음.
NAND Flash: 우리가 흔히 쓰는 SSD나 USB. 전기적으로 쓰고 지울 수 있다.
Optane: 최근 주목받는 기술로, DRAM과 디스크 사이의 성능을 보여준다.
메모리 계층 구조 (Memory Hierarchy)
컴퓨터 구조의 대원칙은 "빠른 것은 비싸고, 싼 것은 느리다"는 것이다. 따라서 시스템은 계층적으로 구성된다.
L0 (레지스터): 가장 빠름, 용량 작음
L1/L2 캐시 (SRAM): 빠름, 하드웨어가 관리
L3/주 메모리 (DRAM): 보통 속도, OS와 하드웨어가 관리
디스크 (SSD/HDD): 느림, 대용량
이 계층 구조가 효율적으로 동작할 수 있는 이유는 지역성(Locality) 덕분이다. 프로그램은 한번 참조한 데이터를 다시 참조하거나(시간적 지역성), 인접한 데이터를 참조하는(공간적 지역성) 경향이 있기 때문에, 느린 디스크나 메모리까지 매번 가지 않아도 된다는 원리다.
| Locality |
2. 메모리 가상화 (Memory Virtualization)
물리적 현실은 제한적이지만, 프로세스에게는 무한하고 독립적인 공간을 제공해야 한다. 이것이 메모리 가상화의 핵심 목표다.
가상 주소 공간 (Virtual Address Space)
운영체제는 물리 메모리를 추상화하여 각 프로세스에게 '가상 주소 공간'을 부여한다. 64비트 리눅스 환경에서 실행되는 서로 다른 프로그램들이 동일한 주소값을 가질 수 있는 이유가 바로 이 때문이다.
가상 주소 공간의 구성은 다음과 같다.
Code: 명령어 저장
Heap: 동적 할당 메모리 (
malloc,new)Stack: 지역 변수, 함수 인자, 리턴 주소
이러한 가상화는 다음 세 가지 이점을 제공한다.
효율성 (Memory efficiency): 여러 프로그램을 물리 메모리에 동시에 올릴 수 있다.
보호 (Protection): 프로세스 간, 그리고 OS 영역에 대한 침범을 막는다.
단순성 (Ease of programming): 개발자는 물리 메모리의 복잡성을 몰라도 된다.
3. 환상을 현실로: 주소 변환 (Address Translation)
가상화의 핵심은 결국 매핑(Mapping)이다. 프로그램이 사용하는 가상 주소(VA)를 실제 하드웨어의 물리 주소(PA)로 바꾸는 과정이 필요하다.
| Address Translation |
언제 변환할 것인가? (Timing matters)
주소 변환 시점에 따라 시스템의 유연성이 결정된다.
컴파일 시간 (Compile Time): 컴파일러가 물리 주소를 박아버린다. 가장 빠르지만, 멀티태스킹이 불가능하다. (절대 주소 사용)
로드 시간 (Load Time): 프로그램이 메모리에 올라갈 때 주소를 계산한다. 하지만 한번 올라가면 이동이 불가능하고 메모리 보호가 어렵다.
실행 시간 (Execution Time): 현대 OS가 채택한 방식이다. 프로그램 실행 중에 MMU (Memory Management Unit)라는 하드웨어가 실시간으로 주소를 변환한다. 가장 유연하고 안전하다.
| Execution Time Translation |
4. 초기 접근법: 동적 재배치 (Base and Bounds)
가장 간단한 형태의 주소 변환 방식인 Base and Bounds 기법을 살펴보았다. MMU에는 각 프로세스를 위해 두 개의 레지스터가 존재한다.
기준 레지스터 (Base Register): 물리 메모리상에서 프로세스가 시작되는 위치
경계 레지스터 (Bounds Register): 할당된 공간의 크기 (길이)
변환 메커니즘
프로그램이 가상 주소를 참조하면, MMU는 다음과 같이 물리 주소를 계산한다.
동시에, 프로세스가 허용된 범위를 넘어서는지 검사한다.
만약 이 범위를 벗어나면 하드웨어는 예외(Exception)를 발생시키고, OS가 개입하여 해당 프로세스를 종료시킨다.
| Base-and-bounds |
한계점: 빈 공간의 낭비
이 방식은 단순하고 강력하지만(보호 기능), 치명적인 단점이 있다. 내부 단편화(Internal Fragmentation) 문제다. 힙(Heap)과 스택(Stack) 사이에는 사용되지 않는 거대한 빈 공간(Free space)이 존재하는데, Base and Bounds 방식은 이 빈 공간까지 포함하여 물리 메모리를 통째로 할당해야 한다. 메모리 낭비가 심하다는 뜻이다.
아파트 관리인의 지혜
가상 메모리를 학습하며 아파트 관리라는 비유가 가장 와닿았다.
OS는 건물 관리인이고, 프로세스는 세입자다. 각 세입자는 자신이 '101호'에 산다고 생각하지만(가상 주소), 실제로는 건물의 5층, 10층(물리 주소)에 배정되어 있다. 관리인(MMU)이 중간에서 호수(Base)와 평수(Bounds)를 관리하며 서로의 집을 침범하지 않게 막아주는 셈이다.
정리:
가상 메모리는 제한된 물리 자원을 효율적이고 안전하게 쓰기 위한 OS의 핵심 전략이다.
하드웨어(MMU)의 지원 없이는 효율적인 가상화(Runtime Translation)가 불가능하다.
Base and Bounds 방식은 개념적으로 훌륭하지만, 메모리 낭비 문제가 있어 더 발전된 기법(Segmentation, Paging)이 필요하다.
다음에는 이 낭비 문제를 해결하기 위해 등장한 기법들에 대해 더 깊이 파고들어 보려 한다.
추천글:
[운영체제] 운영체제란? | Virtualization, Concurrency, Persistence 핵심 개념 이해
[운영체제] CPU 가상화 | Logical Control Flow에서 Context Switch까지
[운영체제] Operating System 전체 포스팅 모음집
[시스템프로그래밍] Memory Hierachy - RAM / Disk mechanism, Locality, Caching