1 kmallocvmalloc:区别、为何并存与使用场景

本文面向 Linux 内核 内存分配接口,说明 kmallocvmalloc物理连续性、地址空间位置、大小上限、性能与 DMA 语义 上的差异,并解释 为何需要两套机制 以及各自典型用法。具体常量与实现细节随内核版本略有变化,以你目标树中的 include/linux/slab.hinclude/linux/vmalloc.hmm/slab.c / mm/vmalloc.c 为准。


1.1 一句话对比

维度kmallocvmalloc
物理内存通常保证 物理连续(在可分配范围内)不要求物理连续;由离散物理页拼成
虚拟地址落在 直接映射(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_coherentdma_map_page / dma_map_single 等;让内核与 IOMMU 建立正确映射。
  • kmalloc 当「一定物理连续」也要加条件:极大分配可能走不同路径;以 virt_to_phys 文档与架构说明 为准。

1.6 选型决策树(实用)

  • 小块、频繁、可能在中断/原子上下文(在允许的前提下)→ 优先 kmalloc + 合适 GFP_*
  • 大块、只要虚拟连续、不用于「裸物理连续 DMA」vmalloc
  • 设备 DMA bufferDMA 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.cvmallocvmapvmalloc_user 等。
  • Documentation/core-api/memory-allocation.rst(名称随版本调整):官方叙述。

教学向总结;安全关键与驱动开发请以当前内核 DMA 映射 API 与架构手册为最终依据。