引言:内核自己要 1MB 连续内存,buddy 却凑不出来
前面 #2 讲过 buddy 按 2 的幂给物理页,系统跑久了 4KB 空闲页东一块西一块,高阶连续块凑不出来(外部碎片)。用户态 mmap(#16)好办------按需分配 ,虚拟连续、物理页缺页时逐个给。但内核自己(模块加载、BPF 程序、per-CPU 大缓冲)有时也要 1MB 甚至更大的虚拟连续内存,却不想被"物理也必须连续"卡死。
vmalloc 的思路:只要虚拟地址连续,物理页可以逐个从 buddy 拿(order-0 单页也行),再用页表把不连续的物理页"连"成一段连续的虚拟地址。它解耦了"虚拟连续"和"物理连续"------这正是它和 kmalloc(#13,物理连续)的根本区别。
这条链路:vmalloc → __vmalloc_node_range 分配虚拟空间 → __vmalloc_area_node 逐个拿物理页 + 建页表映射 → 返回虚拟连续的内核地址。
全景图
#mermaid-svg-Fn2Omu9ncsjiuQVF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Fn2Omu9ncsjiuQVF .error-icon{fill:#552222;}#mermaid-svg-Fn2Omu9ncsjiuQVF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Fn2Omu9ncsjiuQVF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .marker.cross{stroke:#333333;}#mermaid-svg-Fn2Omu9ncsjiuQVF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Fn2Omu9ncsjiuQVF p{margin:0;}#mermaid-svg-Fn2Omu9ncsjiuQVF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster-label text{fill:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster-label span{color:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster-label span p{background-color:transparent;}#mermaid-svg-Fn2Omu9ncsjiuQVF .label text,#mermaid-svg-Fn2Omu9ncsjiuQVF span{fill:#333;color:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .node rect,#mermaid-svg-Fn2Omu9ncsjiuQVF .node circle,#mermaid-svg-Fn2Omu9ncsjiuQVF .node ellipse,#mermaid-svg-Fn2Omu9ncsjiuQVF .node polygon,#mermaid-svg-Fn2Omu9ncsjiuQVF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .rough-node .label text,#mermaid-svg-Fn2Omu9ncsjiuQVF .node .label text,#mermaid-svg-Fn2Omu9ncsjiuQVF .image-shape .label,#mermaid-svg-Fn2Omu9ncsjiuQVF .icon-shape .label{text-anchor:middle;}#mermaid-svg-Fn2Omu9ncsjiuQVF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .rough-node .label,#mermaid-svg-Fn2Omu9ncsjiuQVF .node .label,#mermaid-svg-Fn2Omu9ncsjiuQVF .image-shape .label,#mermaid-svg-Fn2Omu9ncsjiuQVF .icon-shape .label{text-align:center;}#mermaid-svg-Fn2Omu9ncsjiuQVF .node.clickable{cursor:pointer;}#mermaid-svg-Fn2Omu9ncsjiuQVF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .arrowheadPath{fill:#333333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Fn2Omu9ncsjiuQVF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Fn2Omu9ncsjiuQVF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Fn2Omu9ncsjiuQVF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster text{fill:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF .cluster span{color:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Fn2Omu9ncsjiuQVF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Fn2Omu9ncsjiuQVF rect.text{fill:none;stroke-width:0;}#mermaid-svg-Fn2Omu9ncsjiuQVF .icon-shape,#mermaid-svg-Fn2Omu9ncsjiuQVF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Fn2Omu9ncsjiuQVF .icon-shape p,#mermaid-svg-Fn2Omu9ncsjiuQVF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Fn2Omu9ncsjiuQVF .icon-shape .label rect,#mermaid-svg-Fn2Omu9ncsjiuQVF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Fn2Omu9ncsjiuQVF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Fn2Omu9ncsjiuQVF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Fn2Omu9ncsjiuQVF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vmalloc vmalloc.c:3416
__vmalloc_node_range :3235
先试大页 VM_ALLOW_HUGE_VMAP
__get_vm_area_node :2570
分配虚拟空间(红黑树找空闲区间)
alloc_vmap_area :1582
free_vmap_area_root 红黑树 first-fit
__vmalloc_area_node :3101
拿物理页 + 建页表
vm_area_alloc_pages :2992
逐个从 buddy 拿物理页
vmap_pages_range :628
vmap_range_noflush :278 建页表
pgd→p4d→pud→pmd→pte
逐级把物理页填进页表
返回虚拟连续的内核地址
物理页不连续也能寻址
vfree :2807
remove_vm_area 拆页表 + 逐页还 buddy
一句话:vmalloc → __vmalloc_node_range 分配虚拟空间(红黑树找空闲区间)→ __vmalloc_area_node 逐个从 buddy 拿物理页 + vmap_pages_range 建页表把不连续物理页映射成连续虚拟地址 → 返回虚拟连续内核内存。
一、vmalloc 是什么:虚拟连续 ≠ 物理连续
1.1 三个分配器,三种"连续"
| 物理连续? | 虚拟连续? | 怎么拿到 | 快慢 | 能 DMA? | |
|---|---|---|---|---|---|
| kmalloc / slab(#13) | ✅ | ✅(直接映射区,虚拟=物理+偏移) | buddy 直接给连续块 | 快(无页表开销) | ✅ |
| vmalloc | ❌(逐个 order-0) | ✅ | 逐个拿物理页 + 建页表 | 慢(逐页 + 页表遍历) | ❌(无 IOMMU 时) |
| mmap(#16,用户态) | ❌(按需) | ✅ | 只登记 vma,缺页才给 | 首次访问慢(缺页) | 靠 GUP pin(#6) |
vmalloc 的核心:把"连续"这件事拆成虚拟和物理两个维度 。kmalloc 要求两个都连续(所以受外部碎片限制,拿不到大块);vmalloc 只要求虚拟连续,物理页零散无所谓------虚拟地址连续,指针就能连续寻址,物理页靠页表翻译。
1.2 为什么内核需要 vmalloc
- 内核模块加载 :
insmod要把模块的代码/数据放进内核地址空间,大小不定,用 vmalloc。 - BPF 程序 / kprobe:JIT 出的代码、perf 缓冲区,要大块内核内存。
- per-CPU 大缓冲、DRM 驱动:某些驱动要一大块虚拟连续的工作区。
这些场景的共同点:要"大块连续的虚拟地址",但物理页可以零散。这正是 vmalloc 的用武之地。
二、两个数据结构:vm_struct + vmap_area
2.1 struct vm_struct:一块 vmalloc 区的描述符
c
// struct vm_struct(include/linux/vmalloc.h:49)
struct vm_struct {
struct vm_struct *next; // 链表串起所有 vm_struct
void *addr; // 虚拟起始地址
unsigned long size; // 大小
unsigned long flags; // VM_ALLOC / VM_IOREMAP / VM_ALLOW_HUGE_VMAP...
struct page **pages; // 指向物理页指针数组
unsigned int nr_pages; // 物理页数量
phys_addr_t phys_addr; // ioremap 时的物理基址
const void *caller; // 谁分配的(调试)
};
它对应一块已经分出去的虚拟区间 ,记录虚拟地址、大小、以及背后散落的物理页数组 pages。
2.2 struct vmap_area:虚拟空间的"区间节点"
管理 vmalloc 虚拟地址空间用的是两棵红黑树 ,节点是 vmap_area:
c
// struct vmap_area(include/linux/vmalloc.h:63)
struct vmap_area {
unsigned long va_start; // 区间起点
unsigned long va_end; // 区间终点
struct rb_node rb_node; // 红黑树节点(按地址排序)
struct list_head list; // 链表节点(按地址排序)
union {
unsigned long subtree_max_size; // 在"空闲树"里:子树最大空闲块
struct vm_struct *vm; // 在"已用树"里:指向对应的 vm_struct
};
unsigned long flags;
};
关键设计------两棵树 + union 复用:
- 空闲树
free_vmap_area_root(vmalloc.c:766)+ 空闲链表free_vmap_area_list(:754):存所有空闲 虚拟区间。alloc_vmap_area(:1582)在这棵树上 first-fit 找一块够大的空闲区间。subtree_max_size记录子树里最大的连续空闲块,让"找够大的块"变成 O(log n)------不用挨个遍历。 - 已用树
vmap_area_root:存所有已分配 的区间。释放时remove_vm_area按地址查这棵树找到要回收的区间。
同一个 vmap_area 对象,空闲时挂在空闲树(union 里是 subtree_max_size),分出去后挂到已用树(union 里是 vm 指针)。一个 union 省下一个字段,两棵树各取所需。
Why 虚拟空间也要"分配器" :vmalloc 区(x86-64 是
VMALLOC_START~VMALLOC_END,vmalloc_base起的 32TB,pgtable_64_types.h:134/127)是一段连续的内核虚拟地址区间,跟 buddy 管理物理页一样,虚拟地址区间也得有"谁占着、哪段空着"的记账。所以 vmalloc 复用了"红黑树 + 链表"这套经典区间管理(和用户态 vma 的 maple tree #16 是同一个问题,只是 vmalloc 用红黑树)。
三、分配:vmalloc → 分配虚拟空间 → 拿物理页 → 建页表
3.1 入口:vmalloc 是薄包装
c
// vmalloc(mm/vmalloc.c:3416)
void *vmalloc(unsigned long size)
{
return __vmalloc_node(size, 1, GFP_KERNEL, NUMA_NO_NODE,
__builtin_return_address(0));
}
真正干活的是 __vmalloc_node_range(vmalloc.c:3235)。它先试大页:
c
// __vmalloc_node_range(vmalloc.c:3235)------ 关键片段
if (vmap_allow_huge && (vm_flags & VM_ALLOW_HUGE_VMAP)) {
...
if (arch_vmap_pmd_supported(prot) && size_per_node >= PMD_SIZE)
shift = PMD_SHIFT; // 大页: 2MB 页
...
}
area = __get_vm_area_node(real_size, align, shift, VM_ALLOC | ...); // ① 分虚拟空间(:3280)
...
ret = __vmalloc_area_node(area, gfp_mask, prot, shift, node); // ② 拿物理页 + 建页表(:3320)
vmap_allow_huge + VM_ALLOW_HUGE_VMAP(如 vmalloc_huge :3435 传的)时,用 2MB 大页(arch_vmap_pmd_supported),减少页表层级和 TLB miss------接 #4 大页。
3.2 第一步:分配虚拟空间
__get_vm_area_node(:2570)→ alloc_vmap_area(:1582):
c
// alloc_vmap_area(vmalloc.c:1582)------ 关键片段
va = kmem_cache_alloc_node(vmap_area_cachep, gfp_mask, node); // 分配 vmap_area 节点
...
addr = __alloc_vmap_area(&free_vmap_area_root, &free_vmap_area_list, // :1615
size, align, vstart, vend);
__alloc_vmap_area 在空闲红黑树上 first-fit 找一块对齐的、够大的空闲虚拟区间,摘下来。这一步只动虚拟地址,不碰物理页。
3.3 第二步:拿物理页 + 建页表
__vmalloc_area_node(vmalloc.c:3101):
c
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask, ...)
{
/* ① 分配"物理页指针数组" */
array_size = nr_small_pages * sizeof(struct page *);
if (array_size > PAGE_SIZE)
area->pages = __vmalloc_node(array_size, 1, ...); // 数组大,自己也 vmalloc(:3122)
else
area->pages = kmalloc_node(array_size, ...); // 数组小,kmalloc(:3125)
/* ② 逐个从 buddy 拿物理页(order-0 或大页) */
area->nr_pages = vm_area_alloc_pages(gfp_mask | __GFP_NOWARN, // :3139
node, page_order, nr_small_pages, area->pages);
/* ③ 建页表:把不连续的物理页映射进连续的虚拟空间 */
ret = vmap_pages_range(addr, addr + size, prot, area->pages, page_shift); // :3182
...
return area->addr;
}
三步拆开:
- 分配页指针数组
area->pages:struct page *数组记录所有物理页。数组本身可能很大,超过一页就递归用 vmalloc(:3122)。 vm_area_alloc_pages(:2992)逐个拿物理页 :循环alloc_pages(:3063)从 buddy 拿 order-0 单页(或大页 order),不要求它们物理连续------这是 vmalloc 的灵魂。vmap_pages_range(:628)→vmap_range_noflush(:278)建页表 :从pgd_offset_k开始逐级(pgd → p4d → pud → pmd → pte)把每个物理页的 PFN 填进页表项,把不连续的物理地址"连"成连续的虚拟地址。
c
// vmap_range_noflush(vmalloc.c:278)------ 关键片段
pgd = pgd_offset_k(addr);
do {
next = pgd_addr_end(addr, end);
err = vmap_p4d_range(pgd, addr, next, phys_addr, prot, ...); // 逐级填页表
} while (pgd++, phys_addr += (next - addr), addr = next, addr != end);
核心成本在这里:物理不连续 → 必须走页表翻译 → 每次访问都有 TLB miss 风险;且建页表本身要分配各级页表页、填 PTE。
Why vmalloc 物理不连续就慢、且不能 DMA :① 慢 ------物理连续(kmalloc 的直接映射区)虚拟=物理,一条大页映射覆盖整个内核段,TLB 几乎不 miss;vmalloc 的页是零散的,每页一个 PTE,访问大块时 TLB miss 多。② 不能 DMA ------DMA 设备没有 MMU(无 IOMMU 时),只认物理连续 地址;vmalloc 的页物理零散,设备没法一次搬。有 IOMMU 时(IOMMU 篇)
dma_map_single能把零散物理页重映射成连续 IOVA,vmalloc 内存才能做 DMA------这就是 IOMMU 的价值之一。
四、三兄弟:vmalloc / vmap / ioremap
三者共用同一套"分配虚拟空间 + 建页表"的引擎 (__get_vm_area_node + vmap_range_noflush),区别只在物理页从哪来:
| 物理页来源 | 是否分配物理页 | 典型用途 | |
|---|---|---|---|
| vmalloc | 自己从 buddy 逐个拿 | ✅ 分配 | 内核要一大块匿名内核内存 |
| vmap(:2894) | 调用者传入的 struct page **pages 数组 |
❌ 只映射已存在的页 | 把一组已拿到的页映射成连续虚拟(如 struct page 数组) |
| ioremap(:307) | 设备的物理地址(MMIO 寄存器) | ❌ 映射已存在的物理地址 | 把设备 MMIO 映射进内核地址空间 |
c
// vmap(vmalloc.c:2894)------ 映射调用者给的 page 数组
void *vmap(struct page **pages, unsigned int count, ...);
// ioremap_page_range(vmalloc.c:307)------ 映射设备物理地址
int ioremap_page_range(unsigned long addr, unsigned long end,
phys_addr_t phys_addr, pgprot_t prot); // 内部也是 vmap_range_noflush(:312)
一句话:vmalloc = 分配虚拟空间 + 分配物理页 + 建页表 ;vmap = 分配虚拟空间 + 建页表(物理页已有) ;ioremap = 分配虚拟空间 + 建页表(物理地址是设备的,不属于 buddy)。物理页来源是唯一分水岭。
五、释放:vfree
vfree(vmalloc.c:2807)是分配的逆过程:
c
// vfree(vmalloc.c:2807)------ 关键片段
void vfree(const void *addr)
{
...
vm = remove_vm_area(addr); // ① 拆页表 + 从已用树摘回空闲树(:2824)
...
for (i = 0; i < vm->nr_pages; i++) {
__free_page(vm->pages[i]); // ② 逐个物理页还回 buddy(:2842)
cond_resched();
}
kvfree(vm->pages); // ③ 释放页指针数组
kfree(vm); // ④ 释放 vm_struct
}
remove_vm_area(:2684)找到 vmap_area,拆掉页表(__vunmap_range_noflush :414 清 PTE + 刷 TLB),把虚拟区间还回空闲树。然后逐个 __free_page 把物理页还 buddy、释放页数组和描述符。物理页回到 buddy 后,虚拟区间也回到空闲树,两者独立回收。
六、与前面各篇的关系 + 记住三点
6.1 与前面各篇的关系
| 篇 | 关系 |
|---|---|
| #2 buddy | vmalloc 的物理页逐个从 buddy 拿(alloc_pages :3063),order-0 就行,不要求连续 |
| #13 slab/kmalloc | kmalloc 物理连续(直接映射,可 DMA),vmalloc 虚拟连续(页表,不可 DMA)------"连续"两维度的对照 |
| #16 mmap | 用户态 mmap 虚拟连续 + 按需分配;vmalloc 内核态虚拟连续 + 立即分配(当场拿页建表),都不要求物理连续 |
| #4 大页 | vmalloc_huge + VM_ALLOW_HUGE_VMAP + arch_vmap_pmd_supported 用 2MB 大页 |
| #18 KASLR | vmalloc 区起点 vmalloc_base 被 KASLR 随机化(pgtable_64_types.h:134) |
| IOMMU 篇 | vmalloc 物理不连续不能直接 DMA,有 IOMMU 才 dma_map_single 重映射成连续 IOVA |
6.2 与 AI Infra 的关系
- BPF / 网络:eBPF 程序、XDP 元数据、perf 环形缓冲都用 vmalloc(大块虚拟连续、物理零散没问题)。
- RDMA / 网卡驱动 :驱动里要大块工作区时用 vmalloc,但做 DMA 前必须注意------无 IOMMU 时 vmalloc 内存不能直接给设备,要么换 kmalloc(物理连续),要么靠 IOMMU。
- CUDA / GPU:GPU 显存管理(cudaMalloc)是另一套(设备内存),但内核态驱动模块加载、GDR(GPU Direct RDMA)的内核缓冲区会碰到 vmalloc。
- 为什么训练框架关心:DMA 缓冲(网络收发包、checkpoint 落盘)选 kmalloc 还是 vmalloc,取决于有没有 IOMMU------这决定了能不能零拷贝。
6.3 记住三点
-
vmalloc = 虚拟连续、物理不连续 :把"连续"拆成虚拟/物理两维。
vmalloc(vmalloc.c:3416)→__vmalloc_node_range(:3235)先试大页,然后__get_vm_area_node(:2570)→alloc_vmap_area(:1582)在free_vmap_area_root红黑树上 first-fit 分虚拟空间,再__vmalloc_area_node(:3101)逐个vm_area_alloc_pages(:2992)拿物理页 +vmap_pages_range(:628)建页表。 -
两棵树管虚拟空间 :
struct vmap_area(vmalloc.h:63)空闲时挂free_vmap_area_root(union 存subtree_max_size加速 first-fit),分出去挂已用树(union 存vm指针)。struct vm_struct(:49)记录一块分出去的区间 + 背后的页数组。 -
三兄弟同一引擎、区别在物理页来源 :vmalloc(自己从 buddy 拿)、vmap(映射调用者的 page 数组,:2894)、ioremap(映射设备物理地址,:307)共用"分虚拟空间 +
vmap_range_noflush(:278)建页表";物理不连续 → 页表翻译慢 + 无 IOMMU 不能 DMA。释放vfree(:2807)拆页表 + 逐页还 buddy。
附:本篇关键源码索引
| 符号 | 位置 |
|---|---|
vmalloc / vmalloc_huge(入口) |
mm/vmalloc.c:3416 / :3435 |
__vmalloc_node_range(分虚拟空间 + 拿页建表) |
mm/vmalloc.c:3235 |
__get_vm_area_node(分配虚拟空间) |
mm/vmalloc.c:2570 |
alloc_vmap_area(红黑树 first-fit 分虚拟空间) |
mm/vmalloc.c:1582 |
__vmalloc_area_node(拿物理页 + 建页表) |
mm/vmalloc.c:3101 |
vm_area_alloc_pages(逐个从 buddy 拿物理页) |
mm/vmalloc.c:2992 |
vmap_pages_range(建页表入口) |
mm/vmalloc.c:628 |
vmap_range_noflush(pgd→p4d→pud→pmd→pte 填页表) |
mm/vmalloc.c:278 |
vmap(映射调用者的 page 数组) |
mm/vmalloc.c:2894 |
ioremap_page_range(映射设备物理地址) |
mm/vmalloc.c:307 |
vfree(释放) |
mm/vmalloc.c:2807 |
remove_vm_area(拆页表 + 摘回空闲树) |
mm/vmalloc.c:2684 |
struct vm_struct(addr/pages/nr_pages) |
include/linux/vmalloc.h:49 |
struct vmap_area(rb_node + union subtree_max_size/vm) |
include/linux/vmalloc.h:63 |
free_vmap_area_root / free_vmap_area_list |
mm/vmalloc.c:766 / :754 |
VMALLOC_START / VMALLOC_END(vmalloc_base 起 32TB) |
arch/x86/include/asm/pgtable_64_types.h:134 / :152 |