您的位置 首页 java

Java基础——NIO(2)(零拷贝)

1 基本概括

2 主要介绍

2.1 传统拷贝和零拷贝的区别

传统拷贝步骤如下:

  1. 首先,调用read时,文件A拷贝到了 kernel (内核)模式;
  2. 之后,CPU控制将kernel模式数据copy到user模式下;
  3. 调用 write 时,先将user模式下的内容copy到kernel模式下的socket的buffer中;
  4. 最后将kernel模式下的socket buffer的数据 copy 到网卡设备中传送;

linux 2.1方法 内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。

  1. 首先(通过DMA)将数据从磁盘读取到kernel buffer中;
  2. 然后将kernel buffer拷贝到socket buffer中;
  3. 最后将socket buffer中的数据copy到网卡设备(protocol engine)中发

linux2.4 sendFile()改进方法

  1. 将文件拷贝到kernel buffer中;
  2. 向socket buffer中追加当前要发生的数据在kernel buffer中的位置和偏移量;
  3. 根据socket buffer中的位置和偏移量直接将kernel buffer的数据copy到网卡设备(protocol engine)中;

2.2 零拷贝的技术

第一种 直接 I/O :对于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输:这类 零拷贝 技术针对的是操作系统内核并不需要对数据进行直接处理的情况,数据可以在应用程序地址空间的缓冲区和磁盘之间直接进行传输,完全不需要 Linux 操作系统内核提供的页缓存的支持。

第二种 sendfile() : 在数据传输的过程中,避免数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间进行拷贝。有的时候,应用程序在数据进行传输的过程中不需要对数据进行访问,那么,将数据从 Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,传输的数据在页缓存中就可以得到处理。在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。Linux 中提供类似的系统调用主要有 mmap (),sendfile() 以及 splice()。

第三种 写时复制技术 :对数据在 Linux 的页缓存和用户进程的缓冲区之间的传输过程进行优化。该零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统的页缓存之间的拷贝操作。这种方法延续了传统的通信方式,但是更加灵活。在Linux 中,该方法主要利用了写时复制技术。

2.3 java中零拷贝的机制

在Java NIO包中提供了零拷贝机制对应的API,即FileChannel.transferTo()方法。不过FileChannel类是抽象类,transferTo()也是一个抽象方法,因此还要依赖于具体实现。 file Channel的实现类并不在 JDK 本身,而位于sun.nio.ch.FileChannelImpl类中,零拷贝的具体实现自然也都是native方法。

 SocketAddress socketAddress = new InetSocketAddress(HOST, PORT);
SocketChannel  socket Channel = SocketChannel.open();
socketChannel.connect(socketAddress);
File file = new File(FILE_PATH);
FileChannel fileChannel = new FileInputStream(file).getChannel();
fileChannel.transferTo(0, file.length(), socketChannel);
fileChannel.close();
socketChannel.close();
  

2.4 mmap()

mmap将一个文件或者其他对象映射进内存,当文件映射到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作

使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。但是使用 mmap 是有代价的。当你使用 mmap 时,你可能会遇到一些隐藏的陷阱。例如,当你的程序 map 了一个文件,但是当这个文件被另一个进程截断(truncate)时, write系统调用会因为访问非法地址而被 SIGBUS 信号终止。 SIGBUS 信号默认会杀死你的进程并产生一个 coredump ,如果你的服务器这样被中止了,那会产生一笔损失。

通常我们使用以下解决方案避免这种问题:

  1. 为SIGBUS信号建立信号处理程序 当遇到 SIGBUS 信号时,信号处理程序简单地返回, write 系统调用在被中断之前会返回已经写入的字节数,并且 errno 会被设置成success,但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。
  2. 使用文件租借锁 通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的 RT_SIGNAL_LEASE 信号,告诉我们内核正在破坏你加持在文件上的读写锁。这样在程序访问非法内存并且被 SIGBUS 杀死之前,你的 write 系统调用会被中断。 write 会返回已经写入的字节数,并且置 errno 为success。 我们应该在 mmap 文件之前加锁,并且在操作完文件后解锁:

文章来源:智云一二三科技

文章标题:Java基础——NIO(2)(零拷贝)

文章地址:https://www.zhihuclub.com/181776.shtml

关于作者: 智云科技

热门文章

网站地图