4. vmalloc与ioremap:虚拟空间的两把利刃
内存管理这块,我做了十几年,最常被问到的问题就是:「vmalloc和ioremap到底有啥区别?」
说白了,这两个函数都是用来建立虚拟地址到物理地址的映射。但它们的应用场景完全不同。一个管内存,一个管外设。今天咱们就把它们彻底讲透。
4.1 vmalloc区域布局
先说说vmalloc。它分配的是虚拟地址连续、物理地址不连续的内存。
你想想看,kmalloc分配的是物理连续的内存,这在嵌入式系统里是个奢侈品。系统跑久了,内存碎片化严重,想要一大块连续物理内存?门儿都没有。这时候vmalloc就派上用场了。
在x86架构上,vmalloc区域位于VMALLOC_START到VMALLOC_END之间。我习惯用这个命令查看:
# cat /proc/meminfo | grep Vmalloc
VmallocTotal: 34359738367 kB
VmallocUsed: 123456 kB
VmallocChunk: 34359738367 kB
这个区域有多大?一般来说:
- 32位系统:通常128MB~256MB
- 64位系统:非常大,几乎用不完
布局上,vmalloc区域紧挨着模块区域(module region)。嗯,这里要注意,不同架构的布局略有差异,但核心思想一致——把不连续的物理页框映射到连续的虚拟地址空间。
4.2 vmalloc实现原理
vmalloc的实现,我拆成三步来讲:
- 找虚拟地址:在vmalloc区域中找一块足够大的连续虚拟地址
- 分配物理页:调用alloc_page()分配不连续的物理页
- 建立页表:修改内核页表,建立虚拟地址到物理地址的映射
核心代码在mm/vmalloc.c里。我挑重点说:
void *vmalloc(unsigned long size)
{
struct vm_struct *area;
struct page **pages;
unsigned int nr_pages;
// 1. 计算需要的页数
nr_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
// 2. 分配物理页(不连续)
pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
for (i = 0; i < nr_pages; i++) {
pages[i] = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
}
// 3. 分配虚拟地址区域
area = __get_vm_area_node(size, VM_ALLOC, VMALLOC_START, VMALLOC_END);
// 4. 建立页表映射
map_vm_area(area, prot, pages);
return area->addr;
}
我在项目中遇到过一个问题:vmalloc分配的内存,不能用于DMA。为什么?因为DMA需要物理连续的内存,而vmalloc分配的是物理不连续的。这个坑我踩过,后来老老实实改用dma_alloc_coherent了。
4.3 ioremap映射物理地址
ioremap和vmalloc最大的区别在于:ioremap映射的是外设的物理地址,不是内存。
你想想看,CPU要访问GPIO寄存器、UART FIFO、DMA控制器……这些外设都挂在总线上,有各自的物理地址。但CPU只能通过虚拟地址访问。怎么办?用ioremap!
void __iomem *regs;
// 映射GPIO控制器的物理地址
regs = ioremap(0x01C20800, 0x1000);
// 现在可以通过虚拟地址访问外设寄存器
writel(0x1, regs + 0x00); // 设置GPIO方向
val = readl(regs + 0x10); // 读取GPIO电平
// 使用完后解除映射
iounmap(regs);
ioremap的核心流程:
- 在vmalloc区域找一块虚拟地址
- 建立页表,将外设物理地址映射到该虚拟地址
- 设置页表属性为不可缓存(外设寄存器不能缓存!)
这里有个关键点:ioremap映射的物理地址,不能是RAM。我曾经见过有人试图用ioremap映射DDR内存,结果系统崩溃。因为ioremap默认关闭了缓存和写缓冲,而RAM需要缓存才能正常工作。
4.4 vmalloc与ioremap的对比
| 特性 | vmalloc | ioremap |
|---|---|---|
| 映射对象 | 物理内存(RAM) | 外设寄存器/MMIO |
| 物理连续性 | 不连续 | 连续(外设地址空间) |
| 缓存属性 | 可缓存 | 不可缓存 |
| 典型用途 | 大块内存分配 | 外设寄存器访问 |
| 释放函数 | vfree() | iounmap() |
说白了,vmalloc是「内存不够连续时的备胎」,ioremap是「访问外设的桥梁」。两者底层都用了类似的页表操作,但语义完全不同。
4.5 核心逻辑图
我画了一张图,帮你理清vmalloc和ioremap的完整流程:
这张图把两个流程并列展示,你可以看到:前两步几乎一样,区别在第三步——vmalloc分配物理页,ioremap设置缓存属性。第四步都是建立页表,但映射的目标不同。
- vmalloc:虚拟连续,物理不连续,用于大块内存分配
- ioremap:虚拟连续,物理连续(外设地址),用于访问硬件寄存器
- 两者都可能导致睡眠,不能在中断上下文使用
- ioremap映射的地址要用readl/writel访问,不能直接解引用
最后说一句:我见过不少新手把vmalloc和ioremap混用。记住,vmalloc出来的内存可以读写,ioremap出来的地址只能通过readl/writel访问。这个区别,决定了你的驱动是稳定运行还是随机崩溃。
公众号:蓝海资料掘金营,微信deep3321