宜昌网站制作公司亿腾,北京哪个网站建设最好,登封市城乡建设路网站,江门网站制作建设零拷贝是由操作系统实现的#xff0c;使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝#xff0c;如果在不支持零拷贝的操作系统上运行#xff0c;并不会提供零拷贝的功能。
简述内核态和用户态
Linux 的体系结构分为内核态#xff08;内核空间…零拷贝是由操作系统实现的使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝如果在不支持零拷贝的操作系统上运行并不会提供零拷贝的功能。
简述内核态和用户态
Linux 的体系结构分为内核态内核空间和用户态用户空间我们知道一台计算器拥有 CPU、网卡、内存和磁盘等硬件资源内核态相当于 Linux Core它是一种特殊的软件程序也可以看成操作系统本身它控制着计算机的所有硬件资源并给用户态的进程分配所需的资源用户态的进程不能直接访问硬件资源它需要通过内核态体提供的接口来间接操作硬件资源。两者的关系图如下 JVM 就是一个用户态进程。
Linux 的 IO 发展史
传统 IO
在 Java 程序中你要从磁盘读取文件然后将文件发送到网络中在这个场景下看看传统 IO 数据在操作系统中的流转情况如下
开发人员调用 InputStream.read() 方法InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据此时发生了一次上下文切换用户态-内核态操作系统将数据从磁盘拷贝到 read buffer此时发生了一次数据拷贝。总共发生了一次上下文切换和一次数据拷贝。操作系统将数据从 read buffer 拷贝到应用进程JVM即操作系统 read() 接口的返回此时发生了一次数据拷贝和一次上下文切换内核态-用户态总共发生了一次上下文切换和一次数据拷贝。开发人员调用 SocketOutputStream.write() 方法将数据发送到网络中数据被从应用进程JVM拷贝到内核态的 Socket buffer 中此时发生了一次数据拷贝一次上下文切换用户态-内核态此时在用户态总共发生了一次上下文切换和一次数据拷贝。内核态操作系统调用底层接口将数据从 Socket buffer 中拷贝到网络接口中然后底层接口返回写入的结果写入字节数等到应用进程的方法 SocketOutputStream.write()此时发生了一次数据拷贝和一次下文切换内核态-用户态总共发生了一次上下文切换和一次数据拷贝。
整个流程可以分为四步总共需要经过四次数据拷贝和四次上下文切换。其中从硬件到内核态的拷贝称为 DMA copy它使用了 DMADirect Memory Access直接内存存取控制器DMA 的引入可以减少 CPU 的负担现代磁盘基本都支持 DMA 了从内核态到用户态的拷贝称为 CPU 拷贝它需要 CPU 来进行拷贝而零拷贝针对的是 CPU copy即在整个 IO 过程中将 CPU copy 将为 0 次就叫做零拷贝而 DMA copy 是不可避免的。
Linux 操作系统为了提升 IO 的速度对 IO 做了一些系列的优化其中就是以减少 CPU copy 和上下文切换为开发目的的。
mmap 内存映射
最先出现的是 mmap 内存映射使用它之后整个 IO 流程如下 mmap 使用了虚拟内存技术即内核态和用户态不直接操作物理内存而是操作虚拟内存虚拟内存映射到物理内存在 mmap 中将内核态的虚拟内存和用户态的虚拟内存映射到了同一块物理内存中这样数据在被拷贝到内核态之后就不需要再拷贝到用户态了用户态通过虚拟内存来和内核态操作同一块内存整个流程如下
开发人员调用 InputStream.read() 方法InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据此时发生了一次上下文切换用户态-内核态操作系统将数据从磁盘拷贝到 read buffer此时发生了一次数据拷贝总共发生了一次上下文切换和一次数据拷贝。InputStream.read() 方法返回此时发生了上下文切换内核态-用户态但是数据不需要再拷贝到用户态用户态中的应用进程JVM通过虚拟内存技术和内核态共用一块物理内存用户态对内存的操作会直接反映到内核态总共发生了一次上下文切换。开发人员调用 SocketOutputStream.write() 准备发送数据到网络中此时发生了一次上下文切换用户态-内核态然后内核态将与用户态共享的那块内存拷贝到内核态的 socket buffer 中总共发生了一次上下文切换和一次数据拷贝。内核态将数据从 socket buffer 中拷贝到网络接口中然后 SocketOutputStream.write() 方法返回写入结果此时发生了一次上下文切换内核态-用户态总共发生了一次上下文切换和一次数据拷贝。
整个流程分为了四步总共需要经过三次数据拷贝和四次上下文切换其中一次 CPU copy 和 两次 DMA copy。和传统 IO 相比减少了一次 CPU copy提高了 IO 的性能以及减少了 CPU 的负载。
sendfile
Linux 2.1 出现了 sendfile 技术使用它之后整个 IO 流程如下 sendfile 和 mmap 有点类似相比 mmap它取消了内存映射的部分这也导致了用户态的进程无法操作要发送的数据磁盘文件但是在不需要操作数据的场景中比 mmap 的性能更好。整个流程如下
开发人员调用 FileChannel.transferTo() 方法该方法底层调用内核态接口此时发生了一次上下文切换用户态-内核态内核态将数据从磁盘拷贝到 kernel buffer总共发生了一次上下文切换和一次数据拷贝。内核态将数据从 kernel buffer 拷贝到 socket buffer此时发生了一次数据拷贝总共发生了一次数据拷贝。内核态将数据从 socket buffer 拷贝到 网络接口此时发生了一次数据拷贝总共发生了一次数据拷贝。FileChannel.transferTo() 方法返回此次 IO 结束此时发生了一次上下文切换内核态-用户态总共发生了一次上下文切换和一次数据拷贝。
整个流程分为了四步总共需要经过三次数据拷贝和两次上下文切换其中一次 CPU copy 和 两次 DMA copy。和传统 IO 相比减少了一次 CPU copy 和 两个上下文切换和 mmap 相比减少了两次上下文切换。
mmap vs sendfile
如前所述mmap 和 sendfile 最大的不同就是用户态进程是否可以操作数据mmap 通过虚拟内存映射技术是开发人员在 IO 的过程中可以修改数据比如在发送文件之前要修改文件中第一行的数据就必须使用 mmap如果你的需求是直接将文件发送到网络接口中那么推荐使用 sendfile因为在该场景中它比 mmap 更快。
Linux 除了这两种 IO 技术还有其他的 IO 技术如 splice但是 Java 只支持这两种 IO 优化技术而且这两种也是最常见的所以这里只介绍了这两种 IO 优化技术。其实 mmap 和 sendfile 都不算真正的零拷贝因为零拷贝的概念是整个 IO 过程中零次的 CPU 拷贝mmap 和 sendfile 都需要一次 CPU 拷贝。
Java 中的抽象
在 Java 中mmap 技术和 sendfile 技术的实现都抽象在了 FileChannel 类中。下面介绍通过 FileChannel 来使用这两种 IO 技术的方式。
mmap
FileChannel 通过返回 MappedByteBuffer 来操作磁盘文件。
// 打开文件并创建 FileChannel
RandomAccessFile file new RandomAccessFile(yourfile.txt, rw);
FileChannel channel file.getChannel();
// 创建 MappedByteBuffer
MappedByteBuffer mappedBuffer channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
// 读取数据
byte data mappedBuffer.get();
// 写入数据
mappedBuffer.put((byte) 42);
// 关闭通道
channel.close();
file.close();你可以像操作普通的字节数组一样通过 MappedByteBuffer 来操作文件的数据读取和写入操作都会直接影响到文件。mmap 技术的优势在于你将文件内容直接映射到内存中避免了复制数据的开销从而提高了文件 IO 操作的性能。这对于大型文件和需要频繁读写的文件非常有用。
需要注意的是MappedByteBuffer 的大小不能超过文件的大小并且文件的更改会立即反映到映射中这可能会影响到其他访问同一个文件的程序。在多线程或多进程环境中使用 mapp 时要格外小心确保同步访问。
使用 MappedByteBuffer 将文件发送到网络接口
// 创建 SocketChannel
SocketChannel socketChannel SocketChannel.open();
socketChannel.connect(new InetSocketAddress(remote-host, port));
// 创建 MappedByteBuffer
RandomAccessFile file new RandomAccessFile(yourfile.txt, r);
FileChannel channel file.getChannel();
MappedByteBuffer mappedBuffer channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
// 将数据写入 SocketChannel
socketChannel.write(mappedBuffer);
// 关闭资源
socketChannel.close();
channel.close();
file.close();由上可知MappedByteBuffer 可以用来简单的修改磁盘文件内容这在大文件场景下非常拥有。
sendfile
FileChannel#transferTo 和 FileChannel#transferFrom 方法就是 Java 对 sendfile 的抽象。
使用 FileChannel.transferTo 方法发送文件到客户端
FileChannel fileChannel new FileInputStream(example.txt).getChannel();
SocketChannel socketChannel SocketChannel.open(new InetSocketAddress(localhost, 12345));// 将文件内容发送到客户端
long transferred 0;
long size fileChannel.size();
while (transferred size) {transferred fileChannel.transferTo(transferred, size - transferred, socketChannel);
}fileChannel.close();
socketChannel.close();在上面的示例中transferTo 方法将文件内容直接从文件通道发送到套接字通道避免了数据的复制。
使用 FileChannel.transferFrom 方法接收客户端发送的文件
SocketChannel socketChannel ServerSocketChannel.open().accept();
FileChannel fileChannel new FileOutputStream(received.txt).getChannel();// 接收客户端发送的文件并保存到本地
long transferred 0;
long size Long.MAX_VALUE; // 你需要知道文件的大小
while (transferred size) {transferred fileChannel.transferFrom(socketChannel, transferred, size - transferred);
}fileChannel.close();
socketChannel.close();在这个示例中transferFrom 方法将文件内容直接从套接字通道接收到文件通道也避免了数据的复制。
后话
如果你在之前看了很多其他讲解 Java 实现零拷贝的博文可能有很多博文会提到 Channel 对应操作系统内核态缓存这句话是有问题的我们看看 ChatGPT 怎么说
参考 https://springboot.io/t/topic/4843 https://zhuanlan.zhihu.com/p/78869158 https://blog.csdn.net/cringkong/article/details/80274148 https://www.jianshu.com/p/497e7640b57c https://chat.openai.com/