5.3 Pipes

プロセス間通信の伝統的な手法。

$ ls -l | more

パイプの変種は名前付きパイプ(FIFOとして知られる)からなる。

$ mkfifo *pathname*

または

$ mknod *pathname* p
mkfifo(1)
FIFOsをつくる(名前付きパイプ)
mknod(1)
Block special fileかCharacter special fileをつくる
$ mkfifo fifo
$ ls -l fifo
$ prw-r--r-- 1 uki uki - 12月  9 15:50 fifo
$ ls -l >fifo & more <fifo

パイプとFIFOのあいだには似ている点がたくさんあり、Linuxの実装で活用されている。inodeはパイプとFIFO向けの構成要素を持つ。

include/linux/pipe_fs_i.h

struct pipe_inode_info {
    struct wait_queue * wait; /* 待ちキュー */
    char * base;        /* FIFOバッファのアドレス */
    unsigned int start; /* 現在の領域のオフセット */
    unsigned int len;   /* 現在の領域の長さ */
    unsigned int lock;  /* ロック */
    unsigned int rd_openers; /* 読みアクセス用にパイプかFIFOを開いているプロセスの数 */
    unsigned int wr_openers; /* 書きアクセス用にパイプかFIFOを開いているプロセスの数 */
    unsigned int readers;    /* この瞬間に読んでいるプロセスの数 */
    unsigned int writers;    /* この瞬間に書いているプロセスの数 */
};

システムコールpipeはパイプをつくる。これには一時的なinodeを設定し、baseのためにページを割り当てるタスクが伴う。このシステムコールは読み書き用のファイル記述子をひとつ返す。これは別々のファイル操作のベクトルを使うことで実現されている。

FIFOの場合、ページをメモリに割り当てて、読み書き両方の操作ベクトルが割り当てられたファイル記述子を返すopen関数がある。以下の表を見よ。

- - Blocking Non-blocking
読み 書き込んでいるプロセスがない Block 接続する操作を伴いFIFOを開く
読み プロセスが書き込んでいる FIFOを開く FIFOを開く
書き 読んでいるプロセスがない Block ENXIO エラー
書き プロセスが読み込んでいる FIFOを開く FIFOを開く
読み書き - FIFOを開く FIFOを開く

接続する操作が呼び出されたとき、これは書き込み処理を待機する。O_NONBLOCKが設定されている場合はEAGAINエラーが返る。

FIFOとパイプは同じ読み書き操作を使う。パイプ/FIFOに割り当てられたメモリは、startから始まりlenバイト分書き込まれた循環バッファとして解釈される。これらの操作では、記述子のO_NONBLOCKがセットされているかどうかを常に考慮する。設定されている場合は読み書き操作を妨げてはならない。

書き込まれるバイト数がパイプ内のバッファサイズ(標準では4 KB)を超えない限り、書き込み操作は不可分性を保って実行する必要がある。

Linuxで実装されているパイプ/FIFOの読み書き操作の語義は以下の通り。

語義 Blocking Non-blocking
Locked pipe 呼び出されたプロセスをブロックする EAGAIN エラー
Empty pipe 書き込んでいるプロセスが存在するなら、呼び出されたプロセスをブロックする。そうでないなら0を返する もし書き込んでプロセスが存在するなら EAGAIN エラー。そうでなければ0
それ以外 要求された位置までの最大の文字数を読む As for blocking operation
No reading process 書き込んでいるプロセスに SIGPIPE シグナルを送り、 EPIPE エラーを返す。 As for blocking
Locked pipe 呼び出されたプロセスをブロックする。 EAGAIN エラー
可能な限り不可分性を保って書き込むがパイプのバッファを超えた 同上 同上
不可分性を保って書き込むだけの十分なバッファ空間がある バッファに要求されたバイト数を書き込む As for blocking write
それ以外 要求されたバイト数が書き込まれるまでブロックし続ける 可能な限りのバイト数を書き込む

パイプまたはFIFOにアクセスすると、たびたびプロセスが阻止されるので、読み書き操作ではinode待機キュー内のプロセスを起動する必要がある。どのイベントを待っているかに関係なく、すべてのプロセスは単一の待機キューで管理される。

fs/pipe.c

asmlinkage int sys_pipe(unsigned long * fildes)
{
    struct inode * inode;
    struct file * f[2];
    int fd[2];
    int i,j;

    /* verify_area() -- mm/memory.c */
    j = verify_area(VERIFY_WRITE,fildes,8); /* 仮想メモリを割り当てる */
    if (j) /* 正常に割り当てられたなら0が返るので、それのチェックをしている */
        return j;
    for(j=0 ; j<2 ; j++)
        /*
         * get_empty_filp()はfile構造体のポインタを返す。
         * fs/file_table.c
         */
        if (!(f[j] = get_empty_filp()))
            break;
    if (j==1)
        f[0]->f_count--;
    if (j<2)
        return -ENFILE;
    j=0;
    for(i=0;j<2 && i<NR_OPEN && i<current->rlim[RLIMIT_NOFILE].rlim_cur;i++)
        if (!current->files->fd[i]) {
            current->files->fd[ fd[j]=i ] = f[j];
            j++;
        }
    if (j==1)
        current->files->fd[fd[0]]=NULL;
    if (j<2) {
        f[0]->f_count--;
        f[1]->f_count--;
        return -EMFILE;
    }
    /* get_pipe_inode() -- fs/inode.c inode構造体のポインタを返す */
    if (!(inode=get_pipe_inode())) { /* inodeを確保できなかったら… */
        current->files->fd[fd[0]] = NULL;
        current->files->fd[fd[1]] = NULL;
        f[0]->f_count--;
        f[1]->f_count--;
        return -ENFILE;
    }
    f[0]->f_inode = f[1]->f_inode = inode;
    f[0]->f_pos = f[1]->f_pos = 0;
    f[0]->f_flags = O_RDONLY;
    f[0]->f_op = &read_pipe_fops;
    f[0]->f_mode = 1;       /* 読み */
    f[1]->f_flags = O_WRONLY;
    f[1]->f_op = &write_pipe_fops;
    f[1]->f_mode = 2;       /* 書き */
    /*
     * include/asm-i386/segment.h
     *     #define put_fs_long(x,addr) put_user_long((x),(int *)(addr))
     */
    /*
     * include/asm-i386/segment.h
     * static inline void put_user_long(unsigned long val,int * addr)
     * {
     * __asm__ ("movl %0,%%fs:%1": /* no outputs */ :"ir" (val),"m" (*addr));
     * }
     */
    put_fs_long(fd[0],0+fildes);
    put_fs_long(fd[1],1+fildes);
    return 0;
}