You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:44:53