# 리눅스에서 프로세스 메모리를 친절하게 탐험하기

> Clean Markdown view of GeekNews topic #24143. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24143](https://news.hada.io/topic?id=24143)
- GeekNews Markdown: [https://news.hada.io/topic/24143.md](https://news.hada.io/topic/24143.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-11-05T07:32:50+09:00
- Updated: 2025-11-05T07:32:50+09:00
- Original source: [0xkato.xyz](https://www.0xkato.xyz/linux-process-memory/)
- Points: 3
- Comments: 1

## Topic Body

- 리눅스의 **프로세스 메모리 구조**를 실제 동작 수준에서 설명하며, 가상 주소 공간과 물리 메모리 간의 관계를 단계별로 해설  
- **페이지 테이블, VMA, mmap, page fault, CoW** 등 핵심 메커니즘을 중심으로 프로세스가 메모리를 어떻게 소유하고 접근하는지 구체적으로 설명  
- `/proc` 파일 시스템을 통해 **프로세스별 메모리 상태**를 관찰하는 방법과, `pagemap`, `kpageflags` 등 고급 진단 도구의 역할을 소개  
- **Transparent Huge Pages(THP)** , **userfaultfd**, **PAGEMAP_SCAN** 등 최신 커널 기능을 통한 성능 최적화와 사용자 공간 더티 트래킹 기법을 다룸  
- **Meltdown 대응 PTI**, **TLB 플러시**, **W^X 정책** 등 보안 및 성능 관련 커널 설계 원리를 함께 설명하여, 리눅스 메모리 관리의 전체적 이해 제공  

---

### 프로세스 메모리의 기본 구조
- 프로그램이 실행되면 거대한 연속 메모리가 있는 것처럼 보이지만, 실제로는 **리눅스 커널이 페이지 단위로 동적으로 구성**  
  - CPU는 페이지 테이블을 조회해 가상 주소를 물리 프레임으로 변환  
  - 매핑이 없으면 **페이지 폴트(page fault)** 발생, 커널이 새 페이지를 할당하거나 오류를 반환  
- 물리 RAM이 부족하면 커널은 사용하지 않은 페이지를 디스크로 옮기거나 파일 페이지를 제거해 공간 확보  
- `/proc`은 커널이 메모리상에 구성한 **가상 파일 시스템**으로, 프로세스와 커널 상태를 파일 형태로 노출  

### 주소 공간과 VMA
- 각 프로세스는 **하나의 주소 공간 객체**를 가지며, 내부는 여러 **VMA(Virtual Memory Area)** 로 구성  
  - VMA는 동일한 권한(R/W/X)과 동일한 백엔드(익명 메모리 또는 파일)를 가진 연속 주소 범위  
- 페이지 테이블은 하드웨어가 참조하는 구조로, **가상 페이지와 물리 페이지 간 매핑 정보(PTE)** 를 저장  
- 주소 공간 변경은 세 가지 시스템 호출로 수행  
  - `mmap`: 새 영역 생성  
  - `mprotect`: 권한 변경  
  - `munmap`: 매핑 제거  
- 페이지는 **4KiB 기본 단위**, 일부 시스템은 2MiB·1GiB의 큰 페이지도 지원  

### `/proc/self/maps`로 보는 메모리 구성
- `cat /proc/self/maps` 명령으로 프로세스의 메모리 맵을 확인 가능  
  - 실행 파일의 코드·데이터·bss, 힙, 익명 매핑, 공유 라이브러리, 스택 등이 표시  
- `[vdso]`와 `[vvar]` 영역은 커널이 매핑한 **고속 시스템 호출용 코드와 데이터**  

### `mmap`의 동작 원리
- `mmap`은 실제 메모리 할당이 아니라 **주소 공간에 대한 약속을 기록**  
  - 페이지는 첫 접근 시점에 할당  
- 파일 매핑 시 `offset`은 페이지 정렬되어야 하며, 파일 끝을 넘는 접근은 `SIGBUS` 발생  
- `MAP_SHARED`는 파일에 직접 반영, `MAP_PRIVATE`는 **쓰기 시 복사(CoW)** 로 독립 페이지 생성  
- `MAP_FIXED_NOREPLACE`는 지정 주소에 이미 매핑이 있으면 실패하도록 하여 안전성 확보  

### 첫 번째 접근과 페이지 폴트
- 새 매핑에 첫 접근 시 CPU가 페이지 테이블을 찾지 못하면 **페이지 폴트** 발생  
  - 커널은 주소 유효성, 접근 권한, 존재 여부를 검사  
  - 익명 매핑이면 0으로 채운 새 페이지를 할당, 파일 매핑이면 페이지 캐시에서 읽음  
- **minor fault**는 데이터가 이미 RAM에 있을 때, **major fault**는 디스크 I/O가 필요한 경우  
- 스택은 **가드 페이지**로 보호되어, 너무 아래쪽 접근 시 `SIGSEGV` 발생  

### `fork()`와 `MAP_PRIVATE`의 Copy-on-Write
- `fork` 시 부모와 자식은 **동일한 물리 페이지를 공유**, 모두 읽기 전용으로 표시  
  - 쓰기 시점에만 새 페이지를 복사하여 독립 유지  
- `MAP_PRIVATE` 파일 매핑도 동일 원리로 작동  
- 관련 옵션  
  - `vfork`: 부모 주소 공간 공유  
  - `clone(CLONE_VM)`: 스레드 생성  
  - `MADV_DONTFORK`, `MADV_WIPEONFORK`: 자식 프로세스에서 매핑 제외 또는 0으로 초기화  

### 권한 변경과 TLB 무효화
- `mprotect`로 페이지 권한 변경 시 커널은 **VMA 분할 및 페이지 테이블 수정**, 이후 **TLB 무효화** 수행  
- **W^X 정책**에 따라 페이지는 동시에 쓰기·실행 불가  
- TLB(Translation Lookaside Buffer)는 최근 주소 변환 캐시로, 무효화 시 잠시 지연 발생  

### `/proc`을 통한 상세 관찰
- `/proc/&lt;pid&gt;/maps`, `smaps`, `smaps_rollup`으로 영역별 권한·RSS·HugePage 사용량 확인  
- `/proc/&lt;pid&gt;/pagemap`은 페이지 단위 상태(존재, 스왑, PFN 등)를 제공하나, PFN은 일반 사용자에게 비공개  
- `/proc/kpagecount`, `/proc/kpageflags`는 PFN별 매핑 수와 페이지 속성(익명, 파일, dirty 등) 표시  
- `mincore`, `SEEK_DATA/SEEK_HOLE`로 희소 파일의 데이터/홀 구간 식별 가능  
- `PAGEMAP_SCAN`과 `userfaultfd`를 결합해 **사용자 공간 더티 트래킹** 구현 가능  

### Transparent Huge Pages (THP)와 mTHP
- **THP**는 자주 접근하는 메모리를 자동으로 큰 페이지(2MiB 등)로 묶어 **TLB 효율 향상**  
  - `khugepaged` 스레드가 인접 페이지를 병합  
- **mTHP**는 16KiB·64KiB 등 다양한 크기의 **가변 대형 페이지(folio)** 지원  
- `/proc/self/smaps`의 `AnonHugePages`, `FilePmdMapped`로 사용 여부 확인  
- `/sys/kernel/mm/transparent_hugepage/`에서 시스템 전체 설정 관리  
- `MADV_HUGEPAGE`, `MADV_NOHUGEPAGE`로 영역별 제어 가능  

### 사용자 공간 더티 트래킹
- `userfaultfd`와 `PAGEMAP_SCAN`을 이용해 **변경된 페이지만 복사** 가능  
  - 커널이 한 번의 원자적 연산으로 스캔과 쓰기 보호 수행  
  - 스냅샷, 라이브 마이그레이션 등에서 효율적  

### TLB 플러시 메커니즘
- x86에서 TLB 무효화는 두 가지 방식  
  - `INVLPG`: 단일 페이지 무효화  
  - 페이지 테이블 루트 재로딩으로 전체 플러시  
- `PCID`와 `INVPCID`는 **프로세스별 TLB 태그 관리**로 불필요한 플러시 감소  
- `tlb_single_page_flush_ceiling`은 커널이 페이지 단위 vs 전체 플러시를 선택하는 임계값  

### Meltdown 대응: Page Table Isolation (PTI)
- **Meltdown**은 투기 실행 중 커널 데이터가 캐시를 통해 노출될 수 있는 취약점  
- 리눅스는 **PTI(Page Table Isolation)** 로 사용자·커널 주소 공간을 분리  
  - 진입 시 `CR3` 전환으로 커널 전용 페이지 테이블 사용  
  - `PCID`를 활용해 TLB 플러시 최소화  
- 기본적으로 활성화되어 있으며, `nopti`로 비활성화 가능  

### 커널의 안전한 매핑 변경 절차
- 매핑 변경 시 순서  
  1. 캐시 규칙 처리  
  2. 페이지 테이블 수정  
  3. TLB 무효화  
- 커널 내부 매핑(`vmap`, `vmalloc`)도 I/O 전후에 캐시·TLB 동기화 수행  
- 일부 아키텍처는 코드 복사 후 **명령 캐시 플러시** 필요  

### x86의 스택과 호출 구조
- 64비트 모드에서 **RIP, RSP, RBP** 레지스터 사용, 스택은 하향 성장  
- System V AMD64 ABI에 따라 인자 전달은 **RDI, RSI, RDX, RCX, R8, R9**, 반환값은 **RAX**  
- 사용자 모드는 **ring 3**, 커널은 **ring 0**, 시스템 콜과 인터럽트는 게이트를 통해 전환  

### 오류 상황과 진단
- `mmap` → `EINVAL`: 파일 오프셋 정렬 오류  
- `mmap` → `ENOMEM`: 가상 공간 부족 또는 overcommit 제한  
- 파일 매핑 접근 시 `SIGBUS`: EOF 초과 접근  
- `mprotect(PROT_EXEC)` → `EACCES`: `noexec` 마운트 또는 W^X 정책  
- `fork()` 후 RSS 증가: CoW로 인한 페이지 복사  
- `MAP_FIXED`로 기존 매핑 덮어씀 → `MAP_FIXED_NOREPLACE` 권장  

### 실무용 체크리스트
- 즉시 메모리 확보: `mmap` + `PROT_READ|PROT_WRITE` + `MAP_PRIVATE|MAP_ANONYMOUS`  
- 코드 생성 시: W^X 유지, `mprotect(PROT_READ|PROT_EXEC)`  
- 파일 매핑 시: `offset` 페이지 정렬, EOF 초과 접근 금지  
- 페이지 폴트 많을 때: `MADV_WILLNEED` 또는 사전 접근  
- 메모리 사용 분석: `/proc/&lt;pid&gt;/smaps_rollup` → `/proc/&lt;pid&gt;/maps`  
- 대형 프로세스 fork: CoW 고려, 자식에서 `exec` 사용  
- 지연 민감 환경: THP/mTHP, `mlock`, TLB 동작 관찰  

---

## Comments



### Comment 45880

- Author: neo
- Created: 2025-11-05T07:32:51+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=45805539) 
- 이런 **짧은 설명글**들이 정말 마음에 듦  
  이미 알고 있는 내용이라도, 읽는 동안 다시 한번 **확인**할 수 있어서 도움이 됨  

- “mmap, without the fog” 같은 문구를 보면, 뭔가 **LLM이 공동 작성한 글**처럼 느껴져서 괜히 불안하고 짜증이 남  
  - 글의 어조가 마치 Gemini에게 쉬운 설명을 부탁했을 때처럼 느껴짐  
    거기에 “without the fog” 같은 **이상한 표현**이 들어가 있어서 chatgpt가 같이 쓴 것 같은 인상임  

- **Instruction pipelining** 이야기를 보니, 예전 6502 같은 단순한 아키텍처 시절로 돌아가고 싶다는 생각이 듦  
  그때는 복잡한 매핑이나 프록시 없이 “있는 그대로” 작동했음  
  빠른 인터커넥트만 있다면 그런 단순함을 다시 꿈꿔볼 수도 있을 것 같음  
  - 물론 이런 **‘편법들(cheats)’** 이 성능 향상에는 기여했음을 인정함  
    하지만 Meltdown, Spectre 같은 문제를 보면 복잡성이 커진 대가도 분명 있었음  
    지금처럼 무어의 법칙이 한계에 다다른 시점에서는 이런 **복잡성의 트레이드오프**가 과연 최선인지 의문임  
  - 사실 글에서 설명하는 건 **가상 메모리(virtual memory)** 개념인데, 이건 6502보다 10년은 앞선 기술임  
  - 복잡성이 늘어난 건 사실이지만, 그만큼 얻은 것도 많음  
    단순함이 꼭 더 낫다고는 생각하지 않음  
  - 그런데 왜 그런 단순함을 그리워하는지 궁금함  

- 웹사이트가 **위험하거나 안전하지 않은 도메인**으로 차단되었다고 나옴  
  - 혹시 회사 노트북을 쓰는 중인지? 회사의 **보안 부서**가 .xyz 도메인을 신뢰하지 않을 수도 있음  
  - 아마 보안 소프트웨어가 잘못 작동한 것 같음  
    [VirusTotal 검사 결과](https://www.virustotal.com/gui/url/9e0c8d513f58a8053284b8145050f3b86ff4a33866dad115d6dc2676e00d3f81?nocache=1)를 보면 문제없음  
  - 단순한 **오탐(false alarm)** 같음  
  - 어떤 브라우저가 차단한 건지 궁금함  
  - 웃김 lol  

- 에러 리포트가 “잡음(noise)”일 뿐이라는 말이 무슨 뜻인지 궁금함
