2014/07/24

Linux システムコールのブロック・ノンブロックまとめ

はじめに

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 ソースコード