/* 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() の動作は同じであるが、以下の 3 点が異なる:
3 つの独立したファイルディスクリプタ集合の監視を行う。 readfds に入れられたディスクリプタについては、読み込みが可能かどうかを 監視する (より正確にいうと、停止 (block) なしで読むことができるかを 調べる。ファイルの終端 (end-of-file) の場合も、 ファイルディスクリプタは読み込み可能として扱われる)。 writefds に入れられたディスクリプタについては、停止せずに書き込みが 可能かどうかを監視する。 exceptfds にあるものについては、例外の監視を行なう。システムコール終了時に、 どのファイルディスクリプタの状態が実際に変化したか示すために、 集合の内容が変更される。 ある種別のイベントを監視したいファイルディスクリプタが一つもない場合には、 対応するファイルディスクリプタ集合に NULL を指定することができる。
集合を操作するために 4 つのマクロが提供されている。 FD_ZERO() は集合を消去する。 FD_SET() と FD_CLR() はそれぞれ指定したファイルディスクリプタの集合への追加、削除を行う。 FD_ISSET() は集合にファイルディスクリプタがあるかどうか調べる; このマクロは select() が終了した後に使うと便利である。
nfds は 3 つの集合に含まれるファイルディスクリプタの最大値に 1 を足したものである。
timeout は select() が復帰するまでの経過時間の上限である。これは 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; sigprocmask(SIG_SETMASK, &sigmask, &origmask); ready = select(nfds, &readfds, &writefds, &exceptfds, timeout); sigprocmask(SIG_SETMASK, &origmask, NULL);
pselect() が必要になる理由は、シグナルやファイルディスクリプタの状態変化を 待ちたいときには、競合状態を避けるために atomic なテストが必要になる からである。 (シグナルハンドラが大域フラグを設定して戻る場合を考えてみよう。 この大域フラグのテストに続けて select() を呼び出すと、 シグナルがテストの直後かつ呼び出しの直前に届いた時には select() は永久にハングしてしまうかもしれない。 一方、 pselect() を使うと、まずシグナルを禁止 (block) して、入ってくるシグナルを操作し、 望みの sigmask で pselect() を呼び出すことで、前記の競合を避けることができる。)
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 は未定義であると考えるべきである。
pselect() は POSIX.1g と POSIX.1-2001 で定義されている。
型宣言に関しては、昔ながらの状況では 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> をインクルードすればよい。 ヘッダファイル <sys/select.h> は libc4 と libc5 にはなく、glibc 2.0 以降に存在する。 悪いことに glibc 2.0 以前では pselect() のプロトタイプが間違っている。 glibc 2.1-2.2.1 では _GNU_SOURCE が定義されている場合に、 pselect() が提供される。 glibc 2.2.2-2.2.4 では _XOPEN_SOURCE が定義されていて、その値が 600 以上である場合に pselect() が提供される。 もちろん、 POSIX.1-2001 以降では、デフォルトでプロトタイプが提供される。
バージョン 2.1 以降の glibc では、 pselect() は sigprocmask(2) と select() を使ってエミュレートされていた。 この実装にはきわどい競合条件において脆弱性が残っている。 この競合条件における問題を防止するために pselect() は設計されたのである。 pselect() がないシステムにおいて、 シグナルの捕捉を信頼性があり (移植性も高い) 方法で行うには、 自己パイプ (self-pipe) という技を使うとよい (シグナルハンドラはパイプへ 1 バイトのデータを書き込み、同じパイプのもう一端をメインプログラムの select() で監視するという方法である)。
Linux では、 select() がソケットファイルディスクリプタで "読み込みの準備ができた" と報告した場合でも、 この後で read を行うと停止 (block) することがある。このような状況は、 例えば、データが到着したが、検査でチェックサム異常が見つかり廃棄された時 などに起こりえる。他にもファイルディスクリプタが準備できたと間違って 報告される状況が起こるかもしれない。 したがって、停止すべきではないソケットに対しては O_NONBLOCK を使うとより安全であろう。
Linux では、 select() がシグナルハンドラにより割り込まれた場合 (つまり EINTR エラーが返る場合)、 timeout も変更する。 これは POSIX.1-2001 では認められていない挙動である。 Linux の pselect(2) システムコールも同じ挙動をするが、 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), send(2), sigprocmask(2), write(2), epoll(7)