Linuxのハードリンクとシンボリックリンクの違いについて

Linuxではiノードという仕組みでファイルの管理をしています(iノードについては前回の記事を見てください)。ルートディレクトリ配下に「aaa」ディレクトリがあり「aaa」ディレクトリの直下に「ccc」ファイルがあったとします。

この場合、Linuxのファイルシステムではiノードを使って以下のように管理しています。

ルートディレクトリのiノード番号は「2」と決められていて、ディレクトリファイルと一般ファイルはデータブロックに置かれています。ディレクトリファイルを簡単に言うと「iノード番号とファイル名のペアを1つのエントリとして登録しておく入れ物」です。ルートディレクトリ(iノード番号「2」)からポインタをたどっていくと「ccc」ファイルまでたどり着けます。

上の図を見てわかるようにLinuxではファイル名をディレクトリファイルで管理しています。これはアプリケーションがログを書き込んでいる最中にログファイル名を変更したり、ログファイルを別の場所に移動しても影響を与えないということです(アプリケーションから見ると、ログファイルをiノード番号で認識している)。システム開発をしているとログローテーションの機能を組み込みますが、Linuxではアプリケーションがログを書き込んでいる最中にログローテンションをしても影響がないということです。

ディレクトリファイルの保護モードも一般ファイルと異なるので書いておきます。Linuxでは「ls -l」コマンドでファイルの保護モードを見ることができます。一般ファイルだと、r:読み出し可、w:書き込み可、x:実行可、となりますがディレクトリファイルだと以下のようになります。

  • r: 許可されていればlsコマンドで表示することができる
  • w: 許可されていれば新しくファイルを作成したり既存のファイルを削除できる
  • x: 許可されていればcdコマンドでそのディレクトリに移動できる

ここで以下のように「bbb」ディレクトリ直下に「ddd」という名前で「ccc」ファイルのリンクを作成した場合を考えてみます( ddd と ccc は名前は違うけれども実体は同じファイルということ)。

まずはハードリンクで作成した場合です。Linuxのコマンドだと「ln ccc ddd」です。

ハードリンクはディレクトリファイルにiノード番号「300」、ファイル名「ddd」のエントリを追加することでリンクを作っています。次にシンボリックリンクで作成した場合です。Linuxのコマンドだと「ln -s ccc ddd」です。

シンボリックリンクでは新たにiノード番号「400」を使用するとともに、ディレクトリファイルにiノード番号「400」、ファイル名「ddd」のエントリを追加することでリンクを作っています。

ハードリンクではiノードを使用せず、シンボリックリンクではiノードを使用するところを実際に確かめてみました。まずはハードリンクの場合です。

IUsedが194890ですが、リンクを作成した後も194890のままです。次にシンボリックリンクの場合です。

IUsedが194887だったのが194888に1つ増えています。逆にIFreeが1681081から1681080に1つ減っています。シンボリックリンクではiノードを消費しているようです。また、ハードリンクだとcccとdddのファイルサイズ、タイムスタンプはいっしょですが、シンボリックリンクだと異なってますね。

Linuxのブートとファイルシステムについて

Linuxではハードディスクをパーティションに分割して利用します。それぞれのパーティションにはアクセスするためのデバイスファイルが割り当てられ、各パーティションはそれぞれ独立したファイルシステムを持ちます。ファイルシステムには ext2、ext3、ext4、XFS などがあります。

第一パーティションは bootファイルシステムです。Linuxを起動すると(電源を入れると)ROM内のBIOSがPOST(Power On Self Test:ハードウェアの種々のチェックや初期化を行うこと)をし、ディスクの先頭にあるブートローダーと呼ばれるプログラムを実行します。この領域はMBR(Master Boot Record)と呼ばれ、bootファイルシステムに属しています。MBRにはブートローダーの他に各パーティションの情報を保持したテーブル(パーティションテーブル)が格納されています。

起動されたブートローダーはパーティションテーブルからOSが置かれているパーティションを検索します。そしてそのパーティションの先頭(ブートブロック)にあるプログラムを実行します。この領域はPBR(Partition Boot Record)と呼ばれます。このプログラムがOSを起動させています。

ただ、BIOSによる起動(MBR方式)では以下の問題があります。

  1. パーティションテーブルに格納できるエントリは4個まで
  2. MBRで扱えるハードディスクの容量は2TBまで

これらを解消するためにUEFI(GPT方式)が導入されました。GPT(GUID Partition Table)では2TBの制限はなくなりパーティションテーブルのエントリも128個まで拡張されています。UEFIでは「EFI System Partition」と呼ばれるパーティション領域を作成し、そこにブートローダーやパーティションテーブルを格納します。マザーボードに組み込まれたファームウェアがEFIシステムパーティションの内容を読み込み、ブートローダーがOSの起動まで行っています。EFIシステムパーティションはFATベースのファイルシステムでフォーマットされています。

LinuxのブートローダーにはGRUBがあります。GRUBはいくつかのコンポーネントで出来ており、bootファイルシステム(EFIシステムパーティション)、および、ブートブロックに格納されています。

ここからはファイルシステムについて書いてみます。

ファイルシステムはブートブロック、スーパーブロック、iノードリスト、データブロックで構成されています。各パーティションのファイルシステムを確認するには「df -T」コマンドで確認できます。ブートブロックにはOSを起動するプログラムが入っています。

スーパーブロックですが、この領域にはファイルシステムを管理する以下のような情報が格納されています。

  • ファイルシステムの大きさ
  • 空きブロックに関する情報
  • iノードに関する情報
  • スーパーブロックが修正されたことを示すフラグなど

2点目の空きブロックに関する情報ですが「空きブロック数」、「使用可能な空きブロックのリスト」、「空きブロックリスト内における次の空きブロックへのポインタ」などがあります。スーパーブロックの詳細を確認したい場合は「dumpe2fs -h デバイスファイル名」のコマンドで確認できます(ただし、root権限が必要)。

3点目のiノードに関する情報ですが、そもそもiノードとは「1つのファイルを管理するのに必要な情報の入れ物」です。この入れ物に関する情報をスーパーブロックで保持しています。具体的には「iノードリストの大きさ(入れ物がいくつあるか)」、「使用可能な空きiノードの数」、「空きiノードのリスト」、「空きiノードリスト内における次の空きiノードへのポインタ」などです。iノードを区別するためにiノードにはiノード番号と呼ばれる番号が割り当てられています。iノードが枯渇するとディスク容量に空きがあったとしてもファイルを作成することはできません。ディスクに空き容量があるのに「空き容量が足りない」旨のメッセージが出た場合は空きiノード数を確認してみてください。iノードに関する情報は「df -i」コマンドで確認できます。ちなみにiノードは「index node」を短縮したものです。

最後のスーパーブロックが修正されたことを示すフラグですが、スーパーブロックの情報はメモリ上で管理されていて定期的にディスクに書き込まれています。システムが正常な手段で停止しなかった場合、スーパーブロックのフラグが立ったままになるので次のシステム起動時にシステムがそのフラグをチェックして不整合を修復するのに使われます。

スーパーブロックの次の領域には、iノードリストがあります。先に説明したようにファイルを管理する入れ物がリスト形式でここに格納されています。この入れ物の中身(すなわち、ファイルを管理するための情報)ですが、以下のものがあります。

  • ファイルの種類(一般ファイル、ディレクトリファイル、デバイスファイル)
  • ファイルへのリンク数
  • ファイルの所有権
  • ファイルのアクセス権
  • 時間(最終アクセス時刻、最終変更時刻、ステータス変更時刻(iノード自体が最後に変更された時刻))
  • バイト単位でのファイルサイズ
  • データブロックへのポインタなど

iノードの中身には「ファイル名」も「ファイルのデータ自体」も含まれていません。ファイル名はディレクトリファイルに格納されており、この仕組みは次回書こうと思います。ファイルのデータはiノードリストの次の領域にあるデータブロックに格納されています。

最後に、、この記事を書くにあたり以下の書籍を参考にさせていただきました。僕がずいぶん前に読んだ本ですが読み返してみました。