Provided by: manpages-ja-dev_0.5.0.0.20221215+dfsg-1_all
名前
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - 同期 I/O の多重化
書式
/* POSIX.1-2001 に従う場合 */ #include <sys/select.h> /* 以前の規格に従う場合 */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); #include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); glibc 向けの機能検査マクロの要件 (feature_test_macros(7) 参照): pselect(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
説明
select() や pselect() を使うと、プログラムで複数のファイルディスクリプターを監視し、 一 つ以上のファイルディスクリプターがある種の I/O 操作の 「ready (準備ができた)」状態 (例え ば、読み込み可能になった状態) になるまで待つことができる。 ファイルディスクリプターが ready (準備ができた) とは、 (read(2) などの) 対応する I/O 操作が停止 (block) なしに実行し たり、 十分小さな write(2) を実行したりできる状態にあることを意味する。 select() と pselect() の動作は同じであるが、以下の 3 点が異なる: (i) select() では、タイムアウト時間の指定に構造体 struct timeval (秒・マイクロ秒単位) を用いる。 一方、 pselect() 関数では、構造体 struct timespec (秒・ナノ秒単位) を用 いる。 (ii) select() は残り時間を示す timeout 引き数を更新することがある。 pselect() はこの引 き数を変更しない。 (iii) select() は sigmask 引き数を持たない。その動作は sigmask に NULL を指定した場合の pselect() と同じである。 3 つの独立したファイルディスクリプター集合の監視を行う。 readfds に入れられたディスクリプ ターについては、読み込みが可能かどうかを 監視する (より正確にいうと、停止 (block) なしで読 むことができるかを 調べる。ファイルの終端 (end-of-file) の場合も、 ファイルディスクリプ ターは読み込み可能として扱われる)。 writefds に入れられたディスクリプターについては、書き 込み用に利用可能な領域があるかを監視する (ただし、大きな書き込みの場合には停止する可能性は ある)。 exceptfds にあるものについては、例外の監視を行なう。システムコール終了時に、 どの ファイルディスクリプターの状態が実際に変化したか示すために、 集合の内容が変更される。 ある 種別のイベントを監視したいファイルディスクリプターが一つもない場合には、 対応するファイル ディスクリプター集合に NULL を指定することができる。 集合を操作するために 4 つのマクロが提供されている。 FD_ZERO() は集合を消去する。 FD_SET() と FD_CLR() はそれぞれ指定したファイルディスクリプターの集合への追加、削除を行う。 FD_ISSET() は集合にファイルディスクリプターがあるかどうか調べる; このマクロは select() が 終了した後に使うと便利である。 nfds は 3 つの集合に含まれるファイルディスクリプターの最大値に 1 を足したものである。 timeout 引き数は、 select() がファイルディスクリプターが ready になるのを待って停止する時 間を指定する。 呼び出しは以下のいずれかになるまで停止する。 * ファイルディスクリプターが利用可能になる。 * システムコールがシグナルハンドラーにより割り込まれた。 * タイムアウト時間が満了した。 この timeout 時間はシステムクロックの粒度に切り上げられ、 カーネルのスケジューリング遅延に より少しだけ長くなる可能性がある点に注意すること。 timeval 構造体の両方のフィールドが 0 の 場合、 select() はすぐに復帰する (この機能はポーリング (polling) を行うのに便利である)。 timeout に NULL (タイムアウトなし) が指定されると、 select() は無期限に停止 (block) する。 sigmask は、シグナルマスク (sigprocmask(2) を参照) へのポインターである。 sigmask が NULL でない場合、 pselect() は sigmask が指しているシグナルマスクで現在のシグナルマスクを置き 換えてから、 "select" 関数を実行し、 終了後にシグナルマスクを元のシグナルマスクに戻す。 timeout 引き数の精度の違いを除くと、以下の pselect() の呼び出しは、 ready = pselect(nfds, &readfds, &writefds, &exceptfds, timeout, &sigmask); 次のコールを atomic に実行するのと等価である。 sigset_t origmask; pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); ready = select(nfds, &readfds, &writefds, &exceptfds, timeout); pthread_sigmask(SIG_SETMASK, &origmask, NULL); pselect() が必要になる理由は、シグナルやファイルディスクリプターの状態変化を 待ちたいとき には、競合状態を避けるために atomic なテストが必要になる からである。 (シグナルハンドラー が大域フラグを設定して戻る場合を考えてみよう。 この大域フラグのテストに続けて select() を 呼び出すと、 シグナルがテストの直後かつ呼び出しの直前に届いた時には select() は永久にハン グしてしまうかもしれない。 一方、 pselect() を使うと、まずシグナルを禁止 (block) し て、入ってくるシグナルを操作し、 望みの sigmask で pselect() を呼び出すことで、前記の競合 を避けることができる。) タイムアウト これらの関数で使用される時間関連の構造体は、 <sys/time.h> で struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* マイクロ秒 */ }; や struct timespec { long tv_sec; /* 秒 */ long tv_nsec; /* ナノ秒 */ }; のように定義されている。 (POSIX.1-2001 での定義については下記の「注意」を参照) 秒単位以下の精度でスリープを実現する 移植性の高い方法として、 3 つの集合全てを空、 nfds を 0 、 timeout を NULL でない値に設定して select() を呼び出すという方法を使っているコードも ある。 Linux では、 select() は timeout を変更し、残りの停止時間を反映するようになっているが、 他のほとんどの実装ではこのようになっていない (POSIX.1-2001 はどちらの動作も認めている)。 このため、 timeout を参照している Linux のコードを他のオペレーティングシステムへ 移植する 場合、問題が起こる。 また、ループの中で timeval 構造体を初期化せずにそのまま再利用して select() を複数回行なっているコードを Linux へ移植する場合にも、問題が起こる。 select() から復帰した後は timeout は未定義であると考えるべきである。
返り値
成功した場合、 select() と pselect() は更新された 3 つのディスクリプター集合に含まれてい る ファイルディスクリプターの数 (つまり、 readfds, writefds, exceptfds 中の 1 になっている ビットの総数) を返す。 何も起こらずに時間切れになった場合、 ディスクリプターの数は 0 にな ることもある。 エラーならば -1 を返し、 errno にエラーを示す値が設定される; ファイルディス クリプター集合は変更されず、 timeout は不定となる。
エラー
EBADF いずれかの集合に無効なファイルディスクリプターが指定された (おそらくは、すでにク ローズされたファイルディスクリプターか、 エラーが発生したファイルディスクリプターが 指定された)。 EINTR シグナルを受信した。 EINVAL nfds が負、 またはリソース上限 RLIMIT_NOFILE (getrlimit(2) 参照) より大きい。 EINVAL timeout に入っている値が不正である。 ENOMEM 内部テーブルにメモリーを割り当てることができなかった。
バージョン
pselect() はカーネル 2.6.16 で Linux に追加された。 それ以前は、 pselect() は glibc でエ ミュレートされていた (「バグ」の章を参照)。
準拠
select() は POSIX.1-2001 と 4.4BSD (select() は 4.2BSD で最初に登場した) に準拠する。 BSD ソケット層のクローンをサポートしている非 BSD システム (System V 系も含む) との間でだい たい移植性がある。しかし System V 系では たいがい timeout 変数を exit の前にセットするが、 BSD 系ではそうでないので注意すること。 pselect() は POSIX.1g と POSIX.1-2001 で定義されている。
注意
fd_set は固定サイズのバッファーである。 負や FD_SETSIZE 以上の値を持つ fd に対して FD_CLR() や FD_SET() を実行した場合、 どのような動作をするかは定義されていない。 また、 POSIX では fd は有効なファイルディスクリプターでなければならないと規定されている。 型宣言に関しては、昔ながらの状況では timeval 構造体の 2 つのフィールドは (上記のように) 両 方とも long 型であり、構造体は <sys/time.h> で定義されている。 POSIX.1-2001 の下では、以下 のようになっている。 struct timeval { time_t tv_sec; /* 秒 */ suseconds_t tv_usec; /* マイクロ秒 */ }; この構造体は <sys/select.h> で定義されており、データ型 time_t と suseconds_t は <sys/types.h> で定義されている。 プロトタイプに関しては、昔ながらの状況で select() を使いたい場合は、 <time.h> をインク ルードすればよい。 POSIX.1-2001 の環境で select() と pselect() を使いたい場合は、 <sys/select.h> をインクルードすればよい。 glibc 2.0 では <sys/select.h> が提供する pselect() のプロトタイプが間違っている。 glibc 2.1 から 2.2.1 では _GNU_SOURCE が定義されている場合に、 pselect() が提供される。 glibc 2.2.2 以降では、 pselect() を使用するには、「書式」に記載された要件を満たす必要がある。 マルチスレッドアプリケーション select() で監視中のファイルディスクリプターが別のスレッドでクローズされた場合、どのような 結果になるかは規定されていない。いくつかの UNIX システムでは、 select() は停止 (block) せ ず、すぐ返り、ファイルディスクリプターが ready だと報告される (select() が返ってから I/O 操作が実行されるまでの間に、 別のファイルディスクリプターが再度オープンされない限り、 それ 以降の I/O 操作はおそらく失敗するだろう)。 Linux (や他のいくつかのシステム) では、 別のス レッドでファイルディスクリプターがクローズされても select() には影響を与えない。 まとめる と、このような場合に特定の動作に依存しているアプリケーションは「バグっている」と考えなけれ ばならない。 C ライブラリとカーネル ABI の違い このページで説明している pselect() のインターフェースは、glibc に 実装されているものであ る。内部で呼び出される Linux のシステムコールは pselect6() という名前である。このシステム コールは glibc のラッパー 関数とは少し違った動作をする。 Linux の pselect6() システムコールは timeout 引き数を変更する。 しかし、glibc のラッパー関 数は、システムコールに渡す timeout 引き数と してローカル変数を使うことでこの動作を隠蔽して いる。このため、glibc の pselect() 関数は timeout 引き数を変更しない。 これが POSIX.1-2001 が要求している動作である。 pselect6() システムコールの最後の引き数は sigset_t * 型の ポインターではなく、以下に示す構 造体である。 struct { const sigset_t *ss; /* シグナル集合へのポインター */ size_t ss_len; /* 'ss' が指すオブジェクトのサイズ (バイト数) */ }; このようにすることで、ほとんどのアーキテクチャーがサポートしている システムコールの引き数 が最大で 6 個という事実を満たしつつ、 pselect6() システムコールがシグナル集合へのポイン ターとシグナル集合 のサイズの両方を取得することができるのである。
バグ
glibc 2.0 では、 sigmask 引き数を取らないバージョンの pselect() が提供されていた。 バージョン 2.1 以降の glibc では、 pselect() は sigprocmask(2) と select() を使ってエ ミュレートされていた。 この実装にはきわどい競合条件において脆弱性が残っていた。 この競合条 件における問題を防止するために pselect() は設計されたのである。 最近のバージョンの glibc では、カーネルがサポートしている場合には、 (競合が起こらない) pselect() システムコールが 使用される。 pselect() がないシステムにおいて、シグナルの捕捉を信頼性があり (移植 性も高い) 方法で行う には、 自己パイプ (self-pipe) という技を使うとよい。 この方法では、シグナルハンドラーはパ イプへ 1 バイトのデータを書き込み、 同じパイプのもう一端をメインプログラムの select() で監 視する (一杯に なったパイプへの書き込みや空のパイプから読み出しを行った際に起こるであ ろう 停止 (blocking) を避けるためには、パイプへの読み書きの際には 非停止 (nonblocking) I/O を使 用するとよい)。 Linux では、 select() がソケットファイルディスクリプターで "読み込みの準備ができた" と報 告した場合でも、 この後で read を行うと停止 (block) することがある。このような状況は、 例 えば、データが到着したが、検査でチェックサム異常が見つかり廃棄された時 などに起こりえ る。他にもファイルディスクリプターが準備できたと間違って 報告される状況が起こるかもしれな い。 したがって、停止すべきではないソケットに対しては O_NONBLOCK を使うとより安全であろ う。 Linux では、 select() がシグナルハンドラーにより割り込まれた場合 (つまり EINTR エラーが返 る場合)、 timeout も変更する。 これは POSIX.1-2001 では認められていない挙動である。 Linux の pselect() システムコールも同じ挙動をするが、 glibc のラッパー関数がこの挙動を隠蔽してい る。 具体的には、glibc のラッパー関数の内部で、 timeout をローカル変数にコピーし、 この ローカル変数をシステムコールに渡している。
例
#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int main(void) { fd_set rfds; struct timeval tv; int retval; /* stdin (fd 0) を監視し、入力があった場合に表示する。*/ FD_ZERO(&rfds); FD_SET(0, &rfds); /* 5 秒間監視する。*/ tv.tv_sec = 5; tv.tv_usec = 0; retval = select(1, &rfds, NULL, NULL, &tv); /* この時点での tv の値を信頼してはならない。*/ if (retval == -1) perror("select()"); else if (retval) printf("今、データが取得できました。\n"); /* FD_ISSET(0, &rfds) が true になる。*/ else printf("5 秒以内にデータが入力されませんでした。\n"); exit(EXIT_SUCCESS); }
関連項目
accept(2), connect(2), poll(2), read(2), recv(2), restart_syscall(2), send(2), sigprocmask(2), write(2), epoll(7), time(7) 考察と使用例の書かれたチュートリアルとして、 select_tut(2) がある。
この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクト の説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。