2.7. ファイルシステム

通常ファイルとは一次元のバイト列であり、 任意の場所から読み込み・書き込みが可能です。 カーネルは、ファイルのレコード境界を認識しませんが、 多くのプログラムは 改行 (LF) 文字を行の終りと認識します。 またこれとは異なるファイル構造を利用するアプリケーションもあります。 ファイル自身には、ファイルに関するシステム情報はまったく含まれません。 各ファイルのファイル所有者、許可属性、 使用状況などのいくつかの情報はファイルではなくファイルシステムが保持しています。

ファイル名は最大 255 文字までの文字列です。 ファイル名はディレクトリ と呼ばれる型のファイルに保管されます。 ディレクトリに含まれるファイルの情報はディレクトリエントリと呼ばれ、 ファイル名以外にファイルそのものへのポインタも含みます。 ディレクトリエントリには通常のファイル以外に、 他のディレクトリを参照するエントリが含まれます。 このようにしてディレクトリとファイルによる階層が形作られ、 その階層構造をファイルシステム と呼びます。

Figure 2-2. 小規模なファイルシステム

Figure 2-2は小規模なファイルシステムの一例です。 ディレクトリはサブディレクトリを含むことができ、 入れ子の深さには特に制限はありません。 ファイルシステムの一貫性を保つため、 カーネルはプロセスが直接ディレクトリへ書き込むことを禁止しています。 ファイルシステムには、通常ファイル、ディレクトリ以外に、 デバイスファイルやソケットなどの他のオブジェクトへの参照も含まれます。

ファイルシステムは、 ルートディレクトリ を始点とする木構造を持っています。 ルートディレクトリは、 スラッシュ と呼ばれる場合もあり、斜線文字(/)で表されます。 ルートディレクトリにはファイルが含まれます。 図 2.2 の例では、 usr ディレクトリが含まれ、その usr ディレクトリには、 bin ディレクトリが含まれます。 bin ディレクトリには、通常 lsvi をはじめとする、プログラムの実行可能コードが含まれます。

プロセスは、ファイルの指定をパス名によって行います。 パス名は、0 個以上のファイル名を斜線文字(/)で区切った文字列です。 カーネルはパス名を解釈するため、それぞれのプロセスに 2 つのパス名を関連付けます。 プロセスのルートディレクトリは、 プロセスがアクセスできるファイルシステム上で最も上位の点です。 通常、このルートディレクトリは、 ファイルシステム全体のルートディレクトリに設定されます。 斜線文字 (/) ではじまるパス名は絶対パス名と呼ばれ、 カーネルは、そのパス名がプロセスのルートディレクトリから 開始するものと解釈します。

斜線文字 (/) ではじまらないパス名は相対パス名と呼ばれ、 プロセスのカレント作業ディレクトリを基準とした相対的なパスとして解釈されます (このディレクトリは、短縮して カレントディレクトリ または、 作業ディレクトリ とも呼ばれます)。 カレントディレクトリそのものは、 ドット とも呼ばれ、1 つのピリオド (.) で表されます。 ファイル名 ドットドット (..) は、 ディレクトリの親ディレクトリを表します。 ルートディレクトリの親ディレクトリはルートディレクトリ自身です。

chroot システムコールにより、プロセスのルートディレクトリを、 chdir システムコールにより、カレントディレクトリを変更できます。 chdir はいつでも行えますが、 chroot の実行は、管理者特権を持つプロセスに限られます。 chroot は通常、システムに対するアクセス制限を課すために用いられます。

図 2.2 のファイルシステムにおいて、プロセスのルートディレクトリ がファイルシステムのルートディレクトリで、カレントディレクトリが /usr であったとします。このとき、 vi を参照するには、絶対パスを用いて、 /usr/bin/vi とも書けますし、カレントディレクトリからの相対パスを用いて、 bin/vi とも書けます。

システムのユーティリティやデータベースは、 よく知られたある決まったディレクトリに保存されます。 ファイルシステムの階層構造としてよく知られたものに、 各々のユーザのホームディレクトリがあります。 たとえば、図 2.2 の /usr/staff/mckusick/usr/staff/karels などです。 ユーザがログインすると、 シェルのカレントディレクトリはホームディレクトリに設定されます。 ユーザはホームディレクトリ内で通常ファイルの作成と同様にディレクトリも作成できるため、 複雑な階層構造を構築することも可能です。

ユーザからはファイルシステムが 1 つに見えますが、 システムは 1 つの仮想ファイルシステムが、 実際には異なるデバイス上の複数の物理ファイルシステムから構成されていることを認識しています。 物理ファイルシステムは、異なったデバイスにまたがることはできません。 ほとんどの場合、物理ディスクデバイスは複数の論理デバイスに分割されるため、 1 つの物理デバイス上に複数のファイルシステムを構成することもできます。 すべての絶対パス名を解決できるファイルシステムを ルートファイルシステム と呼び、 常に利用可能な状態になっています。 他のファイルシステムは、マウントすることができます。 マウントとは、ルートファイルシステムのディレクトリ構造の一部として統合する操作です。 ファイルシステムにマウントされたディレクトリの参照は、 そのマウントされたファイルシステムのルートディレクトリの参照へと カーネルによって透過的に変換されます。

link システムコールは、既存のファイル名に、別名を与えます。 リンクが成功すると、 ファイルはどちらのファイル名からでもアクセスできるようになります。 ファイル名は unlink システムコールにより削除できます。 ファイルを参照していた最後の名前が削除されると (さらにファイルを開いていた最後のプロセスがファイルを閉じると) ファイルそのものも削除されます。

ファイルはディレクトリ内で階層構造を持って保持されます。 ディレクトリそのものも一種のファイルですが、 ディレクトリは一般のファイルと異なり、 システムによって決められた構造を持っています。 ディレクトリは一般のファイルと同じくプロセスから読み込むことが可能ですが、 ディレクトリに変更を加えられるのはカーネルだけです。 ディレクトリは mkdir システムコールで作成し、 rmdir システムコールで削除します。 4.2BSD 以前のシステムにおける mkdirrmdir システムコールは、一連の linkunlink システムコールの実行として実装されていました。 明示的にディレクトリの作成、 削除を行うシステムコールを新たに追加した理由は、3 つあります。

  1. アトミックな動作を可能にするため。 link システムコールによる実装の場合と異なり、 システムがクラッシュした場合に ディレクトリの構造が中途半端なままになることがありません。

  2. ネットワークファイルシステムを使用している場合には シリアライズ (操作順序の保証) を行うため、ファイルおよびディレクトリの作成、 削除はアトミックに行われる必要があります。

  3. UNIX 以外のファイルシステム (他のパーティション上の MS-DOS ファイルシステムなど) をサポートする場合、 そのファイルシステムが link システムコールをサポートしない可能性があります。 たとえそれがディレクトリをサポートするファイルシステムであっても、 UNIX ファイルシステムとは異なり、ディレクトリをリンクとして作成、 削除しないものもあります。 そのためそのようなファイルシステムでは、 ディレクトリの作成、削除は、 明示的に要求されない限り行われません。

chown システムコールはファイルの所有者とグループを設定します。 chmod システムコールは、ファイルの保護モードを変更します。 これらのファイルの属性は、 stat システムコールをファイル名に対し実行することで読み出すことができます。 fchownfchmodfstat システムコールは、同様な動作をファイル名ではなくファイル記述子に対して行います。 rename システムコールは、ファイルに新しい名前をつけて古い名前を削除します。 ディレクトリ作成・削除操作と同じように、 rename システムコールはローカルファイルシステムの名前変更動作をアトミックにするため 4.2BSD で追加されました。 後に、この動作はネットワーク上の非 UNIX ファイルシステムに対して名前変更操作を行う場合に有効であることがわかりました。

truncateは、 4.2BSD で追加されたファイルを任意の長さに切り詰めるシステムコールです。 このシステムコール追加の主な目的は ランダムアクセスファイルの最後をプログラムが最後にアクセスした場所に設定する、 という動作を持つ Fortran ランタイムライブラリのサポートでした。 truncate システムコールを使用しない場合、 ファイルの長さを縮める唯一の方法は必要な部分をコピーしたファイルを作成し、 元のファイルを削除した後にコピーしたファイルをリネームする方法です。 このアルゴリズムは遅いだけでなく、 空き容量の少ないファイルシステムでは失敗する可能性があります。

ファイルシステムにファイルを縮める機能が追加されると、 それはカーネルが大きな空のディレクトリを小さくする用途に使用するようになりました。 空のディレクトリを縮小すると、 ファイルの作成、 削除時にカーネルがファイルを検索する時間を短縮できるという利点があります。

新規に作成されたファイルには、 作成したプロセスのユーザ識別子と作成が行われたディレクトリのグループ識別子を与えられます。 ファイルの保護用に 3 レベルのアクセス制御機構が用意されています。 この 3 レベルのファイルアクセス許可は

  1. ファイルを所有しているユーザ

  2. ファイルを所有しているグループ

  3. 他のすべて

に対して設定することができます。 それぞれのアクセスレベルは、さらに 読み取り許可、書き込み許可、実行許可に分けられています。

ファイルは作成時に長さが 0 であり、 書き込みされるにつれて長くなっていきます。 システムはファイルが開かれると、 対応する記述子の現在位置を指定するポインタを保持します。 このポインタはファイル内をランダムアクセスするように動かすことが可能です。 forkdup システムコールによりファイル記述子を共有するプロセス間では、 この現在位置ポインタは共有されます。 別々の open システムコールによって 作成されたファイル記述子は、独立した現在位置ポインタを持ちます。 ファイルはを持つことがあります。 穴とはファイルの一次元構造の中で、 データが一度も書き込まれたことのない空の部分です。 ファイルの最後尾より後にポインタを動かし書き込みを行うことで、 ファイルに穴をつくることができます。 読み込まれた場合、穴は 0 の値をもつバイトとして扱われます。

初期の UNIX システムではファイル名に 14 文字以内という制限があり、 よく問題となっていました。 たとえば、ユーザは当然ながら長く説明的なファイル名を付けたいと望みますし、 basename.extension という慣用的なファイル命名規則を考えると、 extension (C のソースファイルの .c、 中間バイナリオブジェクトファイルの .o というように、ファイルの種類を示す部分) に 1 から 3 文字必要ですから、 basename に付けられる文字数は 10 から 12 しか残っていません。 ソースコード制御システムやエディタは通常、 独自の目的のためにさらに 2 文字をファイル名の前後に付加しますので、 実際に使えるのは、8 から 10 文字になります。 basename として英語を一単語 (たとえば multiplexer) 使うだけで、 簡単に 10 から 12 文字になってしまうでしょう。

このような制限を守るのは不可能ではありませんが、 危険な場合もあります。 他の UNIX システムでは、 より長いファイル名を受け付けるものの実際にファイルを作成する時点でファイル名を 切り詰めるものがあるからです。 C のソースコードファイル multiplexer.c (すでに 13 文字です) のソースコード制御ファイルは、 頭に s. が付加されて s.multiplexer となります。 このファイルは、C ソースの文書の troff ソースファイル multiplexer.ms のソースコード制御ファイルと区別がつきません。 ソースコード制御システムはこの問題に対して警告を出さないため、 これらの 2 つのファイル内容の取り違えは容易に発生します。 注意深くコーディングすればこのような問題は避けられますが、 4.2BSD でロングファイルネームが導入されたことで この問題は実質的になくなりました。