ノンブロッキングI/Oについて調べてみました*1。ノンブロッキングI/OはSQLの「SELECT ... FOR UPDATE NOWAIT」のイメージに近いのかなと思いました。通常は待つケースで待たずにエラーが返ってきて、アプリケーション側でエラーハンドリングしてリトライするといった使い方になるようですね。
ポイントは以下の点だと思います。
- 通常、ノンブロッキングI/OはネットワークI/Oに使い、ディスクI/Oには使わない。
- open()にO_NONBLOCKフラグを渡したり、 ioctl(fd, FIONBIO, 1)でノンブロッキングモードにすることができる。
- ノンブロッキングモードでは通常はブロックされるケースで、ブロックされずにerrnoにEAGAINが設定されて返ってくる。
- シングルスレッドで複数の接続を処理できるのでC10K問題を回避できる。
膨大な接続があるが、常に全ての接続がアクティブじゃないし、アクティブでもI/Oできる状態でないこともあるネットワークI/Oにおいて有効な方法ではないかと思いました。
- 作者: Robert Love,ロバートラブ,千住治郎
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/04/16
- メディア: 大型本
- 購入: 5人 クリック: 181回
- この商品を含むブログ (31件) を見る
O_NONBLOCK
可能ならば、ファイルをノンブロッキングモード(nonblocking mode)でオープンします。open()だけではなく、他のI/O操作でもプロセスがブロック(休眠、待ち状態、block、sleep)されることはありません。*2この動作が必要になるのはFIFOだけと言ってよいでしょう。
P.33
2.2.3. ノンブロッキング I/O
読み取れるデータがまだない場合に、read()がブロックされずにリターンし、データがまだないことを判断する必要がある場合もあります。この動作をノンブロッキングI/O(nonblocking I/O)と訕れると言います。))この動作が必要になるのはFIFOだけと言ってよいでしょう。
P.33
2.2.3. ノンブロッキング I/O
読み取れるデータがまだない場合に、read()がブロックされずにリターンし、データがまだないことを判断する必要がある場合もあります。この動作をノンブロッキングI/O(nonblocking I/O)と言います。ノンブロッキングI/Oでは、複数のファイルに対しも、データがまだないという原因ではブロックされずに、I/O を発行できるようになります。
この場合はerrnoがEAGAINになっているか否かが重要になります。前述のようにファイルをノンブロッキングモードでオープンし(open()に O_NONBLOCK フラグを渡した場合。「2.1.1.1 open()のフラグ」を参照)、読み取れるデータがまだ存在しない場合にread()はブロックされずにその場で-1を返し、errnoへはEAGAINを設定します。ノンブロッキングI/Oを使用する場合はEAGAINの確認が必須です。この確認を怠ると、単にデータがまだない場合と重大なエラーを混同してしまう恐れがあります。
P.36
ノンブロッキング書込み
O_NONBLOCK フラグを使用し、ファイルをノンブッキングモードでオープンすると、本来はブロックされるような書き込みの場合に、write()システムコールは-1を返し、errnoへEAGAINを設定します。書き込みは後でやり直すことになります。この状況は通常ファイルの場合では一般的に発生しません。
- 作者: あきみち
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2010/03/03
- メディア: 大型本
- 購入: 1人 クリック: 75回
- この商品を含むブログ (10件) を見る
ソケットは、通常状態では「ブロッキングモード」になっています。ブロッキングモードでは、ソケット入出力システムコールは何らかの処理を行う準備が整うまで制御を返しません。
しかし、シングルスレッドでプログラムを記述したいときや、データが取得可能かを調べるだけのためにシステムコールを使いたい場合もあります。そのような用途のために、ソケットがブロッキングを行わない「ノンブロッキングモード」が用意されています。
一例として、connect()システムコールは通常設定(ブロッキングモード)でブロックします。シングルスレッドでのプログラミングを行っているときに、接続先が応答せずに接続タイムアウト待ちになってしまうと、connect()システムコールでブロックしたままの状態が発生し、プログラム全体が停止する可能性があります。さらにconntect()システムコールのタイムアウトはカーネルが決定するために、ユーザ側から任意の時間にタイムアウトすることができません。
connect()システムコール利用時にブロッキング状態を発生させずに、任意の時間でタイムアウトさせたいプログラムを書くときには、ソケットをノンブロッキングモードにする必要があります。
(中略)
Linux では、ioctl()システムコールにFIONBIOを渡すことにより、ブロッキング/ノンブロッキングの設定をできます。
そういえば、Linux の iostat の出力結果を銀行のATMに例えて説明してみる - ablog を書いたときに、
% git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
でとってきた Linux Kernel 2.6 のソースコードが手元にあるので見よう。
- fs/ioctl.c 468-489 行目
static int ioctl_fionbio(struct file *filp, int __user *argp) { unsigned int flag; int on, error; error = get_user(on, argp); if (error) return error; flag = O_NONBLOCK; #ifdef __sparc__ /* SunOS compatibility item. */ if (O_NONBLOCK != O_NDELAY) flag |= O_NDELAY; #endif spin_lock(&filp->f_lock); if (on) filp->f_flags |= flag; else filp->f_flags &= ~flag; spin_unlock(&filp->f_lock); return error; }
- fs/ioctl.c 546-564 行目
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg) { int error = 0; int __user *argp = (int __user *)arg; struct inode *inode = filp->f_path.dentry->d_inode; switch (cmd) { case FIOCLEX: set_close_on_exec(fd, 1); break; case FIONCLEX: set_close_on_exec(fd, 0); break; case FIONBIO: error = ioctl_fionbio(filp, argp); break;