Provided by: manpages-ja-dev_0.5.0.0.20221215+dfsg-1_all bug

名前

       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) して、入ってくるシグナルを操作し、 望みの sigmaskpselect()  を呼び出すことで、前
       記の競合を避けることができる。)

   タイムアウト
       これらの関数で使用される時間関連の構造体は、 <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_tsuseconds_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/ に書かれている。