pipe
pipe情報を有するstruct pipe_inode_info inode->i_pipeの持つinodeを、書込み/読込みの2つのFILE構造体で共有することで実現します。それぞれのFILE構造体には読み込み/書き込み用独自のファイルオペレーションコールバックが設定され、共通のバッファを2つの独立したコールバック関数で操作しているということです。
pipe:[42416]はパイプのinode番号です。lr-x------の読み込みfdと、l-wx------の書き込みfdが同じinodeであるのが確認できます。
get_unused_fd_flags()でプロセスのFILEインデックスを取得、そのインデックスに読み込み用pipeを、そして書き込み用pipeをfd_install()で設定します。これでfd[0]でreadするとpipeへの読み込みが、fd[1]でwriteするとpipeへの書き込みが可能となります。
inode->i_fop = &rdwr_pipefifo_fopsと、読み書き可能なinodeコールバック関数が設定されていますが、上で見てきたように、pipeでは直接fileコールバックを、write_pipefifo_fops/read_pipefifo_fopsで設定して、inodeコールバックがfileコールバックに反映されることはありません。これはFIFO機能を実現するためです。
FIFOファイルはmknodで作成され、FIFOを参照するプロセスがopenして読み書きするわけですが、open時inodeのコールバックがfileコールバックに設定され、inode->openコールバックが呼び出されます。このopenコールバック処理で、openフラグに応じたFIFO用のファイルオペレーションコールバック関数が、FILEに再設定されるようになっています。
pipe:[42416]はパイプのinode番号です。lr-x------の読み込みfdと、l-wx------の書き込みfdが同じinodeであるのが確認できます。
#include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char *argv[]) { int pipefd[2]; if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } while(1) { sleep(10); } }
[root@localhost kitamura]# ./hoge_pipe & [1] 12987 [root@localhost kitamura]# ls -l /proc/12987/fd 合計 0 lrwx------ 1 root root 64 3月 30 22:04 0 -> /dev/pts/1 lrwx------ 1 root root 64 3月 30 22:04 1 -> /dev/pts/1 lrwx------ 1 root root 64 3月 30 22:04 2 -> /dev/pts/1 lr-x------ 1 root root 64 3月 30 22:04 3 -> pipe:[42416] l-wx------ 1 root root 64 3月 30 22:04 4 -> pipe:[42416]create_write_pipe()で書き込み用fileを取得し、create_read_pipe()で読み込み用fileを取得します。create_read_pipe()でfwを引数とすることで、fwで設定されているinodeと同じfrを作成します。
get_unused_fd_flags()でプロセスのFILEインデックスを取得、そのインデックスに読み込み用pipeを、そして書き込み用pipeをfd_install()で設定します。これでfd[0]でreadするとpipeへの読み込みが、fd[1]でwriteするとpipeへの書き込みが可能となります。
int do_pipe_flags(int *fd, int flags) { struct file *fw, *fr; int error; int fdw, fdr; if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT)) return -EINVAL; fw = create_write_pipe(flags); if (IS_ERR(fw)) return PTR_ERR(fw); fr = create_read_pipe(fw, flags); error = PTR_ERR(fr); if (IS_ERR(fr)) goto err_write_pipe; error = get_unused_fd_flags(flags); if (error < 0) goto err_read_pipe; fdr = error; error = get_unused_fd_flags(flags); if (error < 0) goto err_fdr; fdw = error; audit_fd_pair(fdr, fdw); fd_install(fdr, fr); fd_install(fdw, fw); fd[0] = fdr; fd[1] = fdw; return 0; err_fdr: put_unused_fd(fdr); err_read_pipe: path_put(&fr->f_path); put_filp(fr); err_write_pipe: free_write_pipe(fw); return error; }pipefs下で、get_pipe_inode()でpipeのinodeを、d_alloc_pseudo()で取得したdentryにセットした後、それをalloc_file()で取得したstruct file *fにコールバック関数をwrite_pipefifo_fopsとすることで、このファイルは、pipe書きのファイルとなります。pipeのdentryは参照することがないためname = ""としています。
struct file *create_write_pipe(int flags) { int err; struct inode *inode; struct file *f; struct path path; struct qstr name = { .name = "" }; err = -ENFILE; inode = get_pipe_inode(); if (!inode) goto err; err = -ENOMEM; path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name); if (!path.dentry) goto err_inode; path.mnt = mntget(pipe_mnt); d_instantiate(path.dentry, inode); err = -ENFILE; f = alloc_file(&path, FMODE_WRITE, &write_pipefifo_fops); if (!f) goto err_dentry; f->f_mapping = inode->i_mapping; f->f_flags = O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT)); f->f_version = 0; return f; err_dentry: free_pipe_info(inode); path_put(&path); return ERR_PTR(err); err_inode: free_pipe_info(inode); iput(inode); err: return ERR_PTR(err); }pipe書き込みpathとpipe読み込みread_pipefifo_fopsコールバック関数を引数として、alloc_file()でpipe読み込みFILEを取得します。
struct file *create_read_pipe(struct file *wrf, int flags) { /* Grab pipe from the writer */ struct file *f = alloc_file(&wrf->f_path, FMODE_READ, &read_pipefifo_fops); if (!f) return ERR_PTR(-ENFILE); path_get(&wrf->f_path); f->f_flags = O_RDONLY | (flags & O_NONBLOCK); return f; }pipefsは実デバイスブロックを有してないため、new_inode_pseudo()でinodeを取得し、重要なalloc_pipe_info()で取得したpipe情報をinode->i_pipe = pipeに設定することです。ここに読み書きのバッファー等がアロケートされています。
inode->i_fop = &rdwr_pipefifo_fopsと、読み書き可能なinodeコールバック関数が設定されていますが、上で見てきたように、pipeでは直接fileコールバックを、write_pipefifo_fops/read_pipefifo_fopsで設定して、inodeコールバックがfileコールバックに反映されることはありません。これはFIFO機能を実現するためです。
FIFOファイルはmknodで作成され、FIFOを参照するプロセスがopenして読み書きするわけですが、open時inodeのコールバックがfileコールバックに設定され、inode->openコールバックが呼び出されます。このopenコールバック処理で、openフラグに応じたFIFO用のファイルオペレーションコールバック関数が、FILEに再設定されるようになっています。
static struct inode * get_pipe_inode(void) { struct inode *inode = new_inode_pseudo(pipe_mnt->mnt_sb); struct pipe_inode_info *pipe; if (!inode) goto fail_inode; inode->i_ino = get_next_ino(); pipe = alloc_pipe_info(inode); if (!pipe) goto fail_iput; inode->i_pipe = pipe; pipe->readers = pipe->writers = 1; inode->i_fop = &rdwr_pipefifo_fops; inode->i_state = I_DIRTY; inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR; inode->i_uid = current_fsuid(); inode->i_gid = current_fsgid(); inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; return inode; fail_iput: iput(inode); fail_inode: return NULL; }pippeのファイルオペレーションコールバックで、read_pipefifo_fopsの.writeはbad_pipe_w、write_pipefifo_fopsの.readはbad_pipe_rとし、それぞれ書き込み/読み込みができないようになっています。
const struct file_operations read_pipefifo_fops = { .llseek = no_llseek, .read = do_sync_read, .aio_read = pipe_read, .write = bad_pipe_w, .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_read_open, .release = pipe_read_release, .fasync = pipe_read_fasync, }; const struct file_operations write_pipefifo_fops = { .llseek = no_llseek, .read = bad_pipe_r, .write = do_sync_write, .aio_write = pipe_write, .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_write_open, .release = pipe_write_release, .fasync = pipe_write_fasync, }; static ssize_t bad_pipe_r(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { return -EBADF; } static ssize_t bad_pipe_w(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { return -EBADF; }