sendfileシステムコール
Rev.2を表示中。最新版はこちら。
sendfileシステムコールは、 ファイルの複写をカーネル内で行うもので、cpコマンドdo_sendfile()からdo_splice_direct()/splice_direct_to_actor()をコールし、spliceのpipeによる連結によって実装しています。spliceのサンプルをカーネル内で実装すると言うものです。cpコマンドは入力ファイルを読み込み(ユーザプロセスのメモリ空間)、それを出力ファイルに書き出します(ユーザプロセスのメモリ空間をカーネルメモリに出力)。
[root@localhost test]# strace cp hoge1 hoge2 : open("hoge1", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=13, ...}) = 0 open("hoge2", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4 fstat64(4, {st_mode=S_IFREG|S_ISUID|0350, st_size=0, ...}) = 0 read(3, "hogehogehoge\n", 32768) = 13 write(4, "hogehogehoge\n", 13) = 13 read(3, "", 32768) = 0 close(4) = 0 close(3) = 0 close(0) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? +++ exited with 0 +++サンプル
#include <stdio.h> #include <fcntl.h> int main(int argc, char *argv[]) { struct stat tmp; int fdr, fdw; fdr = open("hoge1", O_RDONLY); fdw = open("hoge2", O_CREAT | O_WRONLY); fstat(fdr, &tmp); sendfile(fdw, fdr, NULL, tmp.st_size); close(fdr); close(fdw); } [root@localhost kitamura]# echo hogehogehoge > hoge1 [root@localhost kitamura]# ./a.out [root@localhost kitamura]# cat hoge2 hogehogehogespliceはpipeを介しての実装で、spliceシステムコールの引数の1つはpipeでなければなりませんが、
splice_direct_to_actor()はファイル対ファイルの連結で、従って、splice_direct_to_actor()内で、新規にpipeを取得しcurrent->splice_pipeに設定し、これを介してファイルの読み書きを行います。
do_splice_to()でpipeに読み込んで、actor(){direct_splice_actor()}でpipeに書き出しことでファイルへ書き出します。
long do_splice_direct(struct file *in, loff_t *ppos, struct file *out, size_t len, unsigned int flags) { struct splice_desc sd = { .len = len, .total_len = len, .flags = flags, .pos = *ppos, .u.file = out, }; long ret; ret = splice_direct_to_actor(in, &sd, direct_splice_actor); if (ret > 0) *ppos = sd.pos; return ret; } ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd, splice_direct_actor *actor) { struct pipe_inode_info *pipe; long ret, bytes; umode_t i_mode; size_t len; int i, flags; i_mode = in->f_path.dentry->d_inode->i_mode; if (unlikely(!S_ISREG(i_mode) && !S_ISBLK(i_mode))) return -EINVAL; pipe = current->splice_pipe; if (unlikely(!pipe)) { pipe = alloc_pipe_info(NULL); if (!pipe) return -ENOMEM; pipe->readers = 1; current->splice_pipe = pipe; } ret = 0; bytes = 0; len = sd->total_len; flags = sd->flags; sd->flags &= ~SPLICE_F_NONBLOCK; while (len) { size_t read_len; loff_t pos = sd->pos, prev_pos = pos; ret = do_splice_to(in, &pos, pipe, len, flags); if (unlikely(ret <= 0)) goto out_release; read_len = ret; sd->total_len = read_len; ret = actor(pipe, sd); if (unlikely(ret <= 0)) { sd->pos = prev_pos; goto out_release; } bytes += ret; len -= ret; sd->pos = pos; if (ret < read_len) { sd->pos = prev_pos + ret; goto out_release; } } done: pipe->nrbufs = pipe->curbuf = 0; file_accessed(in); return bytes; out_release: for (i = 0; i < pipe->buffers; i++) { struct pipe_buffer *buf = pipe->bufs + i; if (buf->ops) { buf->ops->release(pipe, buf); buf->ops = NULL; } } if (!bytes) bytes = ret; goto done; } static int direct_splice_actor(struct pipe_inode_info *pipe, struct splice_desc *sd) { struct file *file = sd->u.file; return do_splice_from(pipe, file, &file->f_pos, sd->total_len, sd->flags); } static long do_splice_from(struct pipe_inode_info *pipe, struct file *out, loff_t *ppos, size_t len, unsigned int flags) { ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); int ret; if (unlikely(!(out->f_mode & FMODE_WRITE))) return -EBADF; if (unlikely(out->f_flags & O_APPEND)) return -EINVAL; ret = rw_verify_area(WRITE, out, ppos, len); if (unlikely(ret < 0)) return ret; if (out->f_op && out->f_op->splice_write) splice_write = out->f_op->splice_write; else splice_write = default_file_splice_write; return splice_write(pipe, out, ppos, len, flags); }
備考
linux 2.6.0では、入力側ファイルのf_op->sendfile()がコールされ、do_generic_mapping_read()で、入力ファイルのfilp->f_dentry->d_inode->i_mappingに、ファイルデータが読み込まれ、書き出し側ファイルのfile->f_op->sendpage()で、filp->f_dentry->d_inode->i_mappingをpage単位で書き出すことで実現しています。ここでの書き出しはファイルへのそれででなく、ソケット通信への書き出しです。f_op->sendpage()のコールバック関数は、ファイルオペレーションとして定義されてなく、ソケットオペレーションで定義されるコールバック関数です。(f_op->sendpage()が定義されていないならエラーとなります。)
検証してませんが、以前はsendfileシステムコールはソケット通信を介するnfsでの機能だったようです。