本文探讨Linux中 次要的几种零拷贝技术 以及零拷贝技术 实用的场景 。为了迅速建设起零拷贝的概念,咱们拿一个罕用的场景进行引入:
引文
在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能。这时候服务端的工作是:将服务端主机磁盘中的文件不做批改地从已连贯的socket收回去,咱们通常用上面的代码实现:
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
write(sockfd, buf , n);
基本操作就是循环的从磁盘读入文件内容到缓冲区,再将缓冲区的内容发送到socket。然而因为Linux的I/O操作默认是缓冲I/O。这外面次要应用的也就是read和write两个零碎调用,咱们并不知道操作系统在其中做了什么。实际上在以上I/O操作中,产生了屡次的数据拷贝。
当应用程序拜访某块数据时,操作系统首先会查看,是不是最近拜访过此文件,文件内容是否缓存在内核缓冲区,如果是,操作系统则间接依据read零碎调用提供的buf地址,将内核缓冲区的内容拷贝到buf所指定的用户空间缓冲区中去。如果不是,操作系统则首先将磁盘上的数据拷贝的内核缓冲区,这一步目前次要依附DMA来传输,而后再把内核缓冲区上的内容拷贝到用户缓冲区中。
接下来,write零碎调用再把用户缓冲区的内容拷贝到网络堆栈相干的内核缓冲区中,最初socket再把内核缓冲区的内容发送到网卡上。说了这么多,不如看图分明:
数据拷贝
从上图中能够看出,共产生了四次数据拷贝,即便应用了DMA来解决了与硬件的通信,CPU依然须要解决两次数据拷贝,与此同时,在用户态与内核态也产生了屡次上下文切换,无疑也减轻了CPU累赘。
在此过程中,咱们没有对文件内容做任何批改,那么在内核空间和用户空间来回拷贝数据无疑就是一种节约,而零拷贝次要就是为了解决这种低效性。
什么是零拷贝技术(zero-copy)?
零拷贝次要的工作就是防止CPU将数据从一块存储拷贝到另外一块存储,次要就是利用各种零拷贝技术,防止让CPU做大量的数据拷贝工作,缩小不必要的拷贝,或者让别的组件来做这一类简略的数据传输工作,让CPU解脱进去专一于别的工作。这样就能够让系统资源的利用更加无效。
咱们持续回到引文中的例子,咱们如何缩小数据拷贝的次数呢?一个很显著的着力点就是缩小数据在内核空间和用户空间来回拷贝,这也引入了零拷贝的一个类型:
让数据传输不须要通过 user space。
应用 mmap
咱们缩小拷贝次数的一种办法是调用mmap()来代替read调用:
buf = mmap(diskfd, len);
write(sockfd, buf, len);
应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不须要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统间接将内核缓冲区的内容拷贝到socket缓冲区中,这所有都产生在内核态,最初,socket缓冲区再把数据发到网卡去。同样的,看图很简略:
mmap
应用mmap代替read很显著缩小了一次拷贝,当拷贝数据量很大时,无疑晋升了效率。然而应用mmap是有代价的。当你应用mmap时,你可能会遇到一些暗藏的陷阱。例如,当你的程序map了一个文件,然而当这个文件被另一个过程截断(truncate)时, write零碎调用会因为拜访非法地址而被SIGBUS信号终止。SIGBUS信号默认会杀死你的过程并产生一个coredump,如果你的服务器这样被停止了,那会产生一笔损失。
通常咱们应用以下解决方案防止这种问题:
1. 为SIGBUS信号建设信号处理程序
当遇到SIGBUS信号时,信号处理程序简略地返回,write零碎调用在被中断之前会返回曾经写入的字节数,并且errno会被设置成success,然而这是一种蹩脚的解决方法,因为你并没有解决问题的本质外围。
2. 应用文件租借锁
通常咱们应用这种办法,在文件描述符上应用租借锁,咱们为文件向内核申请一个租借锁,当其它过程想要截断这个文件时,内核会向咱们发送一个实时的RTSIGNALLEASE信号,通知咱们内核正在毁坏你加持在文件上的读写锁。这样在程序拜访非法内存并且被SIGBUS杀死之前,你的write零碎调用会被中断。write会返回曾经写入的字节数,并且置errno为success。
咱们应该在mmap文件之前加锁,并且在操作完文件后解锁:
if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK 加锁*/
/* l_type can be F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}
应用sendfile
从2.1版内核开始,Linux引入了sendfile来简化操作:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
零碎调用sendfile()在代表输出文件的描述符infd和代表输入文件的描述符outfd之间传送文件内容(字节)。描述符outfd必须指向一个套接字,而infd指向的文件必须是能够mmap的。这些局限限度了sendfile的应用,使sendfile只能将数据从文件传递到套接字上,反之则不行。
应用sendfile不仅缩小了数据拷贝的次数,还缩小了上下文切换,数据传送始终只产生在kernel space。
sendfile零碎调用过程
在咱们调用sendfile时,如果有其它过程截断了文件会产生什么呢?假如咱们没有设置任何信号处理程序,sendfile调用仅仅返回它在被中断之前曾经传输的字节数,errno会被置为success。如果咱们在调用sendfile之前给文件加了锁,sendfile的行为依然和之前雷同,咱们还会收到RTSIGNALLEASE的信号。
目前为止,咱们曾经缩小了数据拷贝的次数了,然而依然存在一次拷贝,就是页缓存到socket缓存的拷贝。那么能不能把这个拷贝也省略呢?
借助于硬件上的帮忙,咱们是能够办到的。之前咱们是把页缓存的数据拷贝到socket缓存中,实际上,咱们仅仅须要把缓冲区描述符传到socket缓冲区,再把数据长度传过来,这样DMA控制器间接将页缓存中的数据打包发送到网络中就能够了。
总结一下,sendfile零碎调用利用DMA引擎将文件内容拷贝到内核缓冲区去,而后将带有文件地位和长度信息的缓冲区描述符增加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA引擎会将内核缓冲区的数据拷贝到协定引擎中去,防止了最初一次拷贝。
带DMA的sendfile
不过这一种收集拷贝性能是须要硬件以及驱动程序反对的。
应用splice
sendfile只实用于将数据从文件拷贝到套接字上,限定了它的应用范畴。Linux在2.6.17版本引入splice零碎调用,用于在两个文件描述符中挪动数据:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include<fcntl.h>
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsignedint flags);
splice调用在两个文件描述符之间挪动数据,而不须要数据在内核空间和用户空间来回拷贝。他从fdin拷贝len长度的数据到fdout,然而有一方必须是管道设施,这也是目前splice的一些局限性。flags参数有以下几种取值:
- SPLICEFMOVE :尝试去挪动数据而不是拷贝数据。这仅仅是对内核的一个小提示:如果内核不能从pipe挪动数据或者pipe的缓存不是一个整页面,依然须要拷贝数据。Linux最后的实现有些问题,所以从2.6.21开始这个选项不起作用,前面的Linux版本应该会实现。
- SPLICEFNONBLOCK :splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞形式的 I/O ,那么调用 splice 有可能依然被阻塞。
- SPLICEFMORE:前面的splice调用会有更多的数据。
splice调用利用了Linux提出的管道缓冲区机制, 所以至多一个描述符要为管道。
以上几种零拷贝技术都是缩小数据在用户空间和内核空间拷贝技术实现的,然而有些时候,数据必须在用户空间和内核空间之间拷贝。这时候,咱们只能针对数据在用户空间和内核空间拷贝的机会上下功夫了。Linux通常利用写时复制(copy on write)来缩小零碎开销,这个技术又时常称作COW。
因为篇幅起因,本文不具体介绍写时复制。大略形容下就是:如果多个程序同时拜访同一块数据,那么每个程序都领有指向这块数据的指针,在每个程序看来,本人都是独立领有这块数据的,只有当程序须要对数据内容进行批改时,才会把数据内容拷贝到程序本人的利用空间里去,这时候,数据才成为该程序的公有数据。如果程序不须要对数据进行批改,那么永远都不须要拷贝数据到本人的利用空间里。这样就缩小了数据的拷贝。写时复制的内容能够再写一篇文章了。。。
除此之外,还有一些零拷贝技术,比方传统的Linux I/O中加上O_DIRECT标记能够间接I/O,防止了主动缓存,还有尚未成熟的fbufs技术,本文尚未笼罩所有零拷贝技术,只是介绍常见的一些,如有趣味,能够自行钻研,个别成熟的服务端我的项目也会本人革新内核中无关I/O的局部,进步本人的数据传输速率。
作者:卡巴拉的树_
https://www.jianshu.com/p/fad...