关注splice系列系统调用(包括splice,tee和vmsplice)已经有一段时间了,开始的时候并未能领会splice的意义所在,致使得出了“splice系列系统调用不怎么实用”的错误结论。随着内核研究的深入,才逐渐懂得:splice对于其中一个文件描述符必须是管道的要求并不是阻碍其应用的障碍,并且恰恰相反,它正是splice的本质所在。

splice
主要通过去除在内核空间和用户空间之间的内存拷贝的开销来提高系统的性能。它将内核空间和用户空间之间的内存拷贝转变成内核空间和内核空间的内存的拷贝,
这样的话,内核就有机会通过底层的一些机制来避免非必要的拷贝,如通过页面的引用来替换拷贝内存页面。这就引出一个问题:内核空间的缓冲区如何在用户空间
表示?恰巧管道符合这个要求,所以就“勉强”让它来担此重任了。因此,当管道被用来做splice的时候,它队列的概念就被弱化了,这时它代表的就是内核
空间的内存缓冲区。因此有了以下几种对应关系:

  • splice(infd,... pipe,...): 从由infd指向的文件读取数据到由pipe指向的内核缓冲区。
  • splice(pipe,... outfd,...): 把由pipe指向的内核缓冲区中的数据写到由oufd指向的文件。
  • tee(inpipe, outpipe,...): 从由inpipe指向的内核缓冲区“拷贝”数据到由outpipe指向的内核缓冲区。
  • vmsplice(rdpipe, iov...): 从由rdpipe(pipe的读端)指向的内核缓冲区“拷贝”数据到由iov表示的用户缓冲区。
  • vmsplice(wrpipe, iov...): 从由iov表示的用户缓冲区“拷贝”数据到由wrpipe(pipe的写端)指向的内核缓冲区。

可见,splice, tee和vmsplice涵盖了在用户空间控制内核缓冲区的全部情况。应该说,已经很完美了。

不过请稍等一下,如果有需要在两个文件之间“拷贝”数据呢?也许我们不得不这样:

  1. splice(fd, ...pipe, ...)
  2. splice(pipe, ...fd, ...)


的有必要进行两次系统调用么?事实上两次调用可以合而为一,只在内核内部进行上述操作就行了,因为splice原本就只有在文件和内核缓冲区之间移动数据
的意思,并且在两个文件描述符之间移动数据的API原本就有:sendfile。但是,用sendfile也不是尽善尽美的,sendfile多少有些词
不达意,我想这也可能是它在2.6.9之后就只能用于通过socket接口发送支持mmap系统调用的文件的原因之一。

有了sendfile的加入,这个世界又朝着完美更近了一步。

我们经常听到的一句话就是:前途是美好的,道路是曲折的。

之于splice系列系统调用的实现,这也是成立的,前进的道路上难免会由一些困难和反复,因为这个世界是如此的复杂。

splice系列系统调用首次亮相于2.6.17版内核,直到现在,其实现离完美还有很大一段距离,还有一些问题:

  • splice的套接字读实现存在潜在的数据污染(data corruption)。
  • vmsplice当标志位SPLICE_F_GIFT被设置的时候,其内存地址和大小必须是按页对齐的,缺乏灵活性。这导致不能发送非整页的数据,即使以mmap, splice, munmap的顺序组织系统调用也不可以。
  • sendfile系统调用因为把数据的移动过程在内部分成了从输入文件读取数据到pipe的内核缓冲和从pipe的内核缓冲写数据到输出文件两步,并且两步因为其实现的缘故,必须在原子操作内完成,所以,输出操作必须是阻塞的,极大地限制了其灵活性。

祝splice系列系统调的实现能尽快完美!

1. splice函数

#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参数是待输入描述符。如果它是一个管道文件描述符,则off_in必须设置为NULL;否则off_in表示从输入数据流的何处开始读取,此时若为NULL,则从输入数据流的当前偏移位置读入。

fd_out/off_out与上述相同,不过是用于输出。

len参数指定移动数据的长度。

flags参数则控制数据如何移动:

  • SPLICE_F_NONBLOCK:splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。
  • SPLICE_F_MORE:告知操作系统内核下一个 splice 系统调用将会有更多的数据传来。
  • SPLICE_F_MOVE:如果输出是文件,这个值则会使得操作系统内核尝试从输入管道缓冲区直接将数据读入到输出地址空间,这个数据传输过程没有任何数据拷贝操作发生。

2. 使用splice时, fd_in和fd_out中必须至少有一个是管道文件描述符。

调用成功时返回移动的字节数量;它可能返回0,表示没有数据需要移动,这通常发生在从管道中读数据时而该管道没有被写入的时候。

失败时返回-1,并设置errno

3. 代码:通过splice将客户端的内容读入到管道中, 再从管道中读出到客户端,从而实现高效简单的回显服务。整个过程未执行recv/send,因此也未涉及用户空间到内核空间的数据拷贝。

//使用splice实现的回显服务器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h> int main(int argc, char **argv)
{ if (argc <= 2) {
printf("usage: %s ip port\n", basename(argv[0]));
return 1;
} const char *ip = argv[1];
int port = atoi(argv[2]); struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr); int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0); int reuse = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1); ret = listen(sock, 5);
assert(ret != -1); struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client); int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
if (connfd < 0) {
printf("errno is: %s\n", strerror(errno));
}
else {
int pipefd[2]; ret = pipe(pipefd); //创建管道
assert(ret != -1); //将connfd上的客户端数据定向到管道中
ret = splice(connfd, NULL, pipefd[1], NULL,
32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); //将管道的输出定向到connfd上
ret = splice(pipefd[0], NULL, connfd, NULL,
32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1); close(connfd);
} close(sock); return 0;
}

[转] splice系列系统调用的更多相关文章

  1. IO复用——epoll系列系统调用

    1.内核事件表 epoll是Linux特有的I/O复用函数.epoll把用户关心的文件描述上的事件放在内核里的一个事件表中,并用一个额外的文件描述符来标识该内核事件表.这个额外文件描述符使用函数epo ...

  2. Linux高性能server编程——I/O复用

     IO复用 I/O复用使得程序能同一时候监听多个文件描写叙述符.通常网络程序在下列情况下须要使用I/O复用技术: client程序要同一时候处理多个socket client程序要同一时候处理用户 ...

  3. 9 I/O复用

    I/O复用使得程序能够同时监听多个文件描述符,适用于以下情况: 客户端同时处理多个socket,比如非阻塞connect 客户端同时处理用户输入和网络连接,比如聊天室程序 TCP服务器同时处理监听so ...

  4. 服务器编程入门(7)I/O复用

    问题聚焦:     前篇提到了I/O处理单元的四种I/O模型.     本篇详细介绍实现这些I/O模型所用到的相关技术.     核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...

  5. Linux高性能server规划——多进程编程

    多进程编程 多进程编程包含例如以下内容: 复制进程影映像的fork系统调用和替换进程映像的exec系列系统调用. 僵尸进程以及怎样避免僵尸进程 进程间通信(Inter-Process Communic ...

  6. 网络编程:I/O复用

    I/O多路复用是在多线程或多进程编程中常用技术.主要是通过select/epoll/poll三个函数支持的.在此主要对select和epoll函数详细介绍. select函数 该函数运行进程指示内核等 ...

  7. linux实训

    目  录 Unit 1 操作系统安装.... 3 1.1 多操作系统安装... 3 1.1.1 VMware简介... 3 1.1.2 VMWare基本使用... 4 1.2 安装Red Hat Li ...

  8. Linux 高性能服务器编程——多进程编程

    问题聚焦:     进程是Linux操作系统环境的基础.     本篇讨论以下几个内容,同时也是面试经常被问到的一些问题:     1 复制进程映像的fork系统调用和替换进程映像的exec系列系统调 ...

  9. Linux 高性能服务器编程——I/O复用

    问题聚焦:     前篇提到了I/O处理单元的四种I/O模型.     本篇详细介绍实现这些I/O模型所用到的相关技术.     核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...

随机推荐

  1. MySQL 管理

    MySQL 管理 启动及关闭 MySQL 服务器 首先,我们需要通过以下命令来检查MySQL服务器是否启动: ps -ef | grep mysqld 如果MySql已经启动,以上命令将输出mysql ...

  2. Java数据库连接池的几种配置方法(以MySQL数据库为例)

    Java数据库连接池的几种配置方法(以MySQL数据库为例) 一.Tomcat配置数据源: 前提:需要将连接MySQL数据库驱动jar包放进Tomcat安装目录中common文件夹下的lib目录中 1 ...

  3. PS纯手式打造不规则颜色的规则方块—学习教程

  4. ejabberd,erlang,简单看了一下,总结一下,很肤浅

    本来也没打算深入学习erlang,就是看一下他们的大概思路erlang每个自定义函数都能注册成进程,每个节点通过erl -name 'name@ip'.进去后,可以直接做远程调用,节点之间就靠一个连接 ...

  5. javascript获得浏览器工作区域的大小

    浏览器的窗口发生变化时会执行window.onresize方法,通过这个方法我们可以获得到浏览器工作区域的大小: window.onresize=function(){ bodyHeight = wi ...

  6. 利用php的ob缓存机制实现页面静态化

    利用php的ob缓存机制实现页面静态化 首先介绍一下php中ob缓存常用到的几个常用函数ob_start():开启缓存机制ob_get_contents():获取ob缓存中的内容ob_clean()清 ...

  7. JavaScript 类型判断的那些事

    先准备几个变量 var a = "abcde."; var b = 222; var c= [1,2,3]; // 或者 new Array() var d = new Date( ...

  8. uva10561 - Treblecross

    Treblecross is a two player game where the goal is to get three `X' in a row on a one-dimensional bo ...

  9. UIImageView设置为圆形

       CGFloat headimageX = self.view.frame.size.width * 0.2; CGFloat headimageY = self.view.frame.size. ...

  10. Ant快速入门(一)-----Ant介绍

    Ant是一种基于Java的生成工具.从作用上来看,它类似于C编程(UNIX平台上使用比较多)的Make工具,C/C++项目经常使用Make工具来管理整个项目的编译,生成 Make使用Shell命令来定 ...