한양과학기술고등학교

에듀테크 프로젝트 대시보드

가상 메모리 & 페이징

🧠 가상 메모리 & 페이징

프로그램이 쓰는 주소(0x4a1c 같은 값)는 진짜 RAM 주소가 아니다. 운영체제와 CPU의 MMU가 프로그램마다 가짜 주소 공간(가상 주소)을 만들어 주고, 메모리 접근이 일어날 때마다 페이지 테이블을 보고 실제 물리 프레임으로 바꿔치기한다. 덕분에 각 프로세스는 서로를 못 건드리고, 물리 RAM보다 큰 메모리도 쓸 수 있다.

프로세스마다 "나 혼자 RAM 다 쓴다"고 착각한다

두 프로세스가 똑같이 가상 주소 0x0000부터 시작해도, 페이지 테이블이 각각 다른 물리 프레임을 가리키므로 충돌하지 않는다. 이것이 프로세스 격리다.

프로세스 A
가상 주소공간

stack
…
heap
data
code (0x0000)

프로세스 B
가상 주소공간

stack
…
heap
data
code (0x0000)

실제 물리 RAM
(뒤섞여 저장)

B.heap
A.code
B.code
비어있음
A.stack
A.heap
물리 RAM에서는 A와 B의 페이지가 순서 없이 흩어져 있어도 된다 → 외부 단편화(external fragmentation)가 사라진다. 빈 프레임 아무 곳에나 넣으면 되니까.

가상 주소 → 물리 주소 변환 (직접 해보기)

페이지 크기를 256바이트(2⁸)로, 가상 주소를 16비트로 가정하자. 그러면 하위 8비트 = 오프셋(페이지 안에서의 위치), 상위 8비트 = VPN(가상 페이지 번호)이다. 변환은 오프셋은 그대로 두고 VPN만 페이지 테이블로 PFN(물리 프레임 번호)으로 바꾸는 일이다.

0x4A1C
◀── VPN (상위 8비트) ──▶   ◀── offset (하위 8비트) ──▶

페이지 테이블 (프로세스별로 1개)

MMU

변환 결과

VPN  = 0x4A RESIDENT
PFN  = 0x07
offset = 0x1C
물리 주소
0x071C

페이지 폴트 & 스와핑 — RAM보다 큰 메모리

페이지 테이블 엔트리의 valid 비트가 0이면(그 페이지가 디스크에만 있음), CPU가 페이지 폴트 예외를 일으킨다. OS가 개입해 디스크에서 페이지를 읽어 빈 프레임에 올리고, 빈 프레임이 없으면 희생자(victim) 페이지를 골라 쫓아낸다(여기선 LRU).

물리 프레임 (총 6칸)
0
총 접근
0
페이지 히트
0
페이지 폴트
0
페이지 축출
$ 페이지를 접근해 보세요. 처음엔 프레임이 비어 있어 전부 폴트가 납니다…

TLB — 주소 변환도 캐싱한다

변환 한 번에 페이지 테이블을 메모리에서 읽어야 하면 모든 접근이 2배 느려진다. 그래서 CPU 안에 최근 변환 결과를 담는 초고속 캐시 TLB(Translation Lookaside Buffer)를 둔다. 같은 페이지를 다시 쓰면 TLB 히트 → 페이지 테이블을 안 봐도 된다.

0
변환 요청
0
TLB 히트
0
TLB 미스
0%
히트율
TLB 엔트리 4칸. 지역성이 좋으면 히트율이 확 올라갑니다.

핵심 개념 정리

페이지 / 프레임

가상 주소공간을 자른 고정 크기 조각이 페이지, 물리 RAM을 자른 같은 크기 조각이 프레임. 보통 4 KB. 페이지 ↔ 프레임 매핑이 핵심.

MMU

CPU 안의 하드웨어. 모든 메모리 접근마다 가상→물리 변환을 자동으로 수행한다. 변환 실패 시 페이지 폴트 예외를 발생시켜 OS를 부른다.

보호 비트

각 페이지 테이블 엔트리에 valid, read/write, user/kernel 비트가 있다. 코드 페이지는 읽기 전용 → 실수로 덮어쓰면 Segfault.

요구 페이징 / CoW

실제로 접근할 때까지 프레임을 안 준다(demand paging). fork() 직후엔 부모·자식이 같은 프레임을 공유하다 쓰기 순간에만 복사(Copy-on-Write).

스와핑 / 페이지 교체

프레임이 부족하면 안 쓰는 페이지를 디스크(swap)로 내보낸다. 어떤 걸 내보낼지가 교체 알고리즘(LRU, Clock 등). 너무 잦으면 스래싱.

다단계 페이지 테이블

64비트 주소공간을 한 배열로 표현하면 테이블만 페타바이트급. 그래서 테이블을 2~5단계 트리로 나눠 실제 쓰는 부분만 만든다.

# 이 서버에서 직접 확인해보기 $ getconf PAGE_SIZE # 페이지 크기 (보통 4096) $ cat /proc/meminfo | head # MemTotal, SwapTotal, ... $ ps -o pid,vsz,rss,comm -p $(pgrep -f gunicorn | head -1) # VSZ=가상 크기, RSS=실제 올라온 물리 메모리 $ cat /proc/$(pgrep -f gunicorn | head -1)/status | grep -E 'Vm(Peak|Size|RSS|Swap)'
이 서버라면? FastAPI 앱 5개가 각자 독립된 가상 주소공간에서 돌기 때문에 fastapi-school이 터져도 fastapi-lms의 메모리는 멀쩡하다. ps aux로 보면 VSZ(가상)는 수 GB로 크지만 RSS(실제 물리 사용)는 훨씬 작다 — 대부분의 페이지는 아직 물리 프레임에 올라오지도 않았기 때문이다. RSS 합이 MemTotal을 넘어서기 시작하면 스와핑이 늘고 응답이 느려지므로, 그때가 워커 수를 줄이거나 인스턴스를 키울 신호다.