百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>服务器> 谈谈Linux的几种零拷贝技术和适用的场景
分享文章到:

谈谈Linux的几种零拷贝技术和适用的场景

发布时间:09/01 来源:未知 浏览: 关键词:

本文商量Linux中主要的几种零拷贝技术乃至零拷贝技术适用的场景。为了快速创立起零拷贝的概念,我们拿一个常用的场景停止引入:

引文##

在写一个效劳端程序时(Web Server或者文件效劳器),文件下载是一个根本功效。这时候效劳端的任务是:将效劳端主机磁盘中的文件不做修改地从已连接的socket发出去,我们平常用下面的代码完成:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    write(sockfd, buf , n);

根本操纵就是轮回的从磁盘读入文件内容到缓冲区,再将缓冲区的内容发送到socket。但是由于Linux的I/O操纵默许是缓冲I/O。这里面主要使用的也就是readwrite两个系统调取,我们并不知道操纵系统在其中做了什么。实际上在以上I/O操纵中,发生了屡次的数据拷贝。

当利用程序拜访某块数据时,操纵系统第一会检查,是不是比来拜访过此文件,文件内容可否缓存在内核缓冲区,假如是,操纵系统则直接按照read系统调取供给的buf地址,将内核缓冲区的内容拷贝到buf所指定的会员空间缓冲区中去。假如不是,操纵系统则第一将磁盘上的数据拷贝的内核缓冲区,这一步当前主要依托DMA来传输,然后再把内核缓冲区上的内容拷贝到会员缓冲区中。
接下来,write系统调取再把会员缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中,最后socket再把内核缓冲区的内容发送到网卡上。
说了这么多,不如看图分明:

1.webp.jpg


从上图中可以看出,共发生了四次数据拷贝,即便使用了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缓冲区再把数据发到网卡去。
一样的,看图很简便:

2.webp.jpg

使用mmap替换read很明显减少了一次拷贝,当拷贝数据量很大时,无疑晋升了效力。但是使用mmap是有代价的。当你使用mmap时,你大概会碰到一些潜藏的圈套。例如,当你的程序map了一个文件,但是当这个文件被另一个进程截断(truncate)时, write系统调取会由于拜访不法地址而被SIGBUS信号终止。SIGBUS信号默许会杀死你的进程并发生一个coredump,假如你的效劳器这样被中止了,那会发生一笔亏损。

平常我们使用以下解决方案幸免这种问题:

1、为SIGBUS信号创立信号处置程序
当碰到SIGBUS信号时,信号处置程序简便地返回,write系统调取在被中止此前会返回已经写入的字节数,并且errno会被设定成success,但是这是一种糟糕的处置方法,由于你并没有解决问题的本色中心。

2、使用文件租借锁
平常我们使用这种办法,在文件描写符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的RT_SIGNAL_LEASE信号,告诉我们内核正在毁坏你加持在文件上的读写锁。这样在程序拜访不法内存并且被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()在代表输入文件的描写符in_fd和代表输出文件的描写符out_fd之间传送文件内容(字节)。描写符out_fd必需指向一个套接字,而in_fd指向的文件必需是可以mmap的。这些局限限制了sendfile的使用,使sendfile只能将数据从文件传递到套接字上,反之则不可。
使用sendfile不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space

3.jpg

在我们调取sendfile时,假如有其它进程截断了文件会发生什么呢?假设我们没有设定任何信号处置程序,sendfile调取仅仅返回它在被中止此前已经传输的字节数,errno会被置为success。假如我们在调取sendfile此前给文件加了锁,sendfile的行动依然和此前雷同,我们还会收到RT_SIGNAL_LEASE的信号。

当前为止,我们已经减少了数据拷贝的次数了,但是依然存在一次拷贝,就是页缓存到socket缓存的拷贝。那么能不克不及把这个拷贝也省略呢?

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

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

4.jpg

不外这一种收集拷贝功效是需要硬件乃至驱动程序支撑的。

使用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, unsigned int flags);

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调取会有更多的数据。

splice调取利用了Linux提出的管道缓冲区机制, 所以至少一个描写符要为管道。

以上几种零拷贝技术都是减少数据在会员空间和内核空间拷贝技术实现的,但是有些时候,数据必需在会员空间和内核空间之间拷贝。这时候,我们只能针对数据在会员空间和内核空间拷贝的时机上下功夫了。Linux平常利用写时复制(copy on write)来减少系统开销,这个技术又经常称作COW

由于篇幅缘由,本文不具体介绍写时复制。大约描写下就是:假如多个程序同时拜访统一块数据,那么每个程序都具有指向这块数据的指针,在每个程序看来,本人都是独立具有这块数据的,只要当程序需要对数据内容停止修改时,才会把数据内容拷贝到程序本人的利用空间里去,这时候,数据才成为该程序的私有数据。假如程序不需要对数据停止修改,那么永久都不需要拷贝数据到本人的利用空间里。这样就减少了数据的拷贝。写时复制的内容可以再写一篇文章了。。。

除此之外,还有一些零拷贝技术,比方传统的Linux I/O中加上O_DIRECT标志可以直接I/O,幸免了主动缓存,还有尚未成熟的fbufs技术,本文尚未覆盖所有零拷贝技术,只是介绍常见的一些,如有乐趣,可以自行研讨,一样成熟的效劳端项目也会本人革新内核中有关I/O的部分,提高本人的数据传输速率。

引荐教程:《Linux运维》

以上就是谈谈Linux的几种零拷贝技术和适用的场景的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有154人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板