Linux 内核 vmalloc 虚拟内存分配器深度工程实战:从 VMA 分配到页表填充的完整链路
引言
内核中并非所有内存分配都需要物理连续。当模块代码需要加载、I/O 缓冲区需要大块非连续映射、或者 per-CPU 动态区域需要创建时,vmalloc 系列函数便成为不可或缺的工具。
与 kmalloc(基于 SLUB 分配器,保证物理连续)不同,vmalloc 提供的是物理不一定连续但虚拟连续的内存区域。这种设计带来了灵活性,也引入了 TLB 压力大、页表映射开销高、以及地址转换延迟增加等代价。
本文将从工程实战角度深入剖析 vmalloc 的完整工作机制:从 vfree_area 红黑树与伙伴系统的协同分配,到多级页表填充与 TLB shootdown 协议;从 __vmalloc_node_range 的慢速分配路径,到 vmalloc_huge/vmalloc_nohuge 的优化策略;再到 VM_IOREMAP、VM_MAP、VM_USERMAP 四种 vmap 场景的区分与内部标记、set_memory_* 权限改写的底层机制、实际测量 TLB miss 开销的方法与调优建议。
一、vmalloc 的地址空间组织
1.1 vmalloc 区在虚拟地址空间中的位置
在 x86_64 架构中,vmalloc 区位于内核虚拟地址空间的特定范围:
用户空间 0x0000 0000 0000 - 0x0000 7FFF FFFF FFFF (128TB)--- PAGE_OFFSET ------------------------------------------------直接映射区(direct mapping) 0xFFFF 8000 0000 - start_kernel (所有物理内存一一映射)--- VMALLOC_START (典型: 0xFFFF C900 0000) --------------------vmalloc 区 ~128TB - 16TB (具体取决于 CONFIG)--- VMALLOC_END (典型: 0xFFFF E8FF FFFF FFFF) ---模块映射区 (modules) ~640MB--- VMEMMAP_START -----------------------------------struct page 映射区 (vmemmap)--- PKMAP_BASE / FIXADDR_START ---------------------固定映射区--- VSYSCALL / vDSO area ---------------------------vmalloc 区的起始地址 `VMALLOC_START` 随内核配置和处理器架构变化,在 x86_64 上通常为 `0xFFFFC90000000000`。整个 vmalloc 区的总大小由 `CONFIG_KERNEL` 和 LKMM 决定,典型值在 128TB 到 32TB 之间。
// arch/x86/include/asm/pgtable_64.h#define VMALLOC_START (unsigned long)0xFFFFC90000000000UL#define VMALLOC_END (unsigned long)0xFFFFE8FFFFFFFFFFUL#define VMALLOC_SIZE (VMALLOC_END - VMALLOC_START 1) // ~128TB 实际受限于 vmap 空间1.2 vm_struct: vmalloc 区域的内核管理元数据
每个 vmalloc 分配的内核区域都由 vm_struct 结构体描述:
struct vm_struct { struct vm_struct *next; // 全局 vmap_area_list 链表 void *addr; // 虚拟起始地址 unsigned long size; // 区域总大小 (含 guard page) unsigned long flags; // VM_ALLOC/VM_IOREMAP/VM_MAP 等 struct page **pages; // 指向物理页数组 unsigned int nr_pages; // 页数 phys_addr_t phys_addr; // 物理连续时记录首地址 const void *caller; // 分配者 (__builtin_return_address)};关键字段 pages[] 数组保存了指向每个物理页面的 struct page * 指针。当分配成功后,内核通过四级页表(x86_64 上 PGD→P4D→PUD→PMD→PTE)将这些分散的物理页面映射到连续的虚拟地址区间。
1.3 vmap_area: 虚拟区间红黑树节点
为了高效管理 vmalloc 区内的空闲虚拟地址区间,内核使用红黑树组织已分配的 vmap_area:
struct vmap_area { unsigned long va_start; // 虚拟起始地址 unsigned long va_end; // 虚拟结束地址 struct rb_node rb_node; // 红黑树节点 // 按起始地址排序,用于空闲区间查找 // 空闲链表:list_head purge_list;};查找空闲区间时使用 rb_find_next_obj_fit_next_slot() 变体算法,在红黑树中找到第一个不小于请求大小的空闲区间,时间复杂度 O(log n)。当分配请求无法满足时,触发 purge_vmap_area_lazy() 以释放延迟解映射的区域。
二、__vmalloc_node_range: 分配核心路径解析
2.1 快速分配路径(VM_ALLOC)
vmalloc 实际调用 vmalloc_node → vmalloc_node_flags → __vmalloc_node_range 的完整链路:
static void *__vmalloc_node_range(unsigned long size, unsigned long align, unsigned long start, unsigned long end, gfp_t gfp_mask, pgprot_t prot, unsigned long vm_flags, int node, const void *caller){ // 步骤 1: 对齐大小,增加 guard page size = PAGE_ALIGN(size); size = PAGE_SIZE; // 添加一个不可访问的 guard page // 步骤 2: 查找空闲虚拟地址区间 va = alloc_vmap_area(size, align, start, end, node); if (!va) return NULL; // 步骤 2: 向伙伴系统申请物理页 area = kzalloc_node(sizeof(*area), gfp_mask, node); area-

发表评论 取消回复