插件窝 干货文章 Linux驱动IO篇——mmap操作

Linux驱动IO篇——mmap操作

映射 struct page vma 106    来源:    2025-04-14

Linux驱动IO篇——mmap操作详解

1. mmap概述

mmap是一种将文件或设备映射到进程地址空间的内存映射方法,它提供了直接访问设备内存的能力,避免了用户空间和内核空间之间的数据拷贝。

主要特点:

  • 减少数据拷贝次数,提高I/O效率
  • 允许随机访问映射区域
  • 多个进程可以共享映射区域
  • 可以用于大文件处理(只加载需要的部分)

2. 驱动中的mmap实现

在Linux驱动中实现mmap操作需要提供mmap文件操作函数:

int (*mmap) (struct file *filp, struct vm_area_struct *vma);

关键数据结构

struct vm_area_struct:描述进程的虚拟内存区域,包含以下重要字段: - vm_start/vm_end:虚拟地址范围 - vm_page_prot:保护权限 - vm_flags:区域标志 - vm_ops:操作集合指针

3. 基本mmap实现示例

static int mydrv_mmap(struct file *filp, struct vm_area_struct *vma)
{
    struct my_device *dev = filp->private_data;
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    unsigned long size = vma->vm_end - vma->vm_start;

    // 检查映射范围是否合法
    if (offset + size > dev->mem_size)
        return -EINVAL;

    // 设置内存区域不可缓存
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    // 映射物理内存到用户空间
    if (remap_pfn_range(vma, vma->vm_start, 
                        dev->phys_addr >> PAGE_SHIFT,
                        size, vma->vm_page_prot))
        return -EAGAIN;

    return 0;
}

4. 高级mmap技术

4.1 使用vm_operations_struct

static const struct vm_operations_struct mydrv_vm_ops = {
    .open = mydrv_vma_open,
    .close = mydrv_vma_close,
    .fault = mydrv_vma_fault,
};

static int mydrv_mmap(struct file *filp, struct vm_area_struct *vma)
{
    // ... 基本检查 ...

    vma->vm_ops = &mydrv_vm_ops;
    mydrv_vma_open(vma);

    return 0;
}

4.2 处理页错误

当访问未映射的页面时触发页错误处理:

static int mydrv_vma_fault(struct vm_fault *vmf)
{
    struct page *page;
    unsigned long offset;
    struct my_device *dev = vmf->vma->vm_private_data;

    offset = vmf->pgoff - (vmf->vma->vm_pgoff);
    if (offset >= dev->num_pages)
        return VM_FAULT_SIGBUS;

    page = virt_to_page(dev->mem_base + (offset << PAGE_SHIFT));
    get_page(page);
    vmf->page = page;

    return 0;
}

5. 内存类型处理

根据设备特性选择合适的映射方式:

// 无缓存映射(适合寄存器访问)
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

// 写合并映射
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

// 默认缓存映射(适合普通内存)
// 无需修改vm_page_prot

6. 用户空间使用示例

int main()
{
    int fd = open("/dev/mydrv", O_RDWR);
    void *mapped = mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE, 
                       MAP_SHARED, fd, 0);

    if (mapped == MAP_FAILED) {
        perror("mmap");
        return -1;
    }

    // 直接访问设备内存
    *((volatile uint32_t *)mapped) = 0x12345678;
    uint32_t val = *((volatile uint32_t *)mapped);

    munmap(mapped, MAP_SIZE);
    close(fd);
    return 0;
}

7. 常见问题与调试

7.1 常见错误

  • EINVAL: 无效参数(如大小超出范围)
  • EAGAIN: 资源暂时不可用(如remap_pfn_range失败)
  • ENODEV: 设备不支持mmap
  • ENOMEM: 内存不足

7.2 调试技巧

  1. 检查/proc/<pid>/maps查看映射是否正确建立
  2. 使用strace跟踪mmap系统调用
  3. 在驱动中添加调试打印,检查参数和返回值
  4. 使用perfftrace分析性能问题

8. 性能考虑

  1. TLB抖动:频繁的mmap/munmap会导致TLB刷新,影响性能
  2. 页表开销:大映射区域会增加页表内存消耗
  3. 对齐要求:确保映射地址和大小是页面对齐的
  4. 缓存一致性:对于DMA操作,需要处理缓存一致性

9. 安全注意事项

  1. 严格验证用户提供的参数(偏移量、大小等)
  2. 限制单个进程的映射数量
  3. 对于敏感设备,考虑增加访问权限检查
  4. 避免将内核地址空间直接暴露给用户空间

mmap操作是Linux驱动开发中强大的工具,合理使用可以显著提高I/O性能,但也需要谨慎处理安全和稳定性问题。