`
zachary.guo
  • 浏览: 482482 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

NIO - 内存映射文件

    博客分类:
  • NIO
 
阅读更多
        内存映射文件一直没弄明白,这几天在网上到处搜索,看了两篇文章,总算是弄明白了。在讲内存映射文件前,先讲讲 MMU 和内存映射到底是是什么。

        MMU 是 Memory Management Unit 的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟内存、物理内存的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权。

        这里提到了虚拟内存,虚拟地址,物理地址,虚拟地址和物理地址的映射。词语多了,有点晕。我们来举一个例子来说明下。

        任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由 CPU 的位数决定。例如一个 32 位的 CPU,它的地址范围是 0 ~ 0xFFFFFFFF(4G)。这个范围就是我们的程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间。该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址。对于这台机器,如果我们配置的是 2G 的内存,那么,它的虚拟地址空间范围是 0x00000000 ~ 0xFFFFFFFF(4G),而物理地址空间范围是 0x000000000 ~ 0x7FFFFFFF(2G)。每个进程都有自己的 4G 地址空间(32 位操作系统),从 0x00000000 ~ 0xFFFFFFFF。

        目前大多数操作系统都会采用分页(paging)机制。虚拟地址空间的划分以页(page)为单位,而相应的物理地址空间的划分以页桢(frame)为单位。页和页桢的大小必须相同。为了简化描述,这里我们把虚拟地址空间和物理地址空间来划分空间的单位均称为页。

        使用了分页机制之后,4G 的虚拟地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件(虚拟内存)中,或者没有映射任何东西。对于一般程序来说,4G 的地址空间,只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。CPU 是依据于一个叫做页目录和页表的结构来将虚拟地址转换成物理地址。CPU 在执行内存指令时,会自动根据页目录和页表中的信息,把虚拟地址转换成物理地址。

        物理内存分页,一个物理页的大小为 4K 字节,页的索引从 0 开始,即 第 0 页,第 1 页,第 2 页,等等。因此,第 0 个物理页从物理地址 0x00000000 开始,由于一页为 4K,即 0x1000,所以,第 1 个物理页从物理地址 0x00001000 开始,第 2 个物理页从物理地址 0x00002000 开始,以此类推,第 12 个物理页从物理地址 0x0000C000 开始。注意,这里我们描述的是页的起始地址,如果要找某页的第 N(N < 4096,即 4K,因为一页 4K 个字节) 个字节,那么 N 就是所谓的偏移量。很明显,一页的偏移量,即最大 4K,用 12bit 表示即可。因此,对于 32 位 CPU,用 32bit 地址的低 12bit 表示偏移量,剩下的高 20bit 用来寻址(寻找页的起始地址)。64 位的 CPU 同理,一页同样是 4K,所以用低 12bit 表示偏移量,剩下的高 52bit 用来寻址。

        接下来要讲的是页表页目录的概念,这两个概念比较重要,因为 CPU 就是依赖这两个信息来将虚拟地址砖为物理地址的。页表和页目录都是存放在物理页中的。

        一个页表大小为 4K,因此一个页表是存放在一个物理页中的。一个页表由 1024 个页表项组成,所以,一个页表项大小为 4 个字节,即 32bit。页表项会存储一些信息,高 20bit 存储一个物理页的起始地址,低 12bit 存放一些标志。所以,一个页表可以记录 1024 个物理页的起始地址。如果是 64 位的 CPU,20bit 不足以存放物理页的起始地址,我也没弄明白如何处理,当然这不是讨论的范畴。我猜测,64 位 CPU,一个页表由 512 个页表项组成,一个页表项 8 个字节,这样就由足够的 bit 来寻址了。

        一个页目录大小为 4K,因此一个页目录是存放在一个物理页中的。和页表类似,一个页目录由 1024 个页目录项组成,一个页目录项大小为 4 个字节,即 32bit。页目录项同样会存储一些信息,高 20bit 存放一个页表(页表是放在一个物理页中)所在物理页的起始地址,低 12bit 存放一些标志。对于 64 位 CPU,我依然和页表的猜测是一样的思路。

        因此,只要能找到页目录,就能找到页表,进而找到物理页。事实上,对于 x86 系统,页目录的物理地址放在 CPU 的 CR3 寄存器中。

        这里,我们梳理一下几个概念(以 32bit CPU 为例):
  • 每个进程都有独立的 4G 的虚拟地址。因此,对于某个虚拟地址,请问下自己,它是属于哪个进程的。
  • 每个进程都有一个入口的物理地址。这个物理地址存放于 CR3 寄存器中,其实就是该进程的页目录表基地址(物理地址)。可以理解成:进程的入口地址 = CR3 = 页目录表的基地址。
  • 不同的进程可以有相同的虚拟地址,但它们映射成的物理地址是不一样的。因为 CR3 寄存器存的页目录基地址是不一样的。
  • 在用户进程空间中,只有一部分虚拟地址映射着实际物理地址。

        接下来,就开始讲 CPU 将虚拟地址转换成物理地址的过程了,请仔细阅读了。一个虚拟地址 32bit,我们分成三段:高 10bit,中间 10bit,低 12bit。它们依次代表着页目录的索引页表的索引物理页的偏移量。对于一个要转换成物理地址的虚拟地址(假设为 0x01AF5518),将按照以下步骤来操作:

  1. 把虚拟地址拆分成 3 部分(高 10 bit,中 10 bit,低 12 bit),换成二进制如下:
  2.         0000 0001 1010 1111 0101 0101 0001 1000

            按 10,10,12 来分,得到:
            (页目录索引)00 000 00110,(页表项索引)10 1111 0101,(偏移)0101 0001 1000

            换算成十六进制后可以得到如下结果:
            页目录索引 = 6,页表项索引 = 0x2F5,偏移 = 0x518
           
  3. 在 CR3 寄存器中找到该进程的入口物理地址即页目录表基地址。CR3 中存放的就是页目录表基地址。该地址是什么我们不得而知,和进程有关。我们假设页目录表基地址为 0xAA0E5000。
  4.        
  5. 计算页表项的物理地址。页表地址存放在页目录表中的第 6 个项目中(每个页目录项为 4 个字节),即:[0xAA0E5000 + 4 * 6] = [0xAA0E5018]。物理地址 0xAA0E5018 就是要找的页目录项的物理地址。回忆上面的内容,页目录项里的内容,其高 20 bit 就是页目录表的基地址(物理地址)。假设 0xAA0E5018 页目录项的内容为 0x00000867。
  6.        
  7. 计算页面的起始地址。上面得到的 0x00000867,其高 20 bit 才是页表的基地址:0x3D955000。我们要找的页面在这个页表中的第 0x2F5 项:[0x3D955000 + 4 * 0x2F5] = [0x3D955BD4]。即物理地址 0x3D955BD4 存放的内容包含了物理页的起始地址。0x3D955BD4 存放的内容时候什么我们不得而知,假设 [0x3D955BD4] = 0x7095e847。那么物理页的起始地址为 0x7095E847 的高 20 bit,即 x0x7095E000。
  8.        
  9. 计算最终的物理地址:物理地址起始地址 + 偏移量 = x0x7095E000 + 0x00000518 = 0x7095E518。
        现代的多用户多进程操作系统,需要 MMU,才能达到每个用户进程都拥有自己独立的地址空间的目标。Windows 将地址的低 2G 分给用户态空间,高 2G 分给内核态空间,这是默认的分配方式,可以通过启动参数改为 3G/1G。而 Linux 就是 3G/1G 划分:低 3G 作为用户态空间,高 1G 作为内核态空间。内核态和用户态共享 4G 的 32 位地址空间。

        因此,上面我们说的,每个用户有独立的 4G 的用户空间并不准确,用户空间和内核空间共享 4G 地址空间。更多关于地址空间的划分请参见 http://techsingular.net/?p=1035

        之所以让内核态与用户态共享地址空间,其实是因为尽管每次进入内核态都可能发生进程切换,但是大多数情况下并非一定发生这样的切换。因此,共享地址空间可以避免进出内核态的时候进行地址空间(CR3)的切换。在 x86 架构下,切换地址空间要导致所有 TLB 失效,CPU 必须访问主存更新 TLB。所以,共享地址空间是一个性能 hack,仅此而已。这个性能 hack 如此历史悠久,以至于有些人如我一般完全无法想像还能有其它方式。

        传统的文件 I/O 是通过用户进程发布 read() 和 write() 系统调用来传输数据的。为了在内核空间的文件系统页与用户空间的内存区之间移动数据,一次以上的拷贝操作几乎总是免不了的。这是因为,在文件系统页与用户缓冲区之间往往没有一一对应关系。

        有一种大多数操作系统都支持的特殊类型的 I/O 操作,允许用户进程最大限度地利用面向页的系统 I/O 特性,并完全摒弃缓冲区拷贝。这就是内存映射 I/O。这就是将文件映射到内存(这里的内存,就是虚拟内存,不是物理内存),即磁盘上的文件数据就像是在内存中一样。这利用了操作系统的虚拟内存功能,无需在内存(物理内存)中实际保留一份文件的拷贝,就可实现文件内容的动态高速缓存。

        虚拟内存和磁盘 I/O 是紧密关联的,从很多方面看来,它们只是同一件事物的两面。在处理大量数据时,尤其要记得这一点。如果数据缓冲区是按页对齐的,且大小是内建页大小的倍数,那么,对大多数操作系统而言,其处理效率会大幅提升。
       
                                                             用户内存到文件系统页的映射
  • 大小: 54 KB
分享到:
评论

相关推荐

    Java NIO 应用使用内存映射文件实现进程间通信

    Java NIO 应用 -- 使用内存映射文件实现进程间通信

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。...– 支持锁和内存映射文件的文件访问接口。 – 提供多路 (non-bloking) 非阻塞式的高伸缩性网络 I/O 。 本文档将围绕这几个特性进行学习和介绍。

    尚硅谷Java视频_NIO 视频教程

    尚硅谷_NIO_通道的数据传输与内存映射文件 ·06. 尚硅谷_NIO_分散读取与聚集写入 ·07. 尚硅谷_NIO_字符集 Charset ·08. 尚硅谷_NIO_阻塞与非阻塞 ·09. 尚硅谷_NIO_阻塞式 ·10. 尚硅谷_NIO_非阻塞式 ·11. ...

    nio.rar_FastCopyFile.java_NIO_UseFloatBuffer.java_java nio_文件锁

    Java NIO 源码适合初学者,里面包括通道和Buffer的基本适用,以及文件锁,和内存文件映射等等

    文件快速加密

    但是,Java实现采用了多线程、NIO以及内存文件映射,其速度要远远超过Python版实现。 实测结果:加密大小为1G的文件,Java版耗时为3秒左右,而Python版则需要8分钟左右,而且还可能会出现内存溢出错误。 Java版本:...

    Java性能优化之使用NIO提升性能(Buffer和Channel)

    在软件系统中,由于IO的速度要比内存慢,因此,I/O读写在很多场合都会成为系统的瓶颈。提升I/O速度,对提升系统整体性能有着很大的好处。...支持锁和内存映射文件的文件访问接口;提供了基于Selector的异步网

    nio:Clojure对java.nio的支持

    还定义了函数mmap来对文件进行内存映射。 此外,还有三个函数buffer-seq,buffer-nth和buffer-to-array,旨在使将java.nio类集成到Clojure中更加容易。 希望通过将它们的功能滚动到clojure.core中,它们将变得过时...

    Java NIO与IO的差别和比较

    当中还提供了一个特殊类用于内存映射文件的I/O操作。  2. Charset:它提供Unicode字符串影射到字节序列以及逆影射的操作。  3. Channels:包括socket,file和pipe三种管道,它实际上是双向交流的通道。  

    javabitset源码-javaewah:JavaBitSet类的压缩替代品

    还支持内存映射文件:我们可以将位图序​​列化到磁盘,然后使用 java.nio 类将它们映射到内存。 这可以避免浪费的序列化/反序列化例程。 该库还提供了标准 BitSet 类的替代品。 与 JavaEWAH 中的其他位图类一样,这...

    mmfinvoker:简单的进程间 java 请求-响应库

    mmfinvoker 这是一个简单的 java 库,它使用 nio.MappedByteBuffer 在内存映射文件上实现请求/响应功能。

    j2se项目源码及介绍_last指令

    返回说明 @return MappedByteBuffer 内存映射缓冲。 异常说明 throws 考虑异常 流程原理 调用实例 函数原型 private void readLog(MappedByteBuffer buffer, Vector&lt;LogRecord&gt; logins,Vector&lt;LogRecord&gt; ...

    JAVA上百实例源码以及开源项目

    创建发送者和映射消息。发送消息,同时对文本进行少量修改,发送end-of-messages消息,最后关闭连接。 Tcp服务端与客户端的JAVA实例源代码 2个目标文件 摘要:Java源码,文件操作,TCP,服务器  Tcp服务端与客户端的...

    JAVA上百实例源码以及开源项目源代码

    Java访问权限控制源代码 1个目标文件 摘要:Java源码,文件操作,权限控制 Java访问权限控制,为Java操作文件、写入文件分配合适的权限,定义写到文件的信息、定义文件,输出到c:/hello.txt、写信息到文件、关闭输出流...

    ip地址库 很全的库

    // 内存映射文件 private MappedByteBuffer mbb; // 单一模式实例 private static volatile IPSeeker instance = null; // 起始地区的开始和结束的绝对偏移 private long ipBegin, ipEnd; // 为提高...

    百度地图开发java源码-inertiaSearch:挑战赛

    百度地图开发java源码 tmp #inertiaSearch 2016年写的代码,现在觉得思路有很多提升的地方,但是毕竟努力过,还是贴在Readme.md 里面 ...对于原始的数据文件做内存映射,并做对应索引,所有索引做hash

    Eclipse开发分布式商城系统+完整视频代码及文档

    │ 12.nginx的配置文件-通过端口号区分虚拟机.avi │ 13.通过域名配置虚拟机.avi │ 淘淘商城第二天笔记.docx │ ├─03.第三天 │ 01.课程回顾.avi │ 02.课程计划.avi │ 03.什么是反向代理.avi │ 04.nginx的...

    java开源包1

    集中管理请求参数与参数映射 以运行时异常的方式来管理错误的响应 使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息...

    java开源包10

    集中管理请求参数与参数映射 以运行时异常的方式来管理错误的响应 使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息...

    java开源包11

    集中管理请求参数与参数映射 以运行时异常的方式来管理错误的响应 使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息...

Global site tag (gtag.js) - Google Analytics