Linux设备驱动之mmap设备操作

本文介绍mmap系统调用的功能及其实现原理,包括如何通过mmap将文件内容映射到进程虚拟地址空间,并通过示例代码展示mmap的应用场景。此外,还探讨了mmap在设备驱动中的作用。

在Linux设备驱动中,mmap操作允许用户空间程序直接将设备内存映射到其地址空间,从而绕过内核缓冲区的复制,提升I/O效率。以下是实现mmap设备操作的详细步骤和代码示例:


1. 驱动中实现mmap的核心步骤

​(1) 分配设备内存

为设备分配物理连续的内存(如使用dma_alloc_coherentkmalloc):

static char *kernel_buffer;
static dma_addr_t phys_addr;

// 模块初始化时分配内存
static int __init mydev_init(void) {
    kernel_buffer = dma_alloc_coherent(NULL, BUFFER_SIZE, &phys_addr, GFP_KERNEL);
    if (!kernel_buffer)
        return -ENOMEM;
    return 0;
}

// 模块退出时释放内存
static void __exit mydev_exit(void) {
    dma_free_coherent(NULL, BUFFER_SIZE, kernel_buffer, phys_addr);
}
​(2) 实现mmap方法

file_operations中定义mmap函数指针:

static int mydev_mmap(struct file *filp, struct vm_area_struct *vma) {
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;  // 计算字节偏移
    unsigned long pfn = phys_to_pfn(phys_addr + offset);  // 物理地址转PFN
    unsigned long size = vma->vm_end - vma->vm_start;

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

    // 设置非缓存属性
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

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

    return 0;
}

static struct file_operations mydev_fops = {
    .owner = THIS_MODULE,
    .mmap = mydev_mmap,
    // 其他操作(open/release等)...
};
​(3) 处理用户空间偏移和权限
  • 偏移量处理:用户空间的mmap调用通过offset参数选择不同内存区域。
  • 权限设置:禁用缓存以确保实时访问:
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);  // 完全非缓存
    // 或
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);  // 写合并(适用于帧缓冲区)

2. 用户空间测试程序

用户程序通过mmap访问设备内存:

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

#define BUFFER_SIZE 4096

int main() {
    int fd = open("/dev/mydevice", O_RDWR);
    if (fd < 0) {
        perror("open failed");
        return -1;
    }

    // 映射设备内存
    char *user_buffer = mmap(NULL, BUFFER_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (user_buffer == MAP_FAILED) {
        perror("mmap failed");
        close(fd);
        return -1;
    }

    // 读写操作
    sprintf(user_buffer, "Hello from userspace!");
    printf("Read from device: %s\n", user_buffer);

    // 解除映射
    munmap(user_buffer, BUFFER_SIZE);
    close(fd);
    return 0;
}

3. 关键注意事项

​(1) 内存类型选择
  • DMA内存:使用dma_alloc_coherent分配,适用于需要与设备DMA操作共享的内存。
  • 内核常规内存:使用kmalloc分配(物理连续),但需确保GFP_DMA标志以兼容DMA。
​(2) 物理地址转换
  • PFN计算:通过phys_to_pfn将物理地址转换为页帧号。
  • High Memory处理:若设备内存位于高端内存(HIGHMEM),需使用kmap临时映射,但remap_pfn_range已自动处理。
​(3) 安全性检查
  • 偏移和长度校验:防止用户空间映射超出设备内存范围。
  • 并发访问控制:使用互斥锁保护共享资源:
    static DEFINE_MUTEX(mmap_mutex);
    
    static int mydev_mmap(...) {
        mutex_lock(&mmap_mutex);
        // 临界区操作...
        mutex_unlock(&mmap_mutex);
    }

4. 高级场景:非连续内存映射

若设备内存非连续(如通过vmalloc分配),需逐页映射:

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

    for (i = 0; i < (size >> PAGE_SHIFT); i++) {
        unsigned long pfn = vmalloc_to_pfn(kernel_buffer + offset + i*PAGE_SIZE);
        if (vm_insert_page(vma, vma->vm_start + i*PAGE_SIZE, pfn_to_page(pfn)))
            return -EAGAIN;
    }
    return 0;
}

5. 调试技巧

  • 查看映射状态
    cat /proc/<pid>/maps   # 查看进程的内存映射
  • 内核日志调试
    printk(KERN_DEBUG "mmap: offset=0x%lx, size=%lu\n", offset, size);
  • 硬件断点:使用JTAG调试器或QEMU监视设备内存访问。

总结

通过实现mmap方法,设备驱动能够:

  1. 提升性能:避免用户空间与内核间的数据拷贝。
  2. 灵活管理内存:支持直接访问DMA缓冲区、硬件寄存器等。
  3. 简化用户程序:用户空间可直接操作设备内存。

关键点包括正确分配内存、处理物理地址转换、设置页面属性及严格的边界检查。通过合理设计,mmap成为高效设备驱动开发的核心技术之一。

参考:

  1. mmap(三) mmap测试程序,user和设备驱动传递大量数据_驱动 mmap-优快云博客 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浩瀚之水_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值