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

在Linux中将设备驱动的地址映射到用户空间

本期主题:
MMU的简单介绍,以及如何实现设备地址映射到用户空间


往期链接:

  • Linux内核链表
  • 零长度数组的使用
  • inline的作用
  • 嵌入式C基础——ARRAY_SIZE使用以及踩坑分析
  • Linux下如何操作寄存器(用户空间、内核空间方法讲解)

目录

  • 0. 问题背景
  • 1. MMU,虚拟内存
    • 1. 存在的原因
    • 2. 内存管理单元
  • 2. 将设备地址映射到用户空间
    • 1. 原理说明
    • 2. 系统调用mmap例子
    • 3. 驱动的mmap例子


0. 问题背景

在项目开发的过程中,遇到了需要 用户空间访问驱动里分配的数据的问题,趁机把这块知识补齐了一下,由于需要高频访问,所以 copy_to_user这种方案肯定是不可行的。

1. MMU,虚拟内存

1. 存在的原因

首先理解一下,为什么需要有虚拟内存这么一个概念,总结了以下好处:

  1. 进程隔离和内存保护,如果没有虚拟内存,假如A进程发生了地址访问问题,可能直接把不相关的B进程也影响到了,这样是不合理的。虚拟内存的方法就给每个进程提供了自己的虚拟空间,增加了系统的安全性和稳定性。
  2. 地址空间重用,物理内存地址是有限且连续的,而虚拟内存允许操作系统在物理内存中灵活地管理进程使用的地址空间。进程的虚拟地址空间可以是连续的,而实际的物理内存可以是不连续的,这种方式更高效地使用内存并减少碎片。
  3. 简化编程模型,使用虚拟内存,开发人员可以编写和管理程序而不必担心物理内存的实际大小和地址布局,这让开发变得更简单。

2. 内存管理单元

高性能处理器一般会提供一个内存管理单元MMU,该单元辅助操作系统进行内存管理,现代处理器常用的方案是 虚拟寻址方式(virtual addressing),参考下图:
在这里插入图片描述
简单介绍:

使用虚拟寻址时

  1. CPU会通过虚拟地址(virtual address, VA)来访问主存
  2. MMU进行地址翻译,把虚拟地址转换成物理地址
  3. 最后进行访问

2. 将设备地址映射到用户空间

1. 原理说明

一般情况下,用户空间是不能直接访问设备的寄存器或者地址空间的
但是,可以在设备驱动程序中实现mmap()函数,这个函数可以使得用户空间直接访问设备的地址空间。

mmap实现了这样的映射过程:

  1. 将用户空间的一段内存与设备内存关联
  2. 当用户访问用户空间的这段地址范围时,转换成对设备的访问

这种特性对显示一类的设备非常有意义,这样在用户空间就可以直接访问了,不需要从内核空间到用户空间的copy过程。

2. 系统调用mmap例子

写一个例子,mmap将文件映射到memory中去访问
在这里插入图片描述
例子:映射一个文件,并把内容打印出来

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char *argv[]) {// 检查是否传递了文件名if (argc < 2) {printf("Usage: %s <filename>\n", argv[0]);exit(EXIT_FAILURE);}const char *filename = argv[1];// 打开文件int fd = open(filename, O_RDONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 获取文件大小struct stat sb;if (fstat(fd, &sb) == -1) {perror("fstat");close(fd);exit(EXIT_FAILURE);}// 使用 mmap 将文件映射到内存char *mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (mapped == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 关闭文件描述符,文件已映射不需要再保持打开状态close(fd);// 打印文件内容for (off_t i = 0; i < sb.st_size; i++) {putchar(mapped[i]);}// 解除内存映射if (munmap(mapped, sb.st_size) == -1) {perror("munmap");exit(EXIT_FAILURE);}return 0;
}

实测:
在这里插入图片描述

3. 驱动的mmap例子

例子流程:

  1. 驱动里分配好空间
  2. 分配的空间,32PAGE,128KB,虚拟地址通过驱动的mmap file_operations映射出去
  3. 用户进程通过mmap直接访问这段空间,user1去改这段空间,usr2读取这段空间

代码:
driver:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/io.h>#define BUF_SIZE (32*PAGE_SIZE)static void *kbuff;static int remap_pfn_open(struct inode *inode, struct file *file)
{struct mm_struct *mm = current->mm;printk("client: %s (%d)\n", current->comm, current->pid);printk("code  section: [0x%lx   0x%lx]\n", mm->start_code, mm->end_code);printk("data  section: [0x%lx   0x%lx]\n", mm->start_data, mm->end_data);printk("brk   section: s: 0x%lx, c: 0x%lx\n", mm->start_brk, mm->brk);printk("mmap  section: s: 0x%lx\n", mm->mmap_base);printk("stack section: s: 0x%lx\n", mm->start_stack);printk("arg   section: [0x%lx   0x%lx]\n", mm->arg_start, mm->arg_end);printk("env   section: [0x%lx   0x%lx]\n", mm->env_start, mm->env_end);return 0;
}static int remap_pfn_mmap(struct file *file, struct vm_area_struct *vma)
{unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;unsigned long pfn_start = (virt_to_phys(kbuff) >> PAGE_SHIFT) + vma->vm_pgoff;unsigned long virt_start = (unsigned long)kbuff + offset;unsigned long size = vma->vm_end - vma->vm_start;int ret = 0;printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\n", pfn_start << PAGE_SHIFT, offset, size);ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);if (ret)printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]\n",__func__, vma->vm_start, vma->vm_end);elseprintk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __func__, virt_start,vma->vm_start, size);return ret;
}static const struct file_operations remap_pfn_fops = {.owner = THIS_MODULE,.open = remap_pfn_open,.mmap = remap_pfn_mmap,
};static struct miscdevice remap_pfn_misc = {.minor = MISC_DYNAMIC_MINOR,.name = "remap_pfn",.fops = &remap_pfn_fops,
};static int __init remap_pfn_init(void)
{int ret = 0;kbuff = kzalloc(BUF_SIZE, GFP_KERNEL);if (!kbuff) {ret = -ENOMEM;goto err;}ret = misc_register(&remap_pfn_misc);if (unlikely(ret)) {pr_err("failed to register misc device!\n");goto err;}pr_info("register misc device ok!\n");return 0;err:return ret;
}static void __exit remap_pfn_exit(void)
{misc_deregister(&remap_pfn_misc);kfree(kbuff);pr_info("deregister misc device ok!\n");
}module_init(remap_pfn_init);
module_exit(remap_pfn_exit);
MODULE_LICENSE("GPL");

usr:

//user1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>#define PAGE_SIZE (4*1024)
#define BUF_SIZE (16*PAGE_SIZE)
#define OFFSET (0)int main(int argc, const char *argv[])
{int fd;void *addr = NULL;fd = open("/dev/remap_pfn", O_RDWR);if (fd < 0) {perror("open failed\n");exit(-1);}addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);if (!addr) {perror("mmap failed\n");exit(-1);}for (int i = 0; i < 16; i++) {for (int j = 0; j < 10; j++) {*(uint8_t *)((uintptr_t)addr + i*PAGE_SIZE + j) = j;}}return 0;
}
//user2
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>#define PAGE_SIZE (4*1024)
#define BUF_SIZE (16*PAGE_SIZE)
#define OFFSET (0)int main(int argc, const char *argv[])
{int fd;char *addr = NULL;fd = open("/dev/remap_pfn", O_RDWR);if (fd < 0) {perror("open failed\n");exit(-1);}addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, OFFSET);if (!addr) {perror("mmap failed\n");exit(-1);}for (int i = 0; i < 16; i++) {for (int j = 0; j < 10; j++) {if (*(uint8_t *)((uintptr_t)addr + i*PAGE_SIZE + j) != j) {printf("error, i: %d, j: %d", i, j);} else {printf("i: %d, j: %d, data: 0x%x\n", i, j, *(uint8_t *)((uintptr_t)addr + i*PAGE_SIZE + j));}}}return 0;
}

测试结果:
在这里插入图片描述
用户进程2的打印:
在这里插入图片描述

http://www.lryc.cn/news/451543.html

相关文章:

  • 电脑自带dll修复在哪里,dll丢失的6种解决方法总结
  • k8s基于nfs创建storageClass
  • Chrome无法拖入加载.crx扩展文件(以IDM为例)
  • 数字教学时代:构建高效在线帮助中心的重要性
  • 828华为云征文|华为云弹性云服务器FlexusX实例下的Nginx性能测试
  • 知识图谱入门——2:技术体系基本概念:知识表示与建模、知识抽取与挖掘、知识存储与融合、知识推理与检索
  • 【不看会后悔系列】排序之——文件归并【史上最全详解】~
  • 安全点的应用场景及其原理详解
  • 计算机各专业2025毕业设计选题推荐【各专业 | 最新】
  • 【Python报错已解决】IndexError: index 0 is out of bounds for axis 1 with size 0
  • SpringGateway(网关)微服务
  • jQuery面试题:(第三天)
  • 聊聊国内首台重大技术装备(2)
  • python 实现rayleigh quotient瑞利商算法
  • Java Web应用升级故障案例解析
  • Java类和对象、自定义包、static、代码块、方法重写
  • 【系统代码】招投标采购一体化管理系统,JAVA+vue
  • 基于yolov8深度学习的120种犬类检测与识别系统python源码+onnx模型+评估指标曲线+精美GUI界面目标检测狗类检测犬类识别系统
  • UNI-APP_iOS开发技巧之:跳转到TestFlight或者App Store
  • 基于SSM+Vue技术的定制式音乐资讯平台
  • Spring依赖注入和注解驱动详解和案例示范
  • 网络通信——OSPF协议(基础篇)
  • Kubernetes从零到精通(15-安全)
  • 《蓝桥杯算法入门》(C/C++、Java、Python三个版本)24年10月出版
  • Soar项目中添加一条新的SQL审核规则示例
  • RISC-V开发 linux下GCC编译自定义指令流程笔记
  • java代码是如何与数据库通信的?
  • gateway--网关
  • 北京数字孪生工业互联网可视化技术,赋能新型工业化智能制造工厂
  • 土地规划与区域经济发展:筑基均衡未来的战略经纬