libfuse支持splice,来实现文件读写时,fuse内核模块和fuse server的零拷贝支持。
只有读写涉及到大缓存,才需要零拷贝。其它的消息如unlink等,显然不需要splice。
fuse还支持passthrough,passthrough模式和splice不一样。
passthrough直接将一个fd传递给fuse内核模块。这样文件读写,在内核直接操作此fd。这样就不需要和fuse server交互了。
首先来了解一下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将数据从一个描述符移动到另外一个描述符。它有个要求是至少有一个描述符要求是pipe。这可能是内核数据格式的限制导致的。
write是写入一个缓存到fuse内核模块,然后fuse内核模块发送给fuse server。
fuse通信是通过fd的,没开启splice的时候,fuse server直接从fd读取消息和数据。消息和数据是融合在一起的。
fuse处理消息的流程是先收取消息,然后处理:
fuse_session_receive_buf(fs->se, fs->buf);
fuse_session_process_buf(fs->se, fs->buf);
fuse_session_receive_buf:
1 要通过splice处理write,首先需要创建一个pipe。这个pipe缓存在thread local中。
struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
if (llp == NULL) {
llp = malloc(sizeof(struct fuse_ll_pipe));
fuse_pipe(llp->pipe);
}
2 获得管道后,就将fuse fd的内容移动到管道,并且可以返回数据的长度。
res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL, bufsize, 0);
fuse_session_process_buf:
1 首先需要把头部数据读取出来,这样才知道是啥消息
头部数据的长度是:fuse头部 + write头部
res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
2 如果是fuse_write消息,则调用:
do_write_buf(req, in->nodeid, inarg, buf);
如果是其他消息,则需要继续读完管道中的消息。
read需要fuse server返回一块数据给fuse内核模块。对于大数据,可以使用splice。
提供一个fd和offset,调用fuse_reply_data,即可:
buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
buf.buf[0].fd = fd;
buf.buf[0].pos = off;
fuse_reply_data(req_t, &buf, FUSE_BUF_SPLICE_MOVE);
libfuse还支持vmsplice,直接将用户态缓存移动到管道,再移动到fuse fd。