Linux

Linux内核vmap接口用法及使用注意事项,提供一个demo

Linux内核vmap接口详解

接口功能

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

核心函数

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

// 创建映射
void *vmap(struct page **pages, unsigned int count,
unsigned long flags, pgprot_t prot);

// 解除映射
void vunmap(const void *addr);

参数说明

  • pages: 指向 struct page* 数组的指针
  • count: 要映射的页数
  • flags: 映射标志(常用 VM_MAP | VM_IOREMAP
  • prot: 页面保护标志(如 PAGE_KERNEL

使用注意事项

  1. 物理页管理
    需预先分配好物理页(如 alloc_page()kmalloc()),调用方负责释放

  2. 映射生命周期

    • 映射仅在当前上下文有效(不可跨进程)
    • 长期持有可能导致vmalloc空间碎片
  3. 内存类型
    支持普通内存和高端内存(HIGHMEM)

  4. 错误处理
    vmap() 失败返回 NULL,必须检查返回值

  5. 性能影响
    频繁映射/解除映射影响性能,适合静态场景

  6. 并发安全
    非原子操作,需同步机制保护共享访问


完整Demo代码

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
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/slab.h>

#define NUM_PAGES 4 // 映射4个页(16KB on 4K pages)

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

static int __init vmap_demo_init(void)
{
int i;

// 1. 分配物理页指针数组
pages = kmalloc_array(NUM_PAGES, sizeof(struct page*), GFP_KERNEL);
if (!pages) return -ENOMEM;

// 2. 分配物理内存页
for (i = 0; i < NUM_PAGES; i++) {
pages[i] = alloc_page(GFP_KERNEL); // 分配单页
if (!pages[i]) {
while (--i >= 0) __free_page(pages[i]);
kfree(pages);
return -ENOMEM;
}
}

// 3. 执行vmap映射
vaddr = vmap(pages, NUM_PAGES, VM_MAP | VM_IOREMAP, 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: 0x%px\n", vaddr);

// 4. 使用映射内存(示例:写模式)
memset(vaddr, 0xAA, NUM_PAGES * PAGE_SIZE);
printk(KERN_INFO "First byte: 0x%x\n", *((u8 *)vaddr));

return 0;
}

static void __exit vmap_demo_exit(void)
{
if (vaddr) {
// 5. 解除映射
vunmap(vaddr);
printk(KERN_INFO "vunmap completed\n");
}

if (pages) {
// 6. 释放物理页
for (int i = 0; i < NUM_PAGES; i++) {
if (pages[i]) __free_page(pages[i]);
}
kfree(pages);
printk(KERN_INFO "Physical pages freed\n");
}
}

module_init(vmap_demo_init);
module_exit(vmap_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Linux vmap API Demo");

关键操作流程

  1. 分配物理页数组
    kmalloc_array() 分配页指针数组

  2. 获取物理内存
    alloc_page() 为每个元素分配物理页

  3. 建立虚拟映射
    vmap() 创建连续虚拟地址映射

  4. 使用映射内存
    通过返回的虚拟地址访问内存

  5. 清理资源
    vunmap() → 释放物理页 → 释放指针数组


常见问题排查

  1. 映射失败

    • 检查 vmalloc 空间是否耗尽:grep VmallocUsed /proc/meminfo
    • 减少映射页数或重新编译内核增大 vmalloc 空间
  2. 内存泄漏

    • 确保每次 vmap() 都有对应的 vunmap()
    • 使用 kmemleak 工具检测未释放内存
  3. 非法访问

    • 不要跨模块传递 vaddr 指针
    • 访问范围勿超过 NUM_PAGES * PAGE_SIZE

性能提示:对于频繁访问的场景,考虑改用 kmalloc() 获取物理连续内存,避免映射开销。

Linux kmalloc接口用法

Linux kmalloc 接口详解

kmalloc 是 Linux 内核中最常用的内存分配接口之一,用于分配物理地址连续的内核空间内存。

核心特性

  • 物理连续性:分配的页帧在物理内存中连续
  • 大小限制:最大分配尺寸由系统配置决定(通常 4KB-4MB)
  • 高效性:比 vmalloc 更快(无页表操作)
  • 适用场景:DMA 操作、硬件缓冲区等需要物理连续性的场景

函数原型

1
2
3
4
#include <linux/slab.h>

void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *objp);

参数说明

  • size:请求的字节数
  • flags:分配控制标志(关键参数!)
  • objp:要释放的内存指针

常用 GFP 标志(Get Free Page)

标志 适用场景 特性
GFP_KERNEL 进程上下文 可能睡眠,最常用
GFP_ATOMIC 中断/原子上下文 永不睡眠,紧急分配
GFP_DMA DMA 操作 从 ZONE_DMA 分配
GFP_HIGHUSER 用户空间映射 高端内存分配
GFP_ZERO 需要清零内存 额外初始化开销

使用注意事项

  1. 上下文匹配

    • 进程上下文:优先用 GFP_KERNEL
    • 中断上下文:必须GFP_ATOMIC
  2. 大小限制

    • 最大分配尺寸:KMALLOC_MAX_SIZE(通常 4MB)
    • 小内存(< PAGE_SIZE)使用 slab 分配器
  3. 内存对齐

    • 默认按 ARCH_KMALLOC_MINALIGN 对齐
    • 特殊对齐需求用 kmem_cache_alloc
  4. 内存泄漏检测

    • 使用 kmemleak 工具检测未释放内存
  5. 错误处理

    • 必须检查返回的指针是否为 NULL

完整 Demo 代码

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
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>

#define BUF_SIZE 2048 // 分配 2KB 内存

static char *kernel_buffer = NULL;

static int __init kmalloc_demo_init(void)
{
// 1. 分配内核内存 (进程上下文使用 GFP_KERNEL)
kernel_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
if (!kernel_buffer) {
pr_err("kmalloc failed!\n");
return -ENOMEM;
}

pr_info("Allocated %d bytes at address %px\n", BUF_SIZE, kernel_buffer);

// 2. 使用内存
const char *message = "Hello Kernel Memory!";
strncpy(kernel_buffer, message, strlen(message));

// 3. 验证内容
pr_info("Buffer content: %s\n", kernel_buffer);

// 4. 模拟错误处理路径 (不要在实际模块中这样使用)
if (kernel_buffer[0] != 'H') {
pr_err("Verification failed! Cleaning up...\n");
kfree(kernel_buffer);
return -EIO;
}

return 0;
}

static void __exit kmalloc_demo_exit(void)
{
// 5. 释放内存
if (kernel_buffer) {
kfree(kernel_buffer);
pr_info("Memory freed\n");
} else {
pr_warn("No memory to free\n");
}
}

module_init(kmalloc_demo_init);
module_exit(kmalloc_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Linux kmalloc API Demo");

中断上下文示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在中断处理函数中的使用
irqreturn_t interrupt_handler(int irq, void *dev_id)
{
char *temp_buf = kmalloc(256, GFP_ATOMIC); // 必须使用 GFP_ATOMIC!
if (!temp_buf) {
pr_err_ratelimited("Allocation failed in interrupt!\n");
return IRQ_NONE;
}

// 处理中断数据...
process_interrupt_data(temp_buf);

kfree(temp_buf);
return IRQ_HANDLED;
}

最佳实践指南

  1. 分配大小优化

    1
    2
    // 避免碎片化:优先使用2的幂次大小
    buffer = kmalloc(512, GFP_KERNEL); // 优于 kmalloc(500, GFP_KERNEL)
  2. 内存初始化

    1
    2
    // 需要清零的内存:
    buffer = kzalloc(size, GFP_KERNEL); // 等效 kmalloc + memset
  3. 大内存分配

    1
    2
    3
    4
    // 超过 KMALLOC_MAX_SIZE 时使用页分配器
    if (size > KMALLOC_MAX_SIZE) {
    pages = alloc_pages(GFP_KERNEL, get_order(size));
    }
  4. DMA 内存分配

    1
    2
    // 需要 DMA 访问的内存:
    dma_buf = kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA);
  5. 调试技巧

    1
    2
    // 检查分配大小限制
    pr_info("Max kmalloc size: %zu\n", KMALLOC_MAX_SIZE);

常见错误排查

  1. 内核崩溃 (Oops)

    • 检查是否在中断上下文中使用了 GFP_KERNEL
    • 验证是否访问了已释放的内存
  2. 分配失败

    • 检查 /proc/buddyinfo 内存碎片情况
    • 尝试减少分配大小或使用 vmalloc
  3. 内存损坏

    • 使用 KASAN 工具检测越界访问
    • 检查是否有重复释放操作
  4. 性能问题

    • 频繁小内存分配/释放考虑使用内存池
      1
      2
      // 创建内存池
      mempool_t *pool = mempool_create_kmalloc_pool(min_nr, size);