Linux ファイルシステムの徹底調査

階層化構造からの検討

ファイルシステムという点では、Linux® はまさにスイス・アーミー・ナイフのようなオペレーティング・システムです。Linux がサポートするファイルシステムは、ジャーナリング・ファイルシステムからクラスター・ファイルシステム、さらには暗号化ファイルシステムに至るまで多数に及びます。そんな Linux は、標準ファイルシステムと非標準型ファイルシステムのどちらを使用するのにも素晴らしいプラットフォームであるだけでなく、ファイルシステムの開発プラットフォームとしてもうってつけです。この記事では、仮想ファイルシステム・スイッチとしても知られる Linux カーネルの仮想ファイルシステム (VFS: Virtual File System) の詳細を掘り下げ、複数のファイルシステムを 1 つに束ねる主要な構造体をいくつか紹介します。

M. Tim Jones (mtj@mtjones.com), Consultant Engineer, Emulex

M. Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『GNU/Linux Application Programming』や『AI Application Programming』(現在、第 2 版)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。また、コロラド州ロングモン所在のEmulex Corp. の顧問エンジニアでもあります。



2007年 10月 30日

ファイルシステムの基本アーキテクチャー

Linux ファイルシステムのアーキテクチャーは複雑なものを抽象化する興味深い例で、一連の共通 API 関数を使うことで多種多様なストレージ・デバイスで多種多様なファイルシステムをサポートできるようにしています。共通 API 関数の 1 つ、read 関数呼び出しを例に取ると、指定されたファイル記述子から何バイトかを読み取ることが可能なこの関数は、ファイルシステムのタイプが ext3 であるか、NFS であるかといった情報を必要としません。さらにはファイルシステムがマウントされているストレージ・メディアが ATAPI (AT Attachment Packet Interface) ディスク、SAS (Serial-Attached SCSI) ディスク、あるいは SATA (Serial Advanced Technology Attachment) ディスクのどれであるかといった情報も必要としません。それにも関わらず、read 関数がオープン・ファイルに対して呼び出されると期待通りのデータが返されます。この記事ではその仕組みを探るとともに、Linux ファイルシステム層の主要な構造体について調べます。


ファイルシステムとは何か

まずは、ファイルシステムとは何であるかという最も基本的な質問にお答えすることにします。ファイルシステムとは、ストレージ・デバイス上のデータおよびメタデータの編成のことです。このような漠然とした定義だからこそ、ファイルシステムをサポートするために必要なコードが注目に値するということがおわかりでしょう。前述のとおり、ファイルシステムとメディアにはさまざまなタイプがあります。この多様性を考えると、Linux ファイルシステムのインターフェースは階層型アーキテクチャーとして実装され、ユーザー・インターフェース層、ファイルシステム実装、そしてストレージ・デバイスを操作するドライバーをそれぞれ分離するということが想像できるはずです。

プロトコルとしてのファイルシステム

ファイルシステムはプロトコルとして捉えることもできます。ネットワーク・プロトコル (IP など) がインターネットを行き来するデータ・ストリームに意味を持たせるのと同じく、ファイルシステムは特定のストレージ・メディアに存在するデータに意味を持たせます。

マウント

Linux では、ファイルシステムをストレージ・デバイスに関連付けるプロセスのことをマウントと呼びます。mount コマンドを使用して、ファイルシステムを現行のファイルシステム階層 (ルート) に接続します。マウントの際には、ファイルシステムのタイプ、ファイルシステム、そしてマウント・ポイントを指定しなければなりません。

Linux ファイルシステム層の機能 (およびマウントの使用法) を説明するため、これから現行ファイルシステム内のファイルにファイルシステムを作成します。最初に必要な作業は、dd を使って特定のサイズのファイルを作成することです (/dev/zero をデータ・ソースとしてファイルをコピーしてください)。つまり、ゼロで初期化したファイルを作成します (リスト 1 を参照)。

リスト 1. 初期化したファイルの作成
$ dd if=/dev/zero of=file.img bs=1k count=10000
10000+0 records in
10000+0 records out
$

これで、10MB の file.img というファイルが作成されます。losetup コマンドを使用して、このファイルにループ・デバイスを関連付けてください (このファイルをファイルシステム内の通常のファイルとしてではなく、ファイルをブロック・デバイスのように見せるためです)。

$ losetup /dev/loop0 file.img
$

ファイルがブロック・デバイスとして表示される (/dev/loop0 として表される) ようになったので、今度は mke2fs を使ってデバイス上にファイルシステムを作成します。このコマンドは、定義されたサイズで 2 つ目の ext2 ファイルシステムを新規に作成します (リスト 2 を参照)。

リスト 2. ループ・デバイスを使用した ext2 ファイルシステムの作成
$ mke2fs -c /dev/loop0 10000
mke2fs 1.35 (28-Feb-2004)
max_blocks 1024000, rsv_groups = 1250, rsv_gdb = 39
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
2512 inodes, 10000 blocks
500 blocks (5.00%) reserved for the super user
...
$

次に mount コマンドを使って、ループ・デバイス (/dev/loop) で表された file.img ファイルをマウント・ポイント /mnt/point1 にマウントします。ファイルシステムは ext2 として指定することに注意してください。ファイルシステムがマウント・ポイントにマウントされると、ls コマンドを使って、このマウント・ポイントを新規ファイルシステムとして扱えるようになります (リスト 3 を参照)。

リスト 3. マウント・ポイントの作成およびループ・デバイスによるファイルシステムのマウント
$ mkdir /mnt/point1
$ mount -t ext2 /dev/loop0 /mnt/point1
$ ls /mnt/point1
lost+found
$

リスト 4 に示すように、新しくマウントしたファイルシステム内に新しいファイルを作成し、そのファイルをループ・デバイスに関連付けて、そこに別のファイルシステムを作成することで、上記のプロセスを続行することができます。

リスト 4. ループ・ファイルシステム内での新規ループ・ファイルシステムの作成
$ dd if=/dev/zero of=/mnt/point1/file.img bs=1k count=1000
1000+0 records in
1000+0 records out
$ losetup /dev/loop1 /mnt/point1/file.img
$ mke2fs -c /dev/loop1 1000
mke2fs 1.35 (28-Feb-2004)
max_blocks 1024000, rsv_groups = 125, rsv_gdb = 3
Filesystem label=
...
$ mkdir /mnt/point2
$ mount -t ext2 /dev/loop1 /mnt/point2
$ ls /mnt/point2
lost+found
$ ls /mnt/point1
file.img lost+found
$

Linux ファイルシステム (およびループ・デバイス) がいかに強力なファイルシステムになり得るかは、この簡単なデモを見れば一目瞭然です。この手法を使えば、ファイル上にループ・デバイスを使用した暗号化ファイルシステムを作成することもできます。そうしておけば、必要なときにループ・デバイスを使って一時的にファイルをマウントするという方法でデータを保護できるため便利です。


ファイルシステムのアーキテクチャー

ファイルシステムを構成する方法を実演したところで、ここからは Linux ファイルシステム層のアーキテクチャーに話を戻します。この記事では Linux ファイルシステムを 2 つの視点から検討します。最初に検討するのは、アーキテクチャーの全体像です。続いてその内部に踏み込み、ファイルシステムを実装する主要な構造体という点からファイルシステム層を検討していきます。


アーキテクチャーの全体像

ファイルシステム・コードの大部分はカーネル内にありますが (ユーザー空間のファイルシステムの場合は別です。これについては後で説明します)、図 1 のアーキテクチャーにユーザー空間 (User space) とカーネル空間 (Kernel space) の両空間における、主要なファイルシステム関連コンポーネント間の関係を示します。

図 1. Linux ファイルシステム・コンポーネントのアーキテクチャー図
Linux ファイルシステム・コンポーネントのアーキテクチャー図

アプリケーション (User Applications)(ファイルシステムのユーザーなど) および GNU C ライブラリー (GNU C Library)(glibc) が含まれるユーザー空間は、ファイルシステム・コール (オープン、読み取り、書き込み、クローズ) のためのユーザー・インターフェースを提供します。そのシステム・コール・インターフェース (System call interface) はスイッチとして機能し、システム・コールをユーザー空間からカーネル空間内の適切なエンドポイントに送ります。

VFS (Virtual file system) は、その下にあるファイルシステムへの主インターフェースです。このコンポーネントが一連のインターフェースをエクスポートし、互いに振る舞いが大きく異なる個別のファイルシステムに抽象化します。ファイルシステム・オブジェクト (i ノードと d エントリー) には、この後説明するように 2 つのキャッシュがあり、いずれも最近使用されたファイルシステム・オブジェクトのプールとなります。

個別のファイルシステム (Individual file systems) 実装 (ext2、JFS など) のそれぞれが、VFS で使用 (および想定) する共通のインターフェース一式をエクスポートします。これらのファイルシステムとそれぞれが操作対象とするブロック・デバイス間の要求は、バッファー・キャッシュ (Buffer cache) によってバッファーに入れられます。例えば、下位のデバイス・ドライバーへの読み取り要求と書き込み要求はこのバッファー・キャッシュを介して移動するわけですが、このように要求を (物理デバイスまで戻すのではなく) キャッシュに入れることによってアクセスを高速化します。バッファー・キャッシュは一連の LRU (Least Recently Used: 最長未使用期間) リストとして管理されるため、sync コマンドを使用すれば、バッファー・キャッシュをストレージ・メディアにフラッシュできることに注意してください (すべての未書き込みデータを強制的にデバイス・ドライバー (Device drivers)、続いてストレージ・デバイスに送ります)。

ブロック・デバイスとは何か

ブロック・デバイスとは、データの入出力をブロック単位 (ディスク・セクター単位など) で行うデバイスのことで、バッファリングやランダム・アクセス動作 (ブロックを連続して読み取る必要がなく、任意のブロックに随時アクセス可能) などの属性をサポートします。ブロック・デバイスにはハード・ディスク、CD-ROM、RAM ディスクなどがあります。一方、キャラクター・デバイスは物理的にアドレス指定できるメディアを持たないという点でブロック・デバイスとは対照的です。キャラクター・デバイスにはシリアル・ポートやテープ・デバイスなどがあり、これらのデバイスでは 1 文字単位でデータの入出力が行われます。

以上が、20,000 フィートの上空から眺めた VFS とファイルシステム・コンポーネントの全体像です。次に、このサブシステムを実装する主要な構造体に目を向けてみましょう。

主要な構造体

Linux では、すべてのファイルシステムを共通オブジェクトのセットという観点から捉えます。これらのオブジェクトとは、具体的にはスーパーブロック、i ノード、d エントリー、そしてファイルのことです。スーパーブロックは各ファイルシステムのルートに位置し、ファイルシステムの状態を記述して管理します。ファイルシステム (ファイルまたはディレクトリー) 内で管理されるそれぞれのオブジェクトは、Linux では i ノードとして表されます。i ノードに含まれるのは、ファイルシステムに含まれるオブジェクト (ファイルシステムで実行可能な操作も含む) を管理するすべてのメタデータです。構造体のもう 1 つのセットは、名前と i ノードとの変換に使用される d エントリーで、最近使用されたものについてはディレクトリー・キャッシュに保持されます。d エントリーはさらに、ファイルシステムのトラバースのためにディレクトリーとファイル間の関係も管理します。そして最後のオブジェクト、VFS ファイルはオープン・ファイルを表します (書き込みオフセットなど、オープン・ファイルの状態を保持します)。

仮想ファイルシステム層

VFS はファイルシステム・インターフェースのルート・レベルとして機能します。VFS は現在サポートしているファイルシステムだけでなく、現在マウントされているファイルシステムも追跡します。

Linux に対するファイルシステムの追加と削除は、一連の登録関数によって動的に行われます。カーネルが保持する現在サポートしているファイルシステムのリストは、ユーザー空間から /proc ファイルシステムを介して表示することができます。この仮想ファイルはファイルシステムに現在関連付けられているデバイスも示します。新しいファイルシステムを Linux に追加する場合に呼び出されるのは register_filesystem です。この関数は、ファイルシステムの名前や、一連の属性、2 つのスーパーブロック関数などを定義するファイルシステム構造体 (file_system_type) への参照を定義する単一の引数を取ります。ファイルシステムは、登録解除することもできます。

新しいファイルシステムを登録すると、そのファイルシステムと関連情報が file_systems リストに配置されます (図 2 および linux/include/linux/mount を参照)。このリストが、サポート可能なファイルシステムを定義します。リストを表示するには、コマンドラインに cat /proc/filesystems と入力してください。

図 2. カーネルに登録されたファイルシステム
図 2. カーネルに登録されたファイルシステム

VFS で管理しているもう 1 つの構造体はマウント済みファイルシステムです (図 3 を参照)。この構造体から、現在マウントされているファイルシステムがわかります (linux/include/linux/fs.h を参照)。マウント済みファイルシステムの構造体は、次に説明するスーパーブロック構造体にリンクしています。

図 3. マウント済みファイルシステム・リスト
図 3. マウント済みファイルシステム・リスト

スーパーブロック

スーパーブロックはファイルシステムを表す構造体です。スーパーブロックには動作中にファイルシステムを管理するために必要な情報として、ファイルシステムの名前 (ext2 など)、ファイルシステムのサイズとその状態、ブロック・デバイスへの参照、メタデータ情報 (空きリストなど) が含まれます。スーパーブロックは通常、ストレージ・メディアに保管されていますが、存在しない場合にはすぐに作成することができます。スーパーブロック構造体 (図 4 を参照) は ./linux/include/linux/fs.h に記載されています。

図 4. スーパーブロック構造体と i ノードの操作
図 4. スーパーブロック構造体と i ノードの操作

スーパーブロックのなかで重要な要素は、スーパーブロックの操作の定義です。この構造体はファイルシステム内の i ノードを管理するための関数のセットを定義します。例えば、alloc_inodedestroy_inode ではそれぞれ、i ノードを追加、削除することができます。また、read_inodewrite_inode で i ノードの読み取りと書き込みを行ったり、sync_fs でファイルシステムを同期させることもできます。スーパーブロック構造体 (図 4 を参照) は ./linux/include/linux/fs.h に記載されています。各ファイルシステムには独自の i ノード構造があり、その中でi ノードの操作が実装され、VFS 層に対して共通の抽象化が行われます。

i ノードと d エントリー

i ノードはファイルシステム内のオブジェクトを固有の ID で表します。個々のファイルシステムは、ファイル名を固有の i ノード ID に変換し、さらに i ノード参照に変換するためのメソッドを提供します。図 5 に i ノード構造体の抜粋と i ノード構造体に関連するいくつかの構造体を示します。ここでは、関連する構造体のうち、特に inode_operationsfile_operations に注目します。これらの構造体はそれぞれ i ノードで実行される可能性のある個別の操作を参照するもので、例えば inode_operations は i ノードに直接作用する操作を定義し、file_operations はファイルとディレクトリーに関連するメソッド (標準システム・コール) を参照します。

図 5. i ノード構造体およびそれに関連付けられた操作
図 5. i ノード構造体およびそれに関連付けられた操作

最近使用された i ノードと d エントリーはそれぞれ i ノード・キャッシュとディレクトリー・キャッシュに保持されます。ディレクトリー・キャッシュには、i ノード・キャッシュに含まれる i ノードごとに対応する d エントリーがあることに注意してください。i ノードおよび d エントリー構造体は ./linux/include/linux/fs.h に定義されています。

バッファー・キャッシュ

個別のファイルシステム実装 (./linux/fs に記載) は別として、ファイルシステム層の最下層にはバッファー・キャッシュがあります。この要素は、個別のファイルシステム実装および物理デバイスから (デバイス・ドライバーを介して) 行われた読み取り要求と書き込み要求を追跡します。Linux では効率性を高めるためにすべての要求に対し、要求のキャッシュを維持し、要求が行われるたびに物理デバイスに戻らなくても済むようにしています。最近使用されたバッファー (ページ) はバッファー・キャッシュに入れられるため、個別のファイルシステムに素早く戻すことができます。


注目すべきファイルシステム

この記事では Linux 内で利用可能な個別のファイルシステムについては詳細を説明しませんでしたが、ざっと触れておくだけでもその価値はあります。Linux がサポートするファイル・システムは、MINIX、MS-DOS、ext2 といった古いファイルシステムから、ext3、JFS、ReiserFS のような新しいジャーナリング・ファイルシステムに至るまで多種多様です。さらに Linux は CFS などの暗号化ファイルシステムや /proc などの仮想ファイルシステムもサポートします。

もう 1 つ注目に値するファイルシステムとしては、FUSE (Filesystem in Userspace) があります。これは、ファイルシステム要求を VFS を介してユーザー空間にルーティングできるようにする興味深いプロジェクトです。今までに独自のファイルシステムを作成してみたいと考えたことがあるなら、FUSE はそれを実践に移す絶好の手段となります。


まとめ

ファイルシステム実装は決して単純なものではありませんが、スケーラブルで拡張可能なアーキテクチャーの格好の例です。ファイルシステムのアーキテクチャーは長年進化を続けていますが、これまでさまざまなファイルシステムと各種のターゲット・ストレージ・デバイスのサポートに成功してきました。複数レベルの関数間接参照を使用したプラグイン・ベースのアーキテクチャーにより、Linux ファイルシステムは近い将来、興味深い展開を見せてくれることでしょう。

参考文献

学ぶために

製品や技術を入手するために

  • Filesystem in Userspace (FUSE) は、ユーザー空間でのファイルシステムを開発できるようにするカーネル・モジュールです。ファイルシステム・ドライバー実装が VFS からユーザー空間に要求を戻すため、カーネル開発に頼らずにファイルシステム開発を実験するには最適な方法となります。Python に関心がある方も、LUFS-Python を使ってファイルシステムを作成することができます。
  • IBM 製品の評価版をダウンロードして、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を使ってみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=271107
ArticleTitle=Linux ファイルシステムの徹底調査
publish-date=10302007