buddy 分配的是物理连续页——高 order 容易碎片失败。vmalloc 反过来:虚拟连续、物理随意——用内核页表映射散落的物理页到连续 VA。
一、先看图
flowchart LR
VMALLOC[vmalloc 64KB] --> ALLOC[alloc 16 × order-0 页<br/>物理不连续]
ALLOC --> MAP[在 vmalloc 区间<br/>建页表映射<br/>VA 连续]
MAP --> VA[返回虚拟地址<br/>内核可用]
IOREMAP[ioremap 寄存器] --> MMIO[建页表映射<br/>物理=设备 BAR]
MMIO --> UNCACHE[UC/WC 缓存策略]
classDef sw fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef hw fill:#3fb95022,stroke:#3fb950,color:#adbac7;
class VMALLOC,ALLOC,MAP,VA sw
class IOREMAP,MMIO,UNCACHE hw
二、vmalloc
2.1 用法
void *p = vmalloc(size); // 可睡眠
vfree(p);
void *p = vzalloc(size); // 零填充2.2 内部
- 在 vmalloc VA 区间找一段空闲
- 为每页调
alloc_page(GFP_KERNEL)分配 order-0 物理页 - 逐页建内核页表映射
- flush TLB
2.3 vmalloc 区域
x86_64 内核地址空间里:
线性映射 (PAGE_OFFSET ~ VMALLOC_START)
VMALLOC_START ~ VMALLOC_END vmalloc / vmap / ioremap
约 32TB 的虚拟空间给 vmalloc。
2.4 性能
vmalloc 比 kmalloc 慢:
- 需要建/拆页表
- TLB flush
- 物理不连续 → DMA 不能直接用
不适合频繁分配小对象。适合:大缓冲区、BPF 程序加载、module 加载。
2.5 vmap
vmap 是底层接口——给已有的 page
数组建映射:
struct page *pages[N];
void *va = vmap(pages, N, VM_MAP, PAGE_KERNEL);
// 用完
vunmap(va);vmalloc = alloc_pages + vmap。
三、ioremap
3.1 用途
映射设备 MMIO 寄存器到内核虚拟地址:
void __iomem *base = ioremap(phys_addr, size);
u32 val = readl(base + OFFSET);
writel(0x1234, base + OFFSET);
iounmap(base);3.2 缓存策略
设备寄存器不能用 CPU 缓存——必须 UC(Uncacheable):
ioremap(addr, size); // 默认 UC-
ioremap_wc(addr, size); // Write-Combining(帧缓冲等)
ioremap_cache(addr, size); // WB(少见,只特定 RAM 区域)x86 用 PAT(Page Attribute Table)控制每页的缓存策略:PCD/PWT/PAT bit 组合。
3.3 devm_ioremap
设备模型自动管理:驱动 remove 时自动 iounmap:
base = devm_ioremap_resource(dev, res);四、kmap 与 HIGHMEM
4.1 32 位的问题
32 位内核只有 ~1GB 内核空间。物理内存 >1GB 的部分在 ZONE_HIGHMEM,不能直接线性映射。
kmap 把 HIGHMEM 页临时映射到一小块窗口:
void *va = kmap(page); // 可能睡眠
kunmap(page);
void *va = kmap_atomic(page); // 不可睡眠
kunmap_atomic(va);4.2 64 位 = HIGHMEM 退场
64 位内核空间足够直接线性映射所有 RAM → ZONE_HIGHMEM
不存在 → kmap 退化为
page_address(直接取线性映射地址)。
4.3 kmap_local(替代 kmap_atomic)
5.11+ kmap_local_page 替代
kmap_atomic——不禁止抢占,更灵活。
void *va = kmap_local_page(page);
// ... 可以 schedule
kunmap_local(va);五、percpu 的 vmalloc 路径
per-CPU 变量的动态部分(pcpu_alloc)底层用
vmalloc 区域映射——每 CPU 一组页,VA 按 CPU 偏移排布。
六、BPF 与 vmalloc
BPF 程序加载后存在 vmalloc 区域——因为 JIT
代码需要可执行页,大小不定。6.x 有
bpf_prog_pack 优化:把多个小 BPF 程序打包到一个
2M huge page。
七、常见问题
A:vmalloc 空间耗尽
/proc/meminfo 的 VmallocUsed。极端情况(大量
BPF / iptables 规则)可能接近上限。
B:ioremap 返回 NULL 物理地址冲突或 PAT
策略不兼容。检查 /proc/iomem。
C:kmap_atomic 中 schedule 旧代码
bug——会 corrupt 映射窗口。改用
kmap_local_page。
八、观察
cat /proc/meminfo | grep Vmalloc
# VmallocTotal: 34359738367 kB
# VmallocUsed: 123456 kB
cat /proc/vmallocinfo | head
# 0xffff... size=16384 caller=bpf_prog_alloc
cat /proc/iomem # 物理地址布局九、小结
- vmalloc:虚拟连续 + 物理散落,适合大缓冲、BPF、module
- ioremap:设备 MMIO 映射,UC/WC 缓存策略
- kmap 是 HIGHMEM 遗留,64 位下退化
- kmap_local 替代 kmap_atomic,允许 schedule
参考文献
mm/vmalloc.cDocumentation/mm/highmem.rstarch/x86/mm/ioremap.c- Ingo Molnar, “kmap_local_page.” 2020
Documentation/core-api/cachetlb.rst
工具
/proc/meminfo(Vmalloc* 行)/proc/vmallocinfo/proc/iomemperf kmem+ vmalloc 事件
上一篇:Slab/SLUB 分配器 下一篇:per-CPU 变量
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内核内存调试
内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。
【操作系统百科】VFS 四层抽象
Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。
操作系统百科
Linux 6.x 视角下的操作系统系列索引:110 篇覆盖调度、虚拟内存、文件系统与 I/O、并发、隔离、可观测性,按主题、阅读路径与关键问题三种入口组织。
【操作系统百科】用户态分配器:jemalloc vs tcmalloc
jemalloc 与 tcmalloc 都想解决多线程分配器的老问题:锁争抢、碎片、RSS 膨胀与回收抖动。但两者把优化重点放在了不同位置:tcmalloc 更激进地把热路径推到 per-CPU,jemalloc 则把 arena、extent、decay 和 profiling 做成了一套更完整的内存治理工具箱。