Java NIO——Zero-copy

相关文章java  nio是如何实现零拷贝(zero-copy)的

技术介绍

零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,从而可以减少上下文切换及CPU的拷贝时间,通常用于通过网络传输文件时节省CPU周期和内存带宽。

假如我们要实现这样的功能:将文件中的字节复制到套接字中

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

传统实现方式

过程如下

  1. read()函数调用导致了一次用户态到内核态的上下文切换。系统内部的sys_read()调用被用于把数据读取出来。第一次复制是以DMA引擎的方式呈现的,DMA把数据读取出来并存入kernel address space buffer。

  2. 数据被读取返回后导致了上下文从内核态切换到用户态,现在数据存在user address space buffer中。

  3. send()操作再次把上下文从用户态切换到内核态。并且数据被从用户缓存拷贝到kernel address space buffer中。

  4. send()操作返回,这时又从内核态返回到用户态,并且发生最后一次数据拷贝,数据从kernel buffer拷贝到protocol engine中。

这个过程当中一共出现了4次数据拷贝和4次用户态-内核态用户态-内核态上下文切换(每一次系统调用都是两次上下文切换:用户态->内核态->用户态)。

Linux 2.4之前的底层实现

仔细检查上面的流程,其实第二次和第三次复制是不必要的(从buffer到应用程序、从应用程序到buffer),应用程序并没有改变数据内容,只是将其返回到socket buffer中。Java中提供了transferTo()方法,可以让你实现数据直接从read buffer输到 socket buffer。

transferTo() 方法将数据从一个文件channel传输到一个可写channel。在内部它依赖于操作系统对 Zero-copy 的支持,在UNIX/Linux系统上, transferTo() 实际会调用 sendfile() 这个系统函数,将数据从一个文件描述符传输到另一个。

下图展示了使用 transferTo()时的数据拷贝情况

image

过程如下

  1. transferTo()调用使文件内容通过DMA的方式被复制到read buffer。然后将数据复制到与输出的socket相关的buffer中。

  2. 第三次复制发生在DMA将数据复制到protocol engine。

这已经有了改进,我们已将上下文切换次数从四次减少到两次,并将数据拷贝的次数从四次减少到三次(其中只有一次涉及CPU操作)

 Linux 2.4之后的底层实现

image

过程如下

  1. transferTo方法调用使文件内容通过DMA引擎被复制到kernel buffer

  2. 无数据被复制到socket buffer。只是描述了需要被写入的数据的位置和长度。DMA引擎直接把数据从kernel buffer复制到protocol engine

现在整个过程只有两次上下文切换和两次数据拷贝。

在内核为2.4或者以上版本的linux系统上,socket缓冲区描述符将被用来满足这个需求。这个方式不仅减少了内核用户态间的切换,而且也省去了那次需要cpu参与的复制过程。 
从用户角度来看依旧是调用transferTo()方法,但是其本质发生了变化:

调用transferTo方法后数据被DMA从文件复制到了内核的一个缓冲区中。

数据不再被复制到socket关联的缓冲区中了,仅仅是将一个描述符(包含了数据的位置和长度等信息)追加到socket关联的缓冲区中。DMA直接将内核中的缓冲区中的数据传输给协议引擎,消除了仅剩的一次需要cpu周期的数据复制。

mmap方式

内存映射方式,介于上文讲的第一种与第二、三中之间,mmap允许代码将文件映射到内核内存并直接访问它,就好像它在用户空间中一样,从而避免了不必要的复制。 

传统I/O用户空间缓冲区中存有数据,因此应用程序能够对此数据进行修改等操作;而transferTo方法是应用层级的方法,最终是调用sendfile系统方法,sendfile零拷贝消除了所有内核空间缓冲区与用户空间缓冲区之间的数据拷贝过程,因为sendfile零拷贝I/O的实现是完全在内核空间中完成的,这对于应用程序来说就无法对数据进行操作了。为了解决这个问题,Linux提供了mmap零拷贝来实现我们的需求

过程如下

  1. 发出mmap系统调用,导致用户空间到内核空间的上下文切换(第一次上下文切换)。通过DMA引擎将磁盘文件中的内容拷贝到内核空间缓冲区中(第一次拷贝: hard drive ——> kernel buffer)。
  2. mmap系统调用返回,导致内核空间到用户空间的上下文切换(第二次上下文切换)。接着用户空间和内核空间共享这个缓冲区,而不需要将数据从内核空间拷贝到用户空间。因为用户空间和内核空间共享了这个缓冲区数据,所以用户空间就可以像在操作自己缓冲区中数据一般操作这个由内核空间共享的缓冲区数据。
  3. 发出write系统调用,导致用户空间到内核空间的上下文切换(第三次上下文切换)。将数据从内核空间缓冲区拷贝到内核空间socket相关联的缓冲区(第二次拷贝: kernel buffer ——> socket buffer)。
  4. write系统调用返回,导致内核空间到用户空间的上下文切换(第四次上下文切换)。通过DMA引擎将内核空间socket缓冲区中的数据传递到协议引擎(第三次拷贝: socket buffer ——> protocol engine)

通过mmap实现的零拷贝I/O进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝。明显,它与传统I/O相比仅仅少了1次内核空间缓冲区和用户空间缓冲区之间的CPU拷贝。这样的好处是,我们可以将整个文件或者整个文件的一部分映射到内存当中,用户直接对内存中对文件进行操作,然后是由操作系统来进行相关的页面请求并将内存的修改写入到文件当中。我们的应用程序只需要处理内存的数据,这样可以实现非常迅速的I/O操作。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值