spliceのpipe間のやり取りは、in_pipeからout_pipeへ転送されたin_pipeバッファのデータは削除されますが、teeはin_pipeからout_pipeへ転送したin_pipeのバッファのデータは削除されません。
サンプル
[root@localhost c]# cat tee_splice.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void splice_pipe()
{
int pifd1[2], pifd2[2];
char buff[10];
pipe(pifd1);
pipe(pifd2);
printf("splice\n");
write(pifd1[1], "01234", 5);
splice(pifd1[0], NULL, pifd2[1], NULL, 5, 0);
memset(buff, 0, sizeof(buff));
read(pifd2[0], buff, 5);
printf("read from pifd2:%s\n", buff);
memset(buff, 0, sizeof(buff));
read(pifd1[0], buff, 5);
printf("read from pifd1:%s\n", buff);
close(pifd1[0]); close(pifd1[1]);
close(pifd2[0]); close(pifd2[1]);
}
void tee_pipe()
{
int pifd1[2], pifd2[2];
char buff[10];
pipe(pifd1);
pipe(pifd2);
printf("tee\n");
write(pifd1[1], "01234", 5);
tee(pifd1[0], pifd2[1], 5,0);
memset(buff, 0, sizeof(buff));
read(pifd2[0], buff, 5);
printf("read from pifd2:%s\n", buff);
memset(buff, 0, sizeof(buff));
read(pifd1[0], buff, 5);
printf("read from pifd1:%s\n", buff);
close(pifd1[0]); close(pifd1[1]);
close(pifd2[0]); close(pifd2[1]);
}
void main(void)
{
tee_pipe();
printf("\n");
splice_pipe();
}
[root@localhost c]# ./tee_splice.o
tee
read from pifd2:01234
read from pifd1:01234
splice
read from pifd2:01234
^C
pipeは送信データがpipe_inode_infoのpipe inodeの同じバッファー領域のシーケンシャルなデータと管理されるのでなく、送信毎のpipe_bufferデータブロック単位で管理されます。
struct pipe_inode_info {
wait_queue_head_t wait;
unsigned int nrbufs, curbuf, buffers;
unsigned int readers;
unsigned int writers;
unsigned int waiting_writers;
unsigned int r_counter;
unsigned int w_counter;
struct page *tmp_page;
struct fasync_struct *fasync_readers;
struct fasync_struct *fasync_writers;
struct inode *inode;
struct pipe_buffer *bufs;
};
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
spliceのpipe時にコールされ、ipipeに送信されているデータは送信毎に設定されたpipe_bufferをipipe.bufsにリスト(配列)されています。
ipipe.nrbufsはリスト済み(送信)pipe_buffer総数、ipipe.curbufはリストされている先頭pipe_bufferの位置で、ipipe.curbuf + ipipe.nrbufsは書込位置となり、ipipe.nrbufs--とすると最後に送信された送信済みデータの1つを削除します。ipipe.curbuf++とすれば読み込み先頭データの削除となります。
ibuf = ipipe->bufs + ipipe->curbuf : 読込pipe_buffer
obuf = opipe->bufs + opipe->curbuf + opipe->nrbufs: 書込pipe_buffer
ipipe->nrbufs--/ipipe->curbuf++でipipe->curbufのpipe_bufferは削除されます。spliceのサイズがpipe_bufferサイズより小さい時は、ipipeに掛かる変更はなく、pipe_bufferのoffset/lenに掛かる実装となります。
static int splice_pipe_to_pipe(struct pipe_inode_info *ipipe,
struct pipe_inode_info *opipe,
size_t len, unsigned int flags)
{
struct pipe_buffer *ibuf, *obuf;
int ret = 0, nbuf;
bool input_wakeup = false;
retry:
ret = ipipe_prep(ipipe, flags);
if (ret)
return ret;
ret = opipe_prep(opipe, flags);
if (ret)
return ret;
pipe_double_lock(ipipe, opipe);
do {
if (!opipe->readers) {
send_sig(SIGPIPE, current, 0);
if (!ret)
ret = -EPIPE;
break;
}
if (!ipipe->nrbufs && !ipipe->writers)
break;
if (!ipipe->nrbufs || opipe->nrbufs >= opipe->buffers) {
if (ret)
break;
if (flags & SPLICE_F_NONBLOCK) {
ret = -EAGAIN;
break;
}
pipe_unlock(ipipe);
pipe_unlock(opipe);
goto retry;
}
ibuf = ipipe->bufs + ipipe->curbuf;
nbuf = (opipe->curbuf + opipe->nrbufs) & (opipe->buffers - 1);
obuf = opipe->bufs + nbuf;
if (len >= ibuf->len) <--- 転送サイズが対象pipe_bufferのサイズ以上なら、ibuf単位での取得
*obuf = *ibuf;
ibuf->ops = NULL;
opipe->nrbufs++;
ipipe->curbuf = (ipipe->curbuf + 1) & (ipipe->buffers - 1); <-- 次入力pipe_buffe位置を更新
ipipe->nrbufs--; <-- 転送したpipe_buffer数(1ケ)削除
input_wakeup = true;
} else { <--- 転送サイズが対象pipe_bufferのサイズ以下なら、対象ibufからの取得
ibuf->ops->get(ipipe, ibuf);
*obuf = *ibuf;
obuf->flags &= ~PIPE_BUF_FLAG_GIFT;
obuf->len = len;
opipe->nrbufs++;
ibuf->offset += obuf->len;
ibuf->len -= obuf->len;
}
ret += obuf->len;
len -= obuf->len;
} while (len);
pipe_unlock(ipipe);
pipe_unlock(opipe);
if (ret > 0)
wakeup_pipe_readers(opipe);
if (input_wakeup)
wakeup_pipe_writers(ipipe);
return ret;
}
treeからコールされ、spliceと異なり、in/outともpipeでないとエラーです。*obuf = *ibuf / opipe->nrbufs+でopipeに複写し、spliceと異なりipipe->nrbufがデクリメントされに故、ipipeのデータは削除されません。
static int link_pipe(struct pipe_inode_info *ipipe,
struct pipe_inode_info *opipe,
size_t len, unsigned int flags)
{
struct pipe_buffer *ibuf, *obuf;
int ret = 0, i = 0, nbuf;
pipe_double_lock(ipipe, opipe);
do {
if (!opipe->readers) {
send_sig(SIGPIPE, current, 0);
if (!ret)
ret = -EPIPE;
break;
}
if (i >= ipipe->nrbufs || opipe->nrbufs >= opipe->buffers)
break;
ibuf = ipipe->bufs + ((ipipe->curbuf + i) & (ipipe->buffers-1));
nbuf = (opipe->curbuf + opipe->nrbufs) & (opipe->buffers - 1);
ibuf->ops->get(ipipe, ibuf);
obuf = opipe->bufs + nbuf;
*obuf = *ibuf;
obuf->flags &= ~PIPE_BUF_FLAG_GIFT;
if (obuf->len > len)
obuf->len = len;
opipe->nrbufs++;
ret += obuf->len;
len -= obuf->len;
i++;
} while (len);
if (!ret && ipipe->waiting_writers && (flags & SPLICE_F_NONBLOCK))
ret = -EAGAIN;
pipe_unlock(ipipe);
pipe_unlock(opipe);
if (ret > 0)
wakeup_pipe_readers(opipe);
return ret;
}
補足
データ転送は、データその物を複写するのでなく、*obuf = *ibufでpipe_buffer自身を代入する事で実装されており、データのstruct page *pageを共有する事で実装します。