Linux にはブロックするシステムコールとノンブロックなシステムコールがあります。さて、システムコールが「ブロックする」とはどういうことでしょうか。よく、ブロックするシステムコールとは「処理が完了するまでプロセスの動作が中断され待たされること」という説明を見ますが、より詳細に、どういう処理の場合に待たされるのか、整理してみましょう。
「ブロックする」とは
Linux において、システムコールがブロックするとは、「プロセスが、システムコール呼び出しの延長で待状態(TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE) に遷移し、CPU時間を消費せずにあるイベントが完了するのを待つようになる」、ことを指します。ブロックするシステムコールのうち代表的なものと、完了待ち対象イベントをまとめると、以下のようになります。
システムコール | 待ち対象イベント |
---|---|
read, write, fsync | ディスク I/O |
read, write, send, recv, accept, connect, select, poll, epoll | FIFO読み書き可能 |
futex, flock | ロック取得 |
nanosleep | タイマ発火 |
待ち対象イベントをそれぞれ解説します。
ディスクI/O
ディスクをバックエンドとするファイルへの読み書きで、ディスクアクセスが生じる場合にはディスクへのI/O発行が完了するまでプロセスは待ち状態になります。例えば、読み込み対象のファイルがページキャッシュに乗っていない場合や、キャッシュを介さないダイレクトI/Oを発行した場合、fsync(2) でメモリ上の内容をディスクと同期させた場合、です。
なお、細かい話ですが、この場合の待ち状態は TASK_UNINTERRUPTIBLE という、シグナルを受け付けない待ち状態です。ディスクI/O以外の待ち対象イベントでは、TASK_INTERRUPTIBLEで待ちます。
キュー読み書き可能
パイプやソケットなど、キュー(FIFO)の構造を持つファイルを読み書きしようとした時に、キューが空で読み取れるデータがない場合と、キューが満杯でこれ以上書き込めない場合には、読み書きできる状態になるまでプロセスは待ち状態になります。キューに新しくデータが到着すると、キューが読み込み可能になります。キューに空きが出来ると、キューは書き込み可能状態になります。
read(2)、write(2)、select(2) については上記の説明で良いのですが、accept(2) は少し状況が違うので補足して説明します。
accept(2) はクライアントからの接続要求が キューにない場合にプロセスが待状態に遷移します。複数のプロセスが同じファイルに対し accept(2) を発行している時にクライアントからの接続要求があった場合、待状態にあるプロセスたちのうち、一つのプロセスのみが起床され、accept(2) 処理を実行します。一つのプロセスのみが起床されるのは所謂 Thundering Herd 問題への対処です。日本語の解説では、id:naoya さんによるprefork サーバーと thundering herd 問題 が詳しいです。
ロック取得
futex(2)は指定したアドレスに対応するキューでプロセスを待状態にしたり(FUTEX_WAIT)、指定したアドレスに対して待ち状態にあるプロセスを起床する(FUTEX_WAKE)同期機構です。
分かりやすくいうと、pthread_mutex_lock(3) などを通してロックを取得しようとしたが、すでに他のプロセスがロックを取得していた場合に、プロセスはFUTEX_WAIT で待ち状態に遷移します。flock(2) も同様です。
タイマ発火
所謂タイムアウトです。指定時間が経過するとプロセスが起床します。
「ノンブロック」とは
ブロックと対をなす概念:ノンブロックについても触れておきます。
ノンブロックなシステムコールとは、ブロックしないシステムコールのことです。システムコールをノンブロックにするには、対象とするファイルにfcntl(2) でノンブロッキングフラグ(O_NONBLOCK)を付与します。ノンブロッキングフラグを付与されたファイルに対して、完了待ち対象イベントが「キュー読み書き可能」なシステムコールを発行すると、キューが読み書き可能でない場合、システムコールは即座に失敗し(return -1)、errno に EAGAIN が設定されます。キューが読み書き可能になるのを待ちはしません。
完了待ち対象イベントが「ディスクI/O」なシステムコールについては、ノンブロックには出来ませんが、その代わり非同期I/Oシステムコール io_submit(2) が用意されています。
まとめ
システムコールがどのような場合にブロックするのか、完了待ち対象イベント別に分けて説明しました。待ち対象イベントには大きくわけて「ディスクI/O」「キュー読み書き可能」「ロック取得」「タイマ発火」があります。「キュー読み書き可能」なシステムコールについては、ノンブロッキングフラグを付与することで、ノンブロックにできます。
ブロックするシステムコールは上記ですべてではありません。他にもちょくちょくありますが、細かいので省略します。
参考文献
各システムコール Man Page
Linux Kernel 3.15 ソースコード