当前位置: 首页 > news >正文

内存管理之文件内存映射(mmap):外存(磁盘/flash)的文件映射到应用层(跨越内核层)

一、简介

文件内存映射(Memory-mapped File) 是一种将文件内容直接映射到进程虚拟地址空间的技术。通过调用 mmap() 系统调用,程序可以像访问普通内存一样访问文件数据,而无需反复使用 read()write() 进行数据拷贝。内存映射提高了文件访问效率,适合处理大文件、共享内存和与设备驱动的交互等场景。常见用法包括读取大规模日志、图像处理、数据库缓存等。

二、驱动测试代码

driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>#define DEVICE_NAME "led"
#define MEM_SIZE    (PAGE_SIZE * 2)static dev_t dev_num;
static struct cdev led_cdev;
static struct class *led_class;
static char *kernel_buffer = NULL;static int led_open(struct inode *inode, struct file *filp) {return 0;
}static int led_release(struct inode *inode, struct file *filp) {return 0;
}static ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset) {size_t available_size = MEM_SIZE - *offset;// Check if offset exceeds memory sizeif (*offset >= MEM_SIZE) {return 0;}// Adjust len if it exceeds remaining sizeif (len > available_size) {len = available_size;}// Copy data from kernel buffer to user spaceif (copy_to_user(buf, kernel_buffer + *offset, len)) {return -EFAULT;}// Update offset*offset += len;return len;
}static ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset) {size_t available_size = MEM_SIZE - *offset;// Check if offset exceeds memory sizeif (*offset >= MEM_SIZE) {return -ENOMEM;}// Adjust len if it exceeds remaining sizeif (len > available_size) {len = available_size;}// Copy data from user space to kernel bufferif (copy_from_user(kernel_buffer + *offset, buf, len)) {return -EFAULT;}// Update offset*offset += len;return len;
}static int led_mmap(struct file *filp, struct vm_area_struct *vma) { //vma(vm_area_struct):表示用户空间的一段虚拟内存区域//计算用户请求映射的虚拟内存大小(单位:字节),即用户传入的地址范围(或大小)unsigned long size = vma->vm_end - vma->vm_start;//使用virt_to_phys()将内核虚地址转成物理地址,并右移12位(除4096)算得页帧号(Page Frame Number)unsigned long pfn = virt_to_phys(kernel_buffer) >> PAGE_SHIFT;pr_info("Physical address: %lx\n", pfn);// Ensure requested size does not exceed the buffer sizeif (size > MEM_SIZE) {return -EINVAL;}// Map the memory,将物理页帧 pfn 开始的物理内存区域映射到用户空间的 [vm_start, vm_end)if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) {return -EAGAIN;}return 0;
}static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.release = led_release,.read = led_read,.write = led_write,.mmap = led_mmap,
};static int __init led_drv_init(void) {int ret;// Allocate device numberret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);if (ret) {return ret;}// Initialize and add character devicecdev_init(&led_cdev, &led_fops);ret = cdev_add(&led_cdev, dev_num, 1);if (ret) {goto unregister_dev;}// Create device classled_class = class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(led_class)) {ret = PTR_ERR(led_class);goto del_cdev;}// Create device file,不需要再手动 mknoddevice_create(led_class, NULL, dev_num, NULL, DEVICE_NAME);// Allocate memory for the kernel bufferkernel_buffer = (char *)kmalloc(MEM_SIZE, GFP_KERNEL);if (!kernel_buffer) {ret = -ENOMEM;goto destroy_device;}pr_info("LED driver initialized 2\n");return 0;destroy_device:device_destroy(led_class, dev_num);class_destroy(led_class);
del_cdev:cdev_del(&led_cdev);
unregister_dev:unregister_chrdev_region(dev_num, 1);return ret;
}static void __exit led_drv_exit(void) {kfree(kernel_buffer);device_destroy(led_class, dev_num);class_destroy(led_class);cdev_del(&led_cdev);unregister_chrdev_region(dev_num, 1);pr_info("LED driver unloaded\n");
}module_init(led_drv_init);
module_exit(led_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("ChatGPT");
MODULE_DESCRIPTION("LED device driver supporting mmap and read/write");

app.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#define MAP_SIZE 4096  // 建议页大小对齐int main(void)
{int fd;int ret;char *buffer;char *mapBuf;char *ReadBuff;fd = open("/dev/led", O_RDWR);if (fd < 0) {perror("open failed");return -1;}buffer = (char *)malloc(MAP_SIZE);if (!buffer) {perror("malloc failed");close(fd);return -1;}memset(buffer, 0, MAP_SIZE);ReadBuff = (char *)malloc(25);if (!buffer) {perror("malloc failed");close(fd);return -1;}memset(ReadBuff, 0, 25);/*通过 mmap 将指定文件或者设备的内存映射到用户空间,使得用户可以直接读写映射区域的数据,*       而不需要通过常规的 read() 和 write() 系统调用来进行文件操作;*通过 mmap,这行代码会将 /dev/led 设备的前 MAP_SIZE 字节映射到进程的虚拟内存中,*       映射的地址存储在 mapBuf 中。此时,用户可以直接通过 mapBuf 来访问和修改设备文件中的数据。*param1:(NULL), 是指向映射区域的虚拟地址的指针; NULL 可以理解为让系统自动选择一个合适的虚拟地址来映射文件*param2:(MAP_SIZE), 是要映射的内存区域的大小,单位是字节(通常为 4KB 或 8KB)*param3:(PROT_READ | PROT_WRITE), 指定映射区域的访问权限。PROT_READ 允许读访问,PROT_WRITE 允许写访问*param4:(MAP_SHARED), 表示映射区域的更新将会反映到原始文件或设备中,也就是说,*       如果你通过 mmap 修改了 mapBuf 中的数据,这些修改将直接写回到设备文件或者设备的内存区域*param5:(fd), 是要映射的文件描述符,fd 是通过 open() 打开设备文件后返回的文件描述符*param6:(0), 这个参数指定从文件的哪个偏移量开始映射。0 表示从文件或设备的开始位置进行映射 */mapBuf = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapBuf == MAP_FAILED) {perror("mmap failed");free(buffer);close(fd);return -1;}printf("after map, the mapBuf address = %p\n", mapBuf);strncpy(mapBuf, "test data for mmap", MAP_SIZE - 1);mapBuf[MAP_SIZE - 1] = '\0';  // 保证字符串终止ret = read(fd, ReadBuff, 19);if (ret < 0) {printf("read err\n");return -1;	        } //经过实验测试,成功打印目标值printf("we now read from kernel, and we get: %s\n", ReadBuff);strncpy(buffer, mapBuf, MAP_SIZE - 1);buffer[MAP_SIZE - 1] = '\0';printf("read data is: %s\n", buffer);munmap(mapBuf, MAP_SIZE);free(buffer);close(fd);return 0;
}

三、测试结果

分别编译驱动和应用程序,将驱动模块插入内核后,运行应用程序,运行结果如下图:
在这里插入图片描述
基本可以验证:当使用mmap时,由于驱动中实现了将物理地址映射为应用层虚拟地址,因此可以在应用层直接对设备文件进行读写操作,而不再需要经由内核层的拷贝。

http://www.xdnf.cn/news/88291.html

相关文章:

  • 解析芯片低功耗设计的底层逻辑与实现方法
  • 最新项目笔记
  • Java的反射机制(曼波超易懂图文版)
  • 一洽智能硬件行业解决方案探索与实践
  • 从零开始学Python游戏编程33-指令模式2
  • AI大模型-window系统CPU版安装anaconda以及paddle详细步骤-亲测有效
  • c++STL——stack、queue、priority_queue的模拟实现
  • JDK安装超详细步骤
  • c#操作excel
  • Codeforces Round 1019 (Div. 2)(A-D)
  • 【线段树】P10381 「HOI R1」杂赛选比|普及+
  • SpringbootWeb开发(注解和依赖配置)
  • Sqlserver安全篇之_Sqlcmd命令使用windows域账号认证sqlserver遇到问题如何处理的案例
  • 基于STM32、HAL库的MCP4018T数字电位器驱动程序设计
  • 第5章-1 优化服务器设置
  • 08_Docker Portainer可视化管理
  • Kafka 面试,java实战贴
  • Java中常见API的分类概述及示例
  • Spark集群搭建-spark-local
  • [Java · 铢积寸累] 数据结构 — 数组类型 - Arrays 工具类详解
  • 文献分享:不同抗体表位作图技术比较
  • 《计算机视觉度量:从特征描述到深度学习》—深度学习图像特征工程
  • 动态加载内容时selenium如何操作?
  • Kubernetes相关的名词解释etcdctl(20)
  • 鸿蒙移动应用开发--渲染控制实验
  • 【MCP Node.js SDK 全栈进阶指南】初级篇(2):MCP基础服务器开发
  • Python3中使用jupyter notebook
  • Vue2 el-checkbox 虚拟滚动解决多选框全选卡顿问题 - 高性能处理大数据量选项列表
  • 高性能服务器配置经验指南1——刚配置好服务器应该做哪些事
  • 字符串全排列(Java版本自己用)