프로그램이 보는 가상 주소 공간과 실제 RAM, 페이지 테이블, 페이지 폴트, 권한과 대형 페이지(THP), /proc로 관찰하는 방법, 그리고 Meltdown 대응(PTI)까지 x86‑64 리눅스의 프로세스 메모리 동작을 일상적인 흐름으로 설명합니다.
URL: https://www.0xkato.xyz/linux-process-memory/
Title: A Friendly Tour of Process Memory on Linux
토요일. 2025년 11월 01일 - 24분
당신은 프로그램을 실행합니다. 마치 거대한, 연속된 메모리 판이 처음부터 기다리고 있었던 것처럼 주소를 읽고 쓰지요. 사실 그렇지 않습니다. 리눅스는 그 환상을 한 페이지씩, 그때그때 만들어냅니다. 이 글은 당신의 프로세스가 실제로 무엇을 소유하는지, 바이트를 처음 건드릴 때 무슨 일이 일어나는지, 보호(권한)와 대형 페이지가 어떻게 맞물리는지, /proc에서 진실을 읽는 법, 그리고 최신 커널이 Meltdown에 맞서 방어하기 위해 왜 약간의 추가 동작을 하는지를 안내합니다.
주의: 이 투어는 x86‑64의 리눅스를 대상으로 합니다. 다른 아키텍처는 세부(페이지 크기, 캐시 규칙)가 다르지만, 핵심 아이디어는 그대로입니다.
아래 그림은 빠른 도입입니다. 읽는 내내 머릿속에 두고 볼 수 있는 간단한 지도입니다.
물리 RAM은 실제 메모리입니다. 여기저기 흩어진 프레임들의 묶음이죠. 가상 관점은 당신의 프로그램이 보는 깔끔한 선입니다. 실제 배치와 일치하지 않습니다. 페이지 테이블은 목록입니다. 그 선의 어느 지점이 RAM의 어떤 프레임을 가리키는지 알려줍니다. 디스크는 RAM이 꽉 찼을 때 시스템이 사용할 수 있는 추가 공간입니다.
이렇게 동작합니다. 읽거나 쓸 때 CPU는 페이지 테이블을 확인합니다. 항목이 있으면 해당 프레임으로 갑니다. 항목이 없으면 페이지 폴트가 발생합니다. 그러면 시스템은 프레임을 채우고 항목을 추가하거나, 오류로 당신을 중단시킵니다. 폴트는 뒤에서 설명합니다.
RAM이 부족하면 시스템은 공간을 만듭니다. 한동안 사용하지 않은 페이지를 디스크로 내보내거나, 다시 불러올 수 있는 파일 페이지를 버립니다. 나중에 당신이 그것들을 다시 건드리면 다시 가져옵니다.

배경에 상관없이 누구나 따라올 수 있도록 곳곳에 작은 설명을 넣었습니다.
작은 설명:
/proc
/proc는 커널이 메모리에서 구축하는 가상 파일시스템입니다. 프로세스와 커널 상태를 파일로 노출합니다.cat같은 일반 도구로 읽을 수 있습니다.
커널 내부에서, 당신의 프로세스는 전체 주소 공간을 나타내는 하나의 객체를 소유합니다. 평면도라고 생각하세요. 그 평면도의 각 방은 VMA(가상 메모리 영역)로, 동일한 권한(읽기, 쓰기, 실행)과 동일한 백킹(익명 메모리 또는 파일)을 가진 연속 구간입니다.
작은 설명: VMA
VMA는 하나의 권한 집합과 하나의 백킹을 갖는 연속 가상 주소 범위입니다.
그 평면도 아래에는 하드웨어가 당신의 가상 주소를 실제 페이지 프레임으로 변환할 때 참조하는 페이지 테이블이 있습니다.
작은 설명: 페이지 테이블과 PTE
페이지 테이블은 CPU가 주소를 변환하기 위해 따라가는 조회 구조입니다. 페이지 테이블 엔트리(PTE)는 하나의 가상 페이지를 하나의 물리 페이지에 매핑하고, present나 writable 같은 비트를 담습니다.
프로세스의 모든 스레드는 같은 평면도를 공유합니다. 스케줄러가 당신을 실행하면 CPU는 당신의 페이지 테이블을 가리키게 되므로, 매핑이 한 번 만들어지고 나면 포인터 역참조는 시스템 콜 없이도 하드웨어가 스스로 변환합니다.
평면도를 바꾸는 방법은 세 가지입니다: mmap은 방을 그립니다. mprotect는 방 문에 붙은 표지판(R/W/X)을 바꿉니다. munmap은 방을 철거합니다.
작은 설명:
mmap
mmap은 주어진 권한과 백킹 소스를 가진 가상 범위를 예약합니다.작은 설명:
mprotect
mprotect는 기존 범위의 권한을 바꿉니다.작은 설명:
munmap
munmap은 주소 공간에서 매핑을 제거합니다.
그 외의 모든 일(페이지 생성, 파일 데이터 읽기, 스와핑)은 당신이 메모리를 건드릴 때 느긋하게(lazy) 일어납니다.
작은 설명: 페이지
하드웨어는 페이지라 불리는 고정 크기 덩어리로 메모리를 관리합니다. 많은 x86‑64 머신에서 기본 페이지는 4 KiB입니다. 2 MiB와 1 GiB의 더 큰 페이지도 있습니다.
실행:
cat /proc/self/maps | sed -n '1,80p'
메인 바이너리의 세그먼트(코드, 데이터, bss), 힙, 익명 매핑(할당자는 큰 청크에 이걸 씁니다), 공유 라이브러리, 그리고 맨 위 근처의 스레드 스택을 볼 수 있습니다.
보통 두 개의 작은 영역도 보입니다:
[vdso]: gettimeofday 같은 몇 가지 호출이 커널 트랩 없이 실행되도록 커널이 매핑하는 아주 작은 공유 오브젝트입니다.[vvar]: 그 헬퍼들이 사용하는 읽기 전용 데이터입니다.작은 설명:
vdso와vvar
vdso는 일부 시스템 콜을 빠르게 하기 위해 커널이 프로세스에 매핑하는 코드입니다.vvar는 그 코드가 읽는 데이터를 담습니다.
시간을 묻는 것이 빠른 이유가 바로 이것들입니다.
mmapmmap을 호출한다고 해서 “메모리를 할당”하는 것은 아닙니다. 평면도 위에 약속을 그리는 것에 가깝습니다. “이 권한으로, 이 파일과 오프셋 또는 익명 메모리에 의해 백킹되는 주소 범위를 달라”고 말합니다. 리눅스는 주소를 고르고, 충돌하지 않도록 확인하고, 각 VMA가 균일함을 유지하도록 조정하고, 그 약속을 기록합니다.
작은 설명: ASLR
주소 공간 배치 난수화(ASLR)는 익스플로잇을 어렵게 하려고 매핑을 무작위 위치에 둡니다.
작은 설명: 익명 매핑 vs 파일 매핑
익명 메모리는 파일에 묶여 있지 않고 0으로 시작합니다. 파일 매핑은 파일 내용을 반영합니다.
아직 페이지를 할당하지는 않습니다. 그건 첫 터치 때 일어납니다.
자주 나오는 두 가지 함정:
offset은 페이지 정렬되어야 하며, 그렇지 않으면 mmap은 EINVAL을 반환합니다.SIGBUS가 발생합니다. VMA는 존재하지만, 데이터는 없습니다.작은 설명:
MAP_PRIVATE와MAP_SHARED
MAP_SHARED는 쓰기가 파일로 되돌아가며 이를 공유하는 다른 프로세스에서도 보입니다.MAP_PRIVATE는 파일을 보긴 하지만 쓰기는 개인적인 CoW 페이지로 갑니다.
익명 매핑은 0으로 시작합니다. 파일 매핑은 파일을 반영합니다. 파일이 페이지 중간에서 끝나면, 그 마지막 페이지의 꼬리는 0으로 읽히지만 여전히 파일에 속합니다.
MAP_FIXED는 “정확히 여기”를 의미하며, 그 주소에 이미 매핑된 것을 덮어씁니다. 덮어쓰기 대신 실패하도록 MAP_FIXED_NOREPLACE를 선호하세요. 이 둘이 없으면 당신의 addr는 단지 힌트일 뿐입니다.
작은 설명:
MAP_FIXED_NOREPLACE정확한 주소를 요구하고, 이미 무엇인가 있으면 실패하도록 합니다. 덮어쓰기보다 안전합니다.
신규 매핑에 *p = 42;를 한다고 상상해 보세요. CPU는 주소 변환을 시도합니다. 항목을 찾지 못하면 주소와 오류 코드를 포함한 페이지 폴트를 일으킵니다.
작은 설명: 페이지 폴트
페이지 폴트는 특정 주소에 대한 누락되었거나 불법인 변환을 커널이 처리해 달라고 CPU가 요청하는 것입니다.
커널의 핸들러가 당신을 대신해 실행되고, 다음 세 가지를 이 순서로 묻습니다:
그 주소가 어떤 VMA 내부인가?
아니라면, 평면도의 구멍을 찌른 것입니다 → SIGSEGV.
권한이 이 접근을 허용하는가?
읽기 전용 페이지에 쓰거나, 실행 불가 페이지에서 실행하려 하면 → SIGSEGV.
유효하지만 누락된 경우, 실제로 만들어야 하는가?
익명 매핑이라면 커널은 0으로 채워진 물리 페이지를 할당하고, 요청된 권한으로 페이지 테이블 엔트리를 연결한 뒤, 당신의 명령으로 돌아갑니다. 파일 매핑이라면 먼저 페이지 캐시를 확인합니다. 데이터가 RAM에 없으면 저장장치에서 읽고, 변환을 설치한 뒤 명령을 재시도합니다. 당신의 저장(store)이 반영됩니다. 계속 진행합니다.
작은 설명: 페이지 캐시
페이지 캐시는 RAM 내 파일 데이터에 대한 커널의 캐시입니다. 파일 매핑은 이를 통해 읽고 씁니다.
작은 설명: 제로 페이지
새 익명 메모리에 대한 일부 읽기는 공유 읽기 전용 제로 페이지로 만족될 수 있습니다. 첫 쓰기 때 개인 페이지가 생성됩니다.
사람들은 이 폴트를 셉니다:
작은 설명: 스택 가드
사용자 스택에는 가드 페이지가 있습니다. 현재 스택 바로 아래를 건드리면 스택이 커질 수 있습니다. 훨씬 아래를 건드리면 버그로 보아
SIGSEGV가 납니다.
이 같은 ‘첫 터치’의 느긋함은 fork() 이후 메모리가 어떻게 공유되는지와 MAP_PRIVATE가 어떻게 동작하는지도 설명합니다. 다음 섹션에서 그 경로를 보여줍니다.
fork()와 MAP_PRIVATE의 Copy on write왜 여기 있는가. 방금 첫 터치를 이야기했습니다. 같은 규칙이 fork에서 페이지가 복사되지 않는 이유와 MAP_PRIVATE가 파일을 바꾸지 않는 이유를 설명합니다.
fork는 페이지를 복제하지 않습니다. 자식은 부모와 동일한 물리 페이지를 가리킵니다. 커널은 그 페이지들을 둘 다에 대해 읽기 전용으로 바꿉니다. 첫 쓰기에서 CoW 폴트가 발생합니다. 커널은 새 페이지를 할당하고 바이트를 복사하고, 쓰기 주체의 페이지 테이블 엔트리를 쓰기 권한이 있는 새 페이지로 업데이트한 뒤 돌아갑니다. 읽기는 여전히 원래 페이지를 공유합니다. 그래서 fork 이후에는 당신이 쓰기 전까지 RSS가 평평하게 유지됩니다.
작은 설명: RSS
RSS(Resident Set Size)는 이 프로세스의 현재 RAM 상주 페이지 수입니다.
작은 설명: copy on write
읽기는 같은 페이지를 공유하고, 쓰기 발생 시에만 개인 사본을 만듭니다.
MAP_PRIVATE도 같은 아이디어를 씁니다. 파일 데이터는 페이지 캐시를 통해 읽습니다. 쓸 때, 커널은 개인 페이지를 제공합니다. 파일은 바뀌지 않습니다.
또 자주 마주치는 것들:
fork 후 execve. 자식은 곧 전체 주소 공간을 교체합니다. 대부분의 CoW 작업을 피합니다.vfork. 자식은 exec 또는 _exit를 호출할 때까지 부모의 주소 공간에서 실행합니다. 부모는 기다립니다. 자식에서는 메모리를 건드리지 마세요.clone의 CLONE_VM. 이것은 스레드를 만듭니다. 하나의 주소 공간. 복사 없음.MAP_SHARED. 쓰기는 공유 페이지와 파일 또는 shmem으로 간주됩니다. CoW 없음.MADV_DONTFORK. 이 매핑을 자식에 포함하지 마세요.MADV_WIPEONFORK. 자식은 이 매핑을 0으로 봅니다.왜 신경 써야 하나요. JIT와 로더는 코드 생성 후 영역을 쓰기 가능에서 실행 가능으로 뒤집습니다(W^X). 이 뒤집기는 공짜가 아닙니다.
작은 설명: W^X
Write xor Execute 정책입니다. 페이지는 동시에 쓰기 가능이면서 실행 가능일 수 없습니다.
mprotect(addr, len, prot)는 권한을 바꿉니다. 내부적으로 커널은 각 VMA가 균일함을 유지하도록 필요 시 VMA를 쪼개고, 범위의 페이지 테이블 엔트리를 수정한 뒤, 한 가지 더 필요한 일을 합니다. CPU의 작은 주소 변환 캐시인 TLB에서 오래된 변환을 무효화합니다. 이 무효화가 JIT가 RW에서 RX로(또는 반대로) 뒤집을 때 가끔 느껴지는 작은 멈칫의 원인입니다.
작은 설명: TLB
변환 조회 버퍼(TLB)는 최근 변환을 캐시하여 매 접근마다 페이지 테이블을 걷지 않도록 합니다.
대부분의 시스템은 W^X를 강제합니다. 한 페이지가 동시에 쓰기 가능이면서 실행 가능해선 안 됩니다. JIT는 코드 생성 후 뒤집거나, 같은 메모리의 두 개의 가상 매핑을 유지하여 어떤 단일 매핑도 둘 다 되지 않게 함으로써 이를 지킵니다.
권한 체크는 두 층이 있습니다:
noexec)PROT_EXEC)둘 중 어느 층이든 실행을 막을 수 있습니다.
일상적인 질문에는 친절한 뷰면 충분합니다.
/proc/<pid>/maps 윤곽: 주소, 권한, 파일 이름/proc/<pid>/smaps 및 smaps_rollup은 영역별 계정을 추가합니다. 현재 상주량(RSS), private vs shared, 대형 페이지 사용 여부(AnonHugePages, FilePmdMapped) 등페이지 단위의 진실이 필요할 때 리눅스는 더 날카로운 도구를 제공합니다.
/proc/<pid>/pagemap은 가상 페이지당 64비트 엔트리를 하나씩 가집니다. 페이지가 present인지, 스왑됐는지, soft dirty인지, 배경에 주의사항이 있지만 배타적 매핑인지, userfaultfd에 의해 쓰기 보호되었는지, 가드 영역의 일부인지 등을 알려줍니다. 또한 PFN(페이지 프레임 번호)을 드러낼 수도 있으나, 최신 커널은 일반 사용자에게 PFN을 숨깁니다. 적절한 capability나 root가 필요합니다.
작은 설명: PFN
페이지 프레임 번호는 커널 내부에서 사용하는 물리 페이지 인덱스입니다.
작은 설명: userfaultfd
특정 범위의 폴트와 쓰기 보호 이벤트를 사용자 공간 스레드가 처리할 수 있게 해주는 파일 디스크립터입니다.
/proc/kpagecount는 PFN으로 인덱싱되며, 특정 물리 페이지를 가리키는 매핑이 몇 개인지 알려줍니다.
/proc/kpageflags도 PFN으로 인덱싱되며, 익명/파일 백킹, 투명 대형 페이지의 일부인지, LRU에 있는지, 더티인지, writeback 중인지, 페이지 테이블 페이지인지, 공유 제로 페이지인지 등 그 페이지의 종류와 상태를 알려줍니다.
mincore()(상주 여부)와 lseek(..., SEEK_DATA/SEEK_HOLE)을 조합하세요.작은 설명:
mincore
mincore는 매핑의 어떤 페이지가 RAM에 있는지 알려줍니다.작은 설명:
SEEK_DATA와SEEK_HOLE희소 파일에서 다음 데이터 청크 또는 다음 구멍으로 건너뛰게 해주는 파일 오프셋 모드입니다.
작은 설명: soft dirty vs written
Soft dirty는 사용자 공간에 의해 더티된 페이지를 표시하지만, 스왑이나 VMA 병합을 거치면 잃을 수 있습니다. 최신 커널은
PAGEMAP_SCAN이라는 ioctl을 제공하여, 마지막 쓰기 보호 이후에 쓰인 페이지를 범위 내에서 스캔하고, 같은 단계에서 다시 쓰기 보호할 수 있습니다. 이는 userfaultfd 쓰기 보호와 짝을 이루어 스냅샷과 라이브 마이그레이션을 위한 빠르고 레이스 없는 사용자 공간 더티 추적을 제공합니다.
CPU는 TLB에서 더 적은 엔트리로 더 넓은 범위를 커버하길 원합니다. 리눅스는 핫 메모리를 더 큰 페이지로 백킹하여 이를 돕습니다.
작은 설명: THP
투명 대형 페이지(THP)는 안전할 때 성능을 위해 더 큰 페이지 사용을 자동으로 시도합니다.
투명 대형 페이지는 익명 메모리와 shmem 또는 tmpfs에서 자동으로 이를 수행합니다. 폴트가 512개의 작은 페이지 대신 2 MiB 페이지 하나로 만족될 수 있습니다. khugepaged라는 백그라운드 스레드는 안전할 때 인접한 기본 페이지를 대형 페이지로 접을 수도 있습니다.
작은 설명:
khugepaged조건이 맞을 때 인접한 작은 페이지를 대형 페이지로 스캔/병합하는 커널 스레드입니다.
최신 커널은 일부 아키텍처에서 mTHP라 불리는 다중 크기 THP를 추가합니다. 16 KiB나 64 KiB 같은 기본 페이지 묶음이 폴트 수와 TLB 압력을 줄여주며, 항상 2 MiB로 점프하지 않습니다. 여전히 PTE 매핑되지만, VM 내부에서는 더 큰 폴리오로 동작합니다.
작은 설명: mTHP
다중 크기 THP는 가변 차수의 큰 폴리오를 허용하여, 완전한 2 MiB 페이지 없이도 일부 이점을 제공합니다.
madvise(..., MADV_HUGEPAGE)로 특정 영역에서 THP를 요청할 수 있고, MADV_NOHUGEPAGE로 제외할 수 있습니다. 시스템 전역 동작은 /sys/kernel/mm/transparent_hugepage/ 아래에 있으며 크기별 제어가 있습니다. enabled는 always, madvise, never, inherit가 될 수 있습니다. shmem 또는 tmpfs에는 huge= 마운트 옵션(always, advise, within_size, never) 같은 고유한 노브가 있습니다.
작동 여부를 확인하는 법. /proc/self/smaps에서 해당 영역의 줄에 익명 THP는 AnonHugePages, 파일 또는 shmem 대형 매핑은 FilePmdMapped로 표시됩니다. 시스템 전역 /proc/meminfo에는 AnonHugePages, ShmemPmdMapped, ShmemHugePages가 있습니다. /proc/vmstat는 폴트 시 할당, 폴백, 분할, 통째로 스왑 등 THP 이벤트의 일지를 유지합니다.
제어 요약:
/sys/kernel/mm/transparent_hugepage/enabled → always 또는 madvise 또는 never/sys/kernel/mm/transparent_hugepage/defrag → 폴트 경로에서 얼마나 열심히 할지 vs khugepaged로 미루기huge=always|within_size|advise|never + shmem 전용 노브최신 커널은 또한 4 KiB보다 크지만 PTE 매핑인 가변 차수의 큰 폴리오를 만들 수 있습니다(완전한 2 MiB PMD는 아님). 이는 항상 2 MiB로 점프하지 않고도 폴트 수와 TLB 압력을 줄여줍니다. 동작은 커널과 아키텍처에 따라 다릅니다.
한 가지 트레이드오프. 대형 페이지를 조립하려면 연속 청크를 확보하기 위해 압축(compaction)이 필요할 수 있으며, 이는 작은 멈춤을 추가할 수 있습니다. 첫 터치 지연이 정상 상태 속도보다 더 중요하다면, 디프래그 노브로 커널의 노력 정도를 낮추어 인라인 작업보다 khugepaged로 일을 넘기도록 만들 수 있습니다.
작은 설명: THP vs hugetlbfs
THP는 자동이며 페이저블입니다.
MAP_HUGETLB또는 hugetlbfs의 명시적 대형 페이지는 할당량으로 관리되고 스왑되지 않습니다.
마지막 스냅샷 이후 애플리케이션이 수정한 페이지만 복사하고 싶다고 상상해 보세요.
PAGEMAP_SCAN을 사용하여 “마지막 쓰기 보호 이후에 쓰인” 카테고리를 스캔합니다. 커널에 일치하는 페이지를 쓰기 보호하고, 찾은 결과를 압축된 범위로 반환해 달라고 요청합니다.이는 모든 PTE를 걷는 것을 피하고, 당신이 보는 동안 페이지가 더티될 수 있는 고전적 레이스도 피합니다. 또한 스캔과 쓰기 보호가 커널 내부에서 하나의 원자적 연산으로 일어나기 때문에 빠릅니다.
작은 설명:
PAGEMAP_SCAN“마지막 보호 이후 쓰인” 같은 속성을 기준으로 가상 범위를 스캔하고, 같은 단계에서 쓰기 보호도 적용할 수 있는 ioctl입니다.
mprotect가 약간의 비용이 드는가TLB(변환 조회 버퍼)는 최근 변환을 기억하여 CPU가 매 접근마다 페이지 테이블을 걷지 않도록 합니다. 리눅스가 매핑이나 권한을 변경하면, 오래된 엔트리가 사용되지 않도록 해야 합니다.
x86에는 이를 수행하는 두 가지 큰 방법이 있습니다.
INVLPG로 한 번에 한 페이지씩 무효화합니다. 작은 변경에 좋습니다. 대형 페이지 매핑에 대한 단일 무효화는 전체 2 MiB 엔트리를 떨어뜨립니다.어느 쪽이 더 나은지는 변경 규모, 작은 페이지인지 대형 페이지인지, 그리고 마이크로아키텍처에 따라 다릅니다.
작은 설명: PCID
프로세스 컨텍스트 식별자는 TLB 엔트리에 태그를 붙여 페이지 테이블을 바꿔도 전부 플러시하지 않게 합니다.
작은 설명: INVPCID
특정 태그에 대해 스위칭 없이도 대상 TLB 엔트리를 정밀하게 무효화할 수 있게 합니다.
또한 일부 x86 빌드에는 tlb_single_page_flush_ceiling이라는 디버그 노브가 있어, 커널이 페이지 단위 무효화에서 넓은 플러시로 전환하는 시점을 조정합니다.
작은 설명:
INVLPG현재 주소 공간 태그에서 주어진 주소가 포함된 페이지의 TLB 엔트리를 무효화하는 특권 명령입니다.
2018년 초 Meltdown이 등장했습니다. 추측 실행과 캐시 부채널이 사용자와 커널 경계를 넘어 데이터를 누출할 수 있었습니다. 사용자 모드에서 커널 주소를 로드하면 정상적으로는 폴트가 나더라도, CPU는 추측 실행으로 그것을 수행하고 측정 가능한 캐시 흔적을 남길 수 있습니다.
x86‑64에서 리눅스의 방어는 PTI(Page Table Isolation)입니다. 두 개의 뷰를 유지하고, 진입/탈출 때 그 사이를 전환합니다.
작은 설명: CR3
CR3는 현재 페이지 테이블 루트를 담고, x86에서 이를 바꾸면 활성 주소 공간이 바뀝니다.
작은 설명: PTI
PTI는 일반 커널 데이터가 매핑되지 않은 축소된 사용자 공간 뷰와, 커널 내부에서 사용하는 전체 커널 뷰를 유지합니다.
비용. 더 많은 페이지 테이블 전환, 다른 TLB 공유 동작, 추가 최상위 테이블과 CPU별 엔트리 영역 때문에 작은 메모리 증가. PCID로 리눅스는 두 뷰에 대해 별도 TLB 태그를 유지해 플러시를 줄입니다. 허용 가능할 때 nopti로 옵트아웃을 허용하는 시스템도 있습니다. 기본값은 켜짐입니다.
작은 설명: Meltdown이 읽는 것
권한은 결코 꺼지지 않습니다. 아키텍처적으로는 접근이 여전히 폴트납니다. 누출은 순간적인 추측 실행에서 발생하며, 타이밍 흔적을 남깁니다.
리눅스가 페이지 테이블을 편집할 때, 순서는 의도적입니다.
후드 아래에는 변경 범위의 크기에 맞춘 함수들이 있습니다. 주소 공간 전체 플러시, 범위 플러시, 단일 페이지 플러시 등.
커널 전용 매핑(vmap, vmalloc)에도 병행되는 이야기가 있습니다. I/O 전에 커널은 vmap 범위를 플러시하여 물리 페이지가 최신 바이트를 보게 합니다. I/O 후에는 vmap 범위를 무효화하여 추측적 읽기가 오래된 것을 보지 않게 합니다.
작은 설명:
vmap과vmalloc커널 내부에서 사용하기 위해, 불연속 물리 페이지에 대한 커널 가상 매핑을 만드는 API입니다.
x86에서는 보통 명령어 캐시를 크게 신경 쓰지 않습니다. 데이터 저장과 일관(coherent)하기 때문입니다. 다른 아키텍처에서는 실행 가능한 메모리에 코드를 복사한 뒤, 실행 전에 명령어 캐시 플러시가 필요합니다. VM에는 아키텍처가 이런 집안일을 수행하는 copy_to_user_page, flush_icache_range 같은 훅이 있습니다.
작은 설명: icache 플러시
일부 CPU는 새 코드를 쓴 뒤 실행이 그 새 바이트를 보도록 명령어 캐시 동기화가 필요합니다.
64비트 모드에서 레지스터 이름은 R로 시작합니다. RIP는 명령어 포인터, RSP는 스택, RBP는 프레임입니다. 스택은 아래로 자랍니다. push는 RSP를 감소시키고 저장합니다. pop은 읽고 증가시킵니다. CALL은 복귀 주소를 푸시하고 점프합니다. RET은 그것을 RIP로 팝합니다.
리눅스의 System V AMD64 ABI는 첫 몇 개의 인자를 레지스터(RDI, RSI, RDX, RCX, R8, R9)로 전달하고, 반환 값은 RAX에 담습니다. 큰 객체는 포인터로 전달됩니다. 당신의 스택은 읽기/쓰기가 가능해야 합니다.
작은 설명: System V AMD64 ABI
x86‑64의 유닉스 계열 시스템을 위한 호출 규약으로, 인자와 반환값의 위치를 정의합니다.
유저 코드는 링 3에서 실행됩니다. 커널은 링 0에서 실행됩니다. 시스템 콜, 인터럽트, 예외 같은 경계 넘기는 CPU가 정의한 게이트를 통합니다. 64비트 모드의 리눅스는 평평한 세그멘테이션 모델을 사용하고 격리를 페이징에 의존합니다.
작은 설명: 링
링은 CPU 권한 수준입니다. 링 3는 사용자 모드, 링 0은 커널 모드입니다.
ARM64 독자를 위한 짧은 메모
스택 성장, 사용자 vs 커널 분리 같은 아이디어는 유사합니다. 레지스터 이름, 호출 규약, 시스템 콜 진입은 다릅니다.
mmap → EINVAL: 보통 페이지 정렬되지 않은 파일 offset이거나, 불가능한 플래그 조합mmap → ENOMEM: 가상 공간이나 VMA 개수가 부족하거나, 엄격한 오버커밋 정책에 걸림SIGBUS: EOF를 넘어 걸었습니다. VMA는 있었지만 데이터는 없었습니다mprotect(PROT_EXEC) → EACCES: noexec 마운트이거나 W^X 정책일 수 있음malloc이 maps에 새 줄을 만듦: 할당자가 그 크기에 mmap을 사용했기 때문fork() 후 RSS 급증: CoW가 제 역할을 했고, 많은 공유 페이지에 쓰기를 했음MAP_FIXED를 사용했을 것. 덮어쓰기 대신 실패하는 MAP_FIXED_NOREPLACE를 선호하세요수상하다 싶으면, 보세요. 큰 그림은 smaps_rollup으로 시작하고, 윤곽은 maps로 확인하세요. 정말로 페이지 단위의 진실이 필요할 때만 pagemap과 kpage* 파일로 내려가고, 권한이 필요할 것을 예상하세요.
PROT_READ|PROT_WRITE와 MAP_PRIVATE|MAP_ANONYMOUS로 익명 mmapmprotect(PROT_READ|PROT_EXEC)offset은 페이지 정렬이어야 함. 실제 EOF 너머를 건드리면 SIGBUSMADV_WILLNEED로 커널을 살짝 미리 깨우거나, 더 일찍 터치하세요. 페이지 캐시와 스토리지를 지켜보세요/proc/<pid>/smaps_rollup으로 시작, 그 다음 /proc/<pid>/mapsexec를 고려mlock. TLB 동작을 관찰하세요피드백은 언제나 환영입니다! X에서 @0xkato로 연락 주세요