彻底理解零拷贝技术,zero-copy
彻底理解零拷贝技术,zero-copy
在当今的互联网应用中,IO性能优化是一个永恒的话题。传统的IO操作往往伴随着多次数据拷贝,这不仅消耗CPU资源,还增加了延迟。本文将深入探讨零拷贝技术(zero-copy),从操作系统的工作原理出发,详细解析各种零拷贝技术的实现方式,帮助你理解如何在不牺牲安全性和稳定性的前提下,大幅提升IO密集型应用的性能。
为什么IO接口要基于数据拷贝?
操作系统本质上就是一个管家,目的是更加公平合理地给各个进程分配硬件资源。在操作系统出现之前,程序员需要直面各类硬件:
在这一时期,程序员掌控全局,但这也带来了掌控所有细节的负担,不利于生产力的释放。操作系统应运而生,计算机系统结构变为:
现在应用程序不需要和硬件直接交互,仅从IO的角度看,操作系统变成了一个类似路由器的角色,负责数据的分发。数据传递是通过buffer实现的,buffer是一块可用的内存空间,用来暂存数据:
操作系统作为中间商导致的问题是:你需要首先把东西交给操作系统,操作系统再转手交给硬件,这就必然涉及到数据拷贝。这就是为什么传统的IO操作必然需要进行数据拷贝的原因所在。
网络服务器案例分析
以网络服务器为例,当浏览器请求网页时,服务器需要从磁盘中读取文件并通过网络发送出去。代码示例如下:
read(fileDesc, buf, len);
write(socket, buf, len);
这两行代码看似简单,但实际上在底层发生了复杂的操作:
具体过程包括:
- read函数涉及一次用户态到内核态的切换,操作系统向磁盘发起IO请求,数据通过DMA技术拷贝到内核buffer。
- 操作系统将内核buffer中的数据拷贝到用户态buffer,read函数返回,完成第一次数据拷贝。
- send函数导致第二次用户态到内核态的切换,数据从用户态buffer拷贝到网络协议子系统的buffer。
- send函数返回,数据可能依然停留在内核中,最后通过DMA技术从socket buffer拷贝给网卡,完成发送。
发现问题与优化思路
观察上述过程,可以发现用户态并没有对数据进行任何修改,那么为什么不能直接在内核态从磁盘发送到网卡呢?这种优化思路就是所谓的零拷贝技术(Zero Copy)。
总体上来看,优化数据拷贝有三个方向:
- 数据无需用户态感知,完全在内核态完成数据传输。
- 用户态程序绕过内核直接与硬件交互。
- 优化用户态与内核态的数据交互方式。
mmap技术
对于网络服务器,可以使用mmap替换read/write:
buf = mmap(file, len);
write(socket, buf, len);
mmap将文件内容映射到进程地址空间,避免了从内核态到用户态的数据拷贝。但是维护文件与地址空间的映射关系是有代价的,需要正确处理异常情况。
sendfile系统调用
Linux系统专门设计了sendfile系统调用来解决数据拷贝问题:
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
使用sendfile可以节省两次数据拷贝,因为数据无需传输到用户态:
sendfile与DMA Gather Copy
传统的DMA机制必须从一段连续的空间中传输数据:
DMA Gather Copy允许从多个源头收集数据:
这进一步提升了程序性能:
splice系统调用
splice系统调用通过管道实现数据在内核态的传输:
splice和sendfile很像,实际上sendfile后来也是基于splice实现的。保留sendfile是为了保证兼容性。
总结
高效IO的秘诀在于尽量减少CPU参与。零拷贝技术通过多种方式实现了这一目标,包括软件层面的mmap、sendfile、splice,以及硬件层面的DMA Gather Copy。这些技术在现代高性能系统中得到了广泛应用,如Kafka等消息中间件。
本文原文来自CSDN。