linux splice 的解释

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:蚂蚁
链接:https://www.zhihu.com/question/21705041/answer/36219654
来源:知乎

`splice`是 zero copy API 中最重要的一个,签名如下:
       ssize_t splice(int fd_in, loff_t *off_in, int fd_out,
                      loff_t *off_out, size_t len, unsigned int flags);

其中,fd_in 或者 fd_out必须有至少一个是 pipe,其功能是(逻辑上)从 fd_in 消费数据并复制到 fd_out 中,如传入非 NULL 的off_in/off_out则会指定消费或者复制端的 offset,并会禁止对于文件当前 offset 的处理,具体参考 manpage。

splice 的主要场景是从一个 非 pipe 的fd(通常是socket)splice 到一个预先创建的 pipe,然后在这个 pipe 上做一些事情,常规的就是从这个 pipe 再 splice 出去到别的 socket 上,这主要用于转发类应用,比如 haproxy,nginx 这类应用就很适合;在这个 pipe 上还可以做一些别的事情,比如 tee 到别的 pipe 上,tee 逻辑上复制内容(不消费),然后 tee 出来的 pipe 可以去做转储,或者再做转发,等等。

zero-copy 指的是 splice 过程中,有很大的可能(也存在另一种可能) 实现上 不需要进行任何内存拷贝,比方从 socket splice 到 pipe,合适的情况下,内核只需要修改 socket fd 的 current file offset(以实现消费语义),修改 pipe 的管理结构,使其指向 socket 缓冲区中我们通过 splice 调用指定的部分,最后修改 pipe 的当前 offset。

linus 是这么说的:
Actually, there _is_ a fundamental problem. Two of them, in fact.

The reason it goes through a pipe is two-fold:

- the pipe _is_ the buffer. The reason sendfile() sucks is that sendfile cannot work with different buffer representations. sendfile() only works with _one_ buffer representation, namely the "page cache of the file".

By using the page cache directly, sendfile() doesn't need any extra buffering, but that's also why sendfile() fundamentally _cannot_ work with anything else. You cannot do "sendfile" between two sockets to forward data from one place to another, for example. You cannot do sendfile from a streaming device.

The pipe is just the standard in-kernel buffer between two arbitrary points. Think of it as a scatter-gather list with a wait-queue. That's what a pipe _is_. Trying to get rid of the pipe totally misses the whole point of splice().

Now, we could have a splice call that has an _implicit_ pipe, ie if neither side is a pipe, we could create a temporary pipe and thus allow what looks like a direct splice. But the pipe should still be there.

- The pipe is the buffer #2: it's what allows you to do _other_ things with splice that are simply impossible to do with sendfile. Notably, splice allows very naturally the "readv/writev" scatter-gather behaviour of _mixing_ streams. If you're a web-server, with splice you can do

write(pipefd, header, header_len);
splice(file, pipefd, file_len);
splice(pipefd, socket, total_len);


(this is all conceptual pseudo-code, of course), and this very naturally has none of the issues that sendfile() has with plugging etc. There's never any "send header separately and do extra work to make sure it is in the same packet as the start of the data". So having a separate buffer even when you _do_ have a buffer like the page cache is still something you want to do.So there.

再来说说几个不容易了解的问题:
1. 阻塞的问题:

splice 在操作的两端都可能阻塞,如从 socket splice 到 pipe 的情况,在 socket 端可能被阻塞,因为没有数据可以被消费,pipe 端可能被阻塞,因为 pipe 可能装满了,socket 的阻塞由它自己的`O_NONBLOCK`控制,pipe 端操作的阻塞由 splice 的 flag 上`SPLICE_F_NONBLOCK`参数控制。具体参考 manpage。
这是 API 上的问题。就 pipe 来说,有个最大容量的问题,默认大概是64K,可以通过 fcntl 参数和/proc 控制,具体的可以自己查阅相关手册。

2. pipe 操作的原子性的问题
一般的文件 write,操作系统有原子性保证,在 pipe 上,4k 以内有原子性保证,以上就没有了,加上阻塞的问题,pipe 的总的容量有限,所以用 splice 编程的时候,pipe 管理是需要仔细考虑的。

3. splice 和 epoll 等交互的问题
这个没有什么特别的,记住 splice 在读端语义是消费,相当于 read,在写端是生产,相当于 write 就行了。

4. 文件系统一致性的问题
实测,splice 操作 不具备文件系统一致性,就是说,如果你打开一个 regular file descriptor,把它作为 splice 的写端,在 splice 调用成功返回之后的 read 调用或者 mmap 可能 看不见 splice 生产的内容,甚至之后发生在这个 regular file descriptor的 splice 也 看不见 splice 生产的内容。这点非常非常重要。同样的结论对 vmsplice 也成立。

你可能感兴趣的:(LINUX)