QUESTION
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다.
STRUCTURE
구조 그림
mprotect는 접근 bit, madvise는 kernel policy, mlock은 reclaim 가능성을 바꾼다.
permission, reclaim advice, resident lock은 독립 속성이다. 같은 VMA가 split돼 서로 다른 정책 구간으로 나뉠 수 있다.
CALL PATH
호출 흐름
range가 같아도 protection bit, reclaim 가능성, 현재 residency는 별도 상태다. /proc/smaps의 VmFlags, Locked, Rss를 각각 확인한다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
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 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 page_policy.c -o page_policy01#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}
CODE NOTES
코드 조각별 설명
area[0] = 7mprotect 전에 write fault를 발생시켜 page를 실제로 준비한다. read-only 전환 뒤 같은 store를 하면 SIGSEGV다.
MADV_DONTDUMPcore dump에서 이 VMA를 제외하도록 표시한다. 보안 비밀은 dump policy뿐 아니라 swap, 로그, child inheritance도 검토한다.
mlock(areapage를 resident하게 유지하도록 요청한다. privilege와 RLIMIT_MEMLOCK 때문에 실패할 수 있어 반환값을 처리한다.
PROT_READVMA와 PTE에서 write permission을 제거한다. 다른 alias mapping이 writable하면 object 전체가 immutable해지는 것은 아니다.
munlock(area명시적으로 locked accounting을 해제한 뒤 unmap한다. munmap도 해당 range의 lock을 제거한다.
DETAILS
세부 동작
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로 바꾸는 사례다.
madvise는 명령마다 의미가 다르다
MADV_DONTNEED는 private anonymous page 내용을 버려 다음 접근에서 zero-fill을 만들 수 있고, file mapping에서는 page cache 처리 의미가 다르다. MADV_FREE는 memory pressure 전까지 기존 내용이 보일 수 있는 lazy discard다.
advice 이름을 보고 공통된 즉시 효과를 가정하지 말고 해당 mapping type의 man page 조건을 확인한다.
mlock은 latency를 줄이지만 시스템 비용을 고정한다
locked page는 reclaim할 수 없어 과도하면 다른 workload memory pressure를 높인다. real-time path에 필요한 stack, code, buffer 범위를 계측해 제한한다.
mlockall(MCL_FUTURE) 뒤 큰 stack growth가 resource limit에 걸릴 수 있으므로 startup prefault와 실패 정책을 둔다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
VMA protection | mprotect range에서 갱신되고 split된 VMA 수명 동안 유지된다 | VM_READ/WRITE/EXEC |
PTE permission | VMA 정책을 hardware bit에 반영하며 TLB flush와 함께 바뀐다 | writable, executable |
locked_vm accounting | mlock에서 mm에 증가하고 munlock/unmap에서 감소한다 | RLIMIT_MEMLOCK, VmLck |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| mprotect ENOMEM | VMA split로 map count/metadata 부족 | vm.max_map_count와 range 정렬 |
| mlock EPERM/ENOMEM | capability 또는 memlock limit | ulimit -l, CapEff, VmLck |
| read-only인데 다른 경로로 변경 | writable alias 또는 shared backing writer | 모든 mapping과 fd 확인 |
LAB
직접 확인
- mprotect 뒤 area[0]에 쓰고 siginfo.si_addr와 SEGV_ACCERR를 확인하는 handler를 추가한다.
- /proc/self/smaps에서 VmFlags의 dd/lo와 Locked 값이 advice/lock 전후 어떻게 바뀌는지 본다.
- MADV_DONTNEED 뒤 anonymous page 값과 minor fault 수를 확인한다.
./page_policystrace -e trace=mmap,mprotect,madvise,mlock,munlock,munmap ./page_policyPRIMARY REFERENCES