1. 操作系统层面:mmap 与 malloc 的本质区别
| 对比点 | mmap (我们用) | malloc/new (C 库堆) |
|---|---|---|
| 调用者 | 你自己直接发系统调用 | 先进入 glibc 分配器,再由它决定是否 brk / mmap |
| 返回地址 | 必定页对齐 (4 KB/2 MB…) | 只保证满足所请求的对齐(8 byte、16 byte…) |
| 粒度 | 任意「页数 × 4 KB」 | C 库按内部 chunk 机制切分,一般 < 1 MB |
| 虚拟内存类型 | 匿名私有映射 (MAP_PRIVATE | MAP_ANONYMOUS) |
| 真实物理占用 | 按需分配 (Demand Paging)——首次写入触发缺页才分物理页 | 同 |
| 释放方式 | munmap(addr, size):直接返回内核,可立即被内核回收 | free(p):只是回到 glibc arena;是否归还内核取决于 glibc 策略 |
2. mmap 对内存池的“硬优点”
2.1 天生页对齐 —— 最适合 Span / Buddy 切分
-
我们的 PageCache 以「页」为最小单位管理连续段 (
Span)。 -
mmap天然返回“页对齐”的首地址,避免自己再做对齐修正。
2.2 批量申请,减少系统调用
-
一次
mmap(32 KB),后面可以从这 32 KB 切上千个 32 B 小块。 -
如果用
malloc,C 库也会批量拿,但我们看不见内部策略,不可控。
2.3 释放完全可控
-
当 PageCache 判断某些空闲 span 长时间未被使用,就可以
munmap立刻归还,降低 RSS。 -
free()回 glibc 后是否munmap,要等 glibc threshold;你的代码影响不了。
2.4 零初始化 & 安全
-
Linux 匿名页先映射到 “零页” 的 Copy‑on‑Write,第一次写入才分物理页 → 不浪费实际物理内存。
-
你再
memset(0) 保证跨平台一致;也能消除潜在的信息泄漏。
2.5 规避 glibc arena 锁
-
多线程下,glibc
malloc要竞争 main arena / tcache。 -
我们直接
mmap避开了这些锁,对大页分配速度始终如一。
3. 联系到你的项目:三层缓存的使命分工
PageCache (mmap / munmap) ← 页级、稀疏但重量级
↑
CentralCache (spin‑lock) ← 多线程共享、批量切块
↑
ThreadCache (thread_local)← 线程私有、极致快速
-
PageCache:做“大块”(页)申请,一次
mmap,然后自己切分,不让 C 库插手。 -
CentralCache / ThreadCache:在自己层次做链表管理、小块切割,不需要再次系统调用。
-
若最底层用
malloc,就相当于又交给 glibc 管理一个“堆中的堆”,会出现双重碎片化、锁竞争以及失去页级可控释放能力。
4. 细到字节:mmap 调用流程
-
内核 VMA 创建
-
mmap通过do_mmap在进程页表插入一个 VMA(虚拟内存区域),标记VM_ANON | VM_PRIVATE。
-
-
页表条目是 空映射
-
此时并未分配物理页,也不会占用 RSS,只是一段虚地址范围。
-
-
首次访问触发缺页异常
-
内核分配真正物理页 →
ZeroPage→ 拷贝零 → Map。
-
-
memset(0) 实质是触发全部页缺页-
你显式清零,会立刻 fault‑in 每页,确保随后的使用不再缺页。
-
-
munmap-
删除 VMA,回收对应物理页,RSS 立即下降(可在
top或/proc/PID/smaps看到)。
-
5. 面试/答辩时的“金句”总结
“PageCache 直接
mmap匿名私有页,一次拿整块页对齐内存,避免进入 glibc 堆管理队列;这给了我们对页级切分、合并与回收的 100% 主动权。”“
munmap让内存池可以把闲置 span 即时退回内核,避免双重碎片,并降低进程常驻集大小。”“对多线程来说,我们绕过 glibc arena 锁,保证大块分配 O(1) sys‑call 成本,线程伸缩稳定。”

1939

被折叠的 条评论
为什么被折叠?



