一、IO方式
1、中断方式
1)当某个进程要进行IO
操作时,CPU
会将该线程挂起,向相应的IO设备控制器发一条IO
命令,然后CPU
返回继续执行自己原来的任务。
2)设备控制器收到命令,启动指定的IO
设备。此时CPU
和IO
设备并行工作。
3)以读数据为例,IO
设备读完数据之后,设备控制器会向CPU
发送中断信号,CPU
检查输入过程中是否出错。若没有出错,CPU
执行中断处理程序,
在中断处理中,将数据写入内存。
2、DMA方式
1)当CPU
要读数据的时候,向磁盘控制器发送一条读命令,同时将内存起始地址以及磁盘源地址发送至DMA
控制器。之后CPU
执行自己原来的任务,整个数据传送过程由DMA
控制。
2)DMA
控制器从磁盘上读如数据,再将数据写入内存中
3、linux实现
当应用程序想要进行IO
操作时,进程会通过系统调用从用户态切换到内核态,并且CPU
会将该进程挂起。系统调用是通过CPU
执行int 0x80
这条指令实现的,0x80
对应着中断向量表中中断处理程序的入口地址,所以系统调用其实就是一种软中断。切换到内核态之后,CPU
就开始执行linux
内核中的方法。
以从硬盘中读取数据为例,系统调用就是内核中的read
方法,并会给read
方法传入目标文件的文件描述符。内核会先分配页缓存,之后开始读取数据。
读数据有两种方式,一种是CPU
先将数据读到自己的寄存器中,再将数据写入到内核中的页缓存里。一种是通过DMA
,将数据从硬盘直接读到内核的页缓存里。现代计算机都是通过DMA
方式读入数据,可以避免CPU
把大量时间消耗在搬运数据上。
当DMA
将数据读完后,会给CPU
发送一个中断信号,CPU
收到信号,将进程又切换为就绪态。当CPU
下次调度到该进程时,程序恢复现场,并且数据已经读到了,就可以继续执行了。
页缓存的作用:如果没有页缓存,程序每一次IO
都要重新系统调用read
,从用户态切换到内核态,再从硬盘中读数据。有了页缓存,会先去页缓存中找,如果命中,就可以避免系统调用。
二、JDK中的NIO
传统IO
中,读写文件时,会在JVM
内部开辟一块缓冲区,利用这块缓冲区将数据写到linux
内存中是需要通过系统调用的。
使用JDK
中的NIO
读写文件时,会在整个程序的堆外分配缓冲区,即MappedByteBuffer
,这里要注意JVM
运行在linux
内核上的一个程序,内核会给它分配一块空间去运行,这块空间是程序的空间,包括程序代码区,数据区,堆区,栈区,JVM
就整个运行在这块空间堆区,前面的堆外分配中的堆不是JVM
运行时数据区的堆,而是程序的堆,如下图所示。这块缓冲区是虚拟文件系统中存在一块空间,可以根据它的线性地址直接访问内核内存空间中的页缓存。
所以在读写数据时,数据从JVM
中到内核内存这一步是不需要通过系统调用完成的,这样就避免了多余的系统调用。
简单理解就是数据从JVM
到linux
内存这个过程,传统IO
是需要系统调用read
和write
方法的,而NIO
中是直接往内存里的页缓存读写,是不需要通过系统调用的。所以在大文件的读写时,使用NIO
效率更高。