vmap函数接口用法及示例

linux vmap函数接口用法及示例

Linux 内核中的 vmap 函数详解

vmap 是 Linux 内核中用于将一组物理上不连续的页面映射到连续虚拟地址空间的函数。它常用于需要大块”连续”虚拟内存但物理内存分散的场景(如 DMA 缓冲区、文件系统缓存等)。


函数原型

1
2
3
4
5
6
7
8
#include <linux/vmalloc.h>

void *vmap(
struct page **pages, // 物理页指针数组
unsigned int count, // 要映射的页数
unsigned long flags, // 映射标志(如 VM_MAP)
pgprot_t prot // 页面保护标志(如 PAGE_KERNEL)
);

参数说明

参数 说明
pages 指向 struct page* 数组的指针,每个元素代表一个物理页
count 要映射的物理页数量
flags 映射行为标志:
- VM_MAP:普通映射
- VM_IOREMAP:I/O 内存映射
prot 页面保护属性:
- PAGE_KERNEL:内核读写权限
- PAGE_KERNEL_RO:只读

返回值

  • 成功:映射区域的起始虚拟地址(内核空间地址)
  • 失败NULL

使用步骤

  1. 分配物理页:获取一组物理页(可离散)
  2. 构建 page 数组:创建 struct page* 数组并填充物理页指针
  3. **调用 vmap**:映射到连续虚拟地址
  4. 使用虚拟地址:通过返回的虚拟地址访问内存
  5. 释放映射:使用 vunmap 解除映射

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/slab.h>

#define NUM_PAGES 4 // 映射4个页(4 * 4096 = 16KB)

static struct page **pages = NULL;
static void *vaddr = NULL;

static int __init vmap_example_init(void) {
int i;
gfp_t gfp_mask = GFP_KERNEL; // 内存分配标志

// 1. 分配 pages 数组
pages = kmalloc_array(NUM_PAGES, sizeof(struct page *), gfp_mask);
if (!pages) return -ENOMEM;

// 2. 为每个页分配物理内存
for (i = 0; i < NUM_PAGES; i++) {
pages[i] = alloc_page(gfp_mask); // 分配单个物理页
if (!pages[i]) {
while (i--) __free_page(pages[i]); // 失败时回滚
kfree(pages);
return -ENOMEM;
}
}

// 3. 调用 vmap 映射到连续虚拟地址
vaddr = vmap(pages, NUM_PAGES, VM_MAP, PAGE_KERNEL);
if (!vaddr) {
printk(KERN_ERR "vmap failed\n");
for (i = 0; i < NUM_PAGES; i++) __free_page(pages[i]);
kfree(pages);
return -ENOMEM;
}

printk(KERN_INFO "Mapped virtual address: %p\n", vaddr);

// 4. 使用虚拟地址(示例:写数据)
memset(vaddr, 0xAA, NUM_PAGES * PAGE_SIZE); // 写入数据
return 0;
}

static void __exit vmap_example_exit(void) {
if (vaddr) vunmap(vaddr); // 5. 解除映射

if (pages) {
for (int i = 0; i < NUM_PAGES; i++)
__free_page(pages[i]); // 释放物理页
kfree(pages); // 释放数组
}
printk(KERN_INFO "Module unloaded\n");
}

module_init(vmap_example_init);
module_exit(vmap_example_exit);
MODULE_LICENSE("GPL");

关键说明

  1. 物理页来源

    • 示例使用 alloc_page() 分配零散物理页
    • 实际场景可从其他来源获取(如 dma_alloc_coherent() 返回的物理页)
  2. 内存释放

    • 必须调用 vunmap(vaddr) 解除映射
    • 物理页需单独释放(示例中的 __free_page()
    • 数组内存需用 kfree(pages) 释放
  3. 使用场景

    1
    2
    3
    4
    5
    6
    // 典型场景:映射分散的物理页
    struct page *dma_pages = get_dma_pages();
    void *vaddr = vmap(dma_pages, count, VM_MAP, PAGE_KERNEL);

    // 当作连续内存使用(如拷贝数据)
    memcpy(vaddr, src_data, size);
  4. vmalloc 区别

    特性 vmap vmalloc
    物理内存来源 由调用者提供(可离散) 自动分配零散物理页
    虚拟地址连续性 连续 连续
    适用场景 已有物理页需虚拟连续访问 直接分配大块虚拟连续内存

常见问题

  1. 为什么映射失败?

    • 物理页不足(alloc_page() 失败)
    • 虚拟地址空间耗尽(特别是 32 位系统)
    • 无效的 flagsprot 参数
  2. 能否用于用户空间?

    • 否!vmap 返回的是内核空间地址,用户空间需通过 mmap 等机制访问。
  3. 性能注意事项

    • 频繁映射/解除映射会导致 TLB 刷新,影响性能
    • 长期存在的映射更高效

提示:完整示例需编译为内核模块测试。确保系统有足够内存,并检查内核日志(dmesg)查看输出结果。