内存重映射

文章目录

  • 1 kmap
  • 2 映射内核内存到用户空间
    • 使用remap_pfn_range
    • 使用io_remap_pfn_range
  • mmap文件操作
    • 建立VMA和实际物理地址的映射
    • mmap 之前分配 + 一次性映射
    • mmap 之前分配 + Page Fault
    • Page Fault 中分配 + 映射

内核内存有时需要重新映射,无论是从内核到用户空间还是从内核空间到内核。常见的情况是将内核内存重新映射到用户空间,但还有其他一些情况,例如需要访问高内存的情况。

1 kmap

kmap()用于将指定的页面映射到内核地址空间

Linux内核将其896MB地址空间永久映射到物理内存较低的896MB(低端内存)。在4GB系统上,内核仅剩下128MB用来映射剩余3.2GB物理内存(高端内存)。由于低端内存采用永久一对一映射,因此内核可以直接寻址。而对于高端内存(高于896MB的内存),内核必须将所请求的高端内存区域映射到其地址空间,前面提到的128MB就是专门为此保留。用于执行此操作的函数是kmap()。kmap()用于将指定的页面映射到内核地址空间。

void *kmap(struct page *page);

当分配到高端内存页时,它不能直接寻址,必须调用调用kmap()函数将高端内存映射到内存地址空间。该映射将持续到kunmap()位置:

void *kunmap(struct page *page);

所谓暂时,指的是映射应该在不需要的时候立即撤销。请记住,128MB不足以映射3.2GB。最好的编程习惯是在不需要时取消高端内存映射。这就是必须在每次访问高端内存页面时输入kmap()-kunmap序列的原因了,

该函数适用于高端内存和低端内存,也就是说,如果页面结构驻留在低端内存中,那么返回的是页面的虚拟地址(因为低端内存页面已经有永久映射)。如果页面属于高端内存,则在内核页表中创建永久映射,并返回地址。

2 映射内核内存到用户空间

映射物理地址是其中一个有用的功能,特别是在嵌入式系统中。有时可能想要与用户空间共享部分内核内存。如前所述,CPU在用户空间时以非特权模式运行要让进程访问内核内存区域,需要将该区域映射到进程地址空间

使用remap_pfn_range

remap_pfn_range()将物理内存(通过内核逻辑地址)映射到用户空间进程。它对于实现mmap()特别有用。
在文件上调用mmap()系统调用后,CPU切换到特权模式,运行相应的file_operations.mmap()内核函数,它反过来调用remap_pfn_range()。这将产生映射区域的PTE,将其赋给进程,当然还有不同的保护标志,进程的VMA列表更新为新的VMA项,这将使用PTE访问相同的内存。

这样,内核不是通过复制来浪费内存,而只是复制PTE,但是内核和用户空间PTE具有不同的属性。remap_pfn_range()原型如下:

int remap_pfn_range (	struct vm_area_struct * vma,unsigned long virt_addr,unsigned long pfn,unsigned long size,pgprot_t prot);
  • vma: 这个我们不用担心,因为在调用file_operations.mmap函数时,mmap调用do_mmap()会创建一个新的VMA并初始化,此vma就是创建的新的VMA,加入进程的虚拟地址空间里,这个已经确定了。
  • virt_addr:VMA开始位置的用户虚拟地址(vma->vm_start),这将导致映射的虚拟地址范围位于virt_addr~virt_addr+size
  • pfn:所映射内核内存区域的页面帧码,它对应于通过PAGE_SHIFT位右移得到的物理地址。产生pfn时应应该考虑vma偏移量。由于vma结构的vm_pgoff字段在页码中包含偏移值,因此需要以字节形式精确提取偏移量:offset= vma->vm_pgoff<<PAGE_SHIFT.最后,pnf=virt_to_phys(buffer+offset)>>PAGE_SHIFT.
  • size:需要建立映射的VMA的大小,以字节为单位
  • prot:代表新VMA所要求的保护。驱动程序可以修改默认值,但应该使用OR运算符将vma->vm_page_port中的值作基础,因为它的某些位已经由用户空间设置,其中一些标志如下:
  1. VM_IO:指定设备内存映射I/O
  2. VM_DONTCOPY:告诉内核不要在分叉上复制该vma
  3. VM_DONTEXPAND:防止vma通过mremap扩展
  4. VM_DONTDUMP:禁止在核心转储内包含vma

使用io_remap_pfn_range

前面讨论的remap_pfn_range()不适用于将I/O内存映射到用户空间。在这种情况下,相应的函数是io_remap_pfn_range(),它们的参数相同,唯一改变的是PFN的来源,其原型如下:

  int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long phys_addr, unsigned long size, pgprot_t prot);

当试图将I/O内存映射到用户空间时,不需要使用ioremap(),ioremap()用于内核映射(将I/O内存映射到内核地址空间)。

只需要将真实的物理I/O地址(通过PAGE_SHIFT向下移位生成PFN)直接传递给io_remap_pfn_range()。即使有一些体系将io_remap_pfn_range()定义为remap_pfn_range(),但在其他体系结构中并非如此,考虑到移植能力,只有在PFN参数指向RAM的情况下,才使用remap_pfn_range(),在pys_addr指向I/O聂村的情况下,才使用io_remap_pfn_range()。

mmap文件操作

内核mmap函数是struct file_operations结构的一部分,当用户执行系统调用mmap(2),把物理内存映射到用户虚拟地址时才执行它。出于安全考虑,用户空间进程不能直接访问设备内存,因此,用于空间进程使用mmap()系统调用将该设备映射到调用进程的虚拟地址空间。在映射之后,用户空间进程可以通过返回的地址直接写入设备内存。

       #include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);int munmap(void *addr, size_t length);

用户空间的mmap()会通过系统调用调用内核的do_mmap()函数。
do_mmap()函数会:

  1. 首先创建一个新的VMA并初始化,然后加入进程的虚拟地址空间里
  2. 调用底层的mmap函数建立VMA和实际物理地址的映射(建立页表)

什么是底层的mmap函数呢?在不同的设备是不一样的。比如说我们映射的是一个普通文件,底层文件系统已经帮我们实现了mmap,我们可以直接使用,但是如果我们新写了一个驱动,我们想为驱动提供mmap的接口,那么就需要我们实现mmap的接口,设备驱动的mmap实现主要是将这个物理设备的可操作区域映射到一个进程的虚拟地址空间,这样用户空间就可以直接采用指针的方式访问设备的可操作区域。在驱动中的mmap实现主要完成一件事,就是建立设备的可操作区域到进程虚拟空间地址的映射过程。

  • addr:映射开始的用户空间虚拟地址,如果指定NULL,则自动确定正确的地址
  • length:指定映射长度
  • prot:指定VMA的权限
  • flags:决定映射类型(私有还是共享)
  • fd:设备文件描述符
  • offset:指定映射区的偏移量(在物理内存里面)

建立VMA和实际物理地址的映射

在 linux 驱动中建立映射关系的方法主要有如下两种:

  1. 一次性映射 —— 在 mmap 回调函数中,一次性建立好整块内存的映射关系,通常以 remap_pfn_range() 为代表 。
  2. Page Fault —— mmap 先不建立映射关系,等上层触发缺页异常时,在 fault 中断处理函数中建立映射关系,缺哪块补哪块,通常以 vm_insert_page() 为代表。

而内存分配的时机也会影响驱动程序的设计,大致分为如下三种:

  • 在 mmap 系统调用之前分配
  • 在 mmap 系统调用过程中分配
  • 在 fault 中断处理函数中分配

因此不同的分配时机 + 不同的映射机制,就会得到不同的 mmap 的实现策略。

下面就以示例代码的形式为大家展示几种典型的 mmap 驱动实现方式。

mmap 之前分配 + 一次性映射

请添加图片描述

描述:

  1. 驱动初始化时先分配好 3 个 PAGE。
  2. 上层执行 mmap 系统调用时,在底层 mmap 回调函数中通过 remap_pfn_range() 一次性建立好所有的映射关系,并将映射后的起始虚拟地址返回给应用程序。
  3. 应用程序使用返回的虚拟地址进行内存读写操作。

驱动代码:

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static void *kaddr;static int my_mmap(struct file *file, struct vm_area_struct *vma)
{return remap_pfn_range(vma, vma->vm_start,(virt_to_phys(kaddr) >> PAGE_SHIFT) + vma->vm_pgoff,vma->vm_end - vma->vm_start, vma->vm_page_prot);
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(&mdev);
}
module_init(my_init);

mmap 之前分配 + Page Fault

请添加图片描述
描述:

  1. 驱动初始化时预先分配好 3 个 PAGE。
  2. 上层执行 mmap 系统调用,底层驱动在 mmap 回调函数中不建立映射关系,而是将本地实现的 vm_ops 挂接到进程的 vma->vm_ops 指针上,然后函数返回。
  3. 上层获取到一个未经映射的进程地址空间,并对其进行内存读写操作,导致触发缺页异常。缺页异常最终会调用前面挂接的 vm_ops->fault() 回调接口,在该接回调中通过 vm_insert_page() 建立物理内存与用户地址空间的映射关系。
  4. 异常返回后,应用程序就可以继续之前被中断的读写操作了。

注意:这种情况每次 Page Fault 中断只能映射一个 Page

驱动代码:

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static void *kaddr;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma = vmf->vma;int offset, ret;offset = vmf->pgoff * PAGE_SIZE;ret = vm_insert_page(vma, vmf->address, virt_to_page(kaddr + offset));if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops = {.fault = my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma->vm_flags |= VM_MIXEDMAP;vma->vm_ops = &vm_ops;return 0;
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{kaddr = kzalloc(PAGE_SIZE * 3, GFP_KERNEL);return misc_register(&mdev);
}
module_init(my_init);

Page Fault 中分配 + 映射

请添加图片描述
映射的过程和示例二完全一样,只是内存分配的时机是在 page fault 中断处理函数中进行的。

这里为了简化代码,总共只分配一个 page,多个 page 可通过 vmf->pgoff 来进行区分。

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>static struct page *page;static int my_fault(struct vm_fault *vmf)
{struct vm_area_struct *vma = vmf->vma;int ret;if (!page)page = alloc_page(GFP_KERNEL);ret = vm_insert_page(vma, vmf->address, page);if (ret)return VM_FAULT_SIGBUS;return VM_FAULT_NOPAGE;
}static const struct vm_operations_struct vm_ops = {.fault = my_fault,
};static int my_mmap(struct file *file, struct vm_area_struct *vma)
{vma->vm_flags |= VM_MIXEDMAP;vma->vm_ops = &vm_ops;return 0;
}static struct file_operations my_fops = {.owner	= THIS_MODULE,.mmap	= my_mmap,
};static struct miscdevice mdev = {.minor = MISC_DYNAMIC_MINOR,.name = "my_dev",.fops = &my_fops,
};static int __init my_init(void)
{return misc_register(&mdev);
}
module_init(my_init);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379519.shtml

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

相关文章

math.sqrt 有问题_JavaScript中带有示例的Math.sqrt()方法

math.sqrt 有问题JavaScript | Math.sqrt()方法 (JavaScript | Math.sqrt() Method) The Math.sqrt() method is inbuilt in JavaScript to find the square root of a number. In this tutorial, we will learn about the sqrt() method with examples. JavaScript中内置了Mat…

ISAPI Rewrite 实现简单url重写、二级域名重写

实现步骤&#xff1a; 第一步&#xff1a;下载ISAPI_Rewrite.rar&#xff0c;将Rewrite文件夹和httpd.ini直接放在项目根目录下面。 第二步&#xff1a;IIS配置&#xff0c;筛选Rewrite文件夹里面的Rewrite.dll文件&#xff0c;如图&#xff1a; 第三步&#xff1a;在httpd.ini…

用户登录

用户登录 代码namespace 用户登录 {public partial class Form1 : Form{public Form1(){InitializeComponent();}bool b1, b2, b3, b4, b5, b6;private void button1_Click(object sender, EventArgs e){try{if (b1 && b2 && b3 && b4 && b5 &…

进程上下文和中断上下文

文章目录进程的preempt_count变量thread_infopreempt_counthardirq相关softirq相关上下文原文链接&#xff1a; https://zhuanlan.zhihu.com/p/88883239进程的preempt_count变量 thread_info 在内核中&#xff0c;上下文的设置和判断接口可以参考 include/linux/preempt.h 文…

标题:凑算式

标题&#xff1a;凑算式 这个算式中AI代表19的数字&#xff0c;不同的字母代表不同的数字。 比如&#xff1a; 68/3952/714 就是一种解法&#xff0c; 53/1972/486 是另一种解法。 这个算式一共有多少种解法&#xff1f; 注意&#xff1a;你提交应该是个整数&#xff0c;不要…

Linux内存地址管理

文章目录系统内存布局内核地址的低端和高端内存概念低端内存高端内存地址转换和MMULinux中的四级分页模型虚拟地址字段页表处理将虚拟地址转换物理地址Linux系统中的每个内存地址都是虚拟的&#xff0c;它们不直接指向任何物理内存地址。每当访问内存位置时&#xff0c;可以执行…

录制caf 转 mp3

编译需要使用的 lame库http://www.cocoachina.com/bbs/read.php?tid108237参考的文章http://blog.csdn.net/ysy441088327/article/details/7392842说起来&#xff0c;我一直在找一个音频转换成mp3的方法。一年前&#xff0c;我成功编译出了一个lame for armv7的库。苦于不会使…

開發記要 詭異的變量

告別繁體文盲,從寫blog開始 Variable命名很重要,有多重要,看看.net和java的加密就知道, 都是把variable改到一塌糊塗,你想看看都沒門. 但是這幾天看遺留系統的代碼,真是大開眼界。 我一直以為別人寫a,b,c,d這些單字節variable已經很過分。直到我看到以下這幾個&#xff0…

排序算法---快速排序、堆排序、冒泡排序

排序算法1 快速排序代码实现stdlib库快排2 堆排序堆排序的基本思想如何构造一个大顶堆排序3 冒泡排序1 快速排序 文章原地址&#xff1a;https://blog.csdn.net/morewindows/article/details/6684558 快速排序的平均时间复杂度是0(NlogN)&#xff0c;它采用了一种分治的策略&a…

项目总结:华南师范大学校园开发教育android客户端总结

忽略之前小打小闹&#xff0c;这个项目算是我的第一个项目--SCNU的网络公选课的android版本的客户端。项目是从5月中旬开始的&#xff0c;中间经历了几个星期的复习考试时间&#xff0c;到现在可以说是完工了吧&#xff08;或许还有写细节要修改&#xff09;。这个项目带给我蛮…

Linux系统编程---守护进程

1 守护进程的概述 Daemon&#xff08;守护进程&#xff09;是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务&#xff0c;不是对整个系统就是对某个用户程序提供服务。Linux系统的大…

邮箱服务器

一&#xff0e;邮箱服务器的基本概念 邮件的客户端&#xff1a;可以只安装在电脑上&#xff08;C/S&#xff09;的也可以是网页形式&#xff08;B/S&#xff09;的 邮件服务器&#xff1a;起到邮件的接受与推送的作用 邮件发送的协议&#xff1a; 协议&#xff1a;就是数据传输…

类加载器

一、类加载器 1&#xff0c;什么是类加载器&#xff1f; 类加载器就是用来加载字节码文件 2&#xff0c;类加载器的种类有哪些&#xff1f; 1&#xff09;BootStrap&#xff1a;引导类加载器&#xff1a;加载都是最基础的文件 2&#xff09;ExtClassLoader&#xff1a;扩展类加…

算法---链表

文章目录反转链表合并两个有序链表删除重复元素反转链表 反转链表包括两种&#xff0c;反转全部元素或者反转部分元素。在这里&#xff0c;我们约定&#xff1a;数据元素类型是struct LinkNode&#xff0c;要反转链表的第一个节点是head&#xff0c;head的前面一个节点是pre&a…

SSM

二、环境设置&#xff08;MyEclipse&#xff09; 1&#xff0c;字体设置 window–>Preference->General->Appearance->Colors and Fonts->Basic Text->Font 2&#xff0c;workspace字符集设置 window–>Preference->General->Appearance->W…

设计模式--Strategy 策略模式

所谓策略模式(Strategy Pattern)&#xff0c;就是将策略 (算法) 封装为一个对象&#xff0c;易于相互替换&#xff0c;如同 USB 设备一样可即插即用&#xff1b;如果将策略、具体的算法和行为&#xff0c;编码在某个类或客户程序内部&#xff0c;将导至事后的修改和扩展不易。 …

打开eclipse出现Failed to load the JNI shared library “D:\java\jdk\bin\...\jre\bin\server\jvm.dll”如何解决?

eclipse打开的时候出现Failed to load the JNI shared library “D:\java\jdk\bin…\jre\bin\server\jvm.dll”如何解决&#xff1f;&#xff1f; 如图所示&#xff1a; 即代表你的jdk与eclipse的位数不一样&#xff01;&#xff01;&#xff01; 你可以查看一下eclipse和jd…

圆形坠落模拟算法设计

目标&#xff1a;实现一个算法&#xff0c;模拟在一个封闭二维区域&#xff0c;圆形小球朝给定方向坠落的过程&#xff0c;实现二维区域的紧密填充。 像下面这样&#xff1a; 难点&#xff0c;及其简单解决&#xff1a; 1.如何把粒子移动尽可能远&#xff1f; 图中的粒子i&…

Maven详细教学

一、Maven简介 maven&#xff1a;是apache下的一个开源项目&#xff0c;是纯java开发&#xff0c;并且只是用来管理java项目的 依赖管理&#xff1a;就是对jar包的统一管理 可以节省空间 项目一键构建&#xff1a;mvn tomcat:run该代码可以将一个完整的项目运行起来&#xff0…

网站后台中对html标签的处理

最近做一个CMS&#xff0c;后台中需要使用在线编辑器对新闻进行编辑&#xff0c;然后发表。我用的在线编辑器是CKEditorCKFinder。也许是我为了让CKEditor更本地化吧&#xff0c;改了很多。后来发现在CKEditor中对文字设置字体、颜色、字号大小时文字的<span>标签会出现N…