内存重映射

文章目录

  • 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.pswp.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…

标题:移动距离

标题&#xff1a;移动距离 X星球居民小区的楼房全是一样的&#xff0c;并且按矩阵样式排列。其楼房的编号为1,2,3… 当排满一行时&#xff0c;从下一行相邻的楼往反方向排号。 比如&#xff1a;当小区排号宽度为6时&#xff0c;开始情形如下&#xff1a; 1 2 3 4 5 6 12 11 1…

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;不要…

汇编中imul_JavaScript中带有示例的Math.imul()方法

汇编中imulJavaScript | Math.imul()方法 (JavaScript | Math.imul() Method) Math.imul() is a function in math library of JavaScript that is used to the 32-bit multiplication of the two values passed to it. It uses C-like semantics to find the multiplication. …

AFTER触发器与INSTEAD OF触发器的区别

INSTEAD OF 触发器用来代替通常的触发动作&#xff0c;即当对表进行INSERT、UPDATE 或 DELETE 操作时&#xff0c;系统不是直接对表执行这些操作&#xff0c;而是把操作内容交给触发器&#xff0c;让触发器检查所进行的操作是否正确。如正确才进行相应的操作。因此&#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的库。苦于不会使…

杭电2012-素数判定(C)

Problem Description 对于表达式n^2n41&#xff0c;当n在&#xff08;x,y&#xff09;范围内取整数值时&#xff08;包括x,y&#xff09;(-39<x<y<50)&#xff0c;判定该表达式的值是否都为素数。 Input 输入数据有多组&#xff0c;每组占一行&#xff0c;由两个整数…

math.ceil带小数点_JavaScript中带有示例的Math.ceil()方法

math.ceil带小数点JavaScript | Math.ceil()方法 (JavaScript | Math.ceil() Method) Math.ceil() is a function in math library of JavaScript that is used to round up the number passed to the function. The method will return the nearest integer value indeed is g…

開發記要 詭異的變量

告別繁體文盲,從寫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…

CSS Hack 汇总快查

*:lang(zh) select {font:12px !important;} /*FF的专用*/ select:empty {font:12px !important;} /*safari可见*/ 这里select是选择符&#xff0c;根据情况更换。第二句是MAC上safari浏览器独有的。 仅IE7识别 *html {…} 当面临需要只针对IE7做样式的时候就可以采用这个HACK…

杭电2013-蟠桃记(C++)

Problem Description 喜欢西游记的同学肯定都知道悟空偷吃蟠桃的故事&#xff0c;你们一定都觉得这猴子太闹腾了&#xff0c;其实你们是有所不知&#xff1a;悟空是在研究一个数学问题&#xff01; 什么问题&#xff1f;他研究的问题是蟠桃一共有多少个&#xff01; 不过&#…

c#中重载单目运算符-_C#程序重载二进制运算符(-,*,/)

c#中重载单目运算符-Here, we will design overloaded methods for binary operators: minus, multiply and divide. In the below program, we will create a Calculator class with data member val. 在这里&#xff0c;我们将为二进制运算符设计重载方法&#xff1a;减&…

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

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

火鸟字幕合并器

火鸟字幕合并器-区块独立勾选-保存。汉王 PDF OCR转载于:https://www.cnblogs.com/hnytwn/archive/2009/10/31/1593395.html

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

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