搜索
简帛阁>技术文章>零拷贝——如何高效的通过网络传输文件

零拷贝——如何高效的通过网络传输文件

传统文件传输方式

假如要传输320MB的文件,那么你可能会在应用程序分配32KB的内存空间,然后调用read函数从文件中读出32字节,最后调用write函数通过网络发生出去,其流程如下图所示。

这种方式非常低效,主要有如下的原因:
1、至少经历了4万次用户态与内核态的上下文切换。因为每处理32KB的消息,就需要一次read调用和一次write调用,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。上下文切换的成本并不小,虽然一次切换仅消耗几十纳秒到几微秒,但高并发服务会放大这类时间的消耗。
2、4万次内存拷贝,对320MB文件拷贝的字节数也翻了4倍,到了 1280MB。因为每发送32KB的数据,都发生了4次内存拷贝,第一次从磁盘拷贝到内核的PageCache中,第二次从内核的PageCache拷贝到用户缓冲区中,第三次从用户缓冲区拷贝到内核的socket缓冲区中,最会一次从socket缓冲区拷贝到网卡的环形缓冲区中。

零拷贝技术

linux内核2.1开始引入一个叫sendfile系统调用,这个系统调用可以在内核态内把数据从内核缓冲区直接复制到套接字(SOCKET)缓冲区内,从而可以减少上下文的切换和不必要数据的复制

#include<sys/sendfile.h>
ssize_t senfile(int out_fd,int in_fd,off_t* offset,size_t count);
out_fd是写出的文件描述符,而且必须是一个socket
in_fd是读取内容的文件描述符,必须是一个真实的文件, 不能是管道或socket
offset是开始读的位置
count是将要读取的字节数

有了sendfile这个系统调用后, 传输文件变为:

1. 应用程序开始读文件的操作
2. 应用程序发起系统调用, 从用户态切换到内核态(第一次上下文切换)
3. 内核态中通过DMA把数据从硬盘文件读取到内核PageCache
4. 在内核态中把数据从内核PageCache复制到socket的缓冲区
5. 内核态中再通过DMA把数据从socket的缓冲区拷贝到网卡的环形缓冲区上
6. 从内核态切换到用户态(第二次上下文切换)

流程图如下:

从而使得320MB文件的传输,上下文切换变成2次,总共需要拷贝960MB。
在内核2.4以后的版本中, linux内核对socket缓冲区描述符做了优化。把数据直接从PageCache复制到网卡的环形缓冲区中(这个技术叫SG-DMA(The Scatter-Gather Direct Memory Access),注意网卡也需支持)。从而避免了从"PageCache"拷贝到"socket缓冲区"的这一次拷贝。从而传输文件的流程继续简化为

1. 应用程序开始读文件的操作
2. 应用程序发起系统调用, 从用户态切换到内核态(第一次上下文切换)
3. 内核态中通过DMA把数据从硬盘文件读取到内核PageCache
4. 内核态中通过DMA把数据从PageCache拷贝网卡的环形缓冲区上
5. 从内核态切换到用户态(第二次上下文切换)

流程图如下:

从而使得320MB文件的传输,上下文切换变成2次,总共需要拷贝640MB。

零拷贝技术不但通过减少上下文切换的次数与文件拷贝的次数,还使得我们不用管理一次发生的字节数。我们知道socket的发送缓冲区大小是动态变化的,如果应用程序的缓冲区如果设置的过小,那么上下文切换次数将会增多,但是如果设置的过大,我们调用write函数的时候,又不能一次全部写完。而零拷贝技术由于传输文件都在内核中进行,因此内核可以最大化的利用发送缓冲区的大小,从而最大化的利用网络。

通过如上的技术,零拷贝可以在很大的程度上提高网络传输速度,通常可以提高一倍以上。

大文件传输

那么有没有什么场景不适合使用零拷贝技术呢?当系统中存在多个应用程序读写小文件并且没有做资源隔离时,如果某个应用需要传输大文件,那么传输大文件时不应该使用零拷贝技术。主要原因如下:
从上面的图片可以看出,内核从磁盘读取文件时,都会先读取到PageCache中,根据时间局部性原理(刚被访问的数据在短时间内再次被访问的概率很高)与空间局部性原理(刚被访问的数据的后面的数据在很短的时间内再次被访问的概率很高),为此内核会使用LRU算法缓存PageCache的数据,同时读取磁盘的时候预读一些数据。那么对于大文件,由于动辄几个GB,第一次访问数据的时候,再次被访问的概率比较小。如果读取文件都进过PageCache那么内存将很快被PageCache占用完,那么将会触发LRU淘汰算法,从而导致其他小文件的缓存被淘汰出内存,导致缓存命中低效。为此在这个场景中应该使用直接IO,自然也不应使用零拷贝技术。

直接IO

直接 IO 的应用场景并不多,主要有两种:
1、应用程序已经实现了磁盘文件的缓存,不需要PageCache再次缓存,引发额外的性能消耗。比如 MySQL 等数据库就使用直接 IO;
2、高并发下传输大文件,我们上文提到过,大文件难以命中PageCache缓存,又带来额外的内存拷贝,同时还挤占了小文件使用PageCache时需要的内存,因此,这时应该使用直接 IO。
直接 IO 也有一定的缺点。除了缓存外,内核(IO 调度算法)会试图缓存尽量多的连续IO在PageCache中,最后合并成一个更大的IO发给磁盘,这样可以减少磁盘的寻址操作;另外,内核也会预读后续的 IO 放在PageCache中,减少磁盘操作。直接 IO 绕过了PageCache,所以无法享受这些性能提升。

传统文件传输方式假如要传输320MB文件,那么你可能会在应用程序分配32KB内存空间,然后调用read函数从文件中读出32字节,最后调用write函数通过网络发生出去,其流程如下图所示。这种方式非
基于用户缓冲区传输文件时,过多内存拷贝与上下文切换次数会降低性能。拷贝技术在内核中完成内存拷贝,天然降低了内存拷贝次数。它通过一次系统调用合并了磁盘读取与网络发送两个操作,降低了上下文切换次数。尤
很多Web应用都会提供大量静态内容,也就意味着需要从磁盘读取数据,然后将它们写入到响应socket。这看上去似乎需要很少CPU活动,但是它有点低效:内核从磁盘读取数据,然后穿过内核用户边界把它送
拷贝开销本文仅是中文版本,原文由SathishPalaniappan,PramodNagaraja发布于2008年09月2号。文章适合初次接触拷贝技术并想进一步学习读者,拷贝本身是一种思
半导体、集成电路、芯片等都是我国重点科研工程,并且国产化进程都取得了重大进步。所以,对于半导体这类高端研发制造业来说,核心数据保护就尤为重要,一旦代码等核心数据遭到泄露,不仅仅是企业,甚至国家都
、前言前几天在面试时,被问到了如何保证网络数据传输安全性问题,当时对这一块没怎么研究过,所以当时并没有回答出来。所以,今天花了点时间,研究了一下这方面内容。这篇博客就来简单说一说保证网络传输
网络建设就是为了信息互通,没有了数据交互,网络作用也就缩水了不少,所以,网络隔离与数据共享交换看起来是一对矛盾,但其实不然,只要选好了数据交换产品,在网络隔离环境下,不仅能满足安全防泄密需求
学习拷贝时候,看到一篇写得非常好英文文章,尝试翻译一波很多web应用提供了非常多静态内容,这些数据从磁盘读取并写到响应socket中。这一活动可能只需要相当少CPU活动,然而它往往是有些低效
拷贝开销,更高效拷贝本身是一种思想,不与任何编程语言绑定,不懂Java读者可以跳过拷贝技术在Java中实现具体细节。许多Web应用提供大量静态内容,主要就是从磁盘读取数据然后将数据写
T行业变化快是众人皆知,需要持续去学习新知识内容。但是,往往我们工作之后,经常发现学习东西很少了,学习效率非常低,感觉自己到了一个瓶颈期,久而久之,就演变成『一年工作经验,重复去用十年』