磁盘IO的工作机制
需要搞清的问题,磁盘IO怎么工作的,IO的控制方式有哪几种。
为什么需要对磁盘IO进行探究,在web应用中,涉及到最多的就是数据的流通问题,
无论是在本地机器上的数据流动还是在网络上的数据流通,都会涉及到IO操作,IO
问题也成为应用系统的瓶颈问题,了解磁盘IO的工作机制有助于进一步理解复杂的
web应用问题。
字节和字符
这个问题的引入是来自java中的字符接口(reader,write)和字节接口(Inputstream)
在java中,Inputstream以及和它相似的接口都是用于处理字节流,一个字节就是8bit。
而Reader以及和它相似的接口是用于处理字符流,字符流会涉及到字符集的问题,不同的
字符集可能占用不同的字节,所以将输入内容翻译成不同的字符。例如使用ASCII和unicode
来翻译相同的字节流,肯定会得到不同的字符流。这里是对java中字节和字符做一个简单的
讨论。
访问文件的方式
应用程序想要读入和写入文件IO操作都需要调用操作系统的接口,进行系统调用就意味着要
进行内核空间和用户空间的切换。数据要先从磁盘传输到内核空间,然后再从内核空间传输
到用户空间。对于一个耗时的操作,这样的过程需要非常久。
标准IO
标准IO又称缓冲区IO,它的方式是在内核空间中使用缓冲区机制,数据会先被复制到内核
缓冲区,然后才从内核缓冲区复制到应用程序的地址空间。
在读取IO文件时,如果要读取的数据已经在内核缓冲区中,那么直接从缓冲区复制到用户空
间,而不需要进行实际的读磁盘操作,毕竟磁盘是一个低速设备,对它的访问会需要比较久
的时间。如果内核缓冲区没有所需要的数据,才会进行读磁盘操作。
同样,写操作也是先把数据从用户空间复制到内核空间,其实,这样一个写操作已经完成。
至于内核缓冲区中的数据何时写入到磁盘中,则由操作系统决定。
当然,如果向立即写入,可以试用同步写机制,sync()函数可以帮助完成直接写操作。
标准IO有什么缺点吗?
标准IO的工作方式可以很明显的看出来,它需要多次的复制,从用户空间到缓冲区,从缓冲
区到内核空间,多次的复制会占用更多的CPU时间。
直接IO
有些应用程序并不太需要操作系统的缓存机制,因为它们对自己的数据可能有更好的缓存
方式,数据库管理系统就是这样的一个例子,系统明确知道应该缓存哪些数据,应该失效
哪些数据,所以在这种情况下,并不太需要系统缓存的存在,使用直接IO的方式更适合这
些应用系统。
直接IO有缺点吗?
直接IO的缺点很明显,虽然它能减少CPU的使用时间,但是如果应用缓存中没有想要的数
据,那么就需要花费漫长的时间从磁盘中把数据读入到缓存中。
内存映射
内存映射是指操作系统将内存中的某一块区域与磁盘中的文件关键起来,当要访问内存
的一段数据时,转换为访问文件的某一段数据。
硬盘上的文件的位置和进程逻辑地址空间中一块大小相同的区域之间是一一对应的,逻辑
上的映射。在内存映射过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑
上被放入内存,具体到代码,就是建立了相应的数据结构。系统调用mmap()可以实现,
所以建立内存映射的效率很高。
mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无
需再调用read活write对文件进行读写,而只需要通过ptr就能够操作文件。
但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成
物理地址。
建立内存映射并没有实际复制数据,这时,MMU在地址映射表中是无法找到ptr对应的物
理页面,会产缺页中断,通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存
中。
从硬盘上将文件读入内存,都要经过文件系统进行数据复制,为什么试用内存映射会比
read/write效率高呢?内存映射只是将内存和硬盘文件进行映射,并没有实际的数据
拷贝。真正的数据拷贝是由缺页中断产生的,由于mmap()将文件直接映射到用户空间,
所以根据这个映射,直接将文件从硬盘拷贝到用户空间,整个过程只进行了一次数据拷贝。
而read/write需要进行两次的数据拷贝,所以内存映射在处理大文件的时候效率比较高。
内存映射文件原理探究
IO控制方式
上面所有内容都是在说明如何让磁盘文件复制到用户空间中,何种工作机制能够更加节省
CPU占用时间,或者何种工作机制能够更符合自缓存的应用程序。
但是文件中的内容是如何从磁盘复制到内核空间,又是如何从内核空间复制到用户空间的
呢?是一个一个字符的复制吗?还是一块一块数据的复制?这里就需要讨论的是IO的控
制方式。
程序IO方式(PIO)
程序IO的方式是轮循,CPU首先向IO控制器发送一个读命令,IO控制器会设置自己的状态
字busy。之后CPU会一直轮询busy,当busy=0时,表示控制器已经把需要的字符读入到了
自己的寄存器中,CPU可以取走这个数据。这个过程看似没什么问题,但是这个过程CPU一
直在询问IO控制器的状态,导致CPU在IO控制器读数据的时间也不能去做其他的事情,很
明显会降低CPU的工作效率。
中断方式
中断应该是比较熟悉的一个名词,现代计算机中都有中断系统。进程的切换,方法的调用
和退出都需要中断来协助完成,此时敲击键盘就是一个keyboard interrupt.
使用中断方式就不需要CPU一直处于轮询状态,因为IO控制器在读完数据后会给CPU一个
中段,让CPU来取数据,这样的话,CPU在其他时间就可以去做其他的任务。这样CPU得
到解放,效率会大大提升。
直接存储器访问DMA方式
虽然中断方式已经解放了一部分CPU,但是中断方式每次只能读一个字节(具体可能看IO
控制器数据寄存器的大小,一般都是8bit,一个字节)。所以如果要读入一块数据,例如
要读取1K的数据,那么CPU仍然要进行1K次的干预。有没有什么办法让CPU读1K数据的时
侯只进行一次干预呢?
DMA就是一次读取一个数据块的。
先来看一个DMA控制器
当CPU要从磁盘读入一个数据块时,便向磁盘控制器发送一条读命令,该命令会被送到其中
的命令寄存器(CR)中。同时,还必须发送本次要将数据读入的内存起始目标地址,该地
址被送入内存地址寄存器(MAR)中,本次要读的数据的字(节)数则送入数据计数器(DC)
中,还需要将磁盘中的源地址直接送至DMA控制器的IO控制逻辑上。之后CPU就可以去处理
其他任务,数据的传送由DMA自己完成。
当DMA控制器已经从磁盘中读入一个字节的数据并送入数据寄存器(DR)中,再将该数据送
到内存中区,将DC内容减1,对MAR内容加1.继续传送,直到DC=0为止。
通道控制方式
DMA虽然能够读取一个数据块,问题又出来了,读取多个数据块怎么办?通道方式其实是
DMA的加强版,它自己拥有通道程序,能够执行一系列通道指令。
它把对一个数据块的读写为单位进行中断变成对一组数据块的读写为单位进行中断,
进一步解放CPU。