在实际应用中,如果我们需要把磁盘中的内容发送到远程服务器上,那么需要经过一下几个拷贝的过程。
第一个从磁盘中去读取目标文件的内容去拷贝到内核缓冲区,第二个cpu控制器把内核缓冲区的数据拷贝到用户空间的缓冲区中,第三个在应用程序中调用write()方法把用户空间缓冲区的数据拷贝到内核空间的socketBuffer中,第四个把在内核模式下的socket buffer中的数据复制到网卡缓冲区,叫NIC Buffer 最后,网卡缓冲区再把数据传输到目标服务器上,在这整个过程中,数据从磁盘最终发送出去要经历四次拷贝,这四次拷贝中有两次是可以省略的。第一,从内核空间拷贝到用户空间。第二,从用户空间再次拷贝到内核空间。除此之外,由于用户空间和内存空间的切换,会带来cpu的上下文切换,对于cpu的性能也会造成影响。
而所谓的零拷贝就是把这两次多余的拷贝忽略掉,应用程序可以直接把磁盘中的数据从内核中直接传输到Socket中去,而不需要再次经过应用程序所在的用户空间零拷贝通过DirectMemory Access技术把文件内容复制到内核空间中的ReadBuffer,接着把包含数据长度和位置信息的文件描述符加载到SocketBuffer中,DMA引擎可以直接把数据从内核空间传递到网卡设备,在这个流程中数据只经历了两次拷贝就把数据发送到了网卡当中,并且减少了两次cpu的上下文切换对于效率是有非常大的提高,所以,所谓的零拷贝并不是完全没有数据拷贝只是相对用用户空间来说,不需要再进行数据的拷贝对于前面说的整个流程来说,零拷贝只是减少了不必要的拷贝次数而已。
那么,在程序中如何实现零拷贝呢,在linux系统中,零拷贝依赖于底层sendfile()方法去实现的,而在Java中FileChannal.transferTo()方法的底层实现就是sendfile()方法除此之外还有一个叫mmap的文件映射机制,他的原理是把磁盘文件映射到内存,用户通过修改内存就可以修改磁盘文件,使用这种方式,可以获得很大的io提升,省去了用户空间到内核空间的复制开销。