LinuxのファイルシステムとカーネルのVFSについて

ファイルのアクセス権限などについて調べているうちに,ファイルシステムについて興味を持ち,LinuxのファイルシステムとカーネルのVFSの動作について勉強したのでメモしておく。

ファイルシステムとは

通常,データの「読み込み」「書き込み」などの操作を「ファイル」単位で行う。これは,ファイルシステムが記憶媒体(ハードディスクなど)を抽象化し,データ格納の仲介作業を行っているためである。また,比較的サイズの大きなファイルの書き込みも,それほどストレスを感じないで行うことができる。これも,ファイルシステムがキャッシュメモリを効率的に利用し,アクセス速度の遅い二次記憶装置(ハードディスクなど)に対して非同期書き込みを行うことで,レスポンスタイムを向上しているためである。ファイルシステムは,このように大容量の二次記憶装置を効率的に管理し,利用するための手段を提供する基本的なOSのサービスの1つである。

Linuxがサポートするファイルシステム

ファイルシステムは,ハードディスク等の記憶装置にあるデータをファイル名・更新日付などの属性データや,ファイルデータ自体を管理するための仕組みで,OSによって利用されるファイルシステムが異なる。Linuxの場合は,ディストリビューションによって採用されるファイルシステムが異なるが,ext3, ext4が採用される事が多いようだ。

以下,Linuxがサポートするファイルシステムの一部を表に示す。

名前最大ファイルサイズ備考
ext22TBLinuxオペレーティングシステムで広く利用されていたファイルシステムである。初期のextファイルシステムを拡張したもの。
ext316GB~2TBLinuxで主流のファイルシステムである。ext2にジャーナリングシステム機能が追加されたものである。日付範囲は,1901年12月14日から2038年1月18日となる。
ext416TB最大1EBまでのボリュームサイズと,最大16TBまでのファイルサイズをサポートする。日付範囲は,1901年12月14日から2514年4月25日となる。タイムスタンプがナノ秒をサポートしている。
ReiserFS16TB小さなファイルの扱いに向いた,ジャーナリングファイルシステムである。

※ジャーナリングとは,ファイル処理中に何らかの障害が発生した場合に短時間で復旧できるような手がかり(ログ)を残す,ファイル管理手法である。

LinuxにおけるファイルシステムとVFSの関係

Linuxにおけるファイルシステムの仕組みのベースとなる部分は,カーネルのVFS(Virtual File System:仮想ファイルシステム)という抽象化された管理機構にある。ext2,ext3などの個々のファイルシステムは,こうしたベースの機能を使いながら,さらに多様な要求に応えるための方法を提供している。
VFSは,アプリケーションと記憶装置であるメディアの間に位置する。VFSはアプリケーションからのファイル操作に関する要求を受け付け,それを関連するローカルファイルシステムごとのAPIに変換する。ローカルファイルシステムは,さらにドライバを経由して実際の記憶媒体を読み書きする。

vfs_position

ユーザーにとっては,こうしたVFSでのAPIの変換作業は隠ぺいされているため,単に「ディレクトリ」にある「ファイル」にアクセスするというような意識で媒体のデータを操作できる。つまり,VFSはどのアプリケーションからでも,同じような手順でファイル操作が行えるような,単一のインターフェイスを提供している。
VFSによるトランスレーション(変換機能)によって,ユーザーはファイルシステムがext2なのかReiserFSなのかを意識せずに使用できる。例えば,mountコマンドで異なるファイルシステムをシステムのディレクトリツリーの一部として認識させることが可能である。

VFSを構成するオブジェクト

VFSは,統一的なアクセスを提供するために,ファイルシステムの差異を吸収する仕組みを持っている。次に,この仕組みを解説する。解説にあたって,VFSを構成する主なオブジェクトを次の表にまとめた。

file_system_typeローカルのファイルシステムを定義する構造体
inodeiノード。ファイルの管理情報を格納する。ファイル種別ごとに異なる操作管理テーブルを持つ
super_blockスーパーブロック管理用の構造体
fileファイルのオープン状態を管理する
dentryディレクトリエントリ情報を管理する
file_system_typeオブジェクトはローカルで使用されるファイルシステムを指定,inode,super_block,file,dentryなどのオブジェクトは,プロセスとファイルシステムを結び付けたり,ディレクトリやスーパーブロックの管理情報を保持する役割を果たす。

以下より,上記表との関連情報を整理する。

ファイルシステムタイプ(file_system_type)

ローカルのファイルシステムは,システムの起動時にVFSに登録される。このときに「ext2」「ext3」といった「ファイルシステムタイプ」(file_system_type)がVFSに設定される。VFSは,この「ファイルシステムタイプ」の中に設定されている固有の命令をVFSの統一的な命令に置き換える作業を行う。例えば,open()という命令は,ext2_open_file()といったext2固有の命令に変換される。このため,VFSはトランスレータとも呼ばれる。

iノード(inode)

VFSの抽象化の仕組みの基本単位として提供されているのが,「iノード」である。iノードはファイルとディスクの情報を関連付ける役割を果たす,ディスクのインデックスである(iノードはindex nodeの略)。iノードは,それぞれが実際の記録媒体であるディスク情報やブロック情報,位置情報を持っており,このiノードによってディスクのジオメトリ(ディスクの物理的なサイズ・ヘッド・トラック・セクタなどの情報)が一意に識別される。fsckでよく見かける「inodeの整合性が取れなくなったので修正します」というメッセージは,ディスクとノード番号にズレが生じてしまったりした場合に,それを修正する操作を指している。
iノードは通常,ファイルに対して一意(unique)であり,ファイルが存在する限り同じiノードが利用される。iノードの上限はカーネルによって決められており(/proc/sys/fs/inode-max参照),file-max数の3〜4倍となっている。これは,標準入力,標準出力,ソケットの作成の際にもiノードが使用されるためである。ファイルごとにサイズは異なるため,iノードの消費量とディスク使用量は完全には一致しない。小さいファイルを大量に作成するようなシステムでは,iノードを大量に消費する。そのため,iノードが上限に達すると,たとえディスクの使用量が少なくても,新たなファイルが生成できないという事態が生じる。

スーパーブロック(super_block)

「スーパーブロック」は,1つのファイルシステムについて1つだけ存在し,マウントされたローカルのファイルシステムに対する情報を保存している。通常,iノードは書き込みなどの状態が変化すると,その都度現在の状態を示すフラグ(ビット)を更新する。この更新は,効率化のためにディスクにアクセスせずにメモリ上に保持されているスーパーブロックに対して行われる。
これは,信頼性の面で問題がある。例えば,システムをシャットダウンせずにいきなり電源を切ってしまった場合などは,メモリ上のスーパーブロックとディスク上のスーパーブロック情報で不整合が生じる可能性がある。Linuxでは,書き込みが行われたスーパーブロックを定期的にディスクにコピーし,問題が最小限になるような方法を取っている。

ファイルディスクリプタからiノードを操作する(file, dentry)

実際にユーザーがiノードを利用するには,インターフェイスとなるファイルオブジェクト(file object)がディレクトリを管理するオブジェクト(dentry object)を操作し,さらにiノードを管理するという構造で,階層的に抽象化された仕組みで実現されている。

order_inode

例えば,エディタなどのアプリケーションを利用してファイルを開いたり,そのファイルに書き込みを行って保存する。このとき,アプリケーションの実行単位であるプロセスは,ファイルに対してopen(),write(),read()といったシステムコールを発行し,カーネルに対してファイル操作を行うためのサービスを要求する。
カーネルはシステムコールを受け付け,その要求に従ってファイルオブジェクトを生成し,それを図中の「fd」(ファイルディスクリプタ)に関連付ける。ファイルオブジェクトはカーネル操作によって,ディレクトリパス情報を持つdentryオブジェクトに関連付けられ,dentryオブジェクトはiノード(ディスクに具体的に結び付けられているビットマップ情報)に関連付けられる。
新しいiノードが参照位置の情報を得られたら,ファイルオブジェクトをインターフェイスとするアドレス情報をポインタとしてプロセスに返す。プロセスはサービスの要求の返答として,ファイルディスクリプタという形でファイルオブジェクトへのポインタを得る。つまり,ファイルディスクリプタを通じて間接的にiノードを操作するのである。

状態の管理

iノードはユーザーの要求に素早く応じることができるように,カーネルのメモリ内に常時確保され管理されている。このようにカーネルメモリ内に確保されたオブジェクトは,通常「キャッシュ」と呼ばれる。
実際に書き込みや読み込みが行われた場合,このiノードに対して書き込みがなされたことを示すフラグ(ビットマップ)が記される。これを「ダーティビット」と呼ぶ。ダーティビットが付けられたiノードは,書き込みがされている(汚れた)バッファであり,「ディスクに書き込まれる」ためのキュー「DIRTY」リストに繋がれる。
カーネルは,このようなキューを効率化のためにハッシュを先頭とした「双方向リスト」で管理している。キューはそれぞれiノードの状態によってUSED(使用中),UNSED(未使用),DIRTY(書き込み中)の状態で別リストに繋がれる。
さらに,汚れた(DIRTY)リストにつながれたデータは,バッファキャッシュとしてI/O待ちのI/Oリクエストキューに入れられる(図中黄色い部分)。最終的に,このI/O待ちのキューに入れられたiノードは,順次対応するディスクブロックに書き込まれる。書き込みが終了したリストは使用(USED)に戻され,参照が終了したら未使用(UNUSED)に入れられて,適当な時期に解放される。

非同期処理

アプリケーション側で行われた書き込み操作などは,実際にはカーネルのキャッシュに対して行われる。カーネルはユーザーの書き込み要求に対しては,メモリに「書き込んだ」というビットフラグを立ててすぐにリターンを返す。この書き込み要求はキャッシュバッファに蓄えられて,updateデーモンが定期的にディスクに書き込む。
カーネル内部の処理としては,ビットの付いたデータをDIRTYバッファとしてキャッシュに保存し,バックグラウンドでバスの転送速度に合わせてデータディスクに転送する。読み込み時は,ディスクから読み込む場合は非常に時間がかかるので,一度読み込んだものをキャッシュに置いておき,そこから読み込むという方法が取られる。
ここで注意したいのが,キャッシュ(RAM)に置いてあるデータは,揮発性の情報であり電源を遮断すると同時に消えてしまうことである。これを防ぐために,updateデーモンがディスクに定期的にフラッシュするが,ディスクにバッファをフラッシュする前にシステムダウンが発生すると,バッファに蓄えられていたデータを損失してしまう。最悪の場合は,メタデータ(ファイルの管理情報)まで損失し,ファイルシステムの破壊へとつながることもある。

参考
Linuxファイルシステム技術解説(1):VFSとファイルシステムの基礎技術 (1/2) – @IT
ファイルシステム – Wikipedia
第4章 UNIX の基礎知識 4.5. ディスク構成
Linuxキーワード – ジャーナリング とは:ITpro
Linux パーティションにmkfsでファイルシステムを作る