← System Programming DUJINLABS.COM

Virtual Memory · Linux userspace / kernel ABI

mprotect, madvise, mlock

page protection, reclaim hint, resident 보장을 서로 다른 목적으로 구분하고 TLB·fault·resource limit 비용을 살펴봅니다.

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

page를 메모리에 유지하고 접근 권한을 바꾸는 일은 같은 작업인가?

mprotect는 VMA/PTE permission을 바꾸고 CPU 접근 위반을 fault로 만든다. madvise는 reclaim, readahead, huge page, dump 등 kernel policy에 힌트를 준다. mlock은 page를 unevictable로 유지하려는 resource reservation에 가깝다.

세 API 모두 address range를 받지만 보장과 실패 원인이 다르다. protection 변경은 TLB invalidation을 만들고, mlock은 RLIMIT_MEMLOCK와 page fault를 유발하며, 일부 madvise는 언제든 best-effort다.

구조 그림

그림 1. 하나의 mapping 안에서 서로 다른 page 정책
base+4K+8K+12K+16K
R-X · 실행 코드
R-- · immutable table
RW- · locked secret
RW- · MADV_DONTNEED

mprotect는 접근 bit, madvise는 kernel policy, mlock은 reclaim 가능성을 바꾼다.

permission, reclaim advice, resident lock은 독립 속성이다. 같은 VMA가 split돼 서로 다른 정책 구간으로 나뉠 수 있다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
mmap RW anonymous range
touch page fault/allocate
mprotect read-only PTE
madvise reclaim policy
mlock resident set 고정

range가 같아도 protection bit, reclaim 가능성, 현재 residency는 별도 상태다. /proc/smaps의 VmFlags, Locked, Rss를 각각 확인한다.

그림 3. 커널 내부에서 지나가는 주요 지점
do_mprotect_pkey VMA split/merge
change_protection PTE permission
do_madvise behavior dispatch
mlock_fixup VM_LOCKED
TLB flush CPU translation 갱신

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

Linux 6.18.37 LTS 소스 위치

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

파일함수·구조체여기서 볼 것
mm/mprotect.c do_mprotect_pkey(), mprotect_fixup() VMA permission 변경과 split/merge
mm/madvise.c do_madvise(), madvise_vma_behavior() advice별 처리와 page range operation
mm/mlock.c do_mlock(), mlock_fixup() locked accounting과 page populate

실행 예제 원본

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

빌드cc -std=c17 -Wall -Wextra -O2 page_policy.c -o page_policy
01#define _DEFAULT_SOURCE
02#include <stdio.h>
03#include <sys/mman.h>
04#include <unistd.h>
05
06int main(void)
07{
08    long page = sysconf(_SC_PAGESIZE);
09    unsigned char *area = mmap(NULL, (size_t)page, PROT_READ | PROT_WRITE,
10                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
11    if (area == MAP_FAILED)
12        return 1;
13    area[0] = 7;
14
15    if (madvise(area, (size_t)page, MADV_DONTDUMP) < 0)
16        return 1;
17    if (mlock(area, (size_t)page) < 0)
18        perror("mlock");
19    if (mprotect(area, (size_t)page, PROT_READ) < 0)
20        return 1;
21
22    printf("value=%u; mapping is now read-only\n", area[0]);
23    munlock(area, (size_t)page);
24    return munmap(area, (size_t)page) != 0;
25}

코드 조각별 설명

실제 코드 13행area[0] = 7

mprotect 전에 write fault를 발생시켜 page를 실제로 준비한다. read-only 전환 뒤 같은 store를 하면 SIGSEGV다.

실제 코드 15행MADV_DONTDUMP

core dump에서 이 VMA를 제외하도록 표시한다. 보안 비밀은 dump policy뿐 아니라 swap, 로그, child inheritance도 검토한다.

실제 코드 17행mlock(area

page를 resident하게 유지하도록 요청한다. privilege와 RLIMIT_MEMLOCK 때문에 실패할 수 있어 반환값을 처리한다.

실제 코드 9행PROT_READ

VMA와 PTE에서 write permission을 제거한다. 다른 alias mapping이 writable하면 object 전체가 immutable해지는 것은 아니다.

실제 코드 23행munlock(area

명시적으로 locked accounting을 해제한 뒤 unmap한다. munmap도 해당 range의 lock을 제거한다.

세부 동작

01

mprotect는 W^X와 JIT protocol에 쓰인다

JIT는 code를 RW page에 생성한 뒤 RX로 바꾸는 publish protocol을 쓴다. 동시에 writable+executable인 page를 최소화하고 architecture instruction cache synchronization도 지켜야 한다.

RELRO도 relocation이 끝난 range를 mprotect로 read-only로 바꾸는 사례다.

02

madvise는 명령마다 의미가 다르다

MADV_DONTNEED는 private anonymous page 내용을 버려 다음 접근에서 zero-fill을 만들 수 있고, file mapping에서는 page cache 처리 의미가 다르다. MADV_FREE는 memory pressure 전까지 기존 내용이 보일 수 있는 lazy discard다.

advice 이름을 보고 공통된 즉시 효과를 가정하지 말고 해당 mapping type의 man page 조건을 확인한다.

03

mlock은 latency를 줄이지만 시스템 비용을 고정한다

locked page는 reclaim할 수 없어 과도하면 다른 workload memory pressure를 높인다. real-time path에 필요한 stack, code, buffer 범위를 계측해 제한한다.

mlockall(MCL_FUTURE) 뒤 큰 stack growth가 resource limit에 걸릴 수 있으므로 startup prefault와 실패 정책을 둔다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
VMA protectionmprotect range에서 갱신되고 split된 VMA 수명 동안 유지된다VM_READ/WRITE/EXEC
PTE permissionVMA 정책을 hardware bit에 반영하며 TLB flush와 함께 바뀐다writable, executable
locked_vm accountingmlock에서 mm에 증가하고 munlock/unmap에서 감소한다RLIMIT_MEMLOCK, VmLck

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

겉으로 보이는 현상실제 원인 후보확인 방법
mprotect ENOMEMVMA split로 map count/metadata 부족vm.max_map_count와 range 정렬
mlock EPERM/ENOMEMcapability 또는 memlock limitulimit -l, CapEff, VmLck
read-only인데 다른 경로로 변경writable alias 또는 shared backing writer모든 mapping과 fd 확인

직접 확인

  1. mprotect 뒤 area[0]에 쓰고 siginfo.si_addr와 SEGV_ACCERR를 확인하는 handler를 추가한다.
  2. /proc/self/smaps에서 VmFlags의 dd/lo와 Locked 값이 advice/lock 전후 어떻게 바뀌는지 본다.
  3. MADV_DONTNEED 뒤 anonymous page 값과 minor fault 수를 확인한다.
실행./page_policy
추적strace -e trace=mmap,mprotect,madvise,mlock,munlock,munmap ./page_policy

원문