零拷贝
零拷贝
比如文件网络传输,传统的IO操作需要经历:用户态切换到内核态,CPU向DMA发送请求,DMA准备好后负责传输数据到内核的缓冲区中,然后内核再把缓冲区的内容拷贝给用户态。这仅仅只是向磁盘发起IO读取数据完毕,还要通过网络传输文件,那么读取完后又要发起IO请求,让内核把数据拷回内核再拷给网卡,又要经历一次IO操作。整个过程就涉及到了很多次 "没必要的拷贝开销" + "没必要的切换开销"。 因此想要提升效率可以用「零拷贝」技术。
-
mmap + write。
mmap()
能够将内核缓冲区和用户缓冲区映射起来,那么就省去内核态到用户态的拷贝动作,再直接由内核write()
到socket缓冲区即可,最后由DMA将socket缓冲区内容写回网卡。 -
sendfile
Linux2.1开始支持。它省去了从内核缓冲区拷贝到socket缓冲区的操作,又省下了一步操作。如果网卡支持 SG-DMA的话,那么又又可以省下一步操作:直接从内核缓冲区拷贝到网卡!不用经过socket缓冲区。
PageCache 有什么作用?
前面说的「内核缓冲区」实际上就是磁盘高速缓存(PageCache)。由于程序运行的时候,具有「局部性」,通常刚被访问的数据在短时间内再次被访问的概率很高,于是我们可以用 PageCache 来缓存最近被访问的数据,当空间不足时淘汰最久未被访问的缓存。读取磁盘数据的时候,需要找到数据所在的位置,但是对于机械磁盘来说,就是通过磁头旋转到数据所在的扇区,再开始「顺序」读取数据,但是旋转磁头这个物理动作是非常耗时的,为了降低它的影响,PageCache 使用了「预读功能」:比如
read()
读032KB数据时内核把3264KB的数据也预先读进来了。大文件传输?
实际上普通的IO操作都是异步IO,异步IO也利用了PageCache技术,因为CPU资源宝贵,交给DMA来操作,异步通知CPU读取完毕即可。小文件可以用零拷贝技术,但对于大文件读取,PageCache就不会利用到了,因为缓存命中率低!高并发的场景下,应使用「异步 I/O + 直接 I/O」:内核调度算法尽量缓存多的IO操作请求,再合并成一个较大的IO操作请求再发给磁盘,减少磁盘的寻址操作开销。
零拷贝技术的运用例子?
Kafka就利用了「零拷贝」技术,从而大幅提升了 I/O 的吞吐率,也是Kafka处理量数据为什么这么快的原因之一。源码里最终调用了 Java NIO 库里的
transferTo
方法,transferTo()
实际上最后就会使用到sendfile()
系统调用函数。 nginx也利用了零拷贝技术。
location /video/ { sendfile on; // sendfile()系统函数调用开启 aio on; directio 1024m; // 超过1024m为大文件,传输用「异步 I/O + 直接 I/O」 }