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

news2024/10/1 14:52:38

本期主题:
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);
	else
		printk("%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.coloradmin.cn/o/2182561.html

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

相关文章

利用SpringBoot构建高效社区医院平台

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【初阶数据结构】排序——交换排序

目录 前言冒泡排序快速排序Hoare版前后指针版优化三数取中法取随机数做基准值小区间优化 快排非递归版 前言 对于常见的排序算法有以下几种&#xff1a; 下面这节我们来看交换排序算法。 冒泡排序 基本思想&#xff1a; 在待排序序列中&#xff0c;每一次将相邻的元素进行两…

CSS内边距

内边距&#xff08;padding&#xff09;是指元素内容区与边框之间的区域&#xff0c;与外边距不同&#xff0c;内边距会受到背景属性的影响。您可以通过下面的属性来设置元素内边距的尺寸&#xff1a; padding-top&#xff1a;设置元素内容区上方的内边距&#xff1b;padding-…

2024-09-06 深入JavaScript高级语法十六——JS的内存管理和闭包

目录 1、JS内存管理1.1、认识内存管理1.2、JS的内存管理1.3、JS的垃圾回收1.3.1、常见的 GC 算法 - 引用计数1.3.2、常见的 GC 算法&#xfe63;标记清除 2、JS闭包2.1、JS中函数是一等公民2.2、JS中闭包的定义2.3、闭包的访问过程2.4、闭包的内存泄漏2.5、JS闭包内存泄漏案例2…

数据分析-28-交互式数据分析EDA工具和低代码数据科学工具

文章目录 1 数据分析的七步指南1.1 第一步:问题定义和数据采集1.2 第二步:数据清洗和预处理1.3 第三步:数据探索和分析1.4 第四步:模型建立和分析1.5 第五步:数据可视化1.6 第六步:结果解释和报告1.7 第七步:部署和维护1.8 基础的数据分析库1.9 低代码数据科学工具2 EDA…

yjs09——pandas介绍及相关数据结构

1.什么是pandas 同样&#xff0c;pandas、matplotlib、numpy是python三大库&#xff0c;pandas就像是把matplotlib和numpy结合在一起&#xff0c;让数据以“表格”的形式表现出来&#xff0c;是一个强大的数据处理和分析库&#xff0c;它建立在NumPy库之上&#xff0c;提供了高…

笔试-笔记

前言 记录一下自己遇到的笔试题 1.(单选)下列链表中&#xff0c;其逻辑结构属于非线性结构的是() A.二叉链表 B.双向链表 C.循环链表 D.带链的的栈 解析&#xff1a; 常见线性结构&#xff1a;线性表&#xff0c;栈&#xff0c;队列&#xff0c;双队列&#xff0c;串&…

05-函数传值VS传引用

函数传值 一、没法改变值的方式&#xff1a; 一个变量拷贝到另一个变量, 这种形式的函数调用被称为: 传值调用 局部变量的生命周期在函数的运行期间会一直存在. void Increment(int a)//假设一个 x(只是为了验证实参会被映射到形参这件事情),a的值会被拷贝到x {a a 1; //1…

【d57】【sql】1661. 每台机器的进程平均运行时间

思路 一方面考察自连接&#xff0c;另一方面考察group by 这里主要说明 group by 用法&#xff1a; 1.在 SQL 查询中&#xff0c;GROUP BY 子句用于将结果集中的行分组&#xff0c;目的通常就是 对每个组应用聚合函数&#xff08;如 SUM(), AVG(), MAX(), MIN(), COUNT() 等…

如何理解业务系统的复杂性

鹅厂万人热议&#xff5c;如何理解业务系统的复杂性&#xff1f;-腾讯云开发者社区-腾讯云 腾小云导读 业务系统复杂性一直是令开发者头痛的问题。复杂的不是增加一个需求需要耗费多少时间&#xff0c;而是在增加一个需求后带来的蝴蝶效应&#xff1a;其它功能会不会受到影响、…

MES数据的集成方式

为了实现与其他关键系统的数据共享和协同工作&#xff0c;不同的集成方式应运而生。MES系统与其他系统的常见集成模式&#xff0c;包括封装接口调用模式、直接集成模式、数据聚合模型、中间件集成模式以及XML的信息集成模式等。 1. 封装接口调用模式 封装接口调用是一种常见的…

防反接电路设计

方案1 串联二极管&#xff0c; 优点&#xff1a;成本低、设计简单 缺点&#xff1a;损耗大&#xff0c;P ui 方案2 串联自恢复保险丝 当电源反接的时候&#xff0c;D4导通&#xff0c;F2超过跳闸带你留&#xff0c;就会断开&#xff0c;从而保护了后级电路 方案3 H桥电路…

修改ID不能用关键字作为ID校验器-elementPlus

1、校验器方法 - forbiddenCharValidator const idUpdateFormRef ref(null); const forbiddenCharValidator (rule, value, callback) > {const forbiddenCharacters [as,for,default,in,join,left,inner,right,where,when,case,select];for (let forbiddenCharacter o…

劳动与科技、艺术结合更好提高劳动教育意义

在中小学教育中&#xff0c;劳动教育是培养学生基本生活技能和劳动习惯的重要环节。但当代的劳动教育不在单纯的劳动&#xff0c;而是劳动技能的提升与学习&#xff0c;通过学习劳动技能与实践活动&#xff0c;强化劳动教育与其他课程的融合&#xff0c;学生深刻理解劳动的意义…

python如何判断图片路径是否存在

1、在向文件夹中保存数据前&#xff0c;先判断该文件夹(路径)是否存在。 save_path /root/.../image/result if not os.path.exists(save_path):os.makedirs(save_path) 本来路径里只有到image文件夹的&#xff0c;执行完后会自动在image下创建result文件夹。 2、在打开某些图…

滑动窗口->dd爱框框

1.题目&#xff1a; 2.题解&#xff1a; 2.1为什么用滑动窗口优化&#xff1a; 因为元素都是大于0的 所以&#xff1a;当找到大于等于x的值时&#xff0c;right可以不用返回 两个指针都往后走&#xff1b;因此可以使用滑动窗口优化暴力解法 2.2&#xff1a;滑动窗口具体使用步…

骨传导耳机哪个品牌好用?盘点闭眼入都不踩雷的五大爆款机型!

骨传导耳机是智商税还是真有用&#xff1f;哪款骨传导耳机更值得购买&#xff1f;骨传导耳机作为市场中非常热门的机型&#xff0c;相信很多人都想入手一款&#xff0c;但面对市面鱼龙混杂的耳机品牌&#xff0c;往往不知道从何下手&#xff0c;不过市场重确实存在不少劣质产品…

ubutun nginx 安装和解决端口占用问题

目录 一、删除已有nginx 二、安装nginx 三、端口占用问题 分析问题 解决方法&#xff1a;更换默认端口 nginx是一个高性能的 HTTP 和反向代理 web 服务器&#xff0c;同时也提供了 IMAP/POP3/SMTP 服务。是一款轻量级的 Web 服务器/反向代理服务器及电子邮件&#xff08;I…

Sqoop实战-- Sqoop的Job任务、增量导入、数据格式转换与Lombok的使用指南

数据传输是任何数据驱动型组织的关键时刻。Apache Sqoop 在促进关系型数据库和Hadoop之间的高效数据传输方面表现出色&#xff0c;使其成为大数据工作流程中不可或缺的工具。本文将详细介绍如何使用Sqoop执行Job任务以及进行增量导入&#xff0c;如何在HDFS上指定数据存储格式&…

031集——文本文件按空格分行——C#学习笔记

如下图&#xff0c;读取每行文本&#xff0c;每行文本再按空格分开读取一个字符串&#xff0c;输出到另一个文本&#xff1a; CAD环境下&#xff0c;代码如下&#xff1a; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Runtime; using System; using Sys…