零拷贝


概念

零拷贝意味着数据的传输可以直接使用内核中的缓冲区并且在内核态中完成,无需再回到用户态中使用应用程序缓冲区。

场景

读文件然后通过网络发送到另外一个程序

传统方式

在java中,传统方式使用如下代码来完成.

1
2
File.read(fileDesc, buf, len); //从磁盘读数据
Socket.send(socket, buf, len); //将数据发送到socket

整个流程如下:

  1. 程序进行系统调用,从用户态切换到内核态,开始从磁盘读取数据
  2. 在磁盘中读取数据并复制到内核的读缓存区中完成后,之后内核态切换到用户态,将数据从内核缓冲区复制到应用缓冲区.
  3. 程序进行系统调用,从用户态切换到内核态,将应用缓冲区的数据复制到Socket缓冲区.
  4. 在内核态中将Socket缓冲区的数据复制到NIC缓冲区中.
  5. 数据传输完成,从内核态切换回用户态.

整个过程涉及用户态和内核态四次上下文切换和四次复制操作.如图所示:

img

img

使用零拷贝

使用零拷贝技术,可以将传统方式优化成直接将数据从读缓存拷贝到Socket缓存,不需要经过应用缓冲区

在java中可以使用如下代码实现:

1
public void transferTo(long position, long count, WritableByteChannel target);

整个流程如下:

  1. 程序进行系统调用,从用户态切换到内核态,开始从磁盘读取数据
  2. 在磁盘中复制数据到内核的读缓存区中完成,然后再将数据从读缓存区复制到Socket缓存区,最后从Socket缓存区再复制到NIC缓存区
  3. 数据传输完成,从内核态切换回用户态.

整个过程经过优化,变成了两次上下文切换和三次复制.

img

img

进一步优化

如果底层的网络接口卡支持聚合操作,我们可以进一步减少数据的复制次数.

在java中可以使用如下代码实现:

1
public void transferTo(long position, long count, WritableByteChannel target);

在去除应用缓存的基础上,使用操作系统支持的聚集操作,可以使得全过程基本不需要进行数据拷贝.

整个流程如下:

  1. 程序进行系统调用,从用户态切换到内核态,开始从磁盘读取数据
  2. 在磁盘中复制数据到内核的读缓存区中完成,然后再将数据从读缓存区复制到NIC缓存区
  3. 数据传输完成,从内核态切换回用户态.

整个步骤设计两次上下文切换和两次复制.

img

参考资料

https://developer.ibm.com/articles/j-zerocopy/


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!