基礎●●○○○

ファイルシステム

File System

ブロックデバイスの上に「名前付きの階層とファイル」という抽象を築く仕組み。永続化の土台。

概要

ファイルシステムは、ディスクやSSDのような「番号の付いたブロックがひたすら並んでいるだけの記憶装置」の上に、「名前の付いた階層と、ファイルという扱いやすい単位」という抽象を築く仕組みです。私たちが /home/user/report.txt のようなパスを指定してファイルを開いたり保存したりできるのは、この層がブロックの割り当てと帳簿付けを一手に引き受けてくれているからです。

普段はOSの機能として存在を意識しませんが、「フォルダを作る」「ファイルをコピーする」「空き容量を確認する」といった当たり前の操作はすべてファイルシステムの仕事です。ディスクは本来、電源を切っても内容が消えない「永続化」の担い手であり、ファイルシステムはその永続化を人間とプログラムが使える形に翻訳する土台にあたります。

ext4・APFS・NTFS といった名前を聞いたことがあるかもしれません。これらはすべてファイルシステムの実装であり、同じ「名前付き階層」という抽象を提供しながら、内部の帳簿の付け方やクラッシュへの強さで違いを持っています。

なぜ生まれたか

記憶装置は、ハードウェアのレベルでは「0番から順に番号が振られた、固定長のブロックの羅列」でしかありません。この素のままの世界には「ファイル」も「フォルダ」も存在せず、あるのは「何番のブロックに何バイト書く/読む」という操作だけです。もしこの状態でプログラムがデータを保存しようとすると、「自分のデータをどのブロックに置いたか」「どこがまだ空いているか」「他のプログラムと領域がぶつかっていないか」を、すべてのプログラムが自前で管理しなければなりません。プログラムが増えれば台帳の食い違いでデータを上書きし合う事故が避けられません。

そこで、この面倒な帳簿付けを一箇所に集約し、プログラムには「名前」と「階層」という人間にわかりやすいインターフェースだけを見せることにしました。空きブロックの管理も、名前からデータの位置を引く対応表も、すべてファイルシステムが引き受けます。プログラムは「report.txt を開いて 100 バイト読む」と頼むだけでよく、それが物理的にどのブロックに散らばっているかを知る必要がなくなったのです。この抽象と帳簿管理をOSカーネルが担うことで、多数のプログラムが同じディスクを安全に共有できるようになりました。

詳細

inode — ファイルの実体はメタデータにある

多くのファイルシステムで、1つのファイルの実体は「inode(アイノード)」と呼ばれる管理レコードで表されます。inode が持つのはファイルの中身そのものではなく、そのファイルについての情報(メタデータ)です。サイズ・所有者・アクセス権限・作成/更新時刻、そして「実際のデータがディスク上のどのブロックに置かれているか」を指すポインタの一覧です。ファイルの中身は別のデータブロックにあり、inode がそこへの地図の役割を果たします。

ここで見落としがちなのが、ファイル名は inode の中に入っていないという点です。名前を管理しているのはディレクトリ(フォルダ)で、ディレクトリの正体は「名前 → inode番号 の対応表」にすぎません。/home/user/report.txt を開くとき、システムはルートディレクトリから順に、home の対応表を引いて次のディレクトリの inode を得て、その中で user を引き、さらにその中で report.txt を引く、というように一段ずつ名前を inode に変換していきます。この解決の流れを図にすると次のようになります。

パス /home/user/report.txt を解決するルート /home → inode 2home ディレクトリuser → inode 9user ディレクトリreport.txt → inode 42inode 42サイズ・権限・時刻データ位置の地図データブロック #180データブロック #181データブロック #402
パス解決 — ディレクトリという対応表を一段ずつたどり、inode を経てデータブロックに至る

ブロック割り当てと空き管理

新しくデータを書き込むとき、ファイルシステムは「空いているブロックを探して割り当て、inode の地図に書き足す」という作業をします。どのブロックが使用中でどこが空いているかは、ビットマップ(1ブロックにつき1ビットで使用中か否かを記録する表)などで管理されます。ファイルが飛び飛びのブロックに散らばると読み出しが遅くなる「断片化(フラグメンテーション)」が起きるため、なるべく連続した領域を確保しようとする工夫や、書き込みをまとめて配置する設計が実装ごとに凝らされています。

クラッシュ一貫性とジャーナリング

ファイルシステムが最も神経を使うのが、書き込みの途中で電源が落ちたりクラッシュしたりする場面です。たとえば「新しいブロックを割り当てて、inode を更新して、ディレクトリの対応表を書き換える」という複数ステップの操作は、その途中で中断されると、割り当て済みなのにどこからも参照されない迷子のブロックが生まれたり、対応表と実体が食い違ったりします。

これを防ぐのがジャーナリング(journaling)です。実際の書き換えを行う前に、「これからこういう変更をします」という記録(ジャーナル)を専用領域に先に書いておき、その後で本体を更新します。もし途中で落ちても、再起動時にジャーナルを見れば「やりかけていた操作」を最後までやり直す(あるいは無かったことにする)ことができ、中途半端な状態から復旧できます。これは実はデータベースの世界で使われる WAL(Write-Ahead Logging、先行書き込みログ)とまったく同じ発想で、トランザクションの原子性を保証する仕組みや、変更の記録を残すログの考え方と地続きです。「変更する前に、変更の意図を耐久性のある場所に書いておく」という原則は、永続化を扱うあらゆる層で繰り返し現れます。

代表的な実装とデータ構造の接点

Linux で標準的な ext4、Apple の APFS、Windows の NTFS などが代表的な実装です。基本の考え方は共通ですが、スナップショット(ある時点の状態を丸ごと保存する機能)への対応や、大量のファイルを高速に検索するための工夫に違いがあります。とくにディレクトリ内のファイル数が膨大になると、名前を線形に探すのでは遅すぎるため、多くの実装は B木(B-tree)系のデータ構造で名前を索引化しています。これはデータベースのインデックスがディスク上のデータを高速に引くために使うのと同じ構造で、「ディスクという遅くて大きい記憶の上で、少ないアクセス回数で目的にたどり着く」という共通の課題への共通の答えになっています。

コンテナ・クラウドストレージとの対比

現代のインフラでは、ファイルシステムの応用形が随所に現れます。コンテナのイメージは「レイヤ化ファイルシステム」で構成されており、ベースイメージの上に差分だけを重ねていく仕組みで、複数のコンテナが土台を共有しつつ書き込みは自分のレイヤに閉じ込めます。一方、クラウドのオブジェクトストレージ(Amazon S3 など)は、あえて階層やファイル書き換えという抽象を捨て、「キーに対して塊(オブジェクト)を丸ごと出し入れする」という単純なモデルに割り切ることで、桁違いの規模と分散への強さを得ています。ファイル単位で部分更新できる従来のファイルシステムと、更新は丸ごと置き換えと割り切るオブジェクトストレージ — どちらが優れているかではなく、「何を捨てて何を得るか」の設計判断の違いとして眺めると、永続化という土台の奥行きが見えてきます。