`
zachary.guo
  • 浏览: 487255 次
  • 性别: 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中,内存映射文件(MappedByteBuffer)是一个重要的特性,它允许将文件直接映射到内存中,以便于快速访问和修改文件内容。这一特性不仅提高了读写效率,而且还能用于进程间通信(IPC)。 内存映射文件的...

    Java-NIO-Programming-Cookbook(含源码)

    7. **内存映射文件(Memory-Mapped Files)**:通过映射文件到内存,提供了一种高效的大文件处理方式,使得文件可以直接被内存操作。 8. **效率比较**:在特定场景下,`DirectByteBuffer`可能比`HeapByteBuffer`更...

    java网络编程NIO视频教程

    Java NIO-Buffer-内存映射文件I/O - **主要内容**:演示如何利用Buffer实现内存映射文件I/O操作。 - **学习目标**:掌握高效的文件访问技术。 #### 21. Java NIO-Selector-概述 - **主要内容**:介绍Selector...

    Java-NIO-系列教程

    除了以上提到的 Buffer 类型之外,Java NIO 还提供了一个特殊的 Buffer 类型——`MappedByteBuffer`,用于表示内存映射文件。这种方式能够提高文件的读写效率,但由于其实现较为复杂,本概述中不做详细介绍。 #### ...

    【IT十八掌徐培成】Java基础第27天-01.MappedMemoryBuffer-文件内存映射缓冲区.zip

    1. **内存映射文件**:内存映射文件是一种技术,它将文件的内容直接映射到进程的虚拟地址空间中,使得文件就像一个巨大的字节数组一样可以被直接访问。这种方式减少了I/O操作的开销,因为操作系统负责文件和内存之间...

    java nio 包读取超大数据文件

    ### Java NIO 处理超大数据文件的知识点详解 ...综上所述,使用Java NIO处理超大数据文件时,关键是利用好内存映射文件技术和合理的数据读取策略,通过适当的分块和数据解析方法,可以有效地提升读取速度和处理能力。

    java nio 写文件

    Java NIO还提供了一种叫做内存映射文件的高级特性。通过`FileChannel.map()`方法,可以直接将文件映射到内存中,这样读写文件就像操作内存一样快速。但需要注意的是,内存映射文件可能会消耗大量内存,因此对于大...

    Large-File-Processing-master_javanio_java大文件处理_

    3. **内存映射文件(Memory-Mapped Files)**:Java NIO提供了一个特殊的功能,即内存映射文件,它可以将文件直接映射到内存中,使得文件操作如同访问内存一样快速。对于大文件处理,内存映射文件是一种高效的策略,...

    java 深入理解内存映射文件原理

    在Java中,使用内存映射文件通常通过java.nio包中的MappedByteBuffer类实现。通过FileChannel的map()方法,可以将文件映射到内存,从而提高大文件处理的性能。例如,以下代码展示了如何使用内存映射文件读取10MB的...

    nio-study:尼奥研究

    5. **内存映射文件(Memory-Mapped Files)**:NIO支持将文件直接映射到内存,使得读写操作如同访问数组一样快速高效。 在"nio-study-main"中,可能包含以下内容: - **示例代码**:展示了如何创建和使用通道、...

    java NIO 写文件

    4. **内存映射文件**:`MappedByteBuffer`允许将文件直接映射到内存,提供了极高的读写速度。 然而,尽管Java NIO提供了很多优势,但在实际使用中,开发者还需要注意一些潜在的问题,如内存管理(缓冲区过大可能...

    nio-demo:NIO编辑演示

    与BIO中的`InputStream`和`OutputStream`不同,`FileChannel`可以进行内存映射文件操作,提高了大文件读写的效率。通过`FileChannel`的`transferTo()`和`transferFrom()`方法,我们可以实现文件的拷贝,无需将文件...

    NIO按行读取数据

    ng(new String(content))...这种实现方式充分利用了NIO的内存映射特性,提高了读取效率,但同时也需要开发者关注潜在的兼容性和资源管理问题。在实际应用中,可以根据项目需求和性能要求来选择是否采用此类自定义实现。

    commons-mmf.rar_java nio_java共享内存_共享内存

    在Java中,可以通过`java.nio.MappedByteBuffer`类来实现共享内存功能,这被称为内存映射文件(Memory-Mapped File,MMF)。 `MappedByteBuffer`是NIO中的一种特殊缓冲区,它将文件的一部分映射到内存中,使得文件...

    java.util源码-Java.util-NIO-Source-code:基本输入和输出,包括带有NIO源代码的java.util读取文件

    通过查看源码,我们可以学习到如何使用`transferTo()`和`transferFrom()`方法进行文件间的高效数据传输,或者如何使用`map()`方法将文件映射到内存中,实现内存映射文件。 此外,`java.nio.file.Files`类提供了许多...

    JAVA_IO/NIO(demo,压缩jar文件)

    4. **内存映射文件**:NIO支持内存映射文件,直接将文件映射到内存,提高了大文件读写的性能。 **总结** 这个压缩包的示例代码可能包括了如何使用Java的IO和NIO API进行文件读写,以及如何利用`java.util.jar`包将...

    Java中用内存映射处理大文件的实现代码

    内存映射文件是通过Java的NIO(New Input/Output)包中的FileChannel类来实现的。FileChannel提供了map()方法,可以将文件的一部分或全部映射到Java虚拟机的内存中,形成一个MappedByteBuffer对象。这样,对...

    java NIO实例

    4. **FileChannel**:用于文件的读写,可以实现大文件的高效传输,支持内存映射(Mmap)技术,能直接将文件映射到内存中。 5. **SocketChannel**:用于网络通信,可以建立TCP连接,进行非阻塞的读写。在`NIOServer....

Global site tag (gtag.js) - Google Analytics