新IO和传统IO
新IO和传统IO都是用于进行输入/输出。
新IO采用了内存映射的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了,通过这种方式比传统的输入/输出要快的多。通过内存映射机制操作文件比使用常规方法和使用FileChannel读写高效的多。
内存映射文件和标准IO操作最大的不同之处就在于它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到OS内核缓冲区,而是直接将进程的用户私有地址空间中的一 部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。
“映射”就是建立一种对应关系,在这里主要是指硬盘上文件的位置与进程逻辑地址空间中一块相同区域之间一一对应,这种关系纯属是逻辑上的概念,物理上是不存在的。
1、传统IO
1.1 传统IO操作
传统的文件IO操作中,调用操作系统提供的底层标准IO系统调用函数 read()、write() ,调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态
然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区;
然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去;
这样便完成了一次IO操作。
1.2 传统IO的优化
为了减少磁盘的IO操作,同时程序访问一般都带有部性原理,OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低效率磁盘IO操作。
其过程如下:
为什么要搞一个内核IO缓冲区把原本只需一次拷贝数据的事情搞成需要2次数据拷贝呢?
这么做是为了减少磁盘的IO操作,为了提高性能而考虑的,程序访问一般都带有局部性,局部性原理,在这里主要是指的空间局部性,即我们访问了文件的某一段数据,那么接下去很可能还会访问接下去的一段数据,由于磁盘IO操作的速度比直接访问内存慢了好几个数量级,所以OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低效率磁盘IO操作。
2、新IO
讲新IO前先讲讲背景知识,虚拟空间。
2.1 虚拟空间
很久很久以前的存储管理技术必须将作业全部装入内存才能执行且作业常驻内存直到运行结束,难以满足较大作业或较多作业进入内存执行。 为了能让作业的一部分装入就可以运行的存储管理技术叫做虚拟内存管理技术。
现代操作系统中的进程在使用内存的时候,都不是直接访问内存物理地址的,进程访问的都是虚拟内存地址,然后虚拟内存地址再转化为内存物理地址。 虚拟内存就是硬盘中的一块区域,它用来存放内存里使用频率不高的页面文件,让使用频率高的页面文件活动在内存区域中,提高CPU对数据操作的速度。
进程看到的所有地址组成的空间,就是虚拟空间。虚拟空间是某个进程对分配给它的所有物理地址(已经分配的和将会分配的)的重新映射。 在Linux中,这个区域叫做swap,一般大小应设置为物理内存的2倍。详情见 linux 进程的虚拟内存_rotation ㅤ 的博客-CSDN博客
2.2 局部性原理
大多数程序执行时,在一个较短的时间内仅能使用程序代码的一部分,相应的,程序所访问的存储空间也局限于某个区域,这就是程序执行的局部性原理。
基于局部性原理,在程序装入时可以将程序的一部分放入内存,而将其余部分放在外存,然后启动程序(部分装入)。在程序执行期间,当所访问的信息不在内存中,再由操作系统将所需的部分调入内存(请求调入)。另外,系统将内存中暂时不用的内容置换到外存上,腾出空间存放将要调入内存的信息(置换功能)。
2.3 页式虚拟地址与内存页面物理地址转换
虚拟地址转化为真实地址的时候,不一定会对应内存地址,还可能对应硬盘地址。 内存的一个地址一般对应1byte,硬盘的一个地址一般对应512byte(一个磁盘扇区).
内存和硬盘里的数据做交换时,也就是把一个内存地址对应的数据拷贝到硬盘里或者反过来把硬盘数据拷贝到内存里,想要方便处理操作系统会统一单位(传说中的页对齐)。 页就是一个统一的单位,页的大小总是磁盘扇区大小的倍数,通常是2次幂,比如1024字节。
有了页这个统一单位,接下来我们说的虚拟地址、内存地址、磁盘地址都是对应的一个页。页式虚拟地址与内存物理地址建立一一对应的页表(硬件地址变换机构来执行转换)。将逻辑地址上连续的页号映射到物理内存中称为离散的多个物理块(页面),将页面和物理块一一对应,体现在页表。(页表由页号和块号组成)
虚拟地址空间可以大于实际的内存空间,比如实际内存大小是1G,但是虚拟地址空间可以是4G。这样在操作系统中的普通应用程序看来,就好像是有4G的可用内存。
虚拟地址空间可以大于实际内存空间,这是怎么实现的呢?
比如我实际内存1G,虚拟内存设成了4G,现在往4G的虚拟内存里放了4G的数据,那么当前只有1G的数据在真实内存中,另外的3G因为装不下就只能以文件形式放到硬盘里,这个存放内存内容的硬盘文件就叫页面文件。
虚拟内存的空间=物理内存+页面文件。
3、新IO-内存映射文件
传统IO中当对文件进行操作的时候,一般总是先打开文件,然后申请一块内存用做缓冲区,再将文件数据循环读入并处理,当文件长度大于缓冲区长度的时候需要多次读入。
内存映射文件是将一个文件直接映射到进程的进程空间中(“映射”就是建立一种对应关系,这里指硬盘上文件的位置与进程逻辑地址空间中一块相同区域之间一 一对应,这种关系纯属是逻辑上的概念,物理上是不存在的),这样可以通过内存指针用读写内存的办法直接存取文件内容。
在内存映射过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上放入了内存,具体到代码,就是建立并初始化了相关的数据结构,这个过程由系统调用mmap()实现,所以映射的效率很高.
经验表明,内存映射IO允许加载不能直接访问的潜在巨大文件,在大文件处理方面性能更加优异。它的不足是增加了页面错误的数目(由于操作系统只将一部分文件加载到内存,如果一个请求页面没有在内存中,它将导致页面错误)。
映射文件区域的能力取决于于内存寻址的大小。在32位机器中,你不能一次访问超过4GB或2 ^ 32(以上的文件),只能分批映射。
4、内存映射文件优化本质
mmap()是系统调用,没有进行数据拷贝,数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间(没有拷贝到内核空间),只进行了一次数据拷贝 。
从硬盘上将文件读入内存,都是要经过数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。
内存映射文件的效率比标准IO高的重要原因就是因为少了把数据拷贝到OS内核缓冲区这一步,内存映射只拷贝一次效率要比read/write 拷贝两次高。
5、虚拟内存与内存映射文件的联系
虚拟内存是内存映射文件的基础,内存映射文件的底层还是依赖虚拟内存。虚拟内存和内存映射文件都是将一部分内容加载到内存,另一部分放在磁盘上,二者都是应用程序动态性的基础,由于二者的虚拟性,对于用户都是透明的.
虚拟内存是硬盘的一部分,是计算机RAM(随机存取存储器)与硬盘的数据交换区,因为实际的物理内存可能远小于进程的地址空间,这就需要把内存中暂时不用到的数据放到硬盘上一个特殊的地方,当请求的数据不在内存中时,系统产生缺页中断,内存管理器便将对应的内存页重新从硬盘调入物理内存。
内存映射文件是由一个文件到一块内存的映射,使应用程序可以通过内存指针对磁盘上的文件进行访问,其过程就如同对加载了文件的内存的访问,因此内存文件映射非常适合于用来管理大文件。
6、虚拟内存与内存映射文件的区别
1.虚拟内存使用硬盘只能是页面文件,而内存映射使用的磁盘部分可以是任何磁盘文件。
虚拟内存实现的基础是分页机制和局部性原理,架构在物理内存之上,其引入是因为实际的物理内存运行程序所需的空间,即使现在计算机中的物理内存越来越大,将所有运行着的程序全部加载到内存中非常不现实。
2.二者的架构不同,或者是说应用的场景不同,虚拟内存是架构在物理内存之上, 内存映射文件架构在程序的地址空间之上。
内存映射文件虚拟性并不是由于局部性,而是使进程虚拟地址空间的某个区域建立映射磁盘文件的全部或部分内容,通过该区域可以直接对被映射的磁盘文件进行访问,而不必执行文件I/O操作也无需对文件内容进行缓冲处理。
用图来表示mmap,即为如下所示。mmap函数会在内存中找一段空白内存,然后将这部分内存与文件的内容对应起来。我们对内存的所有操作都会直接反应到文件中去。mmap的主要功能就是建立内存与文件这种对应关系。所以才被命名为memory map。
此图为 Linux 中进程的虚拟存储器,即进程的虚拟地址空间, 32 位操作系统,就有2^32 = 4G的虚拟地址空间,
图中有一块区域: “共享库的内存映射区域” ,这段区域就是在内存映射文件的时候将某一段的虚拟地址和文件对象的某一部分建立起映射关系,此时并没有拷贝数据到内存中去,而是当进程代码第一次引用这段代码内的虚拟地址时,触发了缺页异常,这时候OS根据映射关系直接将文件的相关部分数据拷贝到进程的用户私有空间中去。
内容参考于