浅谈零拷贝机制

预备知识

我相信来看本文章的应该都对操做系统有了一些了解,不过在谈零拷贝以前,还须要讲一些别的东西,避免到时候你们看的晕乎linux

  • 内核态和用户态:这两种不一样的状态分别赋予进程不一样的权限,内核态下能够访问内存的全部数据,可以访问外围设备;而用户态下则只能访问受限的内存。因此若是一个进程想要执行深度操做,就须要涉及用户态到内核态的切换
  • 系统调用:系统调用不是一种调用行为,能够理解成操做系统开放的编程接口,经过使用“系统调用”接口,能够达到一些在用户态下没法完成的行为,固然,会涉及到用户态到内核态的状态切换
  • 缓冲区:缓冲区是内存中的一块区域,是IO操做的基础。任何IO操做均可以理解为数据在缓冲区之间的拷贝,用户空间和内核空间都有对应的缓冲区。操做系统不能直接将数据从磁盘拷贝到用户空间的缓冲区中
  • DMA:DMA即Direct Memory Access,负责将数据从一个地址空间转移到另外一个地址空间,传输动做由CPU初始化,但由DMA来执行。在DMA执行操做期间,CPU能够处理其余的事情
  • 虚拟内存:内存地址分为虚拟内存地址和物理内存地址两种,进程可见的是虚拟内存地址,操做系统会将虚拟内存地址映射到物理内存上,虚拟内存是连续的,可是物理内存能够是不连续的

零拷贝解决什么问题

在讲零拷贝以前,先得明白拷贝是什么。这里的拷贝指“I/O操做时,数据在缓冲区之间的拷贝”。不明白不要紧,咱们先来看linux中的将数据从磁盘读取,并经过网络发送出去的一整个过程是怎样的:编程

  1. DMA将数据从磁盘拷贝到内核缓冲区中网络

  2. cpu将数据从内核缓冲区拷贝到用户缓冲区中(读过程结束,进程能够从用户缓冲区直接读取数据)socket

  3. cpu将数据从用户缓冲区拷贝到内核socket缓冲区中学习

  4. DMA将数据从socket缓冲区发送到协议引擎中(写过程结束,数据被协议引擎经过网络发送)优化

这里借用一下理解零拷贝原理中的图: 操作系统

整个读写的过程大体是这样的,涉及2次用户态和内核态的状态切换,2次DMA拷贝,和2次cpu拷贝

咱们一样也发现,在一个读写过程当中,第2步和第3步仿佛在作“无用功”,数据若是直接从内核缓冲拷贝到Socket缓冲就能够了,为何还要经过用户缓冲中转呢?没错,操做系统的开发者也意识到了这个问题,因此零拷贝就是为了解决这个问题,因此利用零拷贝机制,能够避免状态切换,并减小了cpu的拷贝次数.net

零拷贝的实现方式

虽然看起来只须要容许数据从内核缓冲拷贝到socket缓冲,便可解决数据拷贝的问题,可是具体却有不少种实现方式,咱们来一一介绍一下指针

sendFile

sendFile方式的系统调用为sendfile system call,也是最经典的零拷贝解决方案。采用sendFile方式的的读写过程为:netty

  1. DMA将数据从磁盘拷贝到内核缓冲
  2. cpu将数据从内核缓冲拷贝到内核socket缓冲
  3. DMA将数据从socket缓冲拷贝到协议引擎中

这种标准方式就不用过多介绍了,就是咱们在刚才提到的解决方案,接下来咱们看优化版本的sendFile的读写过程是怎样的:

  1. DMA将数据从磁盘拷贝到内核缓冲
  2. cpu将描述符[1]从内核缓冲拷贝到socket缓冲
  3. DMA将数据从socket缓冲拷贝到协议引擎中

在优化的sendFile方式中,第2步再也不是拷贝数据,而是拷贝描述符,这样就真正实现了数据的零拷贝

mmap

mmap,也能够成为内存映射,效果比传统的I/O要好,可是代价比sendFile要大。咱们来看mmap方式下的读写操做:

  1. DMA将数据从磁盘拷贝到内核缓冲/用户缓冲
  2. cpu将数据从内核缓冲/用户缓冲拷贝到socket缓冲
  3. DMA将数据从socket缓冲拷贝到协议引擎中

这里画了个斜线,表示内核缓冲和用户缓冲是同一块区域,mmap采用虚拟内存映射,虽然进程认为本身的用户缓冲区域是内存中独立的区域,可是实际上用户缓冲和内核缓冲指向的同一块区域,这种方式也能避免数据拷贝问题

FileChannel

FileChannel是Java NIO的解决方案,提供transferTo/transferFrom接口,用于将一个通道链接到另外一个通道,中间就避免了没必要要的数据拷贝

更严格地说,FileChannel并不能算是零拷贝的一种解决方案,实际上仍是须要依赖操做系统的sendFile

Netty Zero-Copy

Netty中在FileRegion封装了Java NIO的transferTo/transferFrom,也能够实现零拷贝,可是这不是重点,Netty还提供了另外一种形式的零拷贝,也是咱们学习的重点 在数据传输时,一般一份完整的消息数据会被切分红多个数据包进行传输,而这些单个的数据包是没有意义的,只有组合在一块儿才有具体的含义,才能被程序进行处理。Netty能够经过零拷贝的方式,将这些数据组合成完成的消息来供使用,减小数据拷贝次数,此时零拷贝的做用范围仅局限于用户空间

同时,Netty能够直接在堆外分配内存,避免了数据从堆内拷贝到堆外的过程

总结

零拷贝的这些方式中,sendFile虽然看起来最美好,可是若是咱们的应用在读取数据后还须要进行更改的话,这种方式就不适用了,就须要使用代价更高的mmap内存映射方式

零拷贝的内容大体就是这些了,若是想更为深刻地去学习零拷贝知识的话,能够去学习一下netty的源码,仍是颇有必要的


  1. 这里我不敢肯定是内存描述符仍是数据描述符,我倾向于内存描述符,是一个相似指针的数据,若是我这里理解错误欢迎指正 ↩︎

相关文章
相关标签/搜索