名前
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) 参照):
説明
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() と同じである。
- *
- ファイルディスクリプターが利用可能になる。
- *
- システムコールがシグナルハンドラーにより割り込まれた。
- *
- タイムアウト時間が満了した。
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; /* ナノ秒 */ };
返り値
成功した場合、 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 は有効なファイルディスクリプターでなければならないと規定されている。struct timeval { time_t tv_sec; /* 秒 */ suseconds_t tv_usec; /* マイクロ秒 */ };
マルチスレッドアプリケーション
select() で監視中のファイルディスクリプターが別のスレッドでクローズされた場合、どのような結果になるかは規定されていない。いくつかの UNIX システムでは、 select() は停止 (block) せず、すぐ返り、ファイルディスクリプターが ready だと報告される ( select() が返ってから I/O 操作が実行されるまでの間に、 別のファイルディスクリプターが再度オープンされない限り、 それ以降の I/O 操作はおそらく失敗するだろう)。 Linux (や他のいくつかのシステム) では、 別のスレッドでファイルディスクリプターがクローズされても select() には影響を与えない。 まとめると、このような場合に特定の動作に依存しているアプリケーションは「バグっている」と考えなければならない。C ライブラリとカーネル ABI の違い
このページで説明している pselect() のインターフェースは、glibc に 実装されているものである。内部で呼び出される Linux のシステムコールは pselect6() という名前である。このシステムコールは glibc のラッパー 関数とは少し違った動作をする。struct { const sigset_t *ss; /* シグナル集合へのポインター */ size_t ss_len; /* 'ss' が指すオブジェクトのサイズ (バイト数) */ };
このようにすることで、ほとんどのアーキテクチャーがサポートしている システムコールの引き数が最大で 6 個という事実を満たしつつ、 pselect6() システムコールがシグナル集合へのポインターとシグナル集合 のサイズの両方を取得することができるのである。
バグ
glibc 2.0 では、 sigmask 引き数を取らないバージョンの pselect() が提供されていた。例
#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)この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。2015-01-22 | Linux |