O_DIRECTで遊ぶ

ここ二日ほど、Linuxをインストールしていたのは、O_DIRECTで遊ぶため。たまには、Linuxでご飯を本当に食べているということを見せようかと。たぶん、間違っている可能性があるので、ご指摘いただけるとうれしいです。一応、参考は、Linxuカーネル解読室のP314あたり。

O_DIRECTというのは、Linuxカーネル2.4から(だよね)openシステムコールにたてれるようになったI/Oの直接転送を指定するフラグ。

自分が今の部署にはいって、最初に書くことになったTestプログラムで使ってくれとたのまれて、ちょうど一年前ぐらいにもさわっていたんだけど(昔のブログにはかいたんだけど、今そのブログがない)、最近、仕事の空いた時間で、ファイル経由のI/Oアクセスについて、いろいろ勉強(主にオライリーカーネル本を読んでいる)していて、昔より何やってるいるかわかったきがするので、ちょっと遊んでみました。

通常のI/Oだと、カーネル空間において、ページキャッシュがとられ、writeの場合は遅延書き込み(通常writeはファイルキャッシュに書き込んでダーティーフラグたてたら返ってくる)となり、readは先読みやキャッシュヒットされたりする。

遅延書き込みやら、先読みはいいようにみえるんだけど、やっかいなのはページキャッシュがとられるというところ。たいていの場合は、まぁ問題ないんだけど、RDBのように自分自身でキャッシュなどをもつようなアプリだと、自分でキャッシュをもちつつ、ファイルキャッシュももつとなると、2重でキャッシュを持つことになり無駄となる。というわけで、一部のRDBなんかだと、自分でI/Oの制御をするために、RAWディバイスとよばれる、I/Oを通常のブロック転送ではなく、そのまま転送(ダイレクト転送)するためのディバイスを使うようにするものもある。

I/Oのダイレクト転送を用いると、ブロック転送が行われないために、iostatなどでディバイスに対するI/O要求が本当にwriteなどの数しかでていないことがわかります(通常のブロック転送では、ファイル層がまとめて多重でI/Oを発行しているので、一つのプロセスでwriteしても多重のI/O要求が発行されている)。そのため、ddコマンドで同じブロックサイズでwriteとかを行うと、スピードは落ちたりするんですが、ページキャッシュを使わないため、キャッシュのディスクへの追い出しが行われないというメリットがあります。

Linuxで、RAWディバイスを扱うには、rawというコマンドを用いて扱えるようになります。

他に、I/Oをページキャッシュを経由せずに転送する方法が、openするときにO_DIRECTフラグをたてるということです。rawコマンドを使ってI/Oのダイレクト転送を行う場合、ディバイス一つ(ex hda1など)をまるまる使うことになってしまいますが、O_DIRECTはopenに対するフラグなので、ファイルに対しても指定できます。

O_DIRECTを使う時には、いくつか注意点がありまして、まずは、_GNU_SOURCEをdefineしてあげるということ。以前書いていたんだけど、これをdefineしないとだめ。以前書いた理由は、どうやら間違っているくさくて、

O_DIRECT フラグは SGI IRIX で導入され、 (ユーザーバッファの) アラインメントの制限は Linux 2.4 と同じである。 IRIX には適切な配置とサイズを取得するための fcntl(2) コールがある。 FreeBSD 4.x もこのフラグを導入したが、アラインメントに制限はない。このフラグが Linux でサポートされたのは、カーネルバージョン 2.4.10 である。古い Linux カーネルは、このフラグを単に無視する。このフラグの定義を有効にするには _GNU_SOURCE を定義しておく必要があるかもしれない。

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/open.2.html

ということらしい。

あと注意する点は、writeや、readに渡す、すべてにおいてアライメントをあわせておかないといけないということ。以前、readやwriteするためのバッファーのアドレスだけアライメントされていれば良いと思っていたんだけど、そうじゃなくて、ファイルディスクリプタのseek位置や、read/writeのサイズもアライメントされていないといけない。Linuxの場合、アライメントというのは、ファイルシステムのブロック境界ではなく、ディバイスの最小ブロック境界(通常は512?)でよい。

また、内部での処理の話になるんですが、O_DIRECTでopenされたファイルに対して、writeを行う際は、mmapしているとunmapを行い、read/writeともにそれまでのdirtyなキャッシュの書き出しと書き出しの待ち合わせを行うことになります。

もうちょっと、通常I/Oの転送部分を含め、I/O周りを勉強してみたいと思います。

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

Linuxカーネル2.6解読室

Linuxカーネル2.6解読室