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)
使用注意事项
物理页管理
需预先分配好物理页(如 alloc_page() 或 kmalloc()),调用方负责释放
映射生命周期
- 映射仅在当前上下文有效(不可跨进程)
- 长期持有可能导致vmalloc空间碎片
内存类型
支持普通内存和高端内存(HIGHMEM)
错误处理
vmap() 失败返回 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 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
static struct page **pages = NULL; static void *vaddr = NULL;
static int __init vmap_demo_init(void) { int i; pages = kmalloc_array(NUM_PAGES, sizeof(struct page*), GFP_KERNEL); if (!pages) return -ENOMEM;
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; } }
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);
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) { vunmap(vaddr); printk(KERN_INFO "vunmap completed\n"); }
if (pages) { 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");
|
关键操作流程
分配物理页数组
kmalloc_array() 分配页指针数组
获取物理内存
alloc_page() 为每个元素分配物理页
建立虚拟映射
vmap() 创建连续虚拟地址映射
使用映射内存
通过返回的虚拟地址访问内存
清理资源
vunmap() → 释放物理页 → 释放指针数组
常见问题排查
映射失败:
- 检查
vmalloc 空间是否耗尽:grep VmallocUsed /proc/meminfo
- 减少映射页数或重新编译内核增大
vmalloc 空间
内存泄漏:
- 确保每次
vmap() 都有对应的 vunmap()
- 使用
kmemleak 工具检测未释放内存
非法访问:
- 不要跨模块传递
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 |
需要清零内存 |
额外初始化开销 |
使用注意事项
上下文匹配
- 进程上下文:优先用
GFP_KERNEL
- 中断上下文:必须用
GFP_ATOMIC
大小限制
- 最大分配尺寸:
KMALLOC_MAX_SIZE(通常 4MB)
- 小内存(< PAGE_SIZE)使用 slab 分配器
内存对齐
- 默认按
ARCH_KMALLOC_MINALIGN 对齐
- 特殊对齐需求用
kmem_cache_alloc
内存泄漏检测
错误处理
完整 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
static char *kernel_buffer = NULL;
static int __init kmalloc_demo_init(void) { 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); const char *message = "Hello Kernel Memory!"; strncpy(kernel_buffer, message, strlen(message)); pr_info("Buffer content: %s\n", kernel_buffer); 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) { 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); 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 2
| buffer = kmalloc(512, GFP_KERNEL);
|
内存初始化
1 2
| buffer = kzalloc(size, GFP_KERNEL);
|
大内存分配
1 2 3 4
| if (size > KMALLOC_MAX_SIZE) { pages = alloc_pages(GFP_KERNEL, get_order(size)); }
|
DMA 内存分配
1 2
| dma_buf = kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA);
|
调试技巧
1 2
| pr_info("Max kmalloc size: %zu\n", KMALLOC_MAX_SIZE);
|
常见错误排查
内核崩溃 (Oops)
- 检查是否在中断上下文中使用了
GFP_KERNEL
- 验证是否访问了已释放的内存
分配失败
- 检查
/proc/buddyinfo 内存碎片情况
- 尝试减少分配大小或使用
vmalloc
内存损坏
- 使用
KASAN 工具检测越界访问
- 检查是否有重复释放操作
性能问题
- 频繁小内存分配/释放考虑使用内存池
1 2
| mempool_t *pool = mempool_create_kmalloc_pool(min_nr, size);
|