← System Programming DUJINLABS.COM

Virtual Memory · Linux userspace / kernel ABI

process 주소 공간과 /proc/maps

ELF segment, heap, shared object, stack, vDSO가 VMA로 배치되고 실제 page는 fault 시점에 연결되는 과정을 구분합니다.

Series
17 / 37
Build
cc -std=c17 -Wall -Wextra -O2 address_space.c -o address_space
Run
./address_space
Kernel
Linux 6.18.37 LTS

가상 주소 범위가 보인다는 것은 물리 메모리가 이미 할당됐다는 뜻인가?

VMA는 연속 가상 주소 범위의 protection과 backing 규칙을 설명한다. mmap이나 exec가 VMA를 만들었다고 모든 PTE와 physical page가 즉시 준비되는 것은 아니다. 접근할 때 page fault가 file page, zero page, anonymous page를 연결한다.

/proc/PID/maps는 범위와 permission을, smaps는 RSS/PSS/private/shared accounting을 보여 준다. heap이라는 이름 하나로 malloc object, brk VMA, allocator mmap arena를 모두 설명할 수 없다.

구조 그림

그림 1. 64-bit process의 VMA와 실제 page 준비 시점
0x7fff… 높은 주소0x0040… 낮은 주소
thread stackgrow-down · guard
shared librariesfile-backed RX/RW
mmap anonymous 256 MiBVMA 존재 · 접근 전 RSS≈0
heapbrk VMA · allocator chunks
main executabletext · rodata · data · bss

maps에 VMA가 보이는 것과 모든 physical page가 resident한 것은 다르다. anonymous range는 최초 write fault에서 private page가 생긴다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
ELF/mmap/brk VMA 생성
virtual address 아직 PTE 없을 수 있음
CPU access page fault
fault handler backing page 선택
PTE/TLB 실제 접근 가능

주소 예약, VMA 등록, PTE 설치, physical page allocation, RSS accounting이 서로 다른 시점에 일어난다.

그림 3. 커널 내부에서 지나가는 주요 지점
do_mmap range/prot 검증
vma_merge VMA tree 갱신
handle_mm_fault fault type
do_anonymous_page zero/private page
set_pte mapping publish

함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.

Linux 6.18.37 LTS 소스 위치

glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.

파일함수·구조체여기서 볼 것
mm/mmap.c do_mmap(), mmap_region() VMA range 선택과 maple tree 등록
mm/memory.c handle_mm_fault(), do_anonymous_page() 접근 시 PTE와 anonymous page 준비
fs/proc/task_mmu.c show_map_vma(), show_smap() /proc maps/smaps가 어떤 필드를 출력하는지 확인

실행 예제 원본

아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.

빌드cc -std=c17 -Wall -Wextra -O2 address_space.c -o address_space
01#define _DEFAULT_SOURCE
02#include <stdio.h>
03#include <stdlib.h>
04#include <sys/mman.h>
05#include <unistd.h>
06
07int main(void)
08{
09    long page = sysconf(_SC_PAGESIZE);
10    size_t length = 256UL * 1024 * 1024;
11    unsigned char *area = mmap(NULL, length, PROT_READ | PROT_WRITE,
12                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
13    if (area == MAP_FAILED)
14        return 1;
15
16    printf("pid=%ld area=%p length=%zu page=%ld\n",
17           (long)getpid(), (void *)area, length, page);
18    puts("mapping created; press Enter");
19    getchar();
20    for (size_t offset = 0; offset < length; offset += (size_t)page)
21        area[offset] = 1;
22    puts("pages touched; press Enter");
23    getchar();
24    return munmap(area, length) != 0;
25}

코드 조각별 설명

실제 코드 9행sysconf(_SC_PAGESIZE)

page 크기를 4096으로 고정하지 않고 실행 환경에서 읽는다. huge page와 base page 구분은 별도다.

실제 코드 10행256UL * 1024 * 1024

큰 virtual range를 예약하지만 mmap 반환 직후 RSS가 같은 양만큼 늘어날 필요는 없다.

실제 코드 12행MAP_PRIVATE | MAP_ANONYMOUS

file backing 없는 private VMA를 만든다. 최초 read는 shared zero page, write는 private anonymous page를 만들 수 있다.

실제 코드 21행area[offset] = 1

각 base page에 쓰기 접근해 minor fault와 physical page allocation을 유도한다.

실제 코드 24행munmap(area, length)

VMA range를 제거하고 page table/reference를 정리한다. 잘린 VMA는 분할·병합될 수 있다.

세부 동작

01

ASLR과 PIE가 주소를 바꾼다

PIE main executable, shared objects, stack, mmap base는 exec마다 달라질 수 있다. symbol offset과 mapping load bias를 결합해야 runtime address를 해석할 수 있다.

absolute pointer를 log에 저장해 다음 실행에서 재사용하는 설계는 성립하지 않는다.

02

RSS와 PSS는 질문이 다르다

RSS는 process page table에 resident한 page를 세고 shared page도 각 process에 포함한다. PSS는 shared page 비용을 mapping 수로 나눠 process별 비례 사용량을 추정한다.

allocator fragmentation을 볼 때 virtual size, RSS, anonymous private dirty를 함께 본다.

03

major/minor fault는 오류가 아니다

minor fault는 disk I/O 없이 page table을 채울 수 있는 정상 demand paging 사건이다. major fault는 backing data를 storage에서 기다려야 했음을 뜻한다.

latency-sensitive path에서는 page를 미리 touch하거나 mlock 정책을 검토하지만 memory pressure와 startup 비용을 교환한다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
vm_area_structmmap/exec/brk에서 생기고 munmap/exec/exit에서 제거된다vm_start/end, vm_flags, vm_file
PTEfault 또는 prefault에서 설치되고 reclaim/unmap에서 지워진다present, writable, dirty, accessed
physical page/foliofault/read-ahead에서 확보되고 map/ref가 사라지면 reclaim 가능mapcount, refcount, dirty

실패 조건과 오해하기 쉬운 부분

겉으로 보이는 현상실제 원인 후보확인 방법
VIRT는 큰데 RSS는 작음예약만 하고 page 미접근maps와 smaps Rss 비교
첫 접근 latencydemand fault 또는 file I/Operf faults, mincore, major faults
mmap ENOMEMVA range, map count, commit limitvm.max_map_count, overcommit, address layout

직접 확인

  1. 첫 Enter 전후 /proc/PID/smaps_rollup의 Rss와 Private_Dirty를 비교한다.
  2. read만 수행하는 loop와 write loop에서 zero page와 private page accounting 차이를 본다.
  3. madvise(MADV_HUGEPAGE/NOHUGEPAGE)를 적용해 AnonHugePages와 fault 수를 비교한다.
실행./address_space
추적perf stat -e page-faults,minor-faults,major-faults ./address_space

원문