K06 —— mmap 申请内存的全流程:demand paging 和物理页分配
金句:你申请 1GB 内存,系统不会真的先给你 1GB——而是在你真正使用时才分配。
mmap 的延迟分配(Lazy Allocation)
mmap 只创建虚拟地址映射,不立即分配物理页。物理页在实际访问时按需分配(Demand Paging)。
mmap 的工作方式:
mmap(NULL, 1GB, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
→ 创建 VMA:vm_start ~ vm_end = 1GB
→ VMA 标记为匿名私有映射
→ 不分配任何物理页
→ 返回起始地址(程序以为有了 1GB)
第一次访问(假设读 0x1000 字节):
→ 访问的页面不在内存(Page Fault)
→ 内核分配物理页
→ 填充全零
→ 建立页表映射
→ 继续执行
结果:
- 如果程序只访问 4KB,实际只用了 4KB 物理页
- 如果程序访问 1GB,全部 1GB 物理页才会被分配Page Fault 的处理流程
当访问 mmap 区域时发生 Page Fault:
do_page_fault() → __do_page_fault():
Step 1:查找 VMA
vma = find_vma(mm, addr);
if (!vma || addr < vma->vm_start)
→ 非法地址,发送 SIGSEGV
Step 2:检查访问权限
if (vma->vm_flags & VM_WRITE && !pte_writable)
→ COW 或写保护错误
Step 3:分配物理页(handle_pte_fault)
if (!pte_present(pte)) {
// 页不在内存,按类型分配
if (vma->vm_file) {
// 文件映射
do_file_fault();
// 从磁盘读取文件内容到页面
} else {
// 匿名映射(MAP_ANON)
do_anonymous_page();
// 分配全零页面
}
}do_anonymous_page() 的详细流程:
1. 从 buddy system 分配物理页
page = alloc_page(GFP_KERNEL | __GFP_ZERO);
// __GFP_ZERO:页面清零
2. 设置 PTE
set_pte(ptel, mk_pte(page, vma->vm_page_prot));
// 设置读写权限
3. 更新 LRU
lru_cache_add(page);
// 把页面加入 LRU(可被回收)
4. 刷新 TLB
flush_tlb_page(vma, addr);
5. 返回,继续执行指令MAP_ANONYMOUS vs MAP_FILE
匿名映射(MAP_ANON)的处理:
mmap(NULL, size, PROT, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
Page Fault:
→ do_anonymous_page()
→ 分配全零页面
→ PTE 指向该页面
特点:
- 内容是全零(不来自文件)
- 页面可以独立存在(不在 page cache 中)
- fork 时走 COW(共享但只读)
---
文件映射(MAP_FILE)的处理:
mmap(NULL, size, PROT, MAP_PRIVATE, fd, 0);
Page Fault:
→ do_file_fault()
→ 从 inode 读取文件内容
→ 分配页面,从磁盘读取数据
→ PTE 指向该页面(file-backed)
特点:
- 内容来自磁盘文件
- 页面在 inode 的 address_space 中(radix tree)
- 写时 COW(私有映射)/proc/meminfo 中的 Page Cache 统计
$ cat /proc/meminfo
MemTotal: 32768176 kB // 总物理内存
MemFree: 12345678 kB // 完全空闲
MemAvailable: 28901234 kB // 空闲 + 可回收(可用的)
Buffers: 123456 kB // 块设备缓冲(Page Cache)
Cached: 5678901 kB // Page Cache + SwapCache
SwapCached: 234567 kB // 已读入内存的 swap 页(不需要再读)
具体含义:
Buffers:原始块设备的缓存(metadata)
Cached:文件系统 Page Cache(文件数据)
SwapCached:已换入内存的 swap 页面(在 LRU 上,不需要重新读 swap)
Active(page)/Inactive(page):File-backed 页
Active(anon)/Inactive(anon):Anonymous 页
$ cat /proc/meminfo | grep -E "^(Anon|File|Cached|Swap):"
AnonPages: 12345678 kB // 匿名页(堆/栈)
Mapped: 1234567 kB // 文件映射的页面(mmap 的文件)
Shmem: 234567 kB // 共享内存(tmpfs)总结
- mmap 延迟分配:mmap 只创建 VMA,不分配物理页,物理页在 page fault 时按需分配
- do_page_fault:查找 VMA → 检查权限 → 按类型分配(anonymous 或 file)
- do_anonymous_page:从 buddy 分配全零页 → 设置 PTE → 加入 LRU → flush TLB
- MAP_ANON:全零页面,不需要磁盘 I/O
- MAP_FILE:从磁盘读取文件内容到页面
- /proc/meminfo:Active/Inactive 分 anonymous 和 file-backed,分别统计
下篇预告(K07):当用户调用 malloc 时,内核是怎么参与分配的?brk、mmap、以及 glibc 的 ptmalloc 分配器——为什么频繁 malloc/free 会变慢。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论