tee
Rev.1を表示中。最新版はこちら。
treeコマンドは画面表示をファイルにも書き出す。もので、teeシステムコールが行っています。実装的には共通pipeの内部バッファを2つのファイル出力先に書き出すというもので、画面表示とファイルという2つに限ったものでありません。treeという経路をイメージさせるネーミングから漠然と、共通のinodeを2つのfileidに割り当てるものだと。思っていましたが、そうでなく共通とするpipeデータをもう一つのfileidに複写することで実現しています。
従って、teeをコール後に共有元のpipeに出力しても、そのpipeから読み出す事は可能ですが、共有先のpipeから読み出す事はできません。teeはコールした時点のpipeが有するバッファーデータが、共有先pipeから読み出すことができるのです。
共有元fdinがFMODE_READ、共有先fdoutがFMODE_WRITEならdo_tee()をコールします。
SYSCALL_DEFINE4(tee, int, fdin, int, fdout, size_t, len, unsigned int, flags) { struct file *in; int error, fput_in; if (unlikely(!len)) return 0; error = -EBADF; in = fget_light(fdin, &fput_in); if (in) { if (in->f_mode & FMODE_READ) { int fput_out; struct file *out = fget_light(fdout, &fput_out); if (out) { if (out->f_mode & FMODE_WRITE) error = do_tee(in, out, len, flags); fput_light(out, fput_out); } } fput_light(in, fput_in); } return error; }ipipe_prep()は共有元pipeをチェックし、opipe_prep()は共有先pipeをチェックします。OKならlink_pipe()で共有します。
static long do_tee(struct file *in, struct file *out, size_t len, unsigned int flags) { struct pipe_inode_info *ipipe = get_pipe_info(in); struct pipe_inode_info *opipe = get_pipe_info(out); int ret = -EINVAL; if (ipipe && opipe && ipipe != opipe) { ret = ipipe_prep(ipipe, flags); if (!ret) { ret = opipe_prep(opipe, flags); if (!ret) ret = link_pipe(ipipe, opipe, len, flags); } } return ret; }共有元にデータがあればOKです。データがなければデータが書き込まれるまでウエイトします。ただし、書き込みプロセスがウエイトしておらず、PLICE_F_NONBLOCKの時、エラー復帰となります。
if (!pipe->writers)なら書き込みとしてこのpipeのinodeを有しているプロセスがいないということで、ウエイトする意味がありません。そのまま復帰します。ただしエラーとならず、共有先には何も書き出されないだけです。
static int ipipe_prep(struct pipe_inode_info *pipe, unsigned int flags) { int ret; if (pipe->nrbufs) return 0; ret = 0; pipe_lock(pipe); while (!pipe->nrbufs) { if (signal_pending(current)) { ret = -ERESTARTSYS; break; } if (!pipe->writers) break; if (!pipe->waiting_writers) { if (flags & SPLICE_F_NONBLOCK) { ret = -EAGAIN; break; } } pipe_wait(pipe); } pipe_unlock(pipe); return ret; }if (pipe->nrbufs < pipe->buffers)で書き込みスペースがあればOKです。なければスペースができるまで、ウエイトします。ただしSPLICE_F_NONBLOCKならエラーで復帰します。
if (!pipe->readers)ではいくらウエイトしても、読み込まれることはありません。ipipe_prep()と異なり、このケースではエラーです。
static int opipe_prep(struct pipe_inode_info *pipe, unsigned int flags) { int ret; if (pipe->nrbufs < pipe->buffers) return 0; ret = 0; pipe_lock(pipe); while (pipe->nrbufs >= pipe->buffers) { if (!pipe->readers) { send_sig(SIGPIPE, current, 0); ret = -EPIPE; break; } if (flags & SPLICE_F_NONBLOCK) { ret = -EAGAIN; break; } if (signal_pending(current)) { ret = -ERESTARTSYS; break; } pipe->waiting_writers++; pipe_wait(pipe); pipe->waiting_writers--; } pipe_unlock(pipe); return ret; }共有元pipeのipipe->nrbufs分、共有先pipeのopipeに設定します。opipe->buffersを超える内容は破棄されます。リングバッファの実装に準拠する内容で、obuf = opipe->bufs + nbufとし、共有元pipeのバッファを共有先pipeのバッファにコピーしています。
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; } static inline void get_page(struct page *page) { if (unlikely(PageTail(page))) if (likely(__get_page_tail(page))) return; VM_BUG_ON(atomic_read(&page->_count) <= 0); atomic_inc(&page->_count); }