select - システムコールの説明 - Linux コマンド集 一覧表
- 名前
- 書式
- 説明
- 返り値
- エラー
- 例
- 準拠
- 注意
- バージョン
- Linux での注意
- バグ
- 関連項目
名前
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);
#define _XOPEN_SOURCE 600#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);
説明
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
は
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
()を呼び出すことで、前記の競合を避けることができる。)
タイムアウト
これらの関数で使用される時間関連の構造体は、
<sys/time.h>
で以下のように定義されている。
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* マイクロ秒 */
};
struct timespec {
long tv_sec; /* 秒 */
long tv_nsec; /* ナノ秒 */
};
(POSIX.1-2001 での定義については下記の「注意」を参照)
秒単位以下の精度でスリープを実現する
移植性の高い方法として、
3 つの集合全てを空、
n
を 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
-
内部テーブルにメモリを割り当てることができなかった。
例
#include <stdio.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");
return 0;
}
準拠
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>
をインクルードすればよい。
ヘッダファイル
<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 以降では、デフォルトでプロトタイプが提供される。
バージョン
pselect
()はカーネル 2.6.16 で Linux に追加された。
それ以前は、
pselect
()は glibc でエミュレートされていた (「バグ」の章を参照)。
Linux での注意
Linux の
pselect
()システムコールは
timeout
引き数を変更する。
しかし、 glibc のラッパー関数は、システムコールに渡す timeout 引き数
としてローカル変数を使うことでこの動作を隠蔽している。
このため、glibc の
pselect
()関数は timeout 引き数を変更しない。
これが POSIX.1-2001 が要求している動作である。
バグ
glibc 2.0 では、
sigmask
引き数を取らないバージョンの
pselect
()が提供されていた。
バージョン 2.1 以降の glibc では、
pselect
()は
sigprocmask
(2) と
select
()を使ってエミュレートされていた。
この実装にはきわどい競合条件において脆弱性が残っている。
この競合条件における問題を防止するために
pselect
()は設計されたのである。
pselect
()がないシステムにおいて、
シグナルの捕捉を信頼性があり (移植性も高い) 方法で行うには、
自己パイプ (self-pipe) という技を使うとよい (シグナルハンドラはパイプへ
1 バイトのデータを書き込み、同じパイプのもう一端をメインプログラムの
select
()で監視するという方法である)。
Linux では、
select
()がソケットファイルディスクリプタで "読み込みの準備ができた" と報告した場合でも、
この後で read を行うと停止 (block) することがある。このような状況は、
例えば、データが到着したが、検査でチェックサム異常が見つかり廃棄された時
などに起こりえる。他にもファイルディスクリプタが準備できたと間違って
報告される状況が起こるかもしれない。
したがって、停止すべきではないソケットに対しては O_NONBLOCK を使うと
より安全であろう。
関連項目
- Linux Tips 関連記事
- Linux Tips(目次)
- Linux ディストリビューション一覧
- rpm のファイル名にあるi386とかi686とは
- 自分のマシンの情報を調べる
- cron の設定
- ssh の root ログインを禁止する
- ssh を、ユーザ、IPでアクセス制限
- 鍵交換方式によるssh接続
- 鍵交換方式によるssh接続( windowsから )
- 複数ファイル内の文字列を置換して上書き保存する
- あるグループをイニシャルグループとするユーザー一覧出力
- 複数ファイルのファイル名を一括変換する
- 連番ファイルをコマンド一発で作成する
- 中身がランダムなファイルを任意のサイズで作成する
- Linux ユーザーアカウントをロック・アンロックする