前言

大白话解释,零拷贝就是没有把数据从一个存储区域拷贝到另一个存储区域。然而没有数据的复制,怎么可能实现数据的传输呢?其实咱们在java NIO、netty、kafka遇到的零拷贝,并不是不复制数据,而是缩小不必要的数据拷贝次数,从而晋升代码性能

零拷贝的益处

内核空间和用户空间

缓冲区和虚拟内存

传统的 I/O

mmap+write 实现的零拷贝

sendfile 实现的零拷贝

带有DMA收集拷贝性能的sendfile实现的零拷贝

java提供的零拷贝形式

关注公众号,一起交换 :潜行前行

零拷贝的益处

缩小或防止不必要的CPU数据拷贝,从而开释CPU去执行其余工作

零拷贝机制能缩小用户空间和操作系统内核空间的上下文切换

缩小内存的占用

内核空间和用户空间

内核空间:Linux本身应用的空间;次要提供过程调度、内存调配、连贯硬件资源等性能

用户空间:提供给各个程序过程的空间;用户空间不具备拜访内核空间资源的权限,如果应用程序须要应用到内核空间的资源,则须要通过零碎调用来实现:从用户空间切换到内核空间,实现相干操作后再从内核空间切换回用户空间

缓冲区和虚拟内存

间接内存拜访(Direct Memory Access)(DMA)

间接内存拜访:DMA容许外设设施和内存存储器之间间接进行IO数据传输,其过程不须要CPU的参加

缓冲区 是所有I/O的根底,I/O 无非就是把数据移进或移出缓冲区

过程发动read申请,内核先查看内核空间缓冲区是否存在过程所需数据,如果曾经存在,则间接copy数据到过程的内存区。如果没有,零碎则向磁盘申请数据,通过DMA写入内核的read缓冲冲区,接着再将内核缓冲区数据copy到过程的内存区

过程发动write申请,则是把过程的内存区数据copy到内核的write缓冲区,而后再通过DMA把内核缓冲区数据刷回磁盘或者网卡中

虚拟内存:古代操作系统都应用虚拟内存,有如下两个益处

一个以上的虚拟地址能够指向同一个物理内存地址

虚拟内存空间可大于理论可用的物理地址

利用第一点个性能够把内核空间地址和用户空间的虚构地址映射到同一个物理地址,这样DMA就能够填充(读写)对内核和用户空间过程同时可见的缓冲区了;大抵如下

传统的 I/O
`#include 
ssize_t write(int filedes, void *buf, size_t nbytes);
ssize_t read(int filedes, void *buf, size_t nbytes);

`

如java在linux零碎上,读取一个磁盘文件,并发送到近程端的服务

1)收回read零碎调用,会导致用户空间到内核空间的上下文切换,而后再通过DMA将文件中的数据从磁盘上读取到内核空间缓冲区

2)接着将内核空间缓冲区的数据拷贝到用户空间过程内存,而后read零碎调用返回。而零碎调用的返回又会导致一次内核空间到用户空间的上下文切换

3)write零碎调用,则再次导致用户空间到内核空间的上下文切换,将用户空间的过程里的内存数据复制到内核空间的socket缓冲区(也是内核缓冲区,不过是给socket应用的),而后write零碎调用返回,再次触发上下文切换

4)至于socket缓冲区到网卡的数据传输则是独立异步的过程,也就是说write零碎调用的返回并不保证数据被传输到网卡

一共有四次用户空间与内核空间的上下文切换。四次数据copy,别离是两次CPU数据复制,两次DMA数据复制

mmap+write实现的零拷贝

`#include 
void mmap(voidstart, size_t length, int prot, int flags, int fd, off_t offset)

`

1)收回mmap零碎调用,导致用户空间到内核空间的上下文切换。而后通过DMA引擎将磁盘文件中的数据复制到内核空间缓冲区

2)mmap零碎调用返回,导致内核空间到用户空间的上下文切换

3)这里不须要将数据从内核空间复制到用户空间,因为用户空间和内核空间共享了这个缓冲区

4)收回write零碎调用,导致用户空间到内核空间的上下文切换。将数据从内核空间缓冲区复制到内核空间socket缓冲区;write零碎调用返回,导致内核空间到用户空间的上下文切换

5)异步,DMA引擎将socket缓冲区中的数据copy到网卡

通过mmap实现的零拷贝I/O进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝;其中3次数据拷贝中包含了2次DMA拷贝和1次CPU拷贝

sendfile实现的零拷贝

`#include 
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
`

1)收回sendfile零碎调用,导致用户空间到内核空间的上下文切换,而后通过DMA引擎将磁盘文件中的内容复制到内核空间缓冲区中,接着再将数据从内核空间缓冲区复制到socket相干的缓冲区

2)sendfile零碎调用返回,导致内核空间到用户空间的上下文切换。DMA异步将内核空间socket缓冲区中的数据传递到网卡

通过sendfile实现的零拷贝I/O应用了2次用户空间与内核空间的上下文切换,以及3次数据的拷贝。其中3次数据拷贝中包含了2次DMA拷贝和1次CPU拷贝

带有DMA收集拷贝性能的sendfile实现的零拷贝

从Linux 2.4版本开始,操作系统提供scatter和gather的SG-DMA形式,间接从内核空间缓冲区中将数据读取到网卡,无需将内核空间缓冲区的数据再复制一份到socket缓冲区

1)收回sendfile零碎调用,导致用户空间到内核空间的上下文切换。通过DMA引擎将磁盘文件中的内容复制到内核空间缓冲区

2)这里没把数据复制到socket缓冲区;取而代之的是,相应的描述符信息被复制到socket缓冲区。该描述符蕴含了两种的信息:A)内核缓冲区的内存地址、B)内核缓冲区的偏移量

3)sendfile零碎调用返回,导致内核空间到用户空间的上下文切换。DMA依据socket缓冲区的描述符提供的地址和偏移量间接将内核缓冲区中的数据复制到网卡

带有DMA收集拷贝性能的sendfile实现的I/O应用了2次用户空间与内核空间的上下文切换,以及2次数据的拷贝,而且这2次的数据拷贝都是非CPU拷贝。这样一来咱们就实现了最现实的零拷贝I/O传输了,不须要任何一次的CPU拷贝,以及起码的上下文切换

java提供的零拷贝形式

java NIO的零拷贝实现是基于mmap+write形式

FileChannel的map办法产生的MappedByteBuffer FileChannel提供了map()办法,该办法能够在一个关上的文件和MappedByteBuffer之间建设一个虚拟内存映射,MappedByteBuffer继承于ByteBuffer;该缓冲器的内存是一个文件的内存映射区域。map办法底层是通过mmap实现的,因而将文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享该缓冲区。用法如下

`public void main(String[] args){
try {
FileChannel readChannel = FileChannel.open(Paths.get(“./cscw.txt”), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get(“./siting.txt”), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 1024 40);
//数据传输
writeChannel.write(data);
readChannel.close();
writeChannel.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
`
FileChannel的transferTo、transferFrom 如果操作系统底层反对的话,transferTo、transferFrom也会应用相干的零拷贝技术来实现数据的传输。用法如下
`public void main(String[] args) {
try {
FileChannel readChannel = FileChannel.open(Paths.get(“./cscw.txt”), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get(“./siting.txt”), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
long len = readChannel.size();
long position = readChannel.position();
//数据传输
readChannel.transferTo(position, len, writeChannel);
//成果和transferTo 一样的
//writeChannel.transferFrom(readChannel, position, len, );
readChannel.close();
writeChannel.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

`

欢送指注释中谬误

参考文章

浅谈 Linux下的零拷贝机制[1]

面试被问到“零拷贝”!你真的了解吗?[2]

java NIO 的通道Channel的了解[3]

Channel根本应用——FileChannel类和内存映射的应用[4]

参考资料

[1]

浅谈 Linux下的零拷贝机制: https://www.jianshu.com/p/e76…

[2]

面试被问到“零拷贝”!你真的了解吗?: …

[3]

java NIO 的通道Channel的了解: …

[4]

Channel根本应用——FileChannel类和内存映射的应用: …