1 kmalloc 与 vmalloc:区别、为何并存与使用场景
本文面向 Linux 内核 内存分配接口,说明 kmalloc 与 vmalloc 在 物理连续性、地址空间位置、大小上限、性能与 DMA 语义 上的差异,并解释 为何需要两套机制 以及各自典型用法。具体常量与实现细节随内核版本略有变化,以你目标树中的 include/linux/slab.h、include/linux/vmalloc.h 与 mm/slab.c / mm/vmalloc.c 为准。
1.1 一句话对比
| 维度 | kmalloc | vmalloc |
|---|---|---|
| 物理内存 | 通常保证 物理连续(在可分配范围内) | 不要求物理连续;由离散物理页拼成 |
| 虚拟地址 | 落在 直接映射(linear map) 区域(典型) | 落在 vmalloc 专用虚拟区间 |
| 典型大小 | 小到中等;受 KMALLOC_MAX_SIZE 一类上限约束 | 可分配 更大 的连续虚拟区间 |
virt_to_phys | 对 直接映射且合法 的 kmalloc 地址常可用 | 不可对 vmalloc 地址直接 virt_to_phys |
| 释放 | kfree(ptr) | vfree(ptr) |
| 典型开销 | 热路径友好(slab 快速路径) | 建立/拆除页表映射,TLB 压力相对更高 |
1.2 kmalloc 是什么、解决什么问题
1.2.1 机制(心智模型)
kmalloc(size, gfp)基于 slab/slob/slub 等分配器,从 物理页 上切出对象;小对象走 per-cpu cache / slab 快速路径。- 在常见架构上,
kmalloc返回的地址位于内核 直接映射区:内核虚拟地址与物理地址之间是 近似线性偏移 的关系(用于简单virt_to_phys的前提之一)。
1.2.2 为何需要它
- 内核大量数据结构 小而多、分配释放频繁,需要 低延迟、低碎片 的路径。
- DMA 一致性缓冲区(
dma_alloc_coherent等)在底层常与 物理连续 页绑定;许多设备驱动的小 buffer、描述符环也依赖 可预测的物理布局(具体 API 仍优先用 DMA 帮助函数,而不是裸virt_to_phys)。
1.2.3 典型使用场景
- 驱动与核心子系统:
task_struct附属结构、小 buffer、网络sk_buff相关辅助结构(具体内核版本路径各异)、小型 lookup 表。 - 需要物理连续、或后续要交给 硬件/DMA API 且文档要求连续物理页的场景(仍以
dma_map_*/dma_alloc_coherent为准)。
1.2.4 限制与注意
- 大小上限:极大
kmalloc会失败;超大应改用vmalloc或 多页__get_free_pages等路径。 GFP_标志:GFP_KERNEL可睡眠;中断上下文须GFP_ATOMIC等;错误使用会导致 睡眠点在中断里 的严重 bug。krealloc:扩展时可能搬迁对象,注意 并发与指针失效。
1.3 vmalloc 是什么、解决什么问题
1.3.1 机制(心智模型)
vmalloc(size)在内核虚拟地址空间申请一段 连续虚拟 区间,底层向伙伴系统逐页申请 物理页,这些页 不必相邻;内核通过 页表 把它们映射到连续虚拟区间。- 释放
vfree会拆除映射并归还物理页。
1.3.2 为何需要它(与 kmalloc 并存的根本原因)
- 物理连续内存是稀缺资源:长时间运行后,伙伴系统可能出现 足够总空闲页、但无法满足大块物理连续 的情况。
- 许多内核子系统只需要 虚拟上连续 的大数组或大 buffer(例如 大表、临时映射、模块加载镜像、BPF JIT 区域 等,随版本不同),不需要整段物理连续;用
vmalloc可避免为「物理连续」付出过高碎片代价。 - 把大块需求从「物理连续池」挪到「可拼接的离散页 + 页表」,提高 可分配性。
1.3.3 典型使用场景
- 较大的内核缓冲区、模块空间、某些子系统的大数组。
- 需要
vmap把分散struct page *映射成连续虚拟地址 的场景(与vmalloc同属 vmalloc 区域 管理思想)。
1.3.4 限制与注意
- 不能假设
virt_to_phys(vmalloc_addr)有意义;需要vmalloc_to_page/vmalloc_to_pfn等按页处理,或走 DMA API。 - 性能:建立映射与访问时 TLB miss 可能更多;不适合 极高频极小分配。
- 对齐:
vmalloc保证页对齐级别的语义;细粒度对齐需求读文档。
1.4 为什么要有两种(设计层面的回答)
kmalloc优化的是「小对象、快路径、尽量物理友好」——服务内核热路径与 DMA 友好分配。vmalloc优化的是「大块、虚拟连续即可、接受页表成本」——在物理碎片存在时仍能拿到大段可用虚拟线性空间。
若只有 kmalloc:大块物理连续需求会 加速碎片枯竭;若只有 vmalloc:小对象走页表拼接会 极慢且浪费。因此二者 分工 而非重复。
1.5 DMA 与调试时的硬规则
- 永远不要对
vmalloc返回的指针 使用virt_to_phys去喂设备寄存器。 - DMA:统一使用
dma_alloc_coherent、dma_map_page/dma_map_single等;让内核与 IOMMU 建立正确映射。 - 把
kmalloc当「一定物理连续」也要加条件:极大分配可能走不同路径;以virt_to_phys文档与架构说明 为准。
1.6 选型决策树(实用)
- 小块、频繁、可能在中断/原子上下文(在允许的前提下)→ 优先
kmalloc+ 合适GFP_*。 - 大块、只要虚拟连续、不用于「裸物理连续 DMA」 →
vmalloc。 - 设备 DMA buffer → DMA API;不要手写「
kmalloc+virt_to_phys」替代dma_map_*(IOMMU 下尤其错误)。 - 需要 几乎任意物理页 映射到连续内核虚拟地址 →
vmap(与vmalloc同族问题)。
1.7 与 kvmalloc(若你的内核提供)
部分内核提供 kvmalloc:小尺寸走 kmalloc 路径,大尺寸自动回落 vmalloc;释放用 kvfree。适合「大小跨度大、希望自动折中」的代码路径,但仍要理解 底层语义差异(尤其 DMA 与 virt_to_phys)。
1.8 延伸阅读(源码入口)
mm/slab.h/slab.c(或 slub):kmalloc实现。mm/vmalloc.c:vmalloc、vmap、vmalloc_user等。- Documentation/core-api/memory-allocation.rst(名称随版本调整):官方叙述。
教学向总结;安全关键与驱动开发请以当前内核 DMA 映射 API 与架构手册为最终依据。