VFS(仮想ファイルシステム)層の抽象化
ext4もNFSもprocも同じopen/readで触れるのはVFSのおかげ。inode・dentry・file・superblockの4オブジェクトとdキャッシュを原理から押さえれば、パス解決の速さとマウントの仕組みが腑に落ちます。
- 1.VFSはファイルシステムの共通インターフェース層。inode(実体のメタデータ)・dentry(名前と親子関係)・file(開いた状態)・superblock(マウント全体)の4オブジェクトで、ext4でもNFSでも同じシステムコールを成立させる。
- 2.パス解決は1コンポーネントずつ dentry を辿る。結果をdキャッシュ(dcache)に載せ、ネガティブエントリ(不在の記録)まで含めて再利用することで、毎回のディスクアクセスを避ける。
- 3.マウントはdentryに別ファイルシステムのrootを接ぎ木する操作で、マウント名前空間ごとにマウントツリーを分けられる。コンテナが独立した/を持てるのはこの連携による。
VFSとは:ファイルシステムの共通インターフェース層
open / read / write / stat といったシステムコールは、相手が ext4 でも、ネットワーク越しの NFS でも、メモリ上の tmpfs でも、/proc のような擬似ファイルシステムでも、まったく同じ形で書けます。この「中身が何であれ同じ操作で触れる」を成立させているのが VFS(Virtual File System、仮想ファイルシステム) です。
VFS は具体的なファイルシステムではなく、それらを束ねる カーネル内の抽象層 です。各ファイルシステムは「open のときはこの関数、read のときはこの関数」という 操作テーブル(関数ポインタの集合) を VFS に登録します。システムコールが来ると VFS が共通の前処理を行い、最終的にそのファイルが属するファイルシステムの関数を呼び出します。オブジェクト指向でいうインターフェースと実装の関係を、C言語の関数ポインタで実現したものと捉えると正確です。
inode->i_op->lookup() のような呼び出しは、同じ lookup でもファイルシステムごとに違う実体を指します。VFS のコードは具体的な実装を知らずに、登録された関数ポインタ経由で各ファイルシステムを駆動します。新しいファイルシステムは、この操作テーブルを埋めるだけで open/read の世界に参加できます。
4つの中核オブジェクト
VFS の世界は、おおむね次の4種類のオブジェクトで構成されます。役割を取り違えると挙動を読み違えるため、まず厳密に区別します。
| オブジェクト | 表すもの | 粒度 | 鍵となる中身 |
|---|---|---|---|
| superblock | マウントされたファイルシステム全体 | 1マウントに1つ | ブロックサイズ・操作テーブル・root dentry |
| inode | ファイルの実体(メタデータ) | 実体1つに1つ | サイズ・権限・所有者・データ位置(名前は持たない) |
| dentry | パス中の名前と親子関係 | パス成分ごと | 名前・親dentry・対応するinode |
| file | プロセスが開いたファイルの状態 | open1回に1つ | 現在のオフセット・アクセスモード・対応dentry |
- superblock:1つのマウント全体を表します。ブロックサイズや空き領域管理、そのファイルシステム固有の操作テーブル、root の dentry などを保持します。
mountのたびに作られます。 - inode:ファイルの 実体 を表すメタデータの入れ物です。サイズ・権限・タイムスタンプ・リンク数・データブロックの位置を持ちますが、ファイル名は持ちません。1つの実体に対し inode は1つで、ハードリンクで複数の名前から共有されます。この「名前と実体の分離」はオンディスクの設計でもあり、ファイルシステム で扱った原則がそのまま VFS に持ち上がっています。
- dentry(ディレクトリエントリ、デントリ):パスの 1成分(名前)と親子関係 を表すメモリ上のオブジェクトです。
/home/alice/xならhome・alice・xそれぞれに dentry があり、親 dentry へのリンクで木構造を成します。各 dentry は対応する inode を指します。dentry はオンディスク構造ではなく、パス解決を高速化するためにカーネルが構築するキャッシュ専用の構造 である点が重要です。 - file:プロセスが
openした結果の 開いている状態 を表します。現在のファイルオフセット、アクセスモード(読み/書き)、対応する dentry を持ちます。openが返すファイルディスクリプタは、プロセスのファイルディスクリプタ表を介してこの file 構造を指します。同じファイルを2回 open すれば inode は共有されますが file は別々で、オフセットも独立します。
fd ──► file(オフセット・モード)──► dentry(名前 "x"・親)──► inode(実体・権限・サイズ)
│
superblock(マウント全体)◄─────────┘
なぜ dentry と inode を分けるのか。理由は 名前と実体が多対一だから です。ハードリンクでは複数の dentry(名前)が同一 inode を指します。逆にパス解決という観点では「名前から次の名前へ」辿る作業が支配的で、ここを inode と切り離して名前専用にキャッシュできると速くなります。これが次の dキャッシュの動機です。
パス解決とdキャッシュ:名前解決を高速化する
/home/alice/x を開くとき、カーネルは 左から1成分ずつ 解決します。root から始め、現在のディレクトリ inode に対して「次の名前を探す(lookup)」を繰り返す、という逐次的な処理です。
パス /home/alice/x の解決(概念):
cur = root の dentry/inode
for name in ["home", "alice", "x"]:
child = dcache を name と cur で引く
if ヒット: cur = child # メモリだけで完了
else: # ミス
child = cur->inode->i_op->lookup(name) # FS実装に問い合わせ(I/Oの可能性)
dcache に child を登録
cur = child
各ステップで、対象ディレクトリの中からその名前のエントリを探す必要があります。これを毎回ファイルシステムの lookup(多くはディスクアクセスを伴う)で行うと、深いパスや頻繁なアクセスで非常に重くなります。そこで VFS は、一度解決した (親dentry, 名前) → 子dentry の対応を dキャッシュ(dentry cache、dcache) にメモリ常駐させ、次回以降は ディスクに触れずに 名前解決を済ませます。
ひとたび /usr/bin/python3 を辿れば、各成分の dentry が dcache に残ります。次に同じパスを開くときは、lookup を1度も呼ばずにメモリ上のリンクを辿るだけで inode に到達できます。サーバーのように同じファイル群を何度も開くワークロードでは、この効果が支配的です。
dキャッシュには、見落としやすいが本質的な仕組みがあります。
- ネガティブ dentry(negative dentry):存在 しない 名前の解決結果もキャッシュします。「このディレクトリに
config.localという名前は無い」という不在の事実を dentry として保持し、次の問い合わせをディスクに行かせず即座に「無い」と答えます。シェルのPATH探索のように、存在しないパスを大量に試すワークロードで効きます。 - 参照カウントと回収:dentry には参照カウントがあり、誰も使っていない dentry は LRU 的に管理されてメモリ逼迫時に回収されます。dentry が inode を参照している間は inode も生かされるため、dcache は inode キャッシュを間接的に保持する役割も担います。
- ハッシュテーブル索引:dcache は
(親dentry, 名前のハッシュ)をキーにしたハッシュテーブルで引かれ、1成分の検索は平均 O(1) です。逐次解決でもステップごとの単価が小さいため、全体が速くなります。
ファイルをリネーム・削除すると、対応する dentry は無効化(unhash)されます。これを怠ると、消えたはずの名前がキャッシュから返ってしまいます。NFS のように 別のクライアントが背後で変更し得る ファイルシステムでは、キャッシュした dentry が古くなる問題があり、d_revalidate で都度有効性を確認します。ローカル FS で速いキャッシュが、ネットワーク FS では整合性のための再検証コストを払う、という非対称があります。
マウントと名前空間:1本の木に別FSを接ぎ木する
VFS のもう1つの役割が、複数のファイルシステムを 単一のディレクトリツリー に統合することです。mount は、ある dentry(マウントポイント)に、別のファイルシステムの root dentry を 接ぎ木 する操作として表現されます。
ここで効くのが dentry と「マウント」の分離です。カーネルは「どの dentry の上に、どの superblock の root が被さっているか」をマウント構造(mount)として別に管理します。パス解決中にマウントポイントの dentry へ到達すると、VFS は 被せられたファイルシステムの root dentry へ乗り換えて 解決を続けます。これにより、/mnt/usb を辿ると自動的に USB 側のファイルシステムへ入っていけます。
解決中にマウントポイントを跨ぐ:
... ─► dentry "/mnt/usb"(ext4側のマウントポイント)
│ ここにvfatのrootが被さっている
▼
vfat の root dentry ─► "photo" ─► ...
このマウントの集合(マウントツリー)は マウント名前空間(mount namespace) ごとに独立して持てます。同じカーネル上のプロセスでも、所属するマウント名前空間が違えば「見えるファイルツリーが違う」状態を作れます。コンテナが、ホストとは別の / を持ち、独自の /usr や /etc を見られるのはこの仕組みによります。VFS が dentry/inode という共通オブジェクトでツリーを構築するからこそ、その ツリーの見え方だけ を名前空間で切り替えられる、という関係です。名前空間そのものの一覧と隔離の原理は Linux名前空間とcgroups を参照してください。
同じファイルシステムの一部を別の場所にも見せる バインドマウント は、既存の dentry サブツリーを別のマウントポイントに接ぎ木する操作です。さらにマウントの伝播(shared/private/slave)を設定すると、ある名前空間でのマウントを他へ波及させるか隔離するかを制御できます。コンテナランタイムは、これらを組み合わせてホストの一部だけをコンテナ内に露出します。
ページキャッシュとの接続
VFS は名前解決とメタデータの層であり、ファイルの 中身 の読み書きはその下のページキャッシュと連携します。各 inode は address_space を持ち、そのファイルの内容ページがメモリのどこにあるかを管理します。read/write は VFS 経由でこの address_space に到達し、ヒットすればメモリから、ミスすればファイルシステムの操作テーブル経由でディスクから読み込みます。mmap でファイルを貼り付けたときに触れるのも、この inode に紐づくページ群です。この層の挙動は ページキャッシュとライトバック と メモリマップトファイル(mmap) で詳説しています。
(1)4オブジェクトの役割分担:superblock=マウント全体、inode=実体メタデータ(名前を持たない)、dentry=名前と親子関係、file=開いた状態(オフセットを持つ)。(2)同じファイルを2回 open すると inode は共有・file は別でオフセットも独立。(3)dキャッシュが (親, 名前)→子 をメモリ保持し、不在も ネガティブ dentry として記録すること。(4)マウントは dentry への接ぎ木で、マウント名前空間ごとにツリーを分けられるからコンテナが独立した / を持てること。この4点が頻出です。
まとめ
- VFS は、ext4・NFS・tmpfs・procfs などを共通の
open/read/writeで扱えるようにする抽象層で、各ファイルシステムが登録した操作テーブル(関数ポインタ)を介して実装を呼び分ける。 - 中核は superblock(マウント全体)・inode(実体メタデータ)・dentry(名前と親子)・file(開いた状態) の4オブジェクト。名前(dentry)と実体(inode)を分けることで、ハードリンクとパス解決の高速化を両立する。
- パス解決は1成分ずつ dentry を辿り、結果を dキャッシュ にメモリ常駐させる。不在も ネガティブ dentry として記録し、毎回のディスク
lookupを避ける。 - マウント は dentry への接ぎ木で、マウント名前空間ごとにツリーを独立に持てる。コンテナが独立した
/を見られるのはこの連携の帰結である。
土台は ファイルシステム、中身の読み書きは ページキャッシュとライトバック と メモリマップトファイル(mmap)、ツリーの隔離は Linux名前空間とcgroups を合わせて読むと、VFS が「どこに位置する抽象か」が立体的に見えてきます。
OS Article
VFS(仮想ファイルシステム)層の抽象化を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
VFS
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
パス解決は1コンポーネントずつ dentry を辿る。結果をdキャッシュ(dcache)に載せ、ネガティブエントリ(不在の記録)まで含めて再利用することで、毎回のディスクアクセスを避ける。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「VFS / dentry」に近いか確認する。
- 強みである「VFSはファイルシステムの共通インターフェース層。inode(実体のメタデータ)・dentry(名前と親子関係)・file(開いた状態)・superblock(マウント全体)の4オブジェクトで、ext4でもNFSでも同じシステムコールを成立させる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。