零拷贝中的DMA/MMAP/sendfile/splice

1.什么是零拷贝?

该技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域
这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽
1.零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之前
不必要的中间拷贝次数,从而有效地提高数据传输效率
2.零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销
可以看出没有说不需要拷贝,指示说减少冗余[不必要]的拷贝
Kafka、Netty、RocketMQ、Nginx均使用了零拷贝技术

目的:减少IO流程中不必要的拷贝,当然零拷贝需要OS支持,也就是需要kernel暴露API

2.DMA

在早期计算机中,用户进程需要读取磁盘数据,需要CPU终端和CPU参与,因此效率比较低
发起IO请求,每次的IO中断,都带来CPU的上下文切换。因此出现了DMA
DMA(Direct Memory Access,直接内存读取)是所有现代电脑的重要特色,它允许不同速度的硬件
装置来沟通,而不需要依赖CPU的大量中断负载.
DMA控制器,接管了数据读写请求,减少CPU的负担,这样一来,CPU能高效工作了,现在硬盘都支持DMA
实际IO读取,涉及两个过程:
1.DMA等待数据准备好,把磁盘数据读取到操作系统内核缓冲区
2.用户进程,将内核缓冲区的数据COPY到用户空间

3.传统传输数据

比如:读取文件,再用socket发送出去,实际经过四次copy,伪代码如下:

buffer = File.read()
Socket.send(buffer)

第一次:将磁盘文件,读取到操作系统内核缓冲区
第二次:将内核缓冲区的数据,copy到应用程序的buffer
第三次:将应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区)
第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输
零拷贝中的DMA/MMAP/sendfile/splice_第1张图片
在上述过程中,虽然引入了DMA来接管CPU的中断请求,但四次的copy是存在不必要的拷贝的
实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传回套接字缓冲区之外
什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区,显然,第二次和第三次的数据
copy其实在这种场景下没有什么帮助,反而带来开销,这也正是零拷贝出现的背景和意义
同时,read,send都属于系统调用,每次调用都牵涉到两次上下文切换

总结下,传统的数据传送所消耗的成本:4次拷贝,4次上下文切换
4次拷贝,其中两次是DMA拷贝,两次是CPU拷贝
零拷贝中的DMA/MMAP/sendfile/splice_第2张图片

4.MMAP

硬盘上文件的位置和应用缓冲区(application buffer)进行映射(建立一种一一对应关系)
由于mmap()将文件直接映射到用户空间,所以实际文件读取时根据这个映射关系,直接将文件
从硬盘拷贝到用户空间,只进行了一次数据拷贝,不再有文件内容从硬盘拷贝到内核空间的一个
缓冲区
mmap内存映射将会经历:3次拷贝:1次CPU copy 两次 DMA copy
以及4次上下文切换,调用mmap函数两次,write函数两次
零拷贝中的DMA/MMAP/sendfile/splice_第3张图片

5.sendfile

Linux 2.1支持的sendfile
当调用sendfile()时,DMA将磁盘数据复制到kernel buffer,然后将内核中的kernel buffer
直接拷贝到socket buffer;但是数据并未被真正复制到socket关联的缓冲区。取而代之的是,
只有记录数据位置和长度的描述符被加入到socket缓冲区中,DMA模块将数据直接从内核缓冲区
传递给协议引擎,从而消除了遗留的最后一次复制。但是要注意,这个需要DMA硬件设备支持,
如果不支持,CPU就必须介入进行拷贝i
一旦数据全部拷贝到socket buffer,sendfile()系统调用将会return,代表数据转换的完成
socket buffer里的数据就能在网络传输了
sendfile会经历3(2,如果硬件设备支持)次拷贝,1(0,如果硬件设备支持)次CPU copy,2次DMA copy
以及两次上下文切换

零拷贝中的DMA/MMAP/sendfile/splice_第4张图片

6.splice

Linux从2.6.17支持splice
数据从磁盘读取到OS内核缓冲区后,在内核缓冲区直接可将其专程内核空间其他数据buffer,
而不用需要拷贝到用户空间
如图所示,从磁盘读取到内核buffer后,在内核空间直接与socket buffer建立pipe管道
和sendfile()不同的是,splice()不需要硬件支持
注意splice()和sendfile不同,sendfile是DMA硬件设备不支持的情况下将磁盘数据加载到
kernel buffer后,需要一次CPU copy,拷贝到socket buffer。而splice()是更进一步,连这个CPU
copy 也不需要了,直接将两个内核空间的buffer进行pipe
splice会经历两次拷贝,0次CPU拷贝,2次DMA copy
以及两次上下文切换
零拷贝中的DMA/MMAP/sendfile/splice_第5张图片

7.Java生态圈中的零拷贝

Linux提工的零拷贝技术,Java并不是全支持,支持两种(内存映射mmap,sendfile)

7.1 NIO提工的内存映射MappedByteBuffer

NIO中的FileChannel.map()方法其实就是采用了操作系统中的内存映射方式
底层就是调用Linux mmap()实现
将内核缓冲区的内存和用户缓冲区的内存做了一个地址映射,这种方式适合读取大文件
同时也能对文件内容进行更改,但是如果其后通过SocketChannel发送,还是需要CPU进行
数据的拷贝

7.2 Kafka中的零拷贝

Kafka两个重要过程都是用了零拷贝技术,且都是操作系统层面的狭义零拷贝
一是Producer生产的数据存到Broker,二是Consumer从broker读取数据
Producer生产的数据持久化到Broker,broker里采用mmap文件映射,实现
顺序的快速写入

Consumer从broker读取数据,broker里采用sendfile,将磁盘文件读到OS内核
缓冲区后,直接转到socket buffer进行网络发送

你可能感兴趣的:(服务器,java)