Skip to content

第七十章:vmalloc

元婴后期

涉及内核源码:

林小源从 Slab 工坊出来,沿着一条狭窄的通道继续往前走。通道的墙壁上嵌满了发光的符号——虚拟地址,一段接一段,像某种古老的铭文。

通道尽头是一片开阔的空间。和别处不同,这里的地面上没有整齐的方块,也没有刻满线条的棋盘。这里的地面是碎片化的——一块块独立的浮岛,悬浮在虚空中,彼此之间有缝隙。但奇妙的是,从高处看下去,这些浮岛排列成一条完美的直线,像是连续的一整块。

"这是什么地方?"

"vmalloc 的领地。"一个声音从半空中传来。

林小源抬头。一个穿着深蓝色长袍的修行者悬浮在半空中,双手各托着一块碎片——两块完全不相邻的浮岛。他的眼睛像两面镜子,映射出无数条光线,每一条都连接着不同的碎片。

"我是 vmalloc。"他说,"虚拟连续,物理不连续。"

他把两块碎片放在空中。它们相距很远——一块在东,一块在西——但他用一条光线把它们连起来。从光线的角度看,两块碎片像是紧挨在一起的。

"这就是我的魔法。"vmalloc 说,"物理页面散落在各处,但通过页表映射,我把它们拼成一段连续的虚拟地址。对使用者来说,它们是连续的——他们看不到缝隙。"

林小源走到一块浮岛旁边,低头看了看。浮岛下面的虚空中,能看到其他浮岛的位置——它们确实不相邻,一块在 0x10000000,另一块在 0x20003000,第三块在 0x30001000。但在虚拟地址空间里,它们被映射成了连续的四个页面。

"为什么不用 kmalloc?"林小源问。

vmalloc 笑了。他的笑容里带着一种"你问到点子上了"的意味。

"kmalloc 分配的是物理连续的内存。"他说,"如果你需要 4KB、8KB、甚至 128KB——kmalloc 没问题,伙伴系统能找到连续的物理页面。但如果你需要 16MB 呢?32MB 呢?"

他伸出手,指向远处。远处的伙伴系统领地里,那些发光的柱子已经很矮了——10 阶的柱子上几乎没有大方块了。

"伙伴系统找不到那么大的连续物理页面。"vmalloc 说,"物理内存已经被分配得七零八落了。但虚拟地址空间——"他指了指头顶,"虚拟地址空间很大。只要能找到零散的物理页面,我就能把它们拼成一段连续的虚拟内存。"

林小源在 vmalloc 的领地里走了一圈,渐渐注意到了这里的布局。

这片空间被划分成三个区域,每个区域的地面颜色不同。最近的一块是金色的,地面上刻着"直接映射区"四个字。中间的一块是银色的,刻着"vmalloc 区"。最远处的一块是铜色的,刻着"固定映射区"。

"那些是什么?"

"内核虚拟地址空间的三个区域。"vmalloc 说。他飘到金色区域旁边,用手指点了点地面。

"直接映射区——这是 kmalloc 使用的地方。物理内存直接映射到虚拟地址,偏移是固定的。物理地址 0x10000000 对应虚拟地址 0xFFFF800010000000——加上一个固定的偏移就行。简单,快速,但需要物理连续。"

他又飘到银色区域。

"vmalloc 区——这是我的地盘。物理页面不连续,但通过页表映射成连续的虚拟地址。分配的时候需要逐页修改页表,所以比 kmalloc 慢。但能处理大块内存。"

"铜色的呢?"

"固定映射区。"vmalloc 说,"特殊的用途,特殊的映射关系。你暂时不需要了解。"

林小源看着这三个区域。金色的区域最大,几乎占了一半的空间。银色的区域其次。铜色的区域最小。

"内核的地址空间也是有分区的。"他喃喃自语。

"当然。"vmalloc 说,"每一寸地址空间都有它的用途。就像一座城市——商业区、工业区、住宅区,各司其职。你不能把工厂建在住宅区里,也不能用 vmalloc 的方式去分配 kmalloc 该分配的内存。"

林小源正要离开,vmalloc 叫住了他。

"你知道我的代价吗?"

"代价?"

vmalloc 从空中飘下来,落在林小源面前。他的表情变得严肃。

"kmalloc 分配内存,只需要一次——伙伴系统找到合适的块,返回地址,完事。"他说,"但我不一样。我需要——"

他伸出手指,一条一条地数:

"第一,在虚拟地址空间中找到一段连续的空闲区域。第二,逐页分配物理页面——每一页可能来自不同的地方。第三,逐页修改页表,把虚拟页面映射到物理页面。第四,刷新 TLB。"

他收回手指。

"每一次分配,都需要修改页表。页表修改是昂贵的——需要获取锁,需要同步多核,需要刷新 TLB。比 kmalloc 慢得多。"

林小源点了点头。他能感觉到——每次 vmalloc 映射一块浮岛的时候,周围的空间都会轻微震动,像是有什么沉重的机制在运转。

"还有一个更严重的问题。"vmalloc 说,"我的内存不能用于 DMA。"

"为什么?"

"因为 DMA 需要物理连续的内存。"vmalloc 说,"DMA 控制器不认虚拟地址——它只认物理地址。如果你给它一段 vmalloc 的内存,它看到的物理页面是散落的,根本没法连续读写。"

他看着林小源,眼神很认真。

"所以你要记住:vmalloc 是用来处理大块内存的——网络缓冲区、内核模块加载、大型数据结构。但如果你需要 DMA,或者需要极高的性能——用 kmalloc。"

林小源看着脚下那些碎片化的浮岛。它们看起来不完整,不连续,但通过页表的魔法,它们变成了一段完美的连续内存。这就是虚拟地址空间的力量——用映射掩盖物理的碎片。

"每种分配器都有自己的战场。"他轻声说。

"对。"vmalloc 说,"没有万能的分配器。只有合适的分配器。"


c
/*
 * kmalloc vs vmalloc:
 *
 * kmalloc:
 *   - 物理连续
 *   - 虚拟连续
 *   - 适合小块内存(通常 < 128KB)
 *   - 使用 Slab 分配器
 *   - 快速
 *
 * vmalloc:
 *   - 物理不连续
 *   - 虚拟连续
 *   - 适合大块内存(可以分配几 MB)
 *   - 使用页表映射
 *   - 较慢(需要修改页表)
 *
 * vmalloc 的工作原理:
 *   1. 在内核虚拟地址空间中找到一段连续的区域
 *   2. 逐页分配物理页面(不连续)
 *   3. 修改页表,把虚拟页面映射到物理页面
 *   4. 返回虚拟地址
 */

struct vm_struct {
    void *addr;         /* 虚拟地址 */
    unsigned long size; /* 大小 */
    int nr_pages;       /* 物理页数 */
    unsigned long pages[4];  /* 物理页地址(不连续) */
};

printf("=== vmalloc — 虚拟连续的内存 ===\n\n");

/* 模拟 vmalloc 分配 16KB (4 页) */
struct vm_struct vm = {
    .addr = (void *)0xFFFF800010000000,
    .size = 16384,
    .nr_pages = 4,
};

/* 物理页面不连续 */
vm.pages[0] = 0x10000000;  /* 物理地址 1 */
vm.pages[1] = 0x20003000;  /* 物理地址 2 */
vm.pages[2] = 0x30001000;  /* 物理地址 3 */
vm.pages[3] = 0x0FFF8000;  /* 物理地址 4 */

printf("vmalloc 分配 16KB:\n");
printf("  虚拟地址: %p\n", vm.addr);
printf("  大小: %lu 字节\n", vm.size);
printf("  物理页数: %d\n\n", vm.nr_pages);

printf("物理页面(不连续):\n");
for (int i = 0; i < vm.nr_pages; i++) {
    unsigned long va = (unsigned long)vm.addr + i * 4096;
    printf("  VA 0x%lx → PA 0x%lx\n", va, vm.pages[i]);
}

printf("\n--- kmalloc vs vmalloc ---\n");
printf("kmalloc:\n");
printf("  物理连续: 是\n");
printf("  虚拟连续: 是\n");
printf("  大小限制: 通常 < 128KB\n");
printf("  速度: 快\n");
printf("  用途: 设备驱动、小数据结构\n\n");
printf("vmalloc:\n");
printf("  物理连续: 否\n");
printf("  虚拟连续: 是\n");
printf("  大大限制: 可以分配几 MB\n");
printf("  速度: 较慢\n");
printf("  用途: 大缓冲区、模块加载\n\n");

printf("--- vmalloc 的使用场景 ---\n");
printf("1. 分配大缓冲区(如网络缓冲区)\n");
printf("2. 加载内核模块\n");
printf("3. 分配大的数据结构\n");
printf("4. 不需要物理连续的场景\n");

道藏笔记

内核启示

分配虚拟连续但物理不连续的内存。

的工作原理:

  1. 在内核虚拟地址空间中找到连续区域
  2. 逐页分配物理页面(不连续)
  3. 修改页表,映射虚拟页面到物理页面
  4. 返回虚拟地址

vs

  • — 物理连续,快速,适合小块
  • — 物理不连续,较慢,适合大块

的使用场景:

  1. 大缓冲区
  2. 内核模块加载
  3. 大数据结构
  4. 不需要物理连续的场景

的代价:

  • 需要修改页表
  • 不能用于 DMA
  • TLB 压力更大

是"虚拟连续"的魔法——物理不连续,但看起来连续。


破关试炼

vmalloc 之试

需要虚拟地址连续但物理页不必连续的大块内核内存时,本章使用哪个接口?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道