如何在Linux内核模块中使用kzalloc与GFP_DMA为mmap分配缓冲区?
Linux内核模块中使用kzalloc与GFP_DMA为mmap分配缓冲区的实现
前置说明
kzalloc()是kmalloc()的封装变体,会自动将分配的内存初始化为0,原型为void *kzalloc(size_t size, gfp_t flags)。当指定GFP_DMA标志时,它会分配物理连续且位于DMA控制器可访问地址范围的内存,这类内存既适合外设DMA操作,也可通过mmap映射到用户空间。
实现步骤与代码示例
1. 分配DMA兼容缓冲区
在模块初始化或合适的上下文(需区分进程/原子上下文)中,使用kzalloc结合GFP_DMA标志分配内存,并获取对应的物理地址:
#include <linux/slab.h> #include <linux/dma-mapping.h> #include <linux/module.h> #define DMA_BUF_SIZE 4096 // 缓冲区大小需符合kmalloc上限(通常≤128KB) static char *dma_buffer; static dma_addr_t dma_phys_addr; static int __init my_module_init(void) { // 进程上下文用GFP_KERNEL,允许内存回收时睡眠 dma_buffer = kzalloc(DMA_BUF_SIZE, GFP_DMA | GFP_KERNEL); if (!dma_buffer) { pr_err("kzalloc DMA buffer failed\n"); return -ENOMEM; } // 转换内核虚拟地址为物理地址,用于后续mmap或DMA操作 dma_phys_addr = virt_to_phys(dma_buffer); pr_info("Allocated DMA buffer at virt: %p, phys: 0x%pa\n", dma_buffer, &dma_phys_addr); return 0; }
- 若处于中断/原子上下文(不可睡眠),需将
GFP_KERNEL替换为GFP_ATOMIC。 - 缓冲区大小不能超过
kmalloc的最大分配值,不同架构/内核版本通常上限为128KB,超出需改用dma_alloc_coherent()等方案。
2. 实现mmap回调函数
要将内核缓冲区映射到用户空间,需在file_operations结构体中实现mmap回调,通过remap_pfn_range完成物理页到用户虚拟地址的映射:
#include <linux/mm.h> #include <linux/fs.h> static int my_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long map_size = vma->vm_end - vma->vm_start; unsigned long pfn; // 校验映射大小是否匹配缓冲区 if (map_size > DMA_BUF_SIZE) { pr_err("mmap size exceeds buffer size\n"); return -EINVAL; } // 设置内存属性:允许用户空间与内核共享读写,标记为IO内存 vma->vm_flags |= VM_SHARED | VM_IO; // 非缓存属性适配DMA操作,避免缓存一致性问题 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); // 转换物理地址为页帧号(PFN) pfn = dma_phys_addr >> PAGE_SHIFT; // 完成物理页到用户虚拟地址的映射 if (remap_pfn_range(vma, vma->vm_start, pfn, map_size, vma->vm_page_prot)) { pr_err("remap_pfn_range failed\n"); return -EAGAIN; } return 0; } // 定义文件操作结构体 static const struct file_operations my_fops = { .owner = THIS_MODULE, .mmap = my_mmap, // 按需补充open、release等其他操作 }; // 注册字符设备(示例,需完善设备号申请逻辑) static dev_t dev_num; static struct cdev my_cdev; static void __exit my_module_exit(void) { // 释放字符设备资源 cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); // 释放DMA缓冲区,kzalloc分配的内存统一用kfree释放 if (dma_buffer) { kfree(dma_buffer); dma_buffer = NULL; } pr_info("Module exited, DMA buffer freed\n"); } module_init(my_module_init); module_exit(my_module_exit); MODULE_LICENSE("GPL");
VM_IO用于标记内存为IO相关,内核会自动处理对应的内存访问规则;VM_SHARED允许用户空间与内核共享该内存区域。- 若不需要DMA操作,可移除
pgprot_noncached设置,使用默认缓存属性。
3. 内存释放
在模块退出或缓冲区不再使用时,必须用kfree()释放kzalloc分配的内存,避免内核内存泄漏。
关键注意事项
- 上下文限制:
GFP_KERNEL会触发内存回收(可能睡眠),仅能在进程上下文使用;原子上下文必须用GFP_ATOMIC。 - 大小限制:
kzalloc+GFP_DMA的缓冲区不能超过kmalloc的最大分配值,不同架构/内核版本通常上限为128KB。 - 缓存一致性:用于DMA操作时,必须设置非缓存属性,避免内核缓存与外设数据不一致。
内容的提问来源于stack exchange,提问作者RCY
相关产品推荐
相关产品推荐

