[2026] Linux 메모리·가상 메모리 심화 — 페이지 테이블, 페이지 폴트, ZONE·슬랩과 프로덕션 튜닝
이 글의 핵심
사용자 공간의 가상 주소는 MMU가 물리 프레임으로 매핑하고, 매핑이 비어 있거나 권한이 맞지 않으면 페이지 폴트로 커널이 개입합니다. 페이지 테이블·영역(zone)·버디·슬랩·회수(reclaim) 경로를 알아야 MemAvailable·스왑·OOM·지연 스파이크를 같은 그림으로 설명할 수 있습니다.
들어가며
리눅스는 프로세스마다 가상 주소 공간을 제공하고, 실제 DRAM은 페이지(대개 4KiB) 단위로 나뉜 물리 프레임에 매핑됩니다. 이 연결은 CPU의 MMU가 페이지 테이블을 통해 이루어지며, 매핑이 없거나 아직 물리 페이지가 없으면 페이지 폴트가 발생해 커널의 do_page_fault 계열 경로가 실행됩니다.
이 글은 Linux 완전 가이드의 페이지 캐시·스왑·OOM 개요를 바탕으로, 가상 메모리 아키텍처, 페이지 테이블과 다단계 변환, 페이지 폴트의 종류와 처리, 메모리 영역(zone)과 버디 할당기, 슬랩(SLUB) 캐시, 마지막으로 프로덕션 튜닝까지 연결합니다. 스케줄러·태스크 상태와의 연결은 Linux 프로세스·스케줄러 심화와 함께 보시면 좋습니다.
가상 주소·물리 프레임·MMU
프로세스 관점의 가상 주소
사용자 프로그램이 보는 포인터는 가상 주소입니다. 같은 주소 숫자라도 프로세스마다 다른 물리 프레임을 가리킬 수 있고, 커널은 이를 페이지 테이블로 관리합니다. 이 덕분에 격리와 과커밋(논리 메모리 > 물리 메모리)의 기반이 마련됩니다.
MMU와 TLB
MMU는 가상 주소를 물리 주소로 바꿀 때 TLB(변환 룩어사이드 버퍼)를 사용합니다. TLB 미스가 잦으면 메모리 접근 자체가 비싸집니다. 그래서 커널은 대형 페이지(huge page)·THP 같은 기법으로 TLB 압력을 줄이려 합니다(워크로드에 따라 이득/손해가 갈립니다).
다단계 페이지 테이블과 PTE
x86-64 같은 일반적인 환경에서는 4단계(레벨 4) 페이지 테이블이 흔합니다(구현·커널 설정에 따라 5레벨 등도 존재). 가상 주소의 비트 필드가 각 레벨의 테이블 인덱스가 되어 최종적으로 PTE(페이지 테이블 엔트리)에 도달합니다.
개념적으로는 아래처럼 “루트 테이블 → 중간 테이블들 → 리프 PTE → 물리 프레임”으로 내려갑니다(레벨 수·비트 분할은 아키텍처/모드에 따라 다름).
가상 주소 ──► [PGD 인덱스] ──► [PUD] ──► [PMD] ──► [PTE] ──► 물리 페이지 오프셋
│ │ │ │
▼ ▼ ▼ ▼
각 레벨은 “다음 테이블의 물리 주소”를 가리키는 엔트리를 담음
PTE에 담기는 정보
PTE에는 물리 프레임 번호뿐 아니라, 프레젠트 비트, 읽기/쓰기/실행 권한, 유저/커널, 접근됨(accessed)·더티(dirty) 같은 플래그가 함께 들어갑니다. 스왑 아웃된 페이지는 PTE가 “디스크의 어디에 있는지”를 가리키는 형태로 바뀌고, 다시 접근하면 major fault로 스왑 인이 일어날 수 있습니다.
커널 페이지 테이블과 mm_struct
각 프로세스의 메모리 설명자는 mm_struct로 표현되고, 여기에 VMA(가상 메모리 영역) 목록과 페이지 테이블 루트가 연결됩니다. mmap·brk·파일 매핑은 VMA를 추가/변경하고, 실제 물리 페이지는 첫 접근 시점에 잡히는 경우가 많습니다(지연 할당).
페이지 폴트 처리: 왜 “폴트”가 정상 경로인가
페이지 폴트는 오류가 아니라 지연된 작업을 수행하는 정상적인 트랩인 경우가 많습니다.
무엇이 폴트를 유발하나
- 프레젠트 비트가 0: 아직 물리 페이지가 없거나 스왑/파일 백킹을 읽어야 함
- 권한 불일치: 쓰기 가능해야 하는데 읽기 전용 → COW 등으로 이어질 수 있음
- 실행 불가 페이지에서 실행 시도: 보안·정책 위반
Minor vs Major(개념적 구분)
- Minor fault: 이미 물리 페이지가 있거나, 상대적으로 가벼운 커널 작업으로 끝나는 경우(예: COW 준비, 영의 페이지(zero page) 공유 해제 등). 여전히 커널 진입 비용은 있습니다.
- Major fault: 디스크·스왑에서 실제 데이터를 가져와야 해 I/O 대기가 큰 경우. 콜드 스타트·메모리 압박·스왑 사용 증가와 함께 관측됩니다.
Copy-On-Write(COW)
fork 직후 부모·자식이 같은 물리 페이지를 공유하다가, 한쪽이 쓰기를 시도하면 COW가 발동해 새 프레임을 할당합니다. 이 순간 폴트·복사 비용이 튈 수 있어, 대량 fork + 쓰기 패턴은 운영에서 성능 이슈가 되기도 합니다.
메모리 영역(ZONE)과 왜 “남는데 실패”가 생기나
물리 메모리는 단순히 “남은 바이트” 하나로 표현되지 않고, ZONE으로 나뉩니다. 대표적으로 ZONE_DMA, ZONE_DMA32, ZONE_NORMAL, ZONE_MOVABLE(구성에 따라 다름) 같은 개념이 있습니다.
DMA와 주소 제약
일부 장치는 낮은 물리 주소만 DMA 가능합니다. 이런 프레임이 고갈되면 “전체 여유는 있는데 특정 할당만 실패”하는 형태가 나올 수 있습니다. 서버 일반 워크로드에서는 드물지만, 드라이버·임베디드·특수 NIC/스토리지에서는 실제 이슈가 됩니다.
이동 가능한 ZONE
ZONE_MOVABLE은 메모리 핫플러그·대형 페이지 관리 등과 연관되어, 메모리 배치 정책에 영향을 줍니다. 커널 버전·부팅 파라미터·하드웨어에 따라 세부가 달라 “문서의 한 줄”만으로 튜닝하지 않는 것이 안전합니다.
버디 할당기(buddy)와 단편화
물리 페이지는 버디 알고리즘으로 연속된 2^n 페이지 블록을 관리합니다. 작은 할당이 반복되면 외부 단편화가 생겨, “충분히 비어 보이는데 연속 큰 블록이 없다”는 문제가 날 수 있습니다. 커널은 컴팩션(compaction) 같은 기법으로 큰 연속 영역을 만들려 시도합니다(메모리 압박이 클 때 지연으로 체감될 수 있음).
슬랩 할당자(SLUB)와 커널 객체
커널 내부의 작고 반복적인 객체(task_struct, inode 등)는 페이지 단위 할당만으로는 비효율적이라 슬랩 캐시로 최적화합니다. 최신 리눅스는 주로 SLUB를 사용합니다.
왜 운영자가 알아야 하나
슬랩은 메타데이터 폭증 워크로드에서 커널 메모리 사용을 키웁니다. 예를 들어 수백만 dentry/inode 상황은 사용자 RSS가 아니라 커널 슬랩이 커지는 패턴으로 나타나기도 합니다. 이때는 slabtop, /proc/slabinfo 등이 단서가 됩니다.
회수(reclaim): kswapd와 직접 회수
메모리가 부족해지면 커널은 페이지 회수를 시도합니다. 파일 기반 페이지는 “깨끗한” 캐시부터 버리기 쉽고, 익명 페이지는 스왑으로 밀어낼 수 있습니다.
백그라운드 kswapd vs 직접 reclaim
kswapd: 워터마크를 보며 미리 회수하려는 스레드입니다.- 직접 reclaim: 할당 요청 경로에서 동기적으로 페이지를 찾습니다. 이게 과하면 지연 스파이크가 납니다.
운영에서 “갑자기 느려짐”은 직접 회수 폭증, 컴팩션, 스왑 I/O와 연결해 보는 것이 좋습니다.
투명 대형 페이지(THP)와 트레이드오프
THP는 대형 페이지를 자동으로 쓰려는 기능입니다. TLB 효율은 좋아질 수 있지만, 메모리 낭비·지연 할당·컴팩션과 상호작용해 워크로드에 따라 해가 될 수도 있습니다. 데이터베이스처럼 메모리 관리를 세밀히 하는 소프트웨어는 THP를 끄는 권고가 흔히 등장합니다(반드시 측정 필요).
프로덕션 튜닝: sysctl·cgroup·관측
vm.swappiness
스왑으로 익명 페이지를 보낼 선호도를 바꾸는 휴리스틱에 영향을 줍니다. “값만 올리면 성능 향상”이 아니라, 워크셋·디스크 지연·서비스 SLO와 함께 봐야 합니다.
더티 페이지와 writeback
vm.dirty_ratio·dirty_background_ratio(또는 바이트 기반)는 더티 페이지 비율에 따라 플러시를 유도합니다. 로그·배치 쓰기가 세면 디스크 대역폭과 맞물려 stall이 납니다.
min_free_kbytes와 워터마크
너무 낮으면 급한 할당에서 압박이 커지고, 너무 높으면 조기 reclaim 등 부작용이 날 수 있습니다. “인터넷에 나온 값” 복붙은 위험합니다.
cgroup 메모리: memory.high vs memory.max
memory.high는 소프트 압박으로 회수를 강하게 유도할 수 있고, memory.max는 하드 리밋에 가깝습니다. Kubernetes limits와 OOM의 관계는 운영 이슈의 중심에 있습니다.
투명성 있는 관측 지표
MemAvailable: 회수 가능성을 포함한 가용성 추정에 유용합니다./proc/pressure/memory: PSI로 메모리 압력을 정량화합니다.vmstat,sar -B,iostat: 페이지 인/아웃, I/O 대기를 함께 봅니다.
OOM killer: 마지막 방어선
회수로도 메모리를 마련하지 못하면 OOM killer가 동작합니다. 선택은 oom_score·cgroup 한도·태스크 특성 등이 섞입니다. “무엇이 죽었는지”만 보지 말고, 왜 그 시점에 압력이 폭발했는지(누수, 캐시 폭증, limit 부족)로 거슬러 올라가야 재발을 막습니다.
트러블슈팅 매핑
| 증상 | 내부적으로 의심할 지점 | 다음 관측 |
|---|---|---|
| 콜드 스타트만 느림 | Major fault, 디스크 | 페이지 폴트 비율, I/O |
| 메모리 여유인데 할당 실패 | ZONE/DMA 제약, 단편화 | dmesg, NUMA/드라이버 |
| 가끔 초장지연 | 직접 reclaim, 컴팩션 | PSI, vmstat, 지연 프로파일 |
| 커널 메모리만 증가 | 슬랩, dentry/inode | slabtop, vfs 캐시 |
| 스왑 사용 후 체감 지연 | 스왑 I/O | sar -W, 디스크 latency |
내부 동작과 핵심 메커니즘
이 글의 주제는 「[2026] Linux 메모리·가상 메모리 심화 — 페이지 테이블, 페이지 폴트, ZONE·슬랩과 프로덕션 튜닝」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.
정리
리눅스 가상 메모리는 MMU·다단계 페이지 테이블·PTE 플래그로 가상→물리 변환을 이루고, 페이지 폴트는 매핑을 완성하거나 COW·스왑 인 등 지연된 작업을 수행하는 관문입니다. 물리 메모리는 ZONE·버디·슬랩으로 관리되며, 압박이 오면 kswapd/직접 reclaim과 스왑이 개입합니다. 프로덕션에서는 vm.* 한 줄보다 MemAvailable/PSI/cgroup/스왑/I/O를 같은 그림으로 읽는 것이 중요합니다.
프로세스 스케줄러와의 연결은 Linux 프로세스·스케줄러 심화를 참고하십시오.