文章目录
Zero Copy
一、Linux中传统服务器进行数据传输的流程
- 传统的Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定义的缓冲区之间进行传输。这样做最大的好处是可以减少磁盘 I/O 的操作,因为如果所请求的数据已经存放在操作系统的高速缓冲存储器中,那么就不需要再进行实际的物理磁盘 I/O 操作。但是数据传输过程中的数据拷贝操作却导致了极大的 CPU 开销,限制了操作系统有效进行数据传输操作的能力。
- 图1
- 如图所示我们看到,应用程序读取磁盘数据然后发送到网卡需要四次拷贝的过程,OS采用DMA技术与硬件交互,应用程序需要2次系统调用,因此对于高性能的程序来说由性能损失。(DMA是一种硬件和软件之间的数据传输的技术,且DMA进行数据传输的过程中几乎不需要CPU参与),下面是自己画的一个数据走向流程图。
- 图2
二、什么是零拷贝?
- 简单一点来说,零拷贝就是一种避免CPU 将数据从一块存储拷贝到另外一块存储的技术。
针对操作系统中的设备驱动程序、文件系统以及网络协议堆栈而出现的各种零拷贝技术极大地提升了特定应用程序的性能,并且使得这些应用程序可以更加有效地利用系统资源。这种性能的提升就是通过在数据拷贝进行的同时,允许 CPU 执行其他的任务来实现的。零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。而且,零拷贝技术减少了用户应用程序地址空间和操作系统内核地址空间之间因为上下文切换而带来的开销。进行大量的数据拷贝操作其实是一件简单的任务,从操作系统的角度来说,如果 CPU 一直被占用着去执行这项简单的任务,那么这将会是很浪费资源的;如果有其他比较简单的系统部件可以代劳这件事情,从而使得 CPU 解脱出来可以做别的事情,那么系统资源的利用则会更加有效。综上所述,零拷贝技术的目标可以概括如下:
避免数据拷贝
- 避免操作系统内核缓冲区之间进行数据拷贝操作。
- 避免操作系统内核和用户应用程序地址空间这两者之间进行数据拷贝操作。
- 用户应用程序可以避开操作系统直接访问硬件存储。
- 数据传输尽量让 DMA 来做。
将多种操作结合在一起
- 避免不必要的系统调用和上下文切换。
- 需要拷贝的数据可以先被缓存起来。
- 对数据进行处理尽量让硬件来做。
三、零拷贝技术分类
直接 I/O
- 应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输。这类零拷贝技术针对的是数不需要OS内核处理的情况,数据可以在应用程序地址空间的缓冲区和磁盘之间直接进行传输,不需要 Linux 操作系统内核提供的页缓存的支持,如下图的5和6箭头所示。
- 图3
避免程序和内核间的拷贝
- 数据传输时避免数据在OS内核缓冲区和应用程序的缓冲区之间拷贝。有时候应用程序不需要在数据传输的过程中访问数据,那么数据从Linux 的页缓存拷贝到用户进程的缓冲区中就可以完全避免,
数据在页缓存中可以得到处理。在某些特殊的情况下,这种零拷贝技术可以获得较好的性能。Linux 中提供类似的系统调用主要有 mmap(),sendfile() 以及 splice(),如下图的1-5-4流程所示。 - 图4
优化程序和内核间的拷贝
- 对数据在 Linux 的页缓存和用户进程的缓冲区之间的传输过程进行优化。该零拷贝技术侧重于灵活地处理数据在用户进程的缓冲区和操作系统的页缓存之间的拷贝操作。这种方法延续了传统的通信方式,但是更加灵活。在Linux中,该方法主要利用了写时复制技术。这样的方式相当于保留了之前1-2-3-4的流程,只是对2,3的步骤进行了优化。
四、Kafka中的零拷贝
-
在上面的第二种零拷贝技术,就是避免了程序和内核之间的零拷贝,其中sendfile()系统调用也是kafka所采用的方法。kafka中大量使用了OS的页缓存,生产者的消息很多都缓存在页缓存中,消费者从kafka中拉取消息时,消息如果在OS系统页缓存中命中的话,就不需要去磁盘加载消息了,而Kafka大量使用了OS的页缓存,这样设计使得消费时缓存命中非常高,这也是kafka高性能的一个原因。如下我们对比零拷贝在kafka中带来的性能优化。
-
正常情况,数据从文件传输到socket的公共数据路径(如图2所示):
操作系统将数据从磁盘读入到内核空间的页缓存
应用程序将数据从内核空间读入到用户空间缓存中(系统调用)
应用程序将数据写回到内核空间的socket缓存中(系统调用)
操作系统将数据从socket缓冲区复制到网卡缓冲区,以便将数据经网络发出
- 这样做明显是低效的,这里有四次拷贝,两次系统调用。按照图2的流程,命中缓存之后需要走2-3-4的流程到网卡,(如果没有命中,就需要走1-2-3-4的流程了)。2和3这两个拷贝的过程是需要耗费CPU的,在大量请求的场景下,积少成多,浪费了不少性能。如果使用sendfile,再次拷贝可以被避免:允许操作系统将数据直接从页缓存发送到网络上。所以在这个优化的路径中,只有最后一步将数据拷贝到网卡缓存中是需要的,类似于图4中的流程。