10. コンピュータはどのようにディスクに情報を保存するのか?

Unix 上のハードディスクには、いろいろな名前がついたディレクトリやファイルが 階層的に並んでいるのをご存知だと思います。通常はそれ以上深く探求する必要は ないのですが、ディスクがクラッシュして内部のファイルをなんとか復活させる 必要が生じた場合には、その水面下で何が起こっているのかを知っていることは 非常に有益になります。残念ながら、ディスクの仕組みについて普段目にしている ファイルのレベルから段々詳しく説明するような分かりやすい方法がないので、 ここでは、ハードウェアレベルの説明から始めて、じょじょに日常的な操作の 方に話をもっていくようにしたいと思います。

10.1. 低レベルでのディスクとファイルシステム構造

ディスクの表面というは、データが保持される場所なわけですが、ここは、 ダーツの的のように幾つかの構成部分に分割されています。同心円状の トラックが何本も走っていて、それらのおのおのが、パイを切り分ける ときのようにセクタに分けられています。ディスクの外周に近いトラックの ほうが中央のスピンドルに近いトラックよりも領域が広いので、外周の トラックは、内周のトラックよりも数多くのセクタに分割されています。 個々のセクタ (もしくは、ディスクブロック (disk block) とも言います) は、同一サイズとなって います。これは、現在の Unix 系 OS では、一般的には、1 バイナリ k (1024 8-bit ワード) となっています。それぞれのディスクブロック には、固有のアドレス、言いかえると ディスクブロック番号(disk block number) が付けられています。

Unix は、ディスクをディスクパーティション (disk partition) へと分割します。個々のパーティションは、連続したディスクブロック から成る一定のディスク領域であり、そうしたパーティションは、 他のパーティションとは全く独立した領域として、ファイルシステムか スワップスペースとして扱われます。パーティションを分割する もともとの理由は、まだディスクのアクセス速度が今よりずっと遅く、 エラーも多かった時代において、ディスククラッシュに対処するため でした。各パーティションの間に境界を設けることによって、 ディスクの一部が、その中にランダムに発生する不良個所によって、 アクセスできなくなったり、破壊されたりする可能性を軽減していた のです。現在、パーティション分割は、より重要性を増しています。すなわち、 (悪意を持った侵入者が重要なシステムファイルを書き換えてしまう ことを防止するために) 特定のパーティションを読み出し専用にしたり、 この文書では触れないような各種の手段によって、ネットワーク越しに 共有可能に設定したりできるからです。最も若い番号が付いたパーティション は、たいてい、ブートパーティション (boot partition) として 特別な扱いを受けます。ブートパーティションとは、起動すべきカーネルを 置くことができるパーティションのことです。

個々のパーティションは、(仮想メモリを実装する ために利用される) スワップスペース (swap space) か、ファイルを保持するために利用される ファイルシステム ( file system) かのどちらかです。スワップスペースのパーティション は、連続した一連のディスクブロックとして扱われます。それとは異なり、 ファイルシステムには、ファイル名をディスクブロックに対応付ける仕組みが 必要です。ファイルは時間の経過とともにサイズが増減したり中身が変わったり するので、ファイルのデータブロックというのは、連続して並んでいる わけではなく、該当するパーティション全体に散らばっていることが よくあります(オペレーティングシステムは、使用中でないディスクブロックを それがパーティション中のどこにあるかには関係なく使うように出来ている からです)。このようにディスクブロックが各所に散らばった場合の 症状は フラグメンテーション (fragmentation) と呼ばれています。

10.2. ファイル名とディレクトリ

各ファイルシステム内では、ファイル名とディスクブロックとの対応関係は、 i-node と呼ばれる構造体 (structure) を媒介にして処理されています。こうした事柄に関する 情報は、ファイルシステムの先頭部分 (小さな番号のディスクブロック群) に 保存されています(実際に最小の番号を持つディスクブロック群は、ここでは説明は しませんが、情報の整理とラベリングの目的で利用されます)。ひとつの i-node が ひとつのファイルの情報を記述するようになっています。(ディレクトリを 含む) ファイルのデータブロックは、i-node よりも上の領域 (大きな番号を もつディスクブロック上)に置かれています。

個々の i-node には、必ずそれが記述するファイルに属するディスク ブロックの番号のリストが含まれています。(これは、実際には小さなファイル の場合にしか当てはならないのですが、例外に関する詳細はここでは 重要性を持っていません。) 注意してほしいのは、i-node には、ファイル名に ついての情報は含まれないということです。

ファイル名は、ディレクトリ構造体 (directory structure) のなかにあります。すなわち、 ディレクトリ構造体が、ファイル名を i-node に対応付けているわけです。 Unix では、ひとつのファイルが正式なファイル名を複数持つことが 可能になっている(これは、ハードリンク (hard links) とも言います) のは、このためです。 それらは複数のディレクトリエントリであり、全部が同一の i-node をポイントしています。

10.3. マウントポイント

最も単純な構成では、Unix ファイルシステムの全体は、たったひとつの ディスクパーティションに格納することができます。こうした構成を 小さな個人用の Unix システム上でご覧になることがあるとしても、 この構成は一般的なものであるとはいえません。むしろ、複数の ディスクパーティションにまたがって構成されるのが普通です。 それゆえ、たとえば、小さなパーティション上にカーネルを置いて、 少し大きめのパーティションに OS のユーティリティを置き、 さらに非常に大きなパーティションをユーザのホームディレクトリに するといった構成が考えられます。

システムの起動直後にアクセスするパーティションは、 ルートパーティション(root partition) だけです。起動の際は、(ほぼ、必ず)ここから起動されるという場所です。 ここには、ファイルシステムのルートディレクトリがあり、他のすべての 起点となる最上位の階層(top node)です。

複数のパーティションを持つファイルシステム全体へのアクセスを可能にするには、 ファイルシステム内にあるそれ以外のパーティションを、このルートパーティション に付け加えていかなければなりません。起動プロセスのだいたい中ほどで、Unix はこうしたルート以外のパーティションをアクセス可能にします。その際、Unix は、そうしたパーティションのおのおのを、ルートパーティションにある ディレクトリ上に マウント(mount) します。

たとえば、/usr というディレクトリが Unix 上にある 場合、おそらくここはマウントポイントであり、インストールされたプログラム のうち、システムの起動に必要のないものを置いているパーティションが マウントされる場所になっているはずです。

10.4. ファイルの問い合わせの仕組み

ここまでの説明で、上部構造から下部構造を眺める準備ができました。読者が ファイルを開くとき (たとえば、/home/esr/WWW/ldp/fundamentals.sgml という名前だとします)、実際の動作は次のようになっています。

カーネルは、まず (ルートパーティションにある) Unix ファイルシステムの ルートディレクトリから検索を開始します。カーネルは、ルートディレクトリで 'home' と呼ばれるディレクトリを探します。たいてい、'home' は、ルート パーティションとは別の、巨大なユーザパーティションのマウントポイント であるので、カーネルはそのパーティションに移動します。そのユーザパーティション の最上層のディレクトリ構造体の中で、カーネルは 'esr' と呼ばれるエントリを 探し、その i-node 番号を抽出します。カーネルがその i-node のある場所に 行くと、それに関連付けられたファイルのデータブロックがディレクトリ構造体に なっていることに気付き、それゆえ次に 'WWW' を探します。そして、その i-node を抽出したら、今度は、その i-node に該当するサブ ディレクトリに行って、'ldp' を探します。これは、さらに別のディレクトリ i-node へとカーネルを導きます。そのディレクトリを開くと、そこで 'fundamentals.sgml' ファイルの i-node 番号を見つけます。この i-node が ディレクトリではなく、そのファイルに関連付けられたディスクブロックの リストを保持しています。

10.5. ファイルの所有者、パーミッション、セキュリティ

プログラムが、事故や他人の悪意ある行為によって、本来アクセスできない はずのデータ領域に侵入するのを防ぐために、Unix には、 パーミッション (permission) という機能があります。この機能はもともと、 昔まだ Unix が主に高価なミニコンピュータとして大勢で共有して利用されて いた頃、同一マシン上の複数のユーザが相互に干渉しないようにすることで、 時分割方式をサポートするために設計されました。

ファイルのパーミッションを理解するには、ログインしたときに何が起こるか?のセクションにあった ユーザとグループの説明を思い出す必要があります。個々のファイルには、 それを所有するユーザとグループとがあります。それらは、当初 そのファイルの作成者のユーザ名やグループ名が付けられますが、 chown (1) や chgrp (1) と いうプログラムを使ってそれらを変更することもできます。

ファイルに関係付けられる基本的なパーミッションには、`read' (そこから データを読み出せるという権限)、`write' (それを変更することができる 権限)、および `execute' (それをプログラムとして実行することが できる権限)というのがあります。そして、個々のファイルは、こうした パーミッションのセットを三つ持っています。ひとつは、ファイル 所有者用に、もうひとつは所有グループに属する全ユーザ用に、 そして三つ目はそれ以外のすべての人用のものです。ユーザがログイン時に 取得する「権限 (privilege)」というのは、ファイルのパーミッションビット が許可している範囲での読み・書き・実行権限となります。すなわち、 ユーザのユーザ ID が、ファイルに付された ID と一致する場合は、 その範囲での権限を取得し、所属グループが一致する場合は、そこでの権限、および 誰もがアクセスできるファイルの場合は、そこで規定された権限を取得 することになります。

これらのパーミッションが相互にどういう働きをしているのか、Unix で どのように表示されるのかを知るために、仮に以下のような Unix システムがある として、そのファイルの一覧を見てみましょう。たとえば、次のように表示 されたとします。

snark:~$ ls -l notes
-rw-r--r--   1 esr      users         2993 Jun 17 11:00 notes

上記は通常のデータファイルです。この表示から分かることは、このファイルは、 ユーザ `esr' が所有するものであり、所有グループ `users' に属するものと して作成されたということです。ここで例として挙げたマシンでは、通常の ユーザはすべてこのグループに属するよう、デフォルトで設定されているの でしょう。タイムシェアリングマシン上でよく見かけるそれ以外のグループ名 には、`stuff' や `admin' 、 `wheel'などがあります(理由はお解りに なると思いますが、個人ユーザ用のワークステーションや PC 上では、 グループはそれほど重要ではありません)。読者の Unix 上では、 デフォルトで違うグループ名が使用されているかもしれません。 おそらく、読者のユーザ ID をもとにして、名前が付けられていることでしょう。

文字列 "-rw-r--r-" というのは、ファイルのパーミッションビットを表して います。先頭のダッシュ(-)は、ディレクトリビットを表示する位置です。 もしこのファイルがディレクトリであったなら、そこには "d" と表示され ます。それ以降の位置については、最初の三つがユーザパーミッション、 次の三つがグループパーミッション、その後の三つがそれ以外の人のための パーミッション(これは、"world" permission とも呼ばれています)です。 このファイルの場合、所有者 "esr" は、ファイルの読み出し・書き込みが でき、"users"グループに属する所有者以外の人はファイルの読み出し、 および全ユーザがファイルの読み出しを出来るようになっています。 これは、通常のデータファイルとして非常に典型的なパーミッションの 設定です。

次に、上記とはかなり異なったパーミッションを設定されたファイルを見てみましょう。 このファイルは、GCC, すなわち、GNU C コンパイラです。

snark:~$ ls -l /usr/bin/gcc
-rwxr-xr-x   3 root     bin         64796 Mar 21 16:41 /usr/bin/gcc

上記ファイルは、"root" というユーザに属し、"bin" というグループ に所属しています。また、書き込み(変更)は "root" しかできませんが、 読み出しと実行は誰もができるようになっています。こうした設定は、 プレインストールされているシステムコマンドによくある所有形態および パーミッション設定です。"bin" とういグループは、Unix 上で システムコマンドをグループ化するのに使われたりします(この名称は 歴史的な名残であり、"binary" の省略形です)。読者の Unix では、 "root" というグループ名が使われているかもしれません(これは、 "root" ユーザというのとはちょっと違います)。

"root" ユーザというのは、ユーザ ID 番号 0 のユーザの慣用的な名称です。 これは、すべての権限設定を超越した、特別かつ特権的なアカウントです。 root でのアクセスは、便利ではありますが、危険でもあります。root でログイン している間にタイプミスをすると、通常のユーザアカウントから同じコマンドを 実行した場合には触ることすらできないような重要なシステムファイルを 破壊してしまうことがあるからです。

root アカウントは非常にパワフルなので、root へのアクセスは慎重にガード しなければなりません。root のパスワードは、システムのセキュリティ情報と しては最重要項目であり、他人のシステムを狙うクラッカーや侵入者が何よりも 取得しようと目論むのが、この root パスワードです。

パスワードは、決して書き留めたりしてはいけません。また、ボーイフレンド やガールフレンド、配偶者の名前のような、簡単に推測が つくようなパスワードを選んだりもしないでください。これは、 驚くほど広く蔓延している悪癖であり、クラッカーにいつまでも 手を貸すようなものです。一般に、辞書に載っているような単語を 選んではいけません。dictionary crackers と呼ばれるプログラムがあり、これは一般的な単語の一覧を使って、 推測でパスワードを探し当てるものです。悪くないテクニックのひとつ として、単語と数字ともうひとつの単語の組み合わせというのがあり ます。"shark6cider" や "jump3joy" といったものです。 これだと、dictionary crack の検索空間が巨大になるので、 見つけるのは難しくなるでしょう。ただ、ここに挙げた例を そのまま使うことはお止めください。クラッカーはこの文書を 読んだあとで、上記のパスワードをすでに検索辞書のなかに 追加しているかもしれないからです。

では、第三のケースを見てみましょう。

snark:~$ ls -ld ~
drwxr-xr-x  89 esr      users          9216 Jun 27 11:29 /home2/esr
snark:~$ 

上記のファイルはディレクトリです("d" という文字がパーミッション の最初の位置にあることに注意をしてください)。これは、esr のみが 書き込み権限を持ち、誰でも読み出しと実行ができるということが 分かると思います。

読み出しのパーミッションがあるので、そのディレクトリ内に何があるのか 表示することが可能になっています。すなわち、そのディレクトリに含まれる ファイルやディレクトリ名の一覧を見ることができるということです。 書き込みパーミッションがあると、そのディレクトリ内にファイルを 作成したり、削除したりすることができます。ディレクトリには、 ファイルとサブディレクトリの名称の一覧が含まれているというのを 思い出すなら、こうしたルールには納得がいくと思います。

ディレクトリの実行パーミッションというのは、そのディレクトリを経由して それ以下の階層にあるファイルやディレクトリを開くことができるという ことを意味しています。実際には、これは、ディレクトリ内の i-node に アクセスするパーミッションを与えています。ディレクトリから 実行権限を全部外してしまうと、そのディレクトリが使えなくなって しまいます。

実行権限は全ての人に与えるが読み出し権限は制限しているディレクトリ というのをときどき御覧になると思います。これは、どのようなユーザでも そのディレクトリ以下にあるファイルやディレクトリに到達することが できるのだが、それらの正確な名前をしっている場合でないとだめだと いうことを意味します(つまり、ディレクトリを一覧表示することが できないわけです)。

あるディレクトリの読み出し・書き込み・実行のパーミッションは、そのディレクトリ 以下にあるファイルやディレクトリのパーミッションとは独立の問題であるという ことを覚えておいてください。特に、ディレクトリの書き込みパーミッション というのは、そこに新規のファイルを作成したり、既存のファイルを削除したり できる権限を意味するものであり、そこにある既存のファイルへの 書き込み権限を自動的に与えるものではありません。

最後に、ログインプログラム自体のパーミッションを見てみましょう。

snark:~$ ls -l /bin/login
-rwsr-xr-x   1 root     bin         20164 Apr 17 12:57 /bin/login

これは、システムコマンドによく見られるパーミッションです。ただ、ここの 's' という、所有者実行 bit (owner-execute bit) の設定というのは例外的 です。これは、'set-user-id' もしくは setuid bit と呼ばれる特別なパーミッションを表しています。

setuid bit というのは、一般に、特殊なプログラムに対して付与される パーミッションです。すなわち、root の権限を、一定の制限付きで一般ユーザにも 与える必要のあるプログラムがそれに該当します。setuid bit が 実行プログラムにセットされた場合、ユーザは、そのプログラムファイルの所有者と 同じ権限を行使できるようになるのですが、他方で、ユーザがその所有者であるか 否かに関らず、そのプログラムはユーザ自身の名で実行されるというものです。

root アカウント自体と同様、setuid されたプログラムも便利ではありますが、 危険です root が所有者である setuid されたプログラムを破壊したり 変更したりできるひとならだれでも、そのプログラムを使って root 権限 を持ったシェルを起動することができるからです。それゆえ、大部分の Unix システムでは、何か書き込むためにそのファイルを開いた時点で、 setuid bit が自動的に無効になるようになっています。Unix セキュリティ に対する攻撃の多くが、setuid プログラムを破壊するために、setuid プログラムのバグを悪用しようとしています。したがって、セキュリティ意識 を持っているシステム管理者は、そうしたプログラムに特に慎重になって いて、出来たばかりの(バグが除去されていない)プログラムはインストール したがりません。

これまでパーミッションについて解説したことの詳細に関して、いくつか 重要なことがあります。すなわち、所有グループとパーミッションは、 そのファイルが作成された際にどのような方法で割り当てられるのか ということです。ユーザは複数のグループのメンバーとなることが できるので、このグループの割り当ては問題ではあるのですが、 そのひとつは、(/etc/passwd ファイル内の そのユーザのエントリで指定されている)ユーザの デフォルトグループ (default group) が割り当てられ、ユーザによって 作成されたファイルは、通常そのグループが所有することになります。

パーミッション bit の先頭 bit の話は、もうすこし複雑です。なんらかの ファイルの作成を伴うプログラムは、通常、そのファイルの初期設定として のパーミッションを指定します。しかし、それらは、 umask と呼ばれるユーザの環境変数によって変更されてしまいます。 umask は、ファイルを作成する際にどのパーミッション bit を無効にするかを指定するものです。最も一般的な値であり、 たいていのシステム上のデフォルトとなっているのは、------w- もしくは、 002 です。これは、誰でも書き込みが出来るという権限を無効にする ものです。これに関する詳しい説明は、読者の shell の man ページ にある umask コマンドに関する文書を御覧ください。

ディレクトリグループの初期設定というのも、やや複雑です。Unix のなかには、 新規ディレクトリのグループには、それを作成したユーザのデフォルトグループ を当てるものがあります(これは、System V 系の慣習です)。それ以外にも、 作成されたディレクトリの親ディレクトリの所有グループが割り当てられる ものもあります(これは、BSD の慣習です)。Linux を含む現在の Unix のなかには、 set-group-ID をそのディレクトリに設定する(chmod g+s)ことによって、 後者のように動作するよう設定できるものがあります。

10.6. 調子が悪いというのは どういうことなのか

以前、ファイルシステムは非常に繊細であるということを漠然といいましたが、 いまの時点では、ファイルに到達するには、ディレクトリと i-node を 参照しながら、どれだけ長いか分からない連鎖を辿っていかなければならない ということが分かっていると思います。では、ハードディスクに不良箇所 が発生した場合、これはどうなるでしょうか?

幸運に恵まれれば、ファイルデータのいくつかをダメにするだけで済みます。 そうでない場合は、ディレクトリ構造や i-node 番号を破壊してしまって、 システム内の特定のサブツリー以下がお釈迦になってしまうかもしれません。 さらに、ファイル構造自体が壊れてしまって、雑多なアクセスが 同一のディスクブロックや i-node に集中してしまったりするかも しれません。そうした不具合は、通常の操作を続けていると、 どんどん広がっていって、最初の不良箇所にあったデータが システム全体にばら撒かれてしまうかもしれません。

幸いにも、この手の偶発事故は、ディスクのハードウェアの信頼性が高まった ことで、ほとんど生じなくなりましたが、それでも、やはり、Unix では、 定期的にファイルシステムの整合性チェックを行なって、何も異常がない ことを確認する必要性があります。現在の Unix は、個々のパーティション に対して、起動時のマウント直前にすばやく整合性のチェックを行うように なっています。何回かに一度は、もう数分時間がかかる完全なチェックを 行なう仕組みになっています。

こうしたことを読んで、Unix は恐ろしく複雑で不具合が起きやすいかのよう に思えたなら、こう考えてください。これらの起動時のチェックは、 通常はよくあるような問題を検出・修正して、それが本当の大惨事に いたるのを防いでいるのです。こうした機能を持たない他のオペ レーティングシステムの場合、確かに起動は高速になりますが、実際に 手動で復旧しなければならなくなった時は、問題がこじれてしまって 深刻な状態になっていることがあります(とはいえ、これは、そもそも Norton Utility かそれに類するツールを持っている場合ですが...)。

最近の Unix の設計上のトレンドのひとつに、 journalling file systems があります。 これは、ディスクとのやり取りを調整することで、常に整合性が 保たれることを保証し、システムが起動するときに復旧が 可能になるようにする仕組みです。これは、起動時の整合性チェックを かなり高速化することができます。