NIO零拷贝
文章目录
- 概述
- 一、socket缓冲区与直接内存
- 二、零拷贝的实现
- 2.1、DMA(直接内存访问)
- 2.2、零拷贝方式一:Linux的MMAP内存映射
- 2.3、零拷贝方式二:Linux的sendFile
- 2.4、零拷贝方式三:Linux的slice
本文为个人学习笔记整理,仅供交流参考,非专业教学资料,内容请自行甄别
概述
在前篇中提到,socket是位于应用层和运输层之间的一个软件抽象层,每次建立连接,都会产生一个socket实例。
每一个socket中有两个缓冲区,发送缓冲区
和接收缓冲区
。缓冲区数据,只有得到对端确认之后才会清除,并非发送后立刻清除,由此保证消息的可靠传输。
一、socket缓冲区与直接内存
应用程序要向对端写数据,调用write首先是将应用进程缓冲区
的数据,写入到socket的发送缓冲区
。操作系统再从socket的发送缓冲区
读取数据经过协议栈的处理,发送到对端。
应用程序缓冲区,通常使用的是直接内存
,而非堆内存
。如果应用程序缓冲区是堆上分配的,jvm首先会在堆外创建一个direct buffer(直接内存),然后将堆上要发送的数据,拷贝到direct buffer,再写到socket的发送缓冲区。为什么要这样操作?因为堆有垃圾回收机制。一旦触发了垃圾回收,数据位于堆上的位置是有可能发生变化的。(整理,复制算法)如果在write的过程中发生,就会导致错误,即有可能把一个地址传给底层的write,但是这段内存却因为垃圾回收整理内存而失效了。堆外的空间,没有垃圾回收机制,需要手动地进行内存管理。同样带来的问题是,出现内存泄漏的问题,不容易去排查。
使用direct buffer,在堆外分配一块区域,数据直接放在堆外区域上,然后写到socket的发送缓冲区,节省了将数据从堆复制到direct buffer的IO。
二、零拷贝的实现
零拷贝指的是,计算机执行操作时,CPU
不需要先将数据从某处内存复制到另一个特定区域,减少了CPU上下文切换
带来的开销。零拷贝只是尽量地去精简拷贝的次数,而非完全无需进行拷贝的操作
2.1、DMA(直接内存访问)
在早期计算机中,用户进程要读取磁盘上的数据,需要CPU的参与,也就会受到CPU上下文切换开销的影响。而DMA正是用于解决上面提到的问题,它是一种硬件技术,允许外部设备(如硬盘、网卡或声卡)直接与系统内存交换数据,而无需中央处理器(CPU)的干预:使CPU专注于计算任务而非数据传输。
DMA的核心思想是让DMA控制器
直接管理数据在内存和外设之间的传输,CPU只需初始化传输参数,后续操作由DMA控制器自主完成,实际的文件传输经历了以下的过程:
- DMA等待数据准备好,把磁盘数据读取到操作系统内核缓冲区。
- 用户进程,将内核缓冲区的数据复制到应用程序缓冲区。
图片来源:图灵学院
上述的过程,发生了四次上下文切换,四次拷贝
上下文切换体现在调用send和read方法,每次调用都涉及到用户态->内核态,内核态->用户态的两次切换。
四次拷贝体现在:
- 磁盘数据通过DMA拷贝到文件读取缓冲区。
- 文件读取缓冲区数据通过CPU拷贝到应用进程缓冲区。
- 应用进程缓冲区数据通过CPU拷贝到发送缓冲区。
- 发送缓冲区数据通过DMA拷贝到网络设备缓冲区准备进行发送。
在上述的过程中,2和3的两次CPU拷贝,实际上是没有必要的,因为在拷贝的过程中,并不需要应用程序对数据进行修改,只是进行了中转。
2.2、零拷贝方式一:Linux的MMAP内存映射
把磁盘和应用程序缓冲区之间的文件读取缓冲区
取消
磁盘文件分成了很多块,将内存中的区域映射到磁盘对应的区块,通过MMAP的调用,直接将磁盘的区块中的内容,读取到对应的内存区域中,不需要文件读取缓冲区进行中转
图片来源:图灵学院
2.3、零拷贝方式二:Linux的sendFile
磁盘->文件读取缓冲区->套接字发送缓冲区->网络设备缓冲区,无需经过应用程序缓冲区的中转。
图片来源:图灵学院
如果DMA设备允许,就是两次拷贝:在进行数据传输时,不需要将数据通过cpu进行拷贝,而是告诉cpu数据放在缓冲区内核中的位置和大小。(cpu不拷贝,只传递参数)DMA直接进行读取。
2.4、零拷贝方式三:Linux的slice
从磁盘读取到内核buffer后,在内核空间直接与socket buffer建立pipe管道。让发送和读取缓冲区共享一个内存区域。(不需要硬件的支持)
目前在JAVA的API层面,只支持前两者零拷贝的实现。