Open20

毎日(?) システムコール

imishinistimishinist

1日目 (read)

openシステムコール

ssize_t read(int fd, void *buf, size_t count);

file descriptor、書き込み先のバッファ、最大何byteまで読み込むかのカウントを渡す。

ファイルがシークできる場合は、ファイルオフセットは読み取られたバイト数だけインクリメントされる。オフセットがファイルの終端もしくは終端を過ぎているときは 0を返す。
countに0を渡した場合は、read()はエラーを返すかもしれない。

成功したときは読み込んだバイト数が返されるが、このバイト数がカウントより少なくなることもある。
例えば、ファイル終端に近かった場合、パイプから読んでいる場合、端末から呼んでいる場合、シグナルによって中断されたときなど。

エラーには-1が返され、errnoが適切に設定される。
このとき、ファイル位置が変更されたかどうかは未定義になる。

imishinistimishinist

返すエラー

  • EAGAIN: non-blocking関連(openでやる)
  • EWOULDBLOCK: non-blocking関連(openでやる)
  • EBADF: ファイルディスクリプタがおかしい
  • EFAULT: バッファがアクセスできない(nullとか、解放済みとか?)
  • EINTR: シグナルで中断された
  • EINVAL: O_DIRECTでオープンされて、アドレス、カウント、オフセットのどれかが適切にalignされていないなど
  • EIO: バックグランドのプロセスグループに属しているときに、制御端末から読んだり、低レベルのIOエラーが発生した場合など
  • EISDIR
imishinistimishinist

標準入力から読み取って出力するコード

    int fd = STDIN_FILENO;
    char buf[4096];
    ssize_t nread;

    // read from stdin
    while ((nread = read(fd, buf, sizeof(buf))) > 0) {
        printf("%.*s", (int)nread, buf);
    }
imishinistimishinist

2日目 (write)

writeシステムコール

ssize_t write(int fd, const void *buf, size_t count);

書き込み先 file descriptor, 書き込む内容のバッファ、カウントを渡す。

bufで始まるバッファからcountバイトまで書き込む。
書き込まれるバイト数はcountより少なくなることがある。(例えば、リソース制限に達した場合、シグナルによって中断された場合など)

シーク可能なファイルでは、書き込みはファイルオフセットに行われて、実際に書き込まれたバイト数だけオフセットは増加する。ファイルが O_APPENDopen (2) されている場合は、書き込みの前にファイルオフセットが最後に設定される。ファイルオフセットの調整と書き込みはアトミックに実行される。

write() が成功したとしても実際にファイルに書き込みが実行されているとは限らない。
カーネルがI/Oをバッファリングしているため(明示的に書き込むときは fsync(2) を使う?)

direct ioを使って書き込みを行っている場合、書き込みに失敗したとき全体が失敗したわけではなく、一部のみ書き込まれる可能性もある。(その場合はおそらくデータが壊れている)

imishinistimishinist

エラーをいくつか抽出

  • EDQUOT: ディスクのquotaに制限された系のエラー
  • EFBIG: ファイルサイズが大きすぎる(プロセスごとの最大サイズとか実装の上限とか)
  • ENOSPC: ディスクがいっぱい
  • EPERM: 権限がない
  • EPIPE: 書き込み先がパイプかソケットに繋がっていて、読み込み側がクローズされている。ただ、そのときはSIGPIPEシグナルを受け取るので、プログラムがこのシグナルをキャッチするか、無視しないとこのエラーは見れない
imishinistimishinist

標準出力に "Hello World" を出力するコード

    int fd = STDOUT_FILENO;
    char *buf = "Hello World\n";
    ssize_t nwrites;
    nwrites = write(fd, buf, strlen(buf));
    assert(nwrites == strlen(buf));
imishinistimishinist

3日目 (signal, pause)

signalシステムコールは、シグナルが送られたときの動作を設定する。

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

ただし、UNIXのバージョンやLinuxのバージョンによって微妙に動作が異なるので、sigaction(2) を使ったほうが良い。
設定するハンドラは、 SIG_IGN , SIG_DFL もしくはシグナルハンドラのアドレスを指定する。
SIG_IGN が設定されている場合、単にシグナルを無視する。
SIG_DFL はシグナルごとのデフォルトのアクションが発生する。

例えば、 SIGWINCHIgn になったり、SIGINTTerm だったりする。 man 7 signal で確認できる。

imishinistimishinist

writeシステムコールをパイプに対して呼び出しているときに、パイプが閉じられている場合は EPIPE エラーになるらしいけど、SIGPIPEのシグナルがデフォルトで Term なのでエラーを取得できなかった。

SIGPIPE を無視して、EPIPE を受け取ってみる

volatile sig_atomic_t e_flag = 0;

void catch(int signum) {
    signal(SIGPIPE, SIG_IGN);
    fprintf(stderr, "**** signal(%d) is received !!! ****\n", signum);
    e_flag = 1;
}

void usr(int signum) {
    fprintf(stderr, "**** signal(%d) is received !!! ****\n", signum);
}

int main() {
    signal(SIGPIPE, catch);
    signal(SIGUSR1, usr);
    ssize_t nwrites;
    while(!e_flag) {
        fprintf(stderr, "before write: e_flag = %d\n", e_flag);
        nwrites = write(STDOUT_FILENO, "hello\n", 6);
        fprintf(stderr, "after write: e_flag = %d\n", e_flag);

        if (nwrites == -1) {
            fprintf(stderr, "error: %s\n", strerror(errno));
            assert(errno == EPIPE);
        }
        pause();
    }
    return 0;
}

signal handlerで出力するのは本来やってはいけない?はずだけど、検証のために一旦無視する。

write(2)は POSIX.1 で async-signal-safe であることが要求されているので、そちらを使えば問題ないはず
fprintf(3) は内部でバッファを管理しているのでだめ
詳しくは signal-safety(7) を見たら書いてある。

SIGPIPEが発行されるように、 head -2 パイプして実行する。
別の端末からシグナル USR1 を実行していくと、headが終了してSIGPIPEが発生する。

  1. helloが出力される
  2. シグナルを待つ
  3. USR1 シグナルを受け取る
  4. helloが出力され、headプロセスが終了する
  5. USR1シグナルを受け取る
  6. helloを書き込もうとして、SIGPIPEシグナルが発生する
  7. e_flag = 1がセットされる
  8. write(2)で EPIPE エラーが発生する
$ ./signal.exe | head -2
before write: e_flag = 0
after write: e_flag = 0
hello
**** signal(10) is received !!! ****
before write: e_flag = 0
after write: e_flag = 0
hello
**** signal(10) is received !!! ****
before write: e_flag = 0
**** signal(13) is received !!! ****
after write: e_flag = 1
error: Broken pipe
**** signal(10) is received !!! ****
$ pkill --signal USR1 signal.exe
$ pkill --signal USR1 signal.exe
$ pkill --signal USR1 signal.exe
imishinistimishinist

3日目(sigaction)

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

sigactionはシグナルを受信したときの動作を設定することができる。 SIGKILLSIGSTOP 以外のvalidなシグナルに対して動作を設定できる。
signum は設定したいシグナルの番号を指定する。act が NULLでなければ、signum に対する新しい動作を act から設定する。 oldact が NULLでなければ設定前の act の設定を取得する。

SIG_DFL もしくは SIG_IGNsa_handler に設定する。handlerの関数ポインタを指定する場合は、 sa_sigaction に設定し、 sa_flagsSA_SIGINFO を設定する。
sa_flags は 設定値を bitで設定する。

handler の関数ポインタのシグネチャ

void
handler(int sig, siginfo_t *info, void *ucontext);

sig: handlerを呼び出す原因となったシグナルの番号
info: siginfo_t へのポインタで、シグナルの情報を保持している。

siginfo_t {
    int      si_signo;     /* Signal number */
    int      si_errno;     /* An errno value */
    int      si_code;      /* Signal code */
    int      si_trapno;    /* Trap number that caused
                              hardware-generated signal
                              (unused on most architectures) */
    pid_t    si_pid;       /* Sending process ID */
    uid_t    si_uid;       /* Real user ID of sending process */
    int      si_status;    /* Exit value or signal */
    clock_t  si_utime;     /* User time consumed */
    clock_t  si_stime;     /* System time consumed */
    union sigval si_value; /* Signal value */
    int      si_int;       /* POSIX.1b signal */
    void    *si_ptr;       /* POSIX.1b signal */
    int      si_overrun;   /* Timer overrun count;
                              POSIX.1b timers */
    int      si_timerid;   /* Timer ID; POSIX.1b timers */
    void    *si_addr;      /* Memory location which caused fault */
    long     si_band;      /* Band event (was int in
                              glibc 2.3.2 and earlier) */
    int      si_fd;        /* File descriptor */
    short    si_addr_lsb;  /* Least significant bit of address
                              (since Linux 2.6.32) */
    void    *si_lower;     /* Lower bound when address violation
                              occurred (since Linux 3.19) */
    void    *si_upper;     /* Upper bound when address violation
                              occurred (since Linux 3.19) */
    int      si_pkey;      /* Protection key on PTE that caused
                              fault (since Linux 4.6) */
    void    *si_call_addr; /* Address of system call instruction
                              (since Linux 3.5) */
    int      si_syscall;   /* Number of attempted system call
                              (since Linux 3.5) */
    unsigned int si_arch;  /* Architecture of attempted system call
                              (since Linux 3.5) */
}

si_signo si_errno si_code はすべてのシグナルで設定されるが、その他の項目については、シグナルの種類によって設定されたりされなかったりする。

si_code は、特定のシグナルの場合に対応したコードが設定される。
例えば、 SIGIO/SIGPOLL シグナルに対応するコードは以下の様になる。

  • POLL_IN
  • POLL_OUT
  • POLL_MSG
  • POLL_ERR
  • POLL_PRI
  • POLL_HUP

詳細については、 man を見る。

imishinistimishinist

シグナルを見てると、SIGILLSIGFPE とか、 SIGSEGV だけじゃなく、SIGPOLL みたいなものもあるので、カーネルから何かしらのイベントを通知するために汎用的に使われているっぽい。

スレッドとか、forkした場合の動きなどはまた調べる。
(シグナルが関係している内容多すぎるので、他のシステムコール調べるときに関連があったら調べる)

imishinistimishinist

SIGUSR1 を受け取ったら情報を表示するプログラム

void handler(int signum, siginfo_t *info, void *context) {
    printf("signum: %d\n", signum);
    printf("signo: %d, errno: %d, code: %d\n", info->si_signo, info->si_errno, info->si_code);
}

int main() {
    struct sigaction act;

    bzero(&act, sizeof(struct sigaction));
    act.sa_sigaction = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &act, NULL);

    pause();
}
imishinistimishinist

4日目(open)

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

open(2) は、指定された pathname のファイルを開く。指定されたファイルが存在せず、 O_CREAT フラグが指定されている場合はファイルが作成される。
open はファイルディスクリプタを返す。ファイルディスクリプタは、非負な小さい整数値で、read(2)write(2) で特定のファイルを指すために使用される。ファイルディスクリプタの数値は、その時点で使用されていない最小の数値が使用されることが保証される。

デフォルトで、ファイルディスクリプタは execve(2) が実行されてもopenされたままになる。ファイルのオフセットはファイルの先頭になる。

open() は、open file description(オープンファイル記述)を作成するが、これはシステム全体で共有なオープンファイルテーブルのエントリである。
オープンファイル記述は、ファイルのオフセットとファイルのステータスフラグを保持している。
ファイルディスクリプタは、オープンファイル記述へのへの参照であり、これはパス名が変更されたり別のファイルを参照するようになっても影響を受けない。

続くフラグは、以下のどれかのアクセスモードに関するフラグが設定されている必要がある。

  • O_RDONLY
  • O_WRONLY
  • O_RDWR

その他のフラグは、論理和で指定することができ、オープンの動作を制御するもので以降のI/Oにも影響を与えるフラグ(file creation flags)と、その後のI/O操作に影響を与えるフラグ(file status flag)に分かれている。

file creation flags

  • O_CLOEXEC: exec時にファイルをcloseする
  • O_CREAT: pathnameのファイルが存在しなければファイルを作成する。
  • O_DIRECTORY: pathnameがディレクトリでなければオープンに失敗する。
  • O_EXCL: O_CREAT と使用された場合、pathnameのファイルが存在していた場合、オープンに失敗する。O_CREATを指定せずにO_EXCL のみ使用した場合の動作は未定義である。
  • O_NOCTTY: pathnameが端末である場合、制御端末になることを抑制する。端末でない場合は単に無視される
  • O_TMPFILE: pathnameに指定されたディレクトリのinodeに、名前を持たないテンポラリファイルのエントリを作成する。何か書き込みが行われても、ファイルがクローズされると書き込んだ内容は消える。名前を設定することで永続化することができる。
  • O_TRUNC: pathnameのファイルが存在し、書き込み許可があれば、サイズを0にする。ファイルがFIFOや制御端末だった場合、このフラグは無視される

file status flag

  • O_APPEND: 書き込み操作時にアトミックにファイルオフセットがファイルの最後に設定される。
  • O_ASYNC: 入力や出力が可能になった時点でSIGIOシグナルが発生するようにする。この昨日はターミナルや擬似端末、ソケット、パイプ、FIFOキューに適用できる。
  • O_DIRECT: I/Oにバッファキャッシュを用いないようにする。基本的にはパフォーマンスが悪化するが、DBなど独自のキャッシュをもつアプリケーションでは有効に働く
  • O_DSYNC: ファイルへの書き込みを同期I/Oとする。書込み後に、 fdatasync(2) が実行されたかのように振る舞う
  • O_LARGEFILE: 32ビットで表すことができないが64ビットで表すことのできるサイズのファイルをオープンできるようにする。64ビット環境では無視される
  • O_NOATIME: ファイルが read(2) された場合に atimeを更新しない。(ただしファイルオーナーが自分もしくは特権を持っている場合のみ指定可能で、条件を満たしていない場合は EPERM を返す。
  • O_NOFOLLOW: シンボリックリンクの場合に、シンボリックリンクを辿らずに ELOOP を返す。
  • O_NONBLOCK: ノンブロッキングモードを有効にする。open()自体も、ファイルディスクリプタへのIO操作もプロセスを待たせることはない。
  • O_PATH: ファイルを読み書きする必要はないが、pathnameに対する参照を使用したい場合に使用する。*at() 系のシステムコールに使用するのが一般的で、 pathnameにはディレクトリを指定し、そのディレクトリのファイルをオープンするときに openat(2)などが使用される。その他読み込み権限はないが実行権限がある実行ファイルをオープンする際などに使用される。このオプション付きでオープンされたfdは、いくつかのシステムコールにおいてディレクトリ用のfdとして指定できる。
  • O_SYNC: ファイルを同期I/O用にオープンする。書込み後に fsync(2) が実行されたかのように振る舞う
imishinistimishinist

エラーは多いのでまた今度調べる。

O_DIRECT を使ってみる

単に O_DIRECT を使ってもエラーが起きる

const char *pathname = "src/open.c";
int fd = openat(AT_FDCWD, pathname, O_RDONLY|O_DIRECT);
if (fd == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    exit(1);
}
char buf[4096];
ssize_t nreads;
while((nreads = read(fd, buf, sizeof(buf))) > 0) {
    write(STDOUT_FILENO, buf, nreads);
}
if (nreads == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    close(fd);
    exit(1);
}
close(fd);
$ ./o_direct_open.exe
Invalid argument

read(2) を見るとこうある

       EINVAL fd  is  attached to an object which is unsuitable for reading; or the file was opened with the O_DIRECT flag, and either the address specified in buf, the
              value specified in count, or the file offset is not suitably aligned.

つまり、 O_DIRECT フラグを設定している場合、バッファのアドレス、カウント、ファイルオフセットすべてがアラインメントされている必要がある。 512バイトにアラインして実行してみるとうまく動く。

open.c
const int BUF_SIZE = 4096;
const int ALIGN = 512;

const char *pathname = "src/open.c";
int fd = openat(AT_FDCWD, pathname, O_RDONLY|O_DIRECT);
if (fd == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    exit(1);
}
char *buf = aligned_alloc(ALIGN, BUF_SIZE);

ssize_t nreads;
while((nreads = read(fd, buf, BUF_SIZE)) > 0) {
    write(STDOUT_FILENO, buf, nreads);
}
if (nreads == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    free(buf);
    close(fd);
    exit(1);
}
free(buf);
close(fd);
imishinistimishinist

O_ASYNC を使用しようと思ったが、open(2) 時には指定できないバグがあるらしい。
fcntl(2) でしかフラグを設定できない。

BUGS
       Currently, it is not possible to enable signal-driven I/O by specifying O_ASYNC when calling open(); use fcntl(2) to enable this flag.
imishinistimishinist

O_TMPFILEO_PATH を使ってみる

const char *pathname = "/tmp";
int fd = open(pathname, O_TMPFILE|O_RDWR);
if (fd == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    exit(1);
}
const char *message = "Hello from open\n";
ssize_t nwrites = write(fd, message, sizeof(message));
if (nwrites == -1) {
    fprintf(stderr, "%s\n", strerror(errno));
    exit(1);
}

close(fd);
$ ./open.exe
$ ls /tmp/open*

やはり O_TMPFILE を指定しただけだとファイルが存在しない。

linkat(2) でファイルを設置してみる

int fd = open("/tmp", O_TMPFILE|O_RDWR, 0644);
if (fd == -1) {
    fprintf(stderr, "open fail: %s\n", strerror(errno));
    exit(1);
}
const char *message = "Hello from open\n";
ssize_t nwrites = write(fd, message, strlen(message));
if (nwrites == -1) {
    fprintf(stderr, "write fail: %s\n", strerror(errno));
    exit(1);
}

char path[2048];
snprintf(path, 2048, "/proc/self/fd/%d", fd);
if (linkat(AT_FDCWD, path, AT_FDCWD, "/tmp/open.tmp", AT_SYMLINK_FOLLOW) == -1) {
    fprintf(stderr, "linkat fail: %s\n", strerror(errno));
    exit(1);
}

close(fd);
$ cat /tmp/open.tmp
Hello from open

これをうまいこと使えば、アトミックな書き込みが実現できるかもしれない

imishinistimishinist

5日目 (open)
オープンのエラーについて調べる

  • EACCES: 要求されたファイルへのアクセスが許可されていないか、pathnameのディレクトリに対して検索の権限がないか、ファイルが存在していなくて親ディレクトリへの書き込み権限がないかその他権限のエラー
  • EBUSY: O_EXCLが指定されていて、pathnameがブロックデバイスを指している場合
  • EDQUOT: O_CREAT が指定されていてファイルが存在しないとき、ユーザーの ディスクブロックもしくはi-nodeのquotaが足りない
  • EEXIST: O_CREATとO_EXCLを指定しているときに、pathnameがすでに存在している(O_CREATは、ファイルがしない場合にファイルを作成するので、O_EXCLを指定しない場合は単に無視される)
  • EFAULT: pathnameがアクセス不可能なポインタ(nullポインタとか)
  • EFBIG: EOVERFLOWを参照
  • EINTR: 遅いデバイス(FIFOとか)のオープンを待っている間に、システムコールがシグナルハンドラによって割り込まれたとき
  • EINVAL: O_DIRECTがサポートされていないとき, フラグに無効な値がある, O_TMPFILEが指定されているのに、書き込みのフラグが立っていない(O_WRONLYもO_RDWRも指定されていない)、basename部分に許可されていない文字が含まれているとき、
  • EISDIR: pathnameがディレクトリを指しているときに、O_WRONLYが指定されているとき、pathnameがディレクトリを指し O_TMPFILEと O_WRONLYかO_RDWRのどちらかが指定されているがカーネルのバージョンがO_TMPFILEをサポートしていないとき
  • ELOOP: pathnameの解決時に、大量のシンボリックの解決が必要になったとき, pathnameがシンボリックリンクを指しているが O_NO_FOLLOWを指定しているとき(O_PATHを指定している場合はエラーは起きない)
  • EMFILE: プロセスごとのオープンできるファイルの上限に達している
  • ENAMETOOLONG: pathnameが長すぎる
  • ENFILE: システム全体でオープンできるファイルの上限に達した
  • ENOENT: O_CREATが指定されず、ファイルが存在しない, pathnameのどれかのディレクトリが存在しない、もしくは参照先が存在しないシンボリックリンク, O_TMPFILEが指定され、必要なフラグがセットされているが カーネルがO_TMPFILEをサポートしていない場合
  • ENOMEM: カーネルのメモリが足りていない, FIFOのためのユーザーごとのメモリリミットに達した
  • ENOSPC: pathnameを作成するが、pathnameを含んでいるデバイスに新しいファイルのための空間がない
  • ENOTDIR: O_DIRECTORYが指定されているがpathnameがディレクトリでない, pathnameに含まれるディレクトリ部分のどれかがディレクトリではない
  • ENXIO: O_NONBLOCK|O_WRONLYが指定されており、オープンしたFIFOをreadしているプロセスが存在しない, またはデバイススペシャルファイルでファイルが存在しない, ファイルがunix domain socketである
  • EOPNOTSUPP: pathname を含んでいるファイルシステムが O_TMPFILE をサポートしていない。
  • EOVERFLOW: 大きすぎてオープンできないファイル。32bitシステムでファイルサイズが (1<<31) -1バイトを超えるファイルを開こうとしたときに発生する。2.6.24より前のlinuxではEBIGを返していた
  • EPERM: O_NOATIMEを指定した際に、実行ユーザーIDがファイルのオーナーと一致しない, 操作が file seal により禁止されている
  • EROFS: pathnameがread-onlyなファイルシステムを指しているが、書き込みのリクエストを受け取った
  • ETXTBSY: pathnameが実行ファイルを参照していて、実行中に書き込みリクエストを受け取った, pathnameが現在使用されているスワップファイルを参照しており、O_TRUNCが指定されている, pathnameがカーネルにより読み取られているファイルを参照しており、書き込みリクエストを受け取った(例えば、ファームウェアのロード)
  • EWOULDBLOCK: O_NONBLOCKが指定されたが、無効なリースを保持している
  • EBADF: 無効なファイルディスクリプタ
imishinistimishinist

EBUSYを起こすコード

ブロックデバイスファイルに対して、O_EXCL を指定してopenすると発生した

int main() {
    int fd = open("/dev/nvme0n1", O_RDONLY | O_EXCL);
    if (fd == -1) {
        printf("open fail: %s\n", strerror(errno));
    }
    close(fd);
    return 0;
}
$ cc open.c -o /tmp/open
$ sudo /tmp/open
open fail: Device or resource busy
imishinistimishinist

EMFILEを起こすコード

setrlimitでRLIMIT_NOFILEを強制的に3にして、openすることで発生した

struct rlimit limit;
// get
if (getrlimit(RLIMIT_NOFILE, &limit) == -1) {
    fprintf(stderr, "get rlimit fail: %s\n", strerror(errno));
    exit(1);
}
printf("%ld %ld\n", limit.rlim_cur, limit.rlim_max);

// set
limit.rlim_cur = 3;
if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
    fprintf(stderr, "set rlimit fail: %s\n", strerror(errno));
    exit(1);
}

int fd = open("/tmp/open.c", O_PATH);
if (fd == -1) {
    fprintf(stderr, "open fail: %s\n", strerror(errno));
    exit(1);
}
return 0;
$ cc /tmp/open.c -o /tmp/open
$ /tmp/open
1024 1048576
open fail: Too many open files
imishinistimishinist

同期IOに関して

O_SYNCとO_DSYNCの違いとして、O_DSYNCは後続の読み取り操作に関連するデータだけをflushすることを保証する。

たとえば、ファイルの更新時には確実に最終更新時刻が更新されるが、ファイル長はファイルの末尾に書き込みが発生する場合のみ更新される。
最終更新時刻は、後続の読み取りが正常に完了するためには必要ないが、ファイル長は必須なので、O_DSYNCはファイル長のメタデータは確実に更新する。

imishinistimishinist

fifoをオープンする場合、自分が書き込む場合は読み込み側が、自分が読み込む場合は書き込む側が開かれるまでopenはブロックされる