本页目录
物理内存管理
覆盖: NUMA → Zone (DMA/DMA32/Normal/Movable) → Buddy Allocator → page allocator API → page struct → memory hotplug 内核版本: 2.6 ~ 6.x
概述
虚拟内存管理回答了"哪个虚拟地址映射到哪个物理页",而物理内存管理回答的是更底层的问题:"这 4KB 物理页属于谁,空闲还是已分配,应该从哪个 NUMA 节点拿?"
物理内存管理的核心是 buddy allocator(伙伴分配器),它将空闲页面组织为不同阶(order)的连续块,通过分裂和合并高效分配和回收。buddy 之上是 slab/slub(小对象分配器),之下是 NUMA 感知的 zone 和 node 拓扑。
NUMA 架构
UMA (Uniform Memory Access):
所有 CPU 访问所有内存的延迟一致
典型: 单 socket, 台式机
NUMA (Non-Uniform Memory Access):
内存被分成多个 node
每个 CPU 有自己的 local memory (访问快)
访问 remote memory (慢 30%~40%)
典型: 多 socket 服务器
查看: numactl --hardware
ls /sys/devices/system/node/
pg_data_t: NUMA 节点描述符
// include/linux/mmzone.h
typedef struct pglist_data pg_data_t;
Zone: 物理内存分区
为什么需要 Zone
Zone 存在的根本原因: 硬件限制导致某些物理内存不能用于特定目的
16-bit ISA DMA: 只能访问物理地址 0~16MB
32-bit PCI DMA: 只能访问 0~4GB (除非 IOMMU)
32-bit 内核: 只能直接映射 896MB (highmem)
64-bit: 没有 highmem,但 zone 结构保留了 DMA/DMA32 限制
Zone 类型
// include/linux/mmzone.h
;
struct zone
// include/linux/mmzone.h
;
Watermarks 与内存压力
// 分配时的 watermark 检查:
// zone_watermark_ok(zone, order, mark, classzone_idx, alloc_flags)
// 计算 zone 的空闲页是否满足 order 分配且不低于 mark
// 如果不够 → 触发回收 (kswapd 或 direct reclaim)
Buddy Allocator
数据结构
// mm/page_alloc.c
// 每个 zone 的每个 order 有一个 free_area
// free_area[order]: 大小为 2^order 页的空闲块链表
;
// 迁移类型 (反碎片):
;
核心算法
// 分配:
// alloc_pages(gfp_mask, order) → __alloc_pages()
// → get_page_from_freelist() // 尝试从合适的 zone 分配
// → rmqueue() // 从 buddy free_area 取
// → __rmqueue_smallest() // 找 >= order 的最小空闲块
// → expand() // 如果找到的比需要的大 → 分裂
// 下半部分放回 buddy free_area
// 举例: 请求 order=1 (2 页 = 8KB)
// free_area[2] 有 16KB 空闲 → expand():
// → 分配 order 0~1 前 8KB 给调用者
// → 剩余 8KB 放入 free_area[1]
// 释放:
// free_pages(addr, order) → __free_pages()
// → __free_one_page()
// → 检查 buddy (伙伴) 是否也空闲
// → 如果 buddy 空闲 → 合并为 order+1 的块 → 递归合并
// → 如果 buddy 不空闲 → 放入对应 order 的 free_area
// Buddy 检查:
// buddy_pfn = page_pfn ^ (1 << order) // XOR: buddy 的 PFN
// 如果 buddy 也是空闲的且同 order → 合并
GFP Flags
// include/linux/gfp_types.h
// 分配行为的控制:
// Zone modifiers:
// Watermark modifiers:
// Reclaim modifiers:
// 常用组合:
// 最常用: 允许睡眠/回收/IO
// 不可睡眠: 用于中断/atomic 上下文
// 用户空间分配
// 不等待: 分配失败立即返回 NULL
Per-CPU Pages (PCP)
// mm/page_alloc.c
// 每个 CPU 有 per-CPU 的 1-page 缓存 (struct per_cpu_pages)
//
// 为什么?
// 分配单个页面 (order 0) 是最常见的操作
// 如果每次都要拿 zone->lock (全局锁) → 锁竞争严重
// PCP 作为 L1 cache: 本地预取一 batch 的页面
// → alloc: PCP 有 → 直接拿 (无锁)
// PCP 空 → 从 buddy 拿一 batch (拿一次 zone lock)
// → free: PCP 未满 → 放回 PCP (无锁)
// PCP 满 → 放回 buddy (拿一次 zone lock)
//
// 设计原理: 批量操作分摊锁开销
struct page
// include/linux/mm_types.h
// 每个物理页都有一个 struct page (4KB page 管理 64 bytes → ~1.5% 开销)
;
struct page 是一个超载的结构体——根据页面用途(buddy 空闲、slab、匿名内存、page cache、THP compound),同一个字段有不同的解释。内核使用 flags 中的 bit 来区分。
Memory Hotplug
// mm/memory_hotplug.c
// 物理添加/移除内存条:
// online: add_memory() → __add_memory() → 初始化 page struct → 加入 buddy
// offline: remove_memory() → __remove_memory() → 迁移已用页面 → 退出 buddy
// DIMM (NVDIMM/CXL):
// 可以做成 ZONE_DEVICE (不被 buddy 管理)
// 或者做成普通内存 (ZONE_NORMAL),由 buddy 分配
调试
# Buddy allocator 状态
# Node 0, zone Normal: 11 4 2 1 0 0 ...
# order 0: 11 blocks (44KB free as 4KB pages)
# order 1: 4 blocks (32KB free as 8KB chunks)
# Zone 信息
# 含: per-zone watermarks, free pages, protection
# NUMA 统计
# 内存碎片指数
# 1.000 = 完全碎片化,无法分配连续页面
# 0.000 = 无碎片
# 触发的 reclaim 统计
|
参考与延伸
- 内核文档:
Documentation/mm/page_alloc.rst,Documentation/admin-guide/sysctl/vm.rst - LWN:
- "The design of the buddy allocator" (lwn.net/Articles/259832/)
- "Memory management APIs" 系列
- 源码文件:
mm/page_alloc.c— buddy allocator (~7000行)include/linux/mmzone.h— zone, pg_data_t, free_areainclude/linux/mm_types.h— struct pageinclude/linux/gfp_types.h— GFP flags
关键词: NUMA, Zone, Buddy Allocator, GFP_KERNEL, GFP_ATOMIC, struct page, watermark, kswapd, memory hotplug, compaction