在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.mzph.cn/news/880845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis篇(最佳实践)(持续更新迭代)

介绍一&#xff1a;键值设计 一、优雅的key结构 Redis 的 Key 虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过 44 字节不包含特殊字符 例如&#xff1a; 我们的登录业务&#xff0…

『功能项目』宠物的攻击巨型化【80】

本章项目成果展示 我们打开上一篇79宠物的召唤跟随的项目&#xff0c; 本章要做的事情是实现在战斗中有几率触发宠物巨型化攻击将怪物击飞的效果 首先在主角预制体中增加隐藏的宠物巨型化 制作巨型化宠物的攻击效果 将该动画控制器放置在隐藏的巨型化宠物的动画控制器上 首先查…

c# object和dynamic的区别

在 C# 编程中&#xff0c;object 和 dynamic 是两个非常有用的关键字&#xff0c;但它们的使用场景和性能影响各不相同。本文将详细探讨这两者的用法、区别以及如何优化它们的使用。 1. object 关键字 1.1 什么是 object object 是 C# 中所有类型的基类。每个类型&#xff0…

mysql学习教程,从入门到精通,SQL CROSS JOIN 语句(26)

1、SQL CROSS JOIN 语句 CROSS JOIN在 SQL 中用于将两个或多个表的每一行进行组合。这意味着如果表 A 有 M 行&#xff0c;表 B 有 N 行&#xff0c;那么CROSS JOIN 的结果将包含 M * N 行。这种连接不依赖于任何连接条件&#xff0c;因此它会生成笛卡尔积。 下面是一个简单的…

Linux下的基本指令/命令(一)

目录 基本命令 1. Is命令/指令: 罗列当前目录下指定的文件或者目录. 2. pwd命令&#xff1a; 查看当前工作的路径 3. cd命令&#xff1a; 切换到指定路径下。 只能切换到目录中 4. tree命令: 树状显式目录 使用前要输入命令 yum install -y tree &#xff0c;用来安装一个…

基于Hive和Hadoop的哔哩哔哩网站分析系统

本项目是一个基于大数据技术的哔哩哔哩平台分析系统&#xff0c;旨在为用户提供全面的哔哩哔哩视频数据和深入的用户行为分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xf…

数据流处理技术与Flink框架

一数据流 数据流定义&#xff1a; 数据流&#xff08;Data Stream&#xff09;是指数据以连续不断的方式到达和处理的序列。在现实世界中&#xff0c;许多数据来源都是以流的形式存在&#xff0c;比如&#xff1a; 1. 用户行为&#xff1a;用户在网站上的点击流、移动应用中…

【Linux】几种常见配置文件介绍

配置文件目录 linux 系统中有很多配置文件目录/etc/systemd/system、/lib/systemd/system 以及/usr/lib/systemd/system 等&#xff0c;这三者有什么样的关系呢&#xff1f; 以下是网络上找的资料汇总&#xff0c;并加了一些操作验证。方便后期使用 介绍 目录/lib/systemd/s…

使用C#,MSSQL开发的钢结构加工系统

很久以前的项目&#xff0c;上位机使用C#开发。数据库使用mssql。控制系统选用了三菱PLC&#xff0c;上位机和PLC之间走ModbusTCP通讯协议。 主要功能&#xff1a;读取加工文件&#xff08;csv格式&#xff09;&#xff0c;导入到数据库&#xff0c;并根据机床刀具规则&#x…

Python编码系列—Python命令模式:将请求封装为对象

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

个人文章合集 - 前端相关

前端&#xff1a;简述表单提交前如何进行数据验证 前端&#xff1a;项目一个html中如何引入另一个html&#xff1f; 前端&#xff1a;一张图快速记忆CSS所有属性 前端&#xff1a;三个CSS预处理器(框架)-Sass、LESS 和 Stylus的比较 前端&#xff1a;基于Java角度理解nodejs/np…

Vant WeApp 开启 NPM 遇到的问题总结

新建小程序工程&#xff0c;默认未启用 NPM 管理组件 使用 Vant WeApp UI 组件库&#xff0c;需使用 NPM 安装组件&#xff0c;报错&#xff0c;因为初始工程未启用 NPM 工具 -> 构建 NPM 在控制台工程路径下初始化 NPM 构建工具环境 npm init -y 使用 NPM 安装 Vant Weap…

nvm安装 出现 Error retrieving “http://xxxx/SHASUMS256.txt“: HTTP Status 404 解决方法

目录 前言1. 问题所示2. 原理分析3. 解决方法前言 nvm的基本知识,推荐阅读:nvm安装教程,实现node的多版本管理(图文界面) 1. 问题所示 nvm安装node版本的时候出现如下问题: F:\vue_project\block_canvas>nvm install 18.19.1 Error retrieving "http://npm.t…

【软件测试】详解软件测试中的测试级别

目录 一、测试级别二、组件测试三、开发者测试3.1测试与调试3.2 组件测试目标3.3 测试功能 四、稳健性测试4.1 效率的测试4.2 测试可维护性4.3 测试策略4.4 白盒测试 一、测试级别 软件系统通常是由许多子系统组成的&#xff0c;而这些子系统又是由多个组件组成的&#xff0c;…

VMware Aria Operations for Logs 8.18 发布,新增功能概览

VMware Aria Operations for Logs 8.18 - 集中式日志管理 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-aria-operations-for-logs/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 集中式日志管理 VMware Aria …

自闭症儿童寄宿学校:打造良好的学习和生活环境

在探讨自闭症儿童的教育与康复之路时&#xff0c;星贝育园无疑是一个值得深入了解的典范。这所全国知名的广泛性发育障碍全托寄宿制儿童康复训练机构&#xff0c;不仅以其独特的CBM干预法引领着行业前沿&#xff0c;更以其对每一个孩子的深切关怀与承诺&#xff0c;构建了一个充…

C#测试调用FreeSpire.PDFViewer浏览PDF文件

Free Spire.PDFViewer是商业版Spire.PDFViewer的社区版本&#xff0c;支持以控件形式打开并查看PDf文件&#xff0c;但由于是免费版本&#xff0c;存在使用限制&#xff0c;打开的PDF文档只显示前10页内容。如果日常操作的pdf文件都不超过10页&#xff0c;可以考虑使用Free Spi…

华为常见命令手册

常见命令 display ip interface brief —> 查看设备上的每个接口的IP地址【地址/掩码/状态】 display interface gi0/0/0 → 查看该接口的MAC 地址 ipconfig → 查看 PC 上面的 IP地址 display arp → 查看设备的 ARP 表 arp -a → 查看 PC 上面的 ARP 表 display m…

Redis: Sorted Set 底层算法的简单分析

概述 我们先看下 Shorted Set 有序集合的内部数据结构所谓有序集合&#xff0c;比如有个容器&#xff0c;容器里边都已经排好序了&#xff0c;那无非就是快速的查找和插入不管你是查找还是插入&#xff0c;肯定要确定那个位置最简单的办法就是从最开头开始&#xff0c;挨个比较…

【unity进阶知识6】Resources的使用,如何封装一个Resources资源管理器

文章目录 一、Unity资源加载的几种方式1、Inspector窗口拖拽2、Resources3、AssetBundle4、Addressables&#xff08;可寻址资源系统&#xff09;5、AssetDatabase 二、准备三、同步加载Resources资源1、Resources.Load同步加载单个资源1.1、基本加载1.2、加载指定类型的资源1.…