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 变量
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。