缓冲区是所有 I/O 的基础,I/O 讲的无非就是把数据移进或移出缓冲区;进程执行 I/O 操作,就是向操作系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读)。
进程发起 Read 请求之后,内核接收到 Read 请求之后,会先检查内核空间中是否已经存在进程所需要的数据,如果已经存在,则直接把数据 Copy 给进程的缓冲区。
如果没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核 Read 缓冲区,这一步通过 DMA 完成。
接下来就是内核将数据 Copy 到进程的缓冲区;如果进程发起 Write 请求,同样需要把用户缓冲区里面的数据 Copy 到内核的 Socket 缓冲区里面,然后再通过 DMA 把数据 Copy 到网卡中,发送出去。
你可能觉得这样挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的出现就是为了解决这种问题的。
关于零拷贝提供了两种方式分别是:
mmap+write
Sendfile
所有现代操作系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样做的好处是:
一个以上的虚拟地址可以指向同一个物理内存地址。
虚拟内存空间可大于实际可用的物理地址。
利用第一条特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样 DMA 就可以填充对内核和用户空间进程同时可见的缓冲区了。
下面看一个简单的读取实例,然后再对 MappedByteBuffer 进行分析:
MapMode:映射的模式,可选项包括:READ_ONLY,READ_WRITE,PRIVATE。
Position:从哪个位置开始映射,字节数的位置。
Size:从 Position 开始向后多少个字节。
大致浏览一下 map() 方法的源码:
上一节中通过 Filechannel 映射出的 MappedByteBuffer 其实际也是 DirectByteBuffer,当然除了这种方式,也可以手动开辟一段空间:
经常需要从一个位置将文件传输到另外一个位置,FileChannel 提供了 transferTo() 方法用来提高传输的效率,首先看一个简单的实例:
通过 FileChannel 的 transferTo() 方法将文件数据传输到 System.out 通道,接口定义如下:
看下面一张图会比较清晰:
可以看一下 Netty 提供的 CompositeChannelBuffer 源码:
本文分享自微信公众号 - 漫话编程(mhcoding)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。