用一个代码案例详解介绍vmalloc函数的功能和作用
vmalloc 函数详解:代码案例与功能分析
功能与作用
vmalloc 是 Linux 内核中用于分配虚拟地址连续但物理地址不连续的内存区域的函数:
虚拟地址连续:用户/内核可通过单一指针访问整个内存块
物理地址不连续:实际内存由多个分散的物理页帧组成
大内存支持:可分配远大于 kmalloc 的内存块(如 1GB)
避免碎片:解决物理内存碎片导致的分配失败问题
核心特点
特性 说明
内存来源 通过 alloc_page() 从伙伴系统获取多个分散的物理页帧
地址映射 修改页表将分散物理页映射到连续的虚拟地址区域(VMALLOC_START~VMALLOC_END)
性能开销 较高(需构建页表项 + TLB 刷新)
适用场景 内核模块、大型缓冲区、文件系统缓存等无需物理连续性的场景
禁止场景 DMA操作(硬件需要物理连续内存)
代码案例:内核模块演示 vmalloc 使用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/slab.h>
#define MEM_SIZE (5 * 1024 * 1024) // 分配5MB内存
static char *vmalloc_ptr = NULL;
static char *kmalloc_ptr = NULL;
static void print_mem_info(const char *prefix, void *ptr, size_t size)
{
struct page *page;
unsigned long virt_start = (unsigned long)ptr;
unsigned long virt_end = virt_start + size;
printk(KERN_INFO "%s: 虚拟地址范围: [%pK - %pK]\n",
prefix, ptr, ptr + size - 1);
// 遍历虚拟地址对应的物理页
for (; virt_start < virt_end; virt_start += PAGE_SIZE) {
page = vmalloc_to_page((void *)virt_start);
if (page) {
printk(KERN_CONT " 虚拟地址 %pK -> 物理页帧: 0x%lx\n",
(void *)virt_start, page_to_pfn(page) << PAGE_SHIFT);
}
}
}
static int __init mem_test_init(void)
{
// 1. vmalloc分配 (物理不连续)
vmalloc_ptr = vmalloc(MEM_SIZE);
if (!vmalloc_ptr) {
printk(KERN_ERR "vmalloc 分配失败!\n");
return -ENOMEM;
}
// 2. kmalloc分配 (物理连续,对比用)
kmalloc_ptr = kmalloc(MEM_SIZE / 10, GFP_KERNEL); // 分配500KB对比
if (!kmalloc_ptr) {
vfree(vmalloc_ptr);
printk(KERN_ERR "kmalloc 分配失败!\n");
return -ENOMEM;
}
// 打印内存信息
print_mem_info("vmalloc", vmalloc_ptr, MEM_SIZE);
print_mem_info("kmalloc", kmalloc_ptr, MEM_SIZE / 10);
// 3. 测试内存访问
memset(vmalloc_ptr, 0xAA, MEM_SIZE); // 成功写入
memset(kmalloc_ptr, 0xBB, MEM_SIZE / 10);
printk(KERN_INFO "内存访问测试完成 (无崩溃即成功)\n");
return 0;
}
static void __exit mem_test_exit(void)
{
if (vmalloc_ptr) {
vfree(vmalloc_ptr); // 释放vmalloc内存
printk(KERN_INFO "释放vmalloc内存");
}
if (kmalloc_ptr) {
kfree(kmalloc_ptr); // 释放kmalloc内存
printk(KERN_INFO "释放kmalloc内存");
}
}
module_init(mem_test_init);
module_exit(mem_test_exit);
MODULE_LICENSE("GPL");
代码解析与输出分析
1. 内存分配
vmalloc_ptr = vmalloc(5 * 1024 * 1024); // 分配5MB
kmalloc_ptr = kmalloc(500 * 1024, GFP_KERNEL); // 分配500KB对比
vmalloc 轻松分配 5MB 大内存
kmalloc 分配 500KB(仅作物理连续性对比)
2. 地址信息输出
# dmesg 输出示例
vmalloc: 虚拟地址范围: [0xffff0000c0000000 - 0xffff0000c04fffff]
虚拟地址 0xffff0000c0000000 -> 物理页帧: 0x7f0000
虚拟地址 0xffff0000c0001000 -> 物理页帧: 0x324000
虚拟地址 0xffff0000c0002000 -> 物理页帧: 0x88a000 # 物理地址不连续!
... (其他分散页帧)
kmalloc: 虚拟地址范围: [0xffff80001134a000 - 0xffff8000113c9fff]
虚拟地址 0xffff80001134a000 -> 物理页帧: 0x134a000
虚拟地址 0xffff80001134b000 -> 物理页帧: 0x134b000
虚拟地址 0xffff80001134c000 -> 物理页帧: 0x134c000 # 物理地址连续!
关键观察
虚拟地址连续性:
两者虚拟地址均连续(单一指针访问整个区域)
物理地址分布:
vmalloc:各页帧物理地址无规律(如 0x7f0000 → 0x324000 → 0x88a000)
kmalloc:物理地址连续递增(0x134a000 → 0x134b000 → 0x134c000)
vmalloc 内部机制图解
典型应用场景
内核模块加载
加载大型内核模块(如文件系统驱动)时使用 vmalloc 分配代码/数据空间
文件系统缓存
EXT4/Btrfs 等文件系统用 vmalloc 管理大型目录项缓存
网络协议栈
分配大数据包缓冲区(如大于 PAGE_SIZE 的 SKB)
动态内存映射
实现用户空间的 mmap 系统调用(如 remap_vmalloc_range)
重要注意事项
禁止中断上下文使用
vmalloc 可能睡眠(调用 alloc_page()),不能在中断处理程序中使用
性能敏感场景慎用
频繁访问时 TLB Miss 率高,性能显著低于 kmalloc
特殊内存特性
默认分配 GFP_KERNEL 内存,不可用于原子上下文
NUMA系统优化
使用 vmalloc_node 可指定内存所在的 NUMA 节点
通过此案例可清晰理解 vmalloc 如何在内核中构建"虚拟连续但物理分散"的内存区域,及其解决大内存分配和碎片问题的核心价值。