前言 在介绍零拷贝之前,咱们先通过简略的例子理解一般的数据传输模式有什么弊病,而后再看看零拷贝技术解决了哪些问题。咱们晓得很多 Web 应用程序提供大量动态内容,这相当于从磁盘读取数据并将完全相同的数据写回响应套接字(也就是 socket),而后再发送给客户端。这个过程仿佛只须要绝对较少的 CPU 流动,但其实这样做是较为低效的。首先咱们须要晓得内核从磁盘读取数据并将其跨内核用户边界推送到应用程序,而后应用程序将其推送回内核用户边界写入套接字。实际上,应用程序更相当于一个低效的搬运工,其从磁盘文件获取数据而后再将其转运到套接字。为什么说下面的流程是低效的呢?首先咱们须要明确每次数据穿梭用户内核边界时(用户态与内核态的切换),都必须进行复制,这会耗费 CPU 周期和内存带宽。那有没有什么办法去缩小这些不必要的复制呢?答案天然是必定的,有请咱们明天的配角:零拷贝技术。何为零拷贝零拷贝(Zero-copy;也被称为零复制)技术是指计算机执行操作时,CPU 不须要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节俭 CPU 周期和内存带宽。应用零拷贝的应用程序申请内核间接将数据从磁盘文件复制到套接字,而不通过应用程序。零拷贝极大地提高了应用程序性能并缩小了内核和用户模式之间的上下文切换次数。Java 类库通过 java.nio.channels.FileChannel.transferTo() 办法反对零拷贝技术,能够通过 transferTo() 办法将字节间接从调用它的 channel 传输到另一个可写字节 channel,而无需数据通过应用程序。具体流程分析接下来咱们详细分析下面所说的流程,事实上这是一个很常见的场景,它形容了很多服务器应用程序的行为,包含 FTP 服务器、邮件服务器等等。咱们首先分析传统计划(将字节从文件复制到 socket)的解决流程:
看上去如同很简略,然而实现起来须要在用户态和内核态之间进行四次上下文切换同时进行了四次数据复制,数据如何在外部从文件挪动到 socket 如下图所示:
上下文切换过程如下图所示:
而后咱们来剖析一下具体的流程,大抵分为以下几步:Read() 调用导致从用户模式到内核模式的上下文切换,并在外部收回一个 sys_read()(或者其余等效的调用)以从文件中读取数据,第一个正本由 DMA 引擎执行,该引擎从磁盘读取文件内容并将它们存储到内核地址空间缓冲区中;2. 申请的数据从读取缓冲区复制到用户缓冲区,而后 read() 调用返回。调用的返回导致了内核态到用户态的切换,并且当初数据存储在用户地址空间缓冲区中;3.send() 调用导致从用户模式到内核模式的上下文切换,并执行第三次复制以再次将数据放入内核地址空间缓冲区。然而留神这一次数据被放入了一个不同的缓冲区,一个与指标套接字相关联的缓冲区。4. 零碎 send() 调用返回,导致第四个上下文切换。当 DMA 引擎将数据从内核缓冲区传递到协定引擎时,会独立且异步地进行第四次复制。零拷贝办法的实现过程如果咱们仔细察看下面的流程,会发现实际上并不需要第二个和第三个数据正本。应用程序除了缓存数据并将其传输回套接字缓冲区外什么也不做。相同,数据能够间接从读取缓冲区传输到套接字缓冲区。在 java 中咱们能够通过上述提到的 transferTo() 办法来实现。
该办法将数据从文件通道传输到给定的可写字节通道。在具体实现中,取决于底层操作系统对零拷贝的反对;在 UNIX 和各种 Linux 零碎中,此调用被路由到 sendfile() 零碎调用,如下图所示,它将数据从一个文件描述符传输到另一个文件描述符:
而后咱们剖析一下具体的数据流转过程,如下图所示:
上下文切换过程如下图所示:
transferTo() 办法使 DMA 引擎将文件内容复制到读取缓冲区中。而后内核将数据复制到与输入套接字关联的内核缓冲区中。第三次复制产生在 DMA 引擎将数据从内核套接字缓冲区传递到协定引擎时。咱们显著能够发现上下文切换的数量从四个缩小到两个,并且数据正本的数量从四个缩小到三个(其中只有一个波及 CPU)。但这还没有使咱们达到零拷贝的指标。如果底层网络接口卡反对收集操作,咱们能够进一步缩小内核所做的数据复制。在 Linux 内核 2.4 及更高版本中,批改了套接字缓冲区描述符来反对该性能。这种办法不仅缩小了屡次上下文切换,而且还打消了须要 CPU 参加的反复数据正本。用户端的用法依然放弃不变,但具体的底层实现函数产生了变动:transferTo() 办法使 DMA 引擎将文件内容复制到内核缓冲区中。留神此时没有数据被复制到套接字缓冲区中。相同,只有蕴含无关数据地位和长度信息的描述符才会附加到套接字缓冲区。也就是说相当于只把数据的元数据拷贝到套接字缓冲区,这份耗费通常是能够忽略不计的。DMA 引擎将数据间接从内核缓冲区传递到协定引擎,从而打消了残余的最终 CPU 正本。具体的流程如下图所示:
须要留神的是 transferTo() 办法的办法签名并没有扭转,只是操作系统的底层调用函数进行了调整优化。性能比拟测试环境:
通过观察测试后果能够很显著的看到性能上的晋升是非常明显的,对于很多适宜的场景,应用零拷贝技术能够显著地进步性能。咱们所熟知的 kafka 外部就采纳了零拷贝技术来提高效率。本文次要对零拷贝技术绝对于传统数据传输的优化点进行了简略的剖析,并没有深刻探索底层的 sendfile() 零碎调用具体是如何实现的,以及如何在编程中具体的实际操作,感兴趣的小伙伴能够本人去深入研究一下噢。
扩大概念 DMA(Direct Memory Access) 直译就是间接内存拜访,是一种无需 CPU 的参加就能够让外设与零碎内存之间进行双向数据传输的硬件机制。应用 DMA 能够使零碎 CPU 从理论的 I/O 数据传输过程中解脱进去,从而显著进步零碎的吞吐率。DMA 形式的数据传输由 DMA 控制器(DMAC)管制,在传输期间,CPU 能够并发的执行其余工作。当 DMA 完结后,DMAC 通过中断告诉 CPU 数据传输曾经完结,由 CPU 执行相应的中断服务程序进行后续解决。中断指处理机处理程序运行中呈现的紧急事件的整个过程。程序运行过程中,零碎内部、零碎外部或者现行程序自身若呈现紧急事件,处理机立刻停止现行程序的运行,主动转入相应的处理程序 (中断服务程序),待处理完后,再返回原来的程序运行,这整个过程称为程序中断;举个简略的例子:比方小王正在工作(相当于处理机正在处理程序运行),忽然接到外卖小哥的电话说外卖到了(相当于接管到中断信号),此时小王就临时停掉手中的工作去拿外卖(相当于执行中断服务程序),而后再回到工位上持续工作(相当于返回原来的程序继续执行)。