什么是零拷贝?

前言:假如写一个服务端程序,文件下载是一个基本功能。这时候服务端的任务是:将服务端主机磁盘中的文件不做修改地从已连接的socket发出去。

基本操作是:循环地从磁盘读入文件到缓冲区,再将缓冲区的内容发送到socket。


数据拷贝

从上图可以看到共发生了4次数据拷贝。首先使用DMA来传输,将磁盘文件写入到内核空间的页缓存中,然后操作系统会根据read系统调用提供的buf地址,将内核缓冲区的内容拷贝到buf所指定的用户空间缓存区中。接下来,write系统调用再把用户缓存区的内容拷贝到网络堆栈相关的内核缓存区中,最后socket再把内核缓存区的内容发送到网卡中。

可以看到,即使使用了DMA传输,CPU依然需要两次的数据拷贝,预与此同时,在用户态和内核态上也发生了多次上下文切换,无疑加重了CPU负担

其实,我们并没有对文件内容进行任何修改,因此在内核空间和用户空间来回拷贝数据无疑是一种浪费。

什么是零拷贝?

零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。


让数据传输不需要经过user space

应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去。

mmap

使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。

使用sendfile

sendfile系统调用过程

    使用sendfile仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space。

能否再去掉一次CPU拷贝?

    借助于硬件上的帮助,我们是可以办到的。之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到socket缓冲区,再把数据长度传过去,这样DMA控制器直接将页缓存中的数据打包发送到网络中就可以了。

    总结一下,sendfile系统调用利用DMA引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次拷贝。


带DMA的sendfile

    不过这一种收集拷贝功能是需要硬件以及驱动程序支持的。




来源:https://www.jianshu.com/p/fad3339e3448

你可能感兴趣的:(什么是零拷贝?)