4、vmalloc区:动态映射非连续内存
说到vmalloc,我得先坦白一件事——我刚开始学内核内存管理时,总觉得它和kmalloc差不多。都是分配内存嘛,能有多大区别?直到我在项目中遇到一个需要分配大块内存的场景,kmalloc直接给我返回NULL,我才意识到事情没那么简单。
说白了,vmalloc和kmalloc虽然名字长得像,但背后的机制完全不同。今天我们就来好好掰扯掰扯。
4.1 vmalloc:让不连续的内存看起来连续
先问一个问题:物理内存是连续的吗?
答案是:不一定。系统运行久了,物理内存会变得碎片化。你想分配一个8MB的连续物理块,但系统中可能没有那么大片的连续区域了。这时候,kmalloc就无能为力了。
vmalloc的解决思路很巧妙——它不要求物理内存连续,而是通过页表映射,把分散的物理页框映射到一段连续的虚拟地址空间。用户看到的是连续的虚拟地址,背后却是东一块西一块的物理页。
核心区别一句话总结:
- kmalloc:物理连续 + 虚拟连续(直接映射区)
- vmalloc:物理不连续 + 虚拟连续(vmalloc区)
我画了一张图,帮你理解vmalloc区的布局:
4.2 vmalloc vs kmalloc:到底该用谁?
这个问题我经常被问到。其实选择标准很简单,我列个表你就明白了:
| 特性 | kmalloc | vmalloc |
|---|---|---|
| 物理连续性 | 连续 | 不连续 |
| 分配速度 | 快(slab分配器) | 慢(需要建立页表) |
| 最大分配大小 | 受限于连续物理内存(通常 < 4MB) | 较大(受限于虚拟地址空间) |
| 适用场景 | 小对象、频繁分配、DMA缓冲区 | 大块内存、模块加载、视频缓冲区 |
| 是否睡眠 | 可能(GFP_KERNEL) | 会睡眠(不能用于中断上下文) |
我的经验之谈:
我个人习惯是:能用kmalloc就不用vmalloc。为什么?因为vmalloc分配时需要建立页表,开销大。而且vmalloc分配的内存,CPU访问时会有TLB miss,性能不如直接映射区。
但如果你需要分配超过一个page的大小,而且不要求物理连续,那就放心用vmalloc吧。
4.3 vmalloc的使用示例
来看看实际代码怎么写。嗯,这里要注意,vmalloc返回的是虚拟地址,你需要自己管理大小:
#include <linux/vmalloc.h>
void *buf;
size_t size = 8 * 1024 * 1024; // 8MB
// 分配内存
buf = vmalloc(size);
if (!buf) {
pr_err("vmalloc failed\n");
return -ENOMEM;
}
// 使用内存
memset(buf, 0, size);
// ... 你的业务逻辑 ...
// 释放内存
vfree(buf);
我曾经在项目中犯过一个低级错误——用vmalloc分配了内存,但忘记调用vfree,结果导致vmalloc区慢慢耗尽。系统跑了三天后,突然所有模块加载都失败了。排查了半天才发现是内存泄漏。
避坑指南:
我曾经在中断处理函数里调用了vmalloc,结果系统直接panic。记住:vmalloc会睡眠(因为它可能触发页表操作和内存回收),所以绝对不能在中断上下文、spinlock持有期间使用。
4.4 ioremap:把设备内存映射到内核空间
讲完vmalloc,我们得聊聊ioremap。这两个东西虽然都涉及虚拟地址映射,但目的完全不同。
ioremap是用来干什么的?说白了,就是让CPU能访问外设的寄存器或内存。外设的物理地址(比如0xF0000000处的MMIO区域)对CPU来说是不可直接访问的,必须通过ioremap建立页表映射。
来看一个典型的ioremap使用场景:
#include <linux/io.h>
// 假设设备寄存器在物理地址 0xF0000000,大小 4KB
#define DEVICE_BASE 0xF0000000
#define DEVICE_SIZE 0x1000
void __iomem *regs;
// 映射设备内存
regs = ioremap(DEVICE_BASE, DEVICE_SIZE);
if (!regs) {
pr_err("ioremap failed\n");
return -ENOMEM;
}
// 读写寄存器(使用专门的访问函数)
u32 val = ioread32(regs + 0x10); // 读寄存器
iowrite32(0x1, regs + 0x00); // 写寄存器
// 解除映射
iounmap(regs);
关键点:
- ioremap返回的是
__iomem标记的指针,不要直接解引用 - 必须使用
ioread32/iowrite32等访问函数 - ioremap和vmalloc不同,它不分配物理内存,只是建立映射
4.5 vmalloc与ioremap的本质区别
我经常看到有人把vmalloc和ioremap搞混。其实它们的区别很明显:
- vmalloc:分配物理内存 + 建立映射。你得到的是实实在在的内存。
- ioremap:只建立映射,不分配内存。你映射的是设备已有的物理地址。
举个例子你就明白了。vmalloc就像你去租房子——房东(内核)给你找了一间房(物理页),然后告诉你地址(虚拟地址)。ioremap呢,就像你拿着地图去找一个已经存在的建筑(设备寄存器),地图(页表)告诉你该怎么走。
4.6 实际项目中的选择策略
最后,我分享一下我在实际项目中的选择策略:
- 小对象(< 1 page):用kmalloc,快就一个字
- 大对象(> 1 page),不要求物理连续:用vmalloc
- 需要DMA:必须用kmalloc或
__get_free_pages,因为DMA需要物理连续 - 访问设备寄存器:用ioremap,别想别的
- 中断上下文:只能用kmalloc(GFP_ATOMIC),别碰vmalloc
一个小技巧:
如果你不确定该用哪个,可以先试试kmalloc。如果返回NULL,再fallback到vmalloc。我在项目中就经常这么干,既保证了性能,又保证了可靠性。
好了,关于vmalloc区的内容就讲到这里。记住一句话:vmalloc解决的是物理碎片化的问题,ioremap解决的是设备访问的问题。两者都是虚拟地址映射,但出发点完全不同。
公众号:蓝海资料掘金营,微信deep3321