LinuxのVFSの仕組みを徹底解説|Inode・Dentry・File・File Descriptorの関係

はじめに

Linuxでは、ext4やXFS、tmpfs、NFSなど、さまざまなファイルシステムを利用できます。それぞれデータの保存方法や特徴は異なりますが、アプリケーションは基本的にopen()、read()、write()、close()といった共通のシステムコールでファイルを操作できます。

この共通した操作を実現しているのが、VFS(Virtual File System)です。VFSはアプリケーションと個別のファイルシステムの間に位置し、ファイル操作を抽象化するLinuxカーネル内の仕組みです。

VFSの内部では、Superblock、Inode、Dentry、Fileという主要なオブジェクトが連携しています。これらを理解すると、パス名からファイルが特定される仕組み、File Descriptorが指しているもの、read()やwrite()が実際のファイルシステムへ渡される流れを整理できます。

本記事では、VFSの役割と主要オブジェクトを紹介し、ファイルをopen、read、writeするときにLinuxカーネル内で何が起こるのかを、処理の流れに沿って解説します。

VFS(Virtual File System)とは

VFSの役割

VFSはVirtual File Systemの略で、Linuxカーネル内に存在するファイルシステムの抽象化レイヤーです。アプリケーションから受け取ったファイル操作を共通の形式で処理し、対象を管理する具体的なファイルシステムへ要求を渡します。

VFSが扱うのは、ファイルの読み書きだけではありません。ファイルやディレクトリを開く、作成する、削除する、名前を変更する、属性を取得する、マウントするなど、多くの操作に関わります。

  • ファイルパスを探索する
  • アクセス権限を確認する
  • File Descriptorとカーネル内オブジェクトを対応付ける
  • 個別のファイルシステムへ処理を振り分ける
  • ファイルシステムを共通の名前空間へ統合する

なぜVFSが必要なのか

ファイルシステムによって、ディスク上のデータ構造やブロックの割り当て方法、ジャーナリング、対応できるファイルサイズなどは異なります。もしVFSがなければ、アプリケーションは保存先のファイルシステムごとに異なる操作方法を実装しなければなりません。

VFSはこうした違いを吸収し、統一されたインターフェースを提供します。そのため、アプリケーションは対象がext4でもXFSでも、基本的に同じread()やwrite()を利用できます。また、Linuxカーネルへ新しいファイルシステムを追加するときも、VFSが定めるインターフェースを実装することで既存のアプリケーションから利用できます。

ApplicationとFilesystemの間にある共通インターフェース

Applicationがシステムコールを呼び出すと、処理はUser SpaceからKernel Spaceへ移ります。VFSは要求された操作、対象ファイル、マウント先などを確認し、適切なFilesystemの実装を呼び出します。

VFSとファイルシステムの関係

ext4・XFSなどの違いをVFSが吸収する

ext4とXFSは、どちらもLinuxで広く使用されるファイルシステムですが、内部のデータ構造や領域管理、ジャーナリングなどの実装は異なります。tmpfsは主にメモリ上へデータを保持し、NFSはネットワーク上のサーバへ処理を要求します。

ファイルシステム概要
ext4Linuxで広く利用される汎用的なジャーナリングファイルシステム
XFS大容量ファイルや並列I/Oを考慮して設計されたジャーナリングファイルシステム
tmpfs主にメモリを利用する一時的なファイルシステム
NFSネットワーク経由でリモートのファイルを利用するファイルシステム

これらは動作する場所やデータの保持方法が異なりますが、VFSの共通オブジェクトと操作を実装することで、Linuxの単一のディレクトリツリーへマウントされます。

Applicationから見たファイル操作

Applicationから見ると、ファイルシステムの違いはほとんど意識する必要がありません。/homeがext4、/dataがXFS、/runがtmpfsであっても、パスを指定してopen()し、取得したFile Descriptorに対してread()やwrite()を実行するという基本操作は同じです。

ただし、性能、最大ファイルサイズ、対応する拡張属性、一貫性の保証、エラー条件など、ファイルシステム固有の違いまで消えるわけではありません。VFSは操作方法を統一しますが、各Filesystemの性質そのものを同一にする仕組みではありません。

VFSから実際のFilesystemへ処理を渡す流れ

各Filesystemは、VFSが定める操作に対応した関数群をカーネルへ登録します。VFSは対象オブジェクトに関連付けられた操作テーブルを参照し、read、write、ディレクトリ検索、属性取得などに対応する関数を呼び出します。

例えば、ext4上のファイルに対する操作であればext4に関連する実装へ、XFS上のファイルであればXFSに関連する実装へ処理が渡されます。共通処理にはカーネルの汎用ヘルパーが使われることもあり、すべてを各Filesystemが独自実装するわけではありません。

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

VFSは、Superblock、Inode、Dentry、Fileなどのオブジェクトを使ってファイルシステムと開かれたファイルを管理します。名前が似た情報を扱うため、何を表すオブジェクトなのかを区別することが重要です。

オブジェクト表すもの主な情報
Superblockマウントされたファイルシステム種類、ブロックサイズ、状態、ルートなど
Inodeファイルシステム内のファイル実体種類、権限、所有者、サイズ、時刻など
Dentryディレクトリ内の名前とInodeの対応名前、親Dentry、関連するInodeなど
Fileプロセスが開いたファイルの状態現在位置、フラグ、操作、関連するパスなど

Superblock

Superblockオブジェクトは、マウントされたファイルシステム全体を表します。ファイルシステムの種類、ブロックサイズ、状態、ルートディレクトリ、使用できる操作など、ファイルシステム単位の情報を保持します。

ディスク上のファイルシステムには永続的なスーパーブロックが存在する場合がありますが、VFSのSuperblockオブジェクトは、カーネルがマウント中のファイルシステムを管理するためのメモリ上の表現です。tmpfsのように物理ディスク上の同等構造を持たないファイルシステムにもVFSのSuperblockオブジェクトがあります。

Inode

Inodeオブジェクトは、ファイルシステム内のファイルやディレクトリの実体を表します。ファイルの種類、アクセス権限、所有者、サイズ、タイムスタンプ、リンク数などを管理し、Filesystem固有の情報や操作にも関連付けられます。

重要なのは、Inode自体が通常のファイル名を表すわけではない点です。複数のハードリンクは異なる名前から同じInodeを参照できます。このため、ファイル名とファイルの実体は別々に管理されます。

Dentry

DentryはDirectory Entryを表し、ディレクトリ階層における名前とInodeの対応を管理します。親Dentryと名前を持つことで、/var/log/app.logのようなパスを構成します。

Dentryは主にメモリ上でパス探索を高速化するためにキャッシュされます。存在する名前だけでなく、存在しない名前の探索結果を表すNegative Dentryが保持されることもあり、同じ存在しないパスを何度もFilesystemへ問い合わせる処理を減らせます。

File

Fileオブジェクトは、プロセスがファイルを開いた状態を表すカーネル内のオブジェクトです。現在のファイルオフセット、open時の状態フラグ、実行できるファイル操作、対象を表すパスなどを保持します。

同じInodeを表すファイルを別々にopenすると、通常は別々のFileオブジェクトが作られ、それぞれが独立したファイルオフセットを持ちます。一方、dup()やfork()によってFile Descriptorが複製または継承された場合は、複数のFile Descriptorが同じFileオブジェクトに相当するOpen File Descriptionを共有し、ファイルオフセットも共有することがあります。

それぞれの役割と関係

Superblockはファイルシステム全体、Inodeはファイルの実体、Dentryは名前とInodeの結び付き、Fileは開かれたファイルの状態を表します。一つのオブジェクトだけですべてを管理するのではなく、役割を分離することで、ハードリンク、複数回のopen、マウント、キャッシュなどを柔軟に扱えます。

readするとVFS内部で何が起こるのか

File DescriptorからFileオブジェクトを参照

Applicationがread()へFile Descriptor、User Spaceのバッファ、読み込みサイズを渡すと、VFSはProcessのFile Descriptor Tableから対応するFileオブジェクトを参照します。

Fileオブジェクトから、対象ファイル、現在のファイルオフセット、open時のアクセスモードなどが分かります。VFSはFile Descriptorが有効か、読み込み可能な状態で開かれているか、指定されたUser Spaceのメモリを利用できるかなどを確認します。

VFSからFilesystemへ処理を渡す

VFSはFileオブジェクトに関連付けられた読み込み処理を通じて、対象のFilesystemやカーネルの汎用読み込み処理へ要求を渡します。FilesystemはInodeやファイルオフセットをもとに、要求された範囲のデータを特定します。

これにより、Applicationのread()は同じ形式のまま、ext4、XFS、tmpfsなど、それぞれに適した実装へ到達します。NFSの場合はネットワーク通信が必要になるなど、その先の処理はFilesystemによって異なります。

writeするとVFS内部で何が起こるのか

write()からVFSへ

Applicationがwrite()へFile Descriptor、書き込むデータのバッファ、サイズを渡すと、処理はKernel Spaceへ移ります。VFSはFile DescriptorからFileオブジェクトを取得し、書き込み可能な状態か、ファイルオフセットやフラグはどうなっているかを確認します。

O_APPENDが設定されている場合はファイル末尾への書き込みとして処理されます。また、アクセス権限、ファイルサイズ制限、読み取り専用Filesystemではないかなど、書き込みを実行できる条件も確認されます。

Filesystemへの処理の受け渡し

VFSは、Fileオブジェクトに関連付けられた書き込み操作を呼び出します。対象のFilesystemは、ファイルサイズ、ブロック割り当て、更新時刻などを管理し、書き込み先に必要な領域を準備します。

ext4やXFSでは、固有の領域管理やジャーナリング処理が関係します。tmpfsであれば主にメモリ上の処理となり、NFSであればサーバとの通信やクライアント側キャッシュの規則が関係します。VFSは共通の入口を提供し、具体的な処理はFilesystemへ委譲します。

Page Cacheへの書き込み

通常のBuffered I/Oでは、User SpaceのバッファからPage Cacheへデータがコピーされます。変更されたページはDirty Pageとして管理され、後からWritebackによってStorageへ書き出されます。

そのため、write()の成功は通常、カーネルが書き込みを受け付けたことを意味しますが、物理Storageへの永続化完了を意味するとは限りません。永続化を要求する場合はfsync()などを使用します。また、Direct I/Oなど、Page Cacheを基本的に迂回する方式では経路が異なります。

File DescriptorとVFSの関係

ProcessごとのFile Descriptor Table

各Processは、File Descriptorを管理するテーブルを持ちます。File Descriptorはこのテーブルのエントリを識別する番号であり、そのエントリからFileオブジェクトが参照されます。

Fileオブジェクト

File Descriptor Tableのエントリは、開かれたファイルを表すFileオブジェクトを参照します。read()やwrite()では、Fileオブジェクトに保存されたファイルオフセットが利用されます。

close()を呼び出すと、File Descriptor Tableのエントリから参照が外れます。ただし、dup()で複製したFile Descriptorや、fork()後の別Processなどが同じFileオブジェクトを参照していれば、最後の参照がなくなるまでオブジェクトは解放されません。

Inodeとの関係

FileオブジェクトはDentryとマウント情報を含むパスを通じて、対象のInodeへ到達できます。Inodeがファイル実体の属性を表すのに対し、Fileオブジェクトは今回開いた状態を表します。

一つのInodeに対して複数のDentryが関連付くことがあります。これはハードリンクにより、複数のファイル名が同じ実体を参照できるためです。また、一つのDentryを通じたファイルが複数回openされ、複数のFileオブジェクトが同じInodeを参照することもあります。

File DescriptorとVFSの関係まとめ

この図は、Applicationが持つFile DescriptorからFilesystemへ到達する関係を簡略化したものです。厳密にはFileはDentryだけでなくマウント情報も含むパスを参照し、Inodeや各種操作を通じてFilesystemの処理へつながります。

VFSによって異なるFilesystemを同じように扱える仕組み

Linuxでは、ext4、XFS、tmpfsなど、さまざまなFilesystemを利用できます。VFSは、これらの違いをApplicationから隠し、共通の方法でファイルを操作できるようにする仕組みです。

Applicationは、Filesystemごとに専用のread()やwrite()を呼び分ける必要はありません。Applicationは共通のシステムコールを呼び出し、VFSを通して対象のFilesystemに対応した処理が実行されます。

VFSオブジェクトとOperations

VFSでは、Filesystemを共通の方法で扱うために、Superblock、Inode、Fileなどのオブジェクトを使用します。

さらに、それぞれのオブジェクトには「どの操作で、どの関数を呼び出すか」を定義するOperationsがあります。

代表的なOperationsは次のとおりです。

Operations対象処理の例
Superblock OperationsFilesystem全体Inodeの管理、Filesystemの状態管理
Inode Operationsファイルやディレクトリ検索、作成、削除、属性変更
File Operations開かれたファイルread、write、mmap、fsync

各Filesystemは、これらのOperationsに対応する関数を設定します。Filesystem固有の関数だけでなく、カーネルが提供する共通処理が利用される場合もあります。

そのためVFS自身が、ext4やXFSなどの具体的な処理方法をすべて知っている必要はありません。

VFSはFile Operationsに設定された読み込み処理を通して、Filesystem固有の処理やカーネルの汎用処理を呼び出します。

ext4やXFSでは通常、Page CacheやBlock Layerなどを通してStorage上のデータへアクセスします。一方、tmpfsはメモリを利用するFilesystemであるため、同じread()でも内部の処理は異なります。

このようにVFSは、Applicationに共通のインターフェースを提供しながら、Operationsを通して各Filesystem固有の処理を呼び出します。

Applicationからは同じread()やwrite()に見えるが、その先ではFilesystemごとに異なる処理が実行されるという点が、VFSの重要な役割です。

VFSの処理を一連の流れで整理する

Applicationがopen()、read()、write()などのSystem Callを呼び出すと、処理はKernel Spaceへ移り、VFSが共通の入口として要求を受け取ります。

open()では、VFSがパスを探索してDentryとInodeを特定し、開かれた状態を表すFileオブジェクトを用意します。そして、ProcessのFile Descriptor Tableへ登録した番号をApplicationへ返します。

read()やwrite()では、File DescriptorからFileオブジェクトを取得し、そこから対象のDentry、Inode、Filesystemへ到達します。VFSはFilesystemが提供する操作を呼び出し、通常のBuffered I/OであればPage Cacheを利用します。

Page Cacheに読み込みデータがない場合や、Dirty PageをStorageへ書き出す場合は、さらにBlock LayerとDevice Driverを経由します。一方、tmpfsやNFSのように、物理的なローカルBlock Deviceへ進まないFilesystemもあります。そのため、この図はローカルのブロックデバイス上にある一般的なFilesystemを中心とした流れです。

まとめ

VFSは、Linuxで異なるファイルシステムを共通の方法で扱うための仕組みです。ext4やXFS、tmpfsなど内部の実装が異なるファイルシステムでも、アプリケーションはopen()、read()、write()などの共通のシステムコールを利用できます。

VFSでは、主にSuperblock、Inode、Dentry、Fileといったオブジェクトを使ってファイルを管理します。File DescriptorはFileオブジェクトを参照し、そこから対象のDentryやInodeへつながります。

また、VFSはOperationsを通して、各ファイルシステムに対応した処理を呼び出します。

大まかな流れは次のように整理できます。

Application → System Call → VFS → File / Dentry / Inode → Operations → Filesystem

VFSが共通のインターフェースを提供することで、アプリケーションはファイルシステムごとの内部実装を意識せずにファイルを操作できます。

コメント