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
hogehogehoge
spliceは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での機能だったようです。




