title: 零拷贝(Zero-copy)学习
categories: IO
tags: 零拷贝
概念
零拷贝(Zero-copy)是一种高效的数据传输机制,是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域
传统IO传输方法
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
accessFile.read(arr);
Socket socket = new ServerSocket(6666).accept();
socket.getOutputStream().write(arr);
}
从操作系统层面来看这一次传输操作
1.JVM向OS发出read()系统调用,触发上下文切换,从用户态切换到内核态。
2.从外部存储(如硬盘)读取文件内容,通过直接内存访问(DMA)存入内核地址空间的缓冲区。
3.将数据从内核缓冲区拷贝到用户空间缓冲区,read()系统调用返回,并从内核态切换回用户态。
4.JVM向OS发出write()系统调用,触发上下文切换,从用户态切换到内核态。
5.将数据从用户缓冲区拷贝到内核中与目的地Socket关联的缓冲区。
6.数据最终经由Socket通过DMA传送到硬件(如网卡)缓冲区,write()系统调用返回,并从内核态切换回用户态。
上面总共是经过了4次上下文切换(严格来讲是模式切换),并且数据也被来回拷贝了4次
第2、3次拷贝(也就是从内核空间到用户空间的来回复制)是没有意义的,数据应该可以直接从内核缓冲区直接送入Socket缓冲区。零拷贝机制就实现了这一点。不过零拷贝需要由操作系统直接支持,不同OS有不同的实现方法
零拷贝
下面的零拷贝机制下的时序图。
零拷贝消除了从内核空间到用户空间的来回复制,因此“zero-copy”这个词实际上是站在内核的角度来说的,并不是完全不会发生任何拷贝。
在Java NIO包中提供了零拷贝机制对应的API,即FileChannel.transferTo()方法,可将拷贝次数变为3次,上下文切换次数减少到2次。
Scatter/Gather的优化
从“Read buffer”到“Socket buffer”,在一般的Block DMA方式中,源物理地址和目标物理地址都得是连续的,所以一次只能传输物理上连续的一块数据,每传输一个块发起一次中断,直到传输完成,所以必须要在两个缓冲区之间拷贝数据。
而Scatter/Gather DMA方式则不同,会预先维护一个物理上不连续的块描述符的链表,描述符中包含有数据的起始地址和长度。传输时只需要遍历链表,按序传输数据,全部完成后发起一次中断即可,效率比Block DMA要高。也就是说,硬件可以通过Scatter/Gather DMA直接从内核缓冲区中取得全部数据,不需要再从内核缓冲区向Socket缓冲区拷贝数据。
对内存映射(mmap)的支持
以上机制,如果想在传输时修改数据本身,就无能为力了,不过,很多操作系统也提供了内存映射机制,对应的系统调用为mmap()/munmap()。通过它可以将文件数据映射到内核地址空间,直接进行操作
但是呢,这样会造成4次上下文切换,另外,它需要在快表(TLB)中始终维护着所有数据对应的地址空间,直到刷写完成,因此处理缺页的overhead也会更大。在使用该机制时,需要权衡效率。
NIO框架中提供了MappedByteBuffer用来支持mmap。它与常用的DirectByteBuffer一样,都是在堆外内存分配空间。相对地,HeapByteBuffer在堆内内存分配空间。
参考:https://www.jianshu.com/p/193cae9cbf07