SELECT(2) | Linux Programmer's Manual | SELECT(2) |
名前
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 (準備ができた) とは、対応する I/O 操作 (例えば read(2) など) が停止 (block) なしに実行可能な状態にあることを意味する。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 引き数で、ファイルディスクリプタが ready になるのを待って select() が停止する最小の停止時間を指定する (この停止時間はシステムクロックの粒度に切り上げられ、カーネルのスケジューリング遅延により少しだけ長くなる可能性がある)。 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; /* ナノ秒 */
};
秒単位以下の精度でスリープを実現する移植性の高い方法として、 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
- n が負、または 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() を使いたい場合は、 <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 以降では、 pselect() を使用するには、「書式」に記載された要件を満たす必要がある。
Multithreaded applications
If a file descriptor being monitored by select() is closed in another thread, the result is unspecified. On some UNIX systems, select() unblocks and returns, with an indication that the file descriptor is ready (a subsequent I/O operation will likely fail with an error, unless another the file descriptor reopened between the time select() returned and the I/O operations was performed). On Linux (and some other systems), closing the file descriptor in another thread has no effect on select(). In summary, any application that relies on a particular behavior in this scenario must be considered buggy.Linux での注意
このページで説明している 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), send(2), sigprocmask(2), write(2), epoll(7), time(7)この文書について
この man ページは Linux man-pages プロジェクトのリリース 3.51 の一部である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。2012-08-17 | Linux |