ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

用户空间与内核空间数据共享:mmap 系统调用在设备驱动中的实现与避坑

用户空间与内核空间数据共享:mmap 系统调用在设备驱动中的实现与避坑 用户空间与内核空间数据共享mmap 系统调用在设备驱动中的实现与避坑在视频图像采集如 4K 60FPS 摄像头、高速网络数据包收发、以及端侧 AI 加速器与 NPU 的权重传递场景中数据吞吐量往往高达每秒数吉字节GB/s。如果沿用传统的read()/write()系统调用内核必须通过copy_to_user()或copy_from_user()在内核缓冲区与用户态缓冲区之间进行内存拷贝。这不仅造成严重的 CPU 资源浪费更会频繁挤占 CPU 缓存导致系统性能断崖式下跌。通过在设备驱动中实现mmapMemory Map内存映射系统调用可以直接将内核空间分配的物理内存如 CMA 连续内存或设备 I/O 寄存器空间直接映射到用户空间的虚拟地址空间中struct vm_area_struct实现**零拷贝Zero-Copy**数据共享。一、传统数据拷贝 vs mmap 零拷贝内存拓扑[传统 read/write 路径 (两次拷贝 冗余开销)] 用户空间进程 Buff ──[ copy_to_user (CPU 拷贝) ]── 内核页缓存/Skb ──[ DMA 拷贝 ]── 硬件外设 [mmap 零拷贝共享路径 (直接映射)] 用户空间虚拟地址 (VMA) ────┐ ├─ (页表直接指向同一片物理页帧) ── [ 物理连续内存 / CMA ] ──[ DMA ]── 硬件外设 内核空间虚拟地址 (Kernel) ──┘二、驱动层 mmap 核心数据结构与函数当用户空间调用mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)时内核 VFS 层会创建一个struct vm_area_struct简称 VMA随后调用驱动注册的f_ops-mmap钩子。驱动的主要任务是建立 VMA 虚拟地址与物理页帧号PFN, Page Frame Number的映射关系。核心函数为remap_pfn_rangeint remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);vma: 内核传入的虚拟内存区域描述符。addr: 用户空间的起始虚拟地址vma-vm_start。pfn: 目标物理内存的起始物理页帧号physical_addr PAGE_SHIFT。size: 映射的字节大小必须是PAGE_SIZE的整数倍。prot: 页表访问保护属性决定 Cache 缓存策略。三、字符设备驱动 mmap 完整实战代码以下是一个基于 Linux 6.x 内核的完整驱动示例使用 CMA连续内存分配器分配物理连续大内存并支持用户态mmap/* cma_mmap_driver.c - 高性能零拷贝内存映射驱动 */ #include linux/module.h #include linux/fs.h #include linux/miscdevice.h #include linux/mm.h #include linux/dma-mapping.h #include linux/slab.h #define BUFFER_SIZE (16 * 1024 * 1024) // 16MB 连续大缓冲区 static void *kernel_buffer NULL; static dma_addr_t dma_handle; static struct device *cma_dev; static int my_driver_open(struct inode *inode, struct file *file) { return 0; } static int my_driver_mmap(struct file *file, struct struct vm_area_struct *vma) { unsigned long size vma-vm_end - vma-vm_start; unsigned long pfn; int ret; if (size BUFFER_SIZE) { pr_err(mmap size 0x%lx exceeds allocated buffer 0x%x\n, size, BUFFER_SIZE); return -EINVAL; } // 1. 获取物理内存对应的页帧号 (PFN) pfn dma_to_phys(cma_dev, dma_handle) PAGE_SHIFT; // 2. 配置页表属性: 对于 DMA 共享内存设置为非缓存或写合并模式规避 Cache 不一致 vma-vm_page_prot pgprot_writecombine(vma-vm_page_prot); // 3. 核心调用: 建立页表映射 ret remap_pfn_range(vma, vma-vm_start, pfn, size, vma-vm_page_prot); if (ret 0) { pr_err(remap_pfn_range failed with %d\n, ret); return ret; } pr_info(mmap successfully mapped PFN 0x%lx to User Virtual 0x%lx\n, pfn, vma-vm_start); return 0; } static const struct file_operations my_fops { .owner THIS_MODULE, .open my_driver_open, .mmap my_driver_mmap, }; static struct miscdevice my_misc_device { .minor MISC_DYNAMIC_MINOR, .name cma_zero_copy, .fops my_fops, }; static int __init cma_mmap_init(void) { int ret misc_register(my_misc_device); if (ret) return ret; cma_dev my_misc_device.this_device; // 分配物理连续的 16MB DMA 缓冲区 kernel_buffer dma_alloc_coherent(cma_dev, BUFFER_SIZE, dma_handle, GFP_KERNEL); if (!kernel_buffer) { misc_deregister(my_misc_device); return -ENOMEM; } pr_info(CMA mmap driver loaded, DMA Physical: 0x%llx\n, (u64)dma_handle); return 0; } static void __exit cma_mmap_exit(void) { if (kernel_buffer) { dma_free_coherent(cma_dev, BUFFER_SIZE, kernel_buffer, dma_handle); } misc_deregister(my_misc_device); pr_info(CMA mmap driver unloaded.\n); } module_init(cma_mmap_init); module_exit(cma_mmap_exit); MODULE_LICENSE(GPL);四、用户空间调用与校验示例在用户空间应用程序通过标准 C 语言直接映射并操作该大内存/* user_mmap_test.c - 用户态零拷贝读写测试 */ #include stdio.h #include fcntl.h #include sys/mman.h #include unistd.h #include string.h #define BUF_SIZE (16 * 1024 * 1024) int main() { int fd open(/dev/cma_zero_copy, O_RDWR); if (fd 0) { perror(Open device failed); return -1; } // 将内核驱动的物理内存映射到本进程虚拟空间 char *user_ptr (char *)mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (user_ptr MAP_FAILED) { perror(mmap failed); close(fd); return -1; } // 直接对物理内存进行读写零数据拷贝 strcpy(user_ptr, Hello Kernel Zero-Copy from User Space!); printf(Write success, data readback: %s\n, user_ptr); munmap(user_ptr, BUF_SIZE); close(fd); return 0; }五、驱动开发中的四大避坑军规必须严格保证物理地址与长度页对齐Page-Alignedremap_pfn_range中的addr和size必须严格按照PAGE_SIZE通常为 4KB对齐。如果不做对齐检查函数将直接返回-EINVAL报错。CPU Cache 与 DMA 一致性陷阱Cache Incoherency如果外设通过 DMA 正在修改该物理内存而用户空间配置了默认的带 Cache 属性pgprot_cachedCPU 可能直接读取 L1/L2 缓存中的陈旧脏数据。对于无硬件 Cache 窥探Snooping的嵌入式平台必须使用pgprot_writecombine或pgprot_noncached。防止跨越边界非法访问VMA Boundary Check在驱动的mmap回调中必须严格校验vma-vm_end - vma-vm_start是否超过了实际物理分配的上限防止恶意用户态程序利用越界映射窥探内核其他物理页面的敏感数据。不要在kmalloc分配的离散内存上直接调用remap_pfn_rangeremap_pfn_range要求物理页面必须在物理地址上完全连续。通过vmalloc分配的内存仅在虚拟地址上连续若要映射vmalloc内存必须遍历每个页面并逐页调用vm_insert_page()。
返回列表