当前位置:首页>科技 >内容

splicejs_splice的原理和使用及代码实现

2024-05-07 23:32:49科技漂亮的斑马

拼接原理综述在文章《splice使用》中,介绍了拼接的原理和用途。现在我们来分析splice的代码实现。让我们先回顾一下拼接的原理:如上图所示

splicejs_splice的原理和使用及代码实现

拼接原理综述

在文章《splice使用》中,介绍了拼接的原理和用途。现在我们来分析splice的代码实现。

让我们先回顾一下拼接的原理:

如上图所示,使用splice复制数据时,需要使用管道作为中转。Splice首先将页面缓存绑定到管道的写端,然后通过管道的读端读取页面缓存的数据并复制到socket缓冲区中。

我们知道流水线有一个环形缓冲区,这个环形缓冲区需要和真实的物理内存页面绑定。Splice是将管道的循环缓冲区绑定到文件的页面缓存,如下图所示:

通过将文件页缓存绑定到流水线的环形缓冲区,可以通过流水线的读取端读取文件页缓存的数据。

拼接代码实现

在文章《splice使用》中,介绍了拼接的使用过程。将文件内容发送到客户端进行连接的步骤如下:

首先,使用splice()系统调用将文件内容绑定到管道。

然后,使用splice()系统调用将管道的数据复制到客户端连接套接字。

让我们来看看splice()系统调用的实现。代码如下:

asmlinkagelongsys _ splice(intfd _ in,loff_t*off_in,intfd_out,loff_t*off_out,size_tlen,unsignedintflags){ long error;structfile*in,* outintfput_in,fput _ out.错误=-EBADF;in=fget_light(fd_in,fput _ in);//1.获取数据输入文件对象if(in){ if(in-f _ mode fmode _ read){ out=fget _ light(FD _ out,fput _ out);//2.获取文件对象if(out){ if(out-f _ mode fmode _ write)//3。调用do_splice()函数进行下一次运算error=do _ splice (in,off _ in,out,off _ out,len,flags);fput_light(out,fput _ out);}}fput_light(in,fput _ in);} returnerror}splice()系统调用主要调用do_splice()函数进行进一步处理。我们来分析一下do_splice()函数的实现。do_splice()函数主要处理两种情况,代码如下:

static long do _ splice(struct file * in,loff_t*off_in,structfile*out,loff_t*off_out,size_tlen,unsignedintflags){ struct pipe _ inode _ info * pipe;loff_toffset,* off朗雷特;//情况一:如果输入端是管道?pipe=pipe _ info(in-f _ path . dentry-d _ inode);如果(管道){.//调用do_splice_from()函数将管道数据复制到目标文件句柄ret=do _ splice _ from (pipe,out,off,len,flags);returnret}//情况二:如果输出端是管道?pipe=pipe _ info(out-f _ path . dentry-d _ inode);如果(管道){.//调用do_splice_to()函数,用管道RET=do _ splice _ to (in,off,pipe,len,flags)绑定文件内容;returnret} return-EINVAL;}如上面的代码所示,do_splice()函数在两种情况下处理,如下所示:

如果输入端是管道,则调用do_splice_from()函数进行处理。

如果输出是管道,则调用do_splice_to()函数进行处理。

下面分别说明这两种情况的处理流程。

1.输入端是管道。

如果输入是管道(即从管道复制数据到输出的句柄),将调用do_splice_from()函数进行处理,do_splice_from()函数实现如下:

static long do _ splice _ from(struct pipe _ inode _ info * pipe,structfile*out,loff_t*ppos,size_tlen,unsignedintflags){.returnout-f _ op-splice _ write(pipe,out,ppos,len,flags);}如果输出的是普通文件,那么out-f_op-splice_write()会指向generic_file_splice_write()函数。如果输出是一个套接字,那么out-f_op-splice_write()将指向generic_splice_sendpage()函数。

generic_file_splice_write()函数将作为分析对象,generic_file_splice_write()函数将调用__splice_from_pipe()进行进一步处理,如下图所示:

ssize _ tgeneric _ file _ splice _ write(struct pipe _ inode _ info * pipe,structfile*out,loff_t*ppos,size_tlen,unsignedintflags){.ret=__splice_from_pipe(管道,sd,管道_到_文件);returnret}然后我们将分析__splice_from_pipe()函数的实现:

ssize_t__splice_from_pipe(结构管道_信息节点_信息*管道,结构拼接_ desc *标清,拼接_演员*演员){.for(;){if(pipe-nrbufs){//1。获取管道环形缓冲结构pipe _ buffer * buf=pipe-bufs pipe-curbuf;const struct pipe _ buf _ operations * ops=buf-ops;//2.将管道环形缓冲区的数据复制到输出文件中。//其中actor指针指向pipe_to_file()函数,err=actor(pipe,buf,sd)由generic_file_splice_write()函数传入;if(err=0){if(!复述!=-ENODATA)ret=err;打破;}.}.}.returnret}简化__splice_from_pipe()函数后,逻辑非常简单。主要流程如下:

获取管道环形缓冲区。

调用pipe_to_file()函数将管道环形缓冲区的数据复制到输出端的文件中。

因此,将输入作为管道的调用链如下所示:

sys _ splice()do _ splice()do _ splice _ from()generic _ file _ splice _ write()_ _ splice _ from _ pipe()

如果输出是管道(即输入绑定到管道),将调用do_splice_to()函数进行处理,do_splice_to()函数实现如下:

static long do _ splice _ to(struct file * in,loff_t*ppos,structpipe_inode_info*pipe,size_tlen,unsignedintflags){.returnin-f_op-splice_read(in,ppos,pipe,len,flags);}如果输入的是普通文件,那么in-f_op-splice_read()会指向generic_file_splice_read()函数。如果输出是套接字,那么in-f_op-splice_read()将指向sock_splice_read()函数。

generic_file_splice_read()函数将作为分析对象,generic_file_splice_read()函数将调用__generic_file_splice_read()进行进一步处理,如下图所示:

static int _ _ generic _ file _ splice _ read(struct file * in,loff_t*ppos,structpipe_inode_info*pipe,size_tlen,unsignedintflags){.struct page * pages[PIPE _ BUFFERS];structsplice_pipe_descspd={。页数=页数,};//1.找到页面SPD。NR _ pages=find _ get _ pages _ contig(mapping,index,NR _ pages,pages)带有现有页面缓存;index=spd.nr _ pages.//2.如果有的页面缓存还不存在,在while(SPD . NR _ page SNR _ pages){ page=find _ get _ page(mapping,index)中申请新的页面缓存;pages[SPD . NR _ pages]=page;指数;}//3.如果页面缓存中的数据与硬盘中的数据不一致,则从硬盘同步到页面缓存,持续(page _ NR=0;page_nra_ops-readpage(in,page);//从硬盘读取数据.}.spd.nr _ pages指数;//4.将页面缓存绑定到管道if(SPD . NR _ pages)return splice _ to _ pipe(pipe,SPD);returnerror}__generic_file_splice_read()函数的代码比较长,为了便于分析,对其进行了简化。从简化代码中可以看出,__generic_file_splice_read()函数主要完成四个步骤:

找出要绑定的页面缓存是否已经存在(已经从硬盘同步到页面缓存)。

如果还有一个页面缓存没有同步到内核,申请一个新的页面缓存。

如果页面缓存与硬盘上的数据不一致,请先从硬盘同步到页面缓存。

调用splice_to_pipe()函数将页面缓存绑定到管道。

因此,最终将调用splice_to_pipe()函数将页面缓存绑定到管道。让我们来看看splice_to_pipe()函数的实现:

ssize _ tsplice _ to _ pipe(struct pipe _ inode _ info * pipe,struct splice _ pipe _ desc * SPD){ unsignedintspd _ pages=SPD-NR _ pages;intret,do_wakeup,page _ nr.for(;){.if(PIPE-nrbufscurbuf PIPE-nrbufs)(PIPE _ BUFFERS-1);struct pipe _ buffer * buf=pipe-bufs new buf;//将环形缓冲区与页面缓存buf-page=spd-pages[page_nr]绑定;buf-offset=SPD-partial[page _ NR]。偏移;buf-len=spd-partial[page_nr]。lenbuf-private=SPD-partial[page _ NR]。私人;buf-ops=SPD-ops;if(SPD-flags splice _ F _ GIFT)BUF-flags |=PIPE _ BUF _ FLAG _ GIFT;pipe-NR bufs;page _ nrret=buf-len;if(pipe-nrbufsPIPE_BUFFERS)继续;打破;}.}.returnret}splice_to_pipe()函数代码比较长,但是逻辑很简单,就是将管道的循环缓冲区与文件的页面缓存绑定,这样就可以通过管道的读取端读取页面缓存的数据。

因此,输出为管道的调用链如下所示:

sys _ splice()do _ splice()do _ splice _ to()generic _ file _ splice _ read()_ _ generic _ file _ splice _ read()

本文主要介绍零拷贝技术的一种实现splice的原理和实现。希望通过这篇文章,让读者对零拷贝技术有更深入的了解。

当然,本文也忽略了很多实现细节,所以当你在阅读过程中对一些细节不理解时,可以直接阅读源代码来解惑。

黄飞

声明本站所有作品图文均由用户自行上传分享,仅供网友学习交流。若您的权利被侵害,请联系我们

Top