A12 — The Complete Journey of the Memory System: From Physical to Virtual to Application
Key Insight: The only way to understand memory is to trace the complete journey of data from hardware to application.
Five-Layer Panorama of the Memory System
内存系统的分层结构:
[应用层] malloc / new / mmap
│
▼
[用户态分配器] ptmalloc / tcmalloc / jemalloc
│
▼
[内核页表] VMA / COW / page fault
│
▼
[内核物理管理] Buddy / SLAB / SLUB / Page Cache
│
▼
[硬件层] DRAM / Cache / MMU / CPU每层的作用:
硬件层(S/H):
- DRAM:实际的存储介质
- Cache(L1/L2/L3):减少 DRAM 访问延迟
- MMU:VA → PA 翻译
- 地址总线:CPU 到 DRAM 的通信
内核物理管理(K):
- Buddy System:分配 4KB+ 的物理页
- SLAB/SLUB:把页切成固定大小的对象
- Page Cache:缓存文件数据
- kswapd:回收页面(LRU)
内核页表(B):
- VMA:进程虚拟地址空间的管理
- COW:fork 时的延迟复制
- page fault:按需分配物理页
- mmap:建立文件或匿名映射
用户态分配器(A):
- ptmalloc:glibc 的 malloc(arena + bin)
- tcmalloc:Google 的高效分配器
- jemalloc:Facebook 的高效分配器
应用层:
- malloc / new:申请内存
- free / delete:释放内存
- mmap:映射文件或分配大块The Complete Journey of a Single Memory Allocation
malloc(256) 的完整路径:
1. ptmalloc 接收请求
- 大小 256 → 找对应 size class(可能是 256)
- 检查 thread cache(tcache)
- tcache 有 → 直接返回(无锁,O(1))
2. tcache 空 → 从 arena 的 bin 分配
- 检查 smallbin 或 largebin
- bin 有 → 取出 chunk(需要锁)
- bin 空 → 从 top chunk 取
3. top chunk 不够 → 调用 brk()
- brk() 系统调用
- 内核分配新的虚拟地址空间(vma)
- 物理页在第一次访问时分配(demand paging)
- 更新 top chunk
4. 物理页分配(page fault):
- MMU 发现 PTE 无效
- OS 分配物理页(alloc_pages → buddy)
- 建立页表映射
- 填充页面(清零或读文件)
- 返回,继续执行
5. 返回虚拟地址给用户程序
总耗时:
- tcache 命中:~10 ns(用户态)
- bin 分配:~50 ns(含锁)
- brk:~500 ns(含系统调用)
- page fault:~10000 ns(首次访问,含物理分配)What Happens After Data Is Written
memcpy(buf, data, 256) 的过程:
1. 用户态 memcpy
- 把 data 复制到 buf(CPU copy)
2. 如果 buf 之前没访问过(第一次 touch):
- page fault
- OS 分配物理页(alloc_pages → buddy)
- 建立页表映射
3. 如果多个进程映射同一文件(MAP_SHARED):
- 修改直接反映到 Page Cache
- 其他进程立即看到
4. 后续访问:
- MMU 查页表 → PA
- L1 Cache hit → ~1 ns
- L1 Cache miss → L2 hit → ~10 ns
- L2 miss → L3 hit → ~30 ns
- L3 miss → DRAM → ~60 nsMemory Copy During fork
fork() 的内存复制路径:
1. copy_page_range() 被调用
- 遍历父进程的每个 VMA
- 复制页表(PTE)
- 共享物理页面(引用计数 +1)
- 设置只读位
2. COW 设置:
- 父子共享同一物理页
- 页面标记为只读
- 写入触发 page fault
3. 写入时的 COW 处理:
- Page fault → 检查 PTE
- 发现只读 + 共享 → 分配新页
- 复制旧页内容
- 更新 PTE 为新页 + 可写
- 继续执行写入Complete Memory Leak Detection Chain
检测内存泄漏的工具对应关系:
1. /proc/status(运行时监控):
- RSS 持续增长 → 可能有泄漏
- VmPeak 持续增长 → 确认泄漏
2. mtrace(glibc 内置):
- 记录所有 malloc/free
- 找出未配对的分配
3. valgrind(全面检测):
- 跟踪所有 malloc/free/realloc
- 报告泄漏的堆块
- 报告 use-after-free
4. ASan(运行时检测):
- 编译时插桩
- 检测越界访问
- 检测 UAF
- 比 valgrind 快 2~5x
5. massif(堆分析):
- 跟踪堆的使用
- 找出峰值和趋势
- 火焰图可视化Memory Allocation in NUMA Systems
NUMA 系统中 malloc 的路径:
1. glibc 分配请求
- 线程缓存 → arena
2. arena 分配物理页:
- first touch:在线程当前运行的节点分配
- mbind:可以绑定到特定节点
3. alloc_pages_node(node, ...):
- 在指定节点从 buddy 分配
- 如果节点内存不足,回退到其他节点
4. 页面迁移(numa_balancing):
- 如果页面和访问线程不在同一节点
- kernel 自动迁移页面
- 减少 NUMA 延迟
numa 感知的分配:
#include <numa.h>
numa_bind(&numa_all_nodes);
// 后续分配在绑定的节点Page Cache and File I/O
文件读取的 Page Cache 路径:
1. read(fd, buf, 4096)
- 查找 Page Cache(radix tree)
2. Cache miss:
- 从 buddy 分配物理页
- 从磁盘读取到 Page Cache
- 复制到用户 Buffer
3. Cache hit:
- 直接从 Page Cache 复制
- 无磁盘 I/O
文件写入的 Page Cache 路径:
1. write(fd, buf, 4096)
- 写入 Page Cache(标记为 dirty)
- 立即返回(写回异步)
2. 定期 flush(pdflush):
- 把 dirty 页面写回磁盘
- 更新 Page Cache 状态Summary
- Five-Layer Structure: Hardware (S/H) → Kernel Physical (B/K) → Kernel Page Table (B) → Userspace Allocator (A) → Application
- malloc Path: tcache → bin → top chunk → brk → page fault → buddy
- Data Write: User memcpy → COW → PTE update → Cache hit or DRAM access
- NUMA: first touch → node assignment → page migration (numa_balancing)
- Page Cache: radix tree indexing → LRU management → periodic flushing
- Leak Detection Chain: /proc/status → mtrace → valgrind → ASan → massif
Memory Series Summary: From S-layer DRAM Cell and DDR5, to H-layer DIMM and dual channel, to B-layer boot/mmu/buddy, to K-layer SLAB/kswapd/PageCache, to A-layer malloc/brk and leak detection. 55 articles cover the complete memory lifecycle.
Comments