什么是Linux零拷贝技术?

南风 2020年10月26日 156次浏览

一、普通文件服务器实现

当你从一个文件服务器下载文件,它会做什么动作呢?

  • 1、用户请求到达服务器,服务器解析用户意图,拿到用户需要下载的文件地址;
  • 2、服务器将服务端主机磁盘中的文件不做修改地从已连接的socket发出去。
    • 1、服务器循环从磁盘读入文件数据内容到Linux kernel页缓冲区;
    • 2、服务器在通过read()从Linux kernel space读入User space;
    • 3、服务器调用write()写入socket缓冲区;
    • 4、服务器从socket缓冲区复制到网络端口。
      文件拷贝流程

二、什么是零拷贝技术(zero-copy)?

零拷贝技术主要就是避免CPU将数据从一块存储拷贝到另外一块存储做无用的搬运。

让数据传输不经过User Space

1、使用mmap()

mmap()直接将kernel page cahce copy to socket cache,而不再经过User Space。使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。但是使用mmap是有代价的。当你使用mmap时,你可能会遇到一些隐藏的陷阱。例如,当你的程序map了一个文件,但是当这个文件被另一个进程截断(truncate)时, write系统调用会因为访问非法地址而被SIGBUS信号终止。SIGBUS信号默认会杀死你的进程并产生一个coredump,如果你的服务器这样被中止了,那会产生一笔损失。

2、使用sendfile()

 系统调用sendfile()在代表输入文件的描述符in_fd和代表输出文件的描述符out_fd之间传送文件内容(字节)。描述符out_fd必须指向一个套接字,而in_fd指向的文件必须是可以mmap的。这些局限限制了sendfile的使用,使sendfile只能将数据从文件传递到套接字上,反之则不行。
 使用sendfile不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space。

三、使用splice()

 splice调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝。他从fd_in拷贝len长度的数据到fd_out,但是有一方必须是管道设备,这也是目前splice的一些局限性。flags参数有以下几种取值:

  • SPLICE_F_MOVE :尝试去移动数据而不是拷贝数据。这仅仅是对内核的一个小提示:如果内核不能从pipe移动数据或者pipe的缓存不是一个整页面,仍然需要拷贝数据。Linux最初的实现有些问题,所以从2.6.21开始这个选项不起作用,后面的Linux版本应该会实现。
  • SPLICE_F_NONBLOCK:splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。
  • SPLICE_F_MORE: 后面的splice调用会有更多的数据。

四、写时复制(copy on write)

 如果多个程序同时访问同一块数据,那么每个程序都拥有指向这块数据的指针,在每个程序看来,自己都是独立拥有这块数据的,只有当程序需要对数据内容进行修改时,才会把数据内容拷贝到程序自己的应用空间里去,这时候,数据才成为该程序的私有数据。如果程序不需要对数据进行修改,那么永远都不需要拷贝数据到自己的应用空间里。这样就减少了数据的拷贝。
 除此之外,还有一些零拷贝技术,比如传统的Linux I/O中加上O_DIRECT标记可以直接I/O,避免了自动缓存,还有尚未成熟的fbufs技术,本文尚未覆盖所有零拷贝技术,只是介绍常见的一些,如有兴趣,可以自行研究,一般成熟的服务端项目也会自己改造内核中有关I/O的部分,提高自己的数据传输速率。