The Unix and Internet Fundamentals HOWTO Eric Raymond esr@thyrsus.com JF Project - 日本語訳 JF@linux.or.jp Revision History Revision 2.4 12 June 2001 Revised by: esr Where to find more. Revision 1.0 29 October 1998 Revised by: esr Initial revision. この文書では、PC 系のコンピュータや Unix ライクなオペレーティングシステ ム、およびインターネットに関する実用的な基礎知識について、技術的な専門 用語を使わずに解説しています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Table of Contents 1. はじめに 1.1. この文書の目的 1.2. この文書の新バージョン 1.3. フィードバックと訂正 1.4. 関連リソース 2. コンピュータ解剖学入門 3. コンピュータの電源を入れた時に何が起こるのか? 4. ログインしたときに何が起こるのか? 5. シェルからプログラムを起動したとき何が起こるか? 6. 入力デバイスや割り込みはどのように動作しているのか? 7. コンピュータはどうやって複数のことを同時に行うのか? 8. コンピュータはどうやって複数のプロセスが干渉しあわないようにしている のか? 8.1. 仮想メモリ:簡易バージョン 8.2. 仮想メモリ:詳細バージョン 8.3. メモリ管理ユニット (memory management unit) 9. コンピュータは情報をどのようにメモリに保持するのか? 9.1. 数字 9.2. 文字 10. コンピュータはどのようにディスクに情報を保存するのか? 10.1. 低レベルでのディスクとファイルシステム構造 10.2. ファイル名とディレクトリ 10.3. マウントポイント 10.4. ファイルの問い合わせの仕組み 10.5. ファイルの所有者、パーミッション、セキュリティ 10.6. 調子が悪いというのはどういうことなのか 11. コンピュータ言語はどのような仕組みで動いているのか 11.1. コンパイル型の言語 11.2. インタプリタ型言語 11.3. P-code 言語 12. インターネットはどのような仕組みで動いているのか 12.1. 名前と場所 12.2. ドメインネームシステム (domain name system) 12.3. パケットとルータ 12.4. TCP と IP 12.5. HTTP : アプリケーションプロトコルの一例 13. もっと詳しく知りたい人のために 14. 日本語訳について 1. はじめに 1.1. この文書の目的 この文書は、Linux やインターネットを使い倒すことで学んでいるユーザを支 援するためのものです。実践から学ぶというのは、特定のスキルを身につける 方法としては申し分ないのですが、(体系的ではないため)ごく基本的なはずの 知識の各所に穴が生じてしまいがちです。そして、そうした欠落があると、自 分で何かをあらたに考え出したり、トラブルを効果的に解決したりする際に困 難に直面するものです。現実に何が起こっているのかを頭の中で思い描くこと ができないからです。 本書では、Linux とインターネットに関する全体の仕組みについて、明確かつ 平易な言葉で説明するつもりです。本書の解説は、PC 系ハードウェア上で Unix か Linux を使っている人を対象にしています。ただ、ここでは、両者を あわせて単に 'Unix' と呼びます。本書での説明の大部分は、各種プラットフ ォームや様々な種類の Unix に共通するものだからです。 また、本書では、読者が Intel プロセッサを搭載した PC を使っているものと 仮定しています。Alpha プロセッサや PowerPC 、もしくはそれ以外の Unix マ シンを動かしている場合、細かな点に多少違いはありますが、それでも基本的 なコンセプトは同じです。 同じことをくどくど述べることはしないので、注意して読んでください。別の 言い方をすると、重複した説明がないので、どこを読んでも新しい知識を得ら れるということでもあります。最初はざっと読んで、学んだことを消化した後 で、さらに何度か読み返すという方法をおすすめします。 この文書は随時更新されています。ユーザのフィードバックをもとにしてセク ションを追加していくつもりなので、これからもちょくちょく本書に目を通し てもらえればと思います。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2. この文書の新バージョン Unix and Internet Fundamentals HOWTO の新バージョンは、定期的に comp.os.linux.help と news: comp.os.linux.announce および news.answers にポスト されています。また、LDP ホームページを含めた各種の Linux WWW サイトや FTP サイトにもアップロードされています。 この文書の最新バージョンは、World Wide Web 上の次の URL で見ることがで きます。 http://www.linuxdoc.org/HOWTO/ Unix-and-Internet-Fundamentals-HOWTO/index.html 本書には、ポーランド語 の翻訳がありま す。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.3. フィードバックと訂正 この文書に関する質問やコメントがあれば、遠慮なく Eric S. Raymond までメールを送ってください。どのような提案や批判でも 歓迎します。特に、本書での個々のコンセプトをより詳しく説明しているサイ トへのハイパーリンクは大歓迎です。この文書に間違いを見つけた場合は、是 非著者に知らせてください。次のバージョンで訂正します。よろしくお願いし ます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4. 関連リソース ハッキングの方法が知りたくてこの文書を読んでいるなら、ついでに How To Become A Hacker FAQ (山形訳 、中谷訳 ) も読んでください。役に立つ情報 へのリンクが記載されています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. コンピュータ解剖学入門 読者のコンピュータには、プロセッサチップが内蔵されていて、これが実際の 計算処理を実行しています。また、内部メモリも内蔵されています ( DOS/ Windows なひとたちは、''RAM'' と呼びますが、Unix なひとたちは ''コア (core)'' と呼んだりもします。この Unix 用語は、まだ RAM がドーナツ型の フェライトコアで出来ていた頃の名残です)。そして、プロセッサとメモリは、 マザーボード上に搭載されていて、このマザーボードがコンピュータの心臓部 となっています。 コンピュータには、スクリーンとキーボードもついています。また、ハードド ライブやフロッピディスクも内蔵されています。スクリーンとディスクにはコ ントローラカードが必要です。こうしたカードはマザーボードに差し込まれ、 コンピュータによるデバイス制御を補助しています(キーボードは非常にシンプ ルなデバイスなので、専用のカードを必要としません。コントローラはキーボ ードの筐体内に埋め込まれています)。 こうしたデバイスの動作原理の詳細については、後ほど説明します。ここでは まず、これらが互いにどういった仕組みで動作しているのかを簡単に説明しま す。 ケース内に収められたコンピュータのパーツはすべて、バス (bus) によって繋 がっています。物理的にいうと、バスとは、コントローラカードを差し込む部 分です(ビデオカードやディスクコントローラ、場合によってはセカンド(ディ スク)コントローラなど)。いわばバスとは、プロセッサやスクリーン、ディス ク等、すべてのパーツ間でデータを高速にやり取りするための通路です。 ( PC 関連の話題で 'ISA' や 'PCI', 'PCMCIA' といった用語を目にするけれど 、それが何なのかよく分からないというかたがいるかもしれません。これらは 、バスの形式のことです。ISA というのは、おおまかに言うと、1980 年代に IBM が販売していた初期の PC で使用されていたのと同一のバスのことで、現 在ではすたれつつあります。PCI とは、Peripheral Component Interconnection の略であり、現代の PC および Macintosh で使用されている バスです。PCMCIA とは、物理コネクタを小型化した ISA の改良版であり、ラ ップトップで使用されています。) プロセッサはすべての機器を統括するパーツなのですが、これは実際には他の パーツを直接見ることはできず、バスを経由して会話するようになっています 。バス以外に、プロセッサが直接かつ高速にアクセスできるサブシステムは、 メモリ (コア)だけです。それゆえ、プログラムを実行しようとするなら、プロ グラムはコア内(メモリ内)に読み込まれる必要があります。 コンピュータがプログラムやデータをディスクから読み出す場合、実際の動作 としては、まずプロセッサが、バスを使って、ディスクからの読み出しリクエ ストをディスクコントローラに対して送信します。しばらくして、ディスクコ ントローラは、バスを使って、データを読み出してメモリ内のある場所に置い たということをプロセッサに伝えます。それによって、プロセッサは、バスを 使って、そのデータを見ることができるようになるわけです。 キーボードとスクリーンも、バス経由でプロセッサとコミュニケーションを取 っているのですが、その方法はもう少し単純です。これについては後述します 。コンピュータに電源を入れた際に何が起こるかを理解するには、いまのとこ ろ、これで充分です。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. コンピュータの電源を入れた時に何が起こるのか? プログラムが動いていないコンピュータは、役立たずな電子部品のかたまりに すぎません。電源を入れた際、コンピュータがまず最初に行うべきことは、オ ペレーティングシステムと呼ばれる特別なプログラムをスタートさせることで す。オペレーティングシステムの仕事は、コンピュータハードウェアの制御と いう極めて煩雑な処理をやってのけることで、その他のプログラムの仕事を支 援することです。 オペレーティングシステムを起動する処理のことは、ブート (boot) と呼ばれ ています(ブートとは、もともと「編み上げ靴のつまみ革(bootstrap)」のこと であり、靴のつまみ革を引っ張って自分自身を持ち上げる(困難な事柄の例え) という言い方がオペレーティングシステムの起動処理に似ていることから名付 けられました)。ブートの手順はコンピュータチップのひとつである BIOS (Basic Input/Output System の略) に書き込まれているので、(オペレーティ ングシステムが起動する前でも) コンピュータはブート方法を知ることができ るようになっています。 BIOS チップは、コンピュータに対して、ある決まった場所にある特別のプログ ラムを探すように指示します。これは通常、一番若い番号が付けられたハード ディスク (ブートディスク (boot disk)) 上にある、ブートローダ (boot loader) とよばれるプログラムです (Linux 上では、このブートローダは LILO とよばれています)。そして、ブートローダがメモリに読み込まれ、起動されま す。ブートローダの仕事は、オペレーティングシステムそのものを起動するこ とです。 このローダによるオペレーティングシステムの起動というのは、カーネルを探 して、それをメモリにロードし、スタートさせることにより実行されます。読 者が Linux を起動すると、スクリーンに "LILO" という文字と、その後にドッ トがいくつか表示されるのが分かると思います。その時は、ブートローダがカ ーネルをロードしているのです (ひとつのドットの表示は、ディスクブロック (disk block) ひとつ分のカーネルコードがロードされたことを意味していま す)。 (読者は、BIOS がなぜ直接カーネルをロードしないのか、なぜわざわざブート ローダを使って 2 段階の処理をするのかについて不思議に思うかもしれません 。これは、BIOS というのが、あまり賢くないからなのです。実際コイツはとて も頭が悪いので、ブートが終われば Linux は全く BIOS を使いません。BIOS はもともとディスクもあまり積んでいない原始的な 8 ビット PC のために書か れたもので、ディスクにアクセスしてカーネルを直接ロードするような処理は 事実上できないのです。また、Unix では都合の悪いような作業があったとして も、ブートローダという処理過程を介在させることで、ディスク上の別の場所 から異なるオペレーティングシステムを起動させることも可能になります。) いったんカーネルが始動すると、カーネルは周辺装置の検出を行い、ハードウ ェアを残らず認識して、プログラムを実行する準備を整えます。その際、カー ネルは、通常のメモリではなく、I/O ポート (I/O ports) という場所とやりと りをします。I/O ポートとは、特別なバスアドレスであり、デバイスコントロ ーラカードはたいていこのポートを監視しながらカーネルからのコマンドを待 っています。この時、カーネルは、I/O ポートをランダムに探すわけではあり ません。どこを探せば何があるのか、コントローラが存在する場合はどういう 反応があるのかといった情報が、カーネルのなかにあらかじめぎっしりと組み 込まれているからです。このプロセスは、自動検出 (autoprobing) と呼ばれて います。 起動時に表示されるメッセージのほとんどは、カーネルが I/O ポート経由でハ ードウェアを自動検出しながら、どういった周辺機器が利用可能であるのかを 認識し、そのマシンの動作環境に適合していく過程で表示されるものです。 Linux カーネルは、この機能が秀逸であり、他の大部分の Unix よりも優れて いて、 DOS や Windows よりもずっと優秀です。実際、多くの Linux 古参ユー ザの考えるところによると、(インストールを比較的容易にする)この起動時の 自動検出機能の出来の良さによって、Linux はフリーな Unix を作るという一 連の実験プロジェクトから抜け出て、膨大な数のユーザを一気に引きつけられ るようになったのだと言われています。 しかし、カーネルを完全にロードして実行することだけで、ブートプロセスが 終了するわけではありません。これはまだ、第一段階にすぎません(この段階は 、ランレベル 1 (run level 1) ともよばれています)。第一段階が済むと、カ ーネルは、'init' と呼ばれる特別なプロセスに制御を渡し、この 'init' プロ セスが各種の管理プロセスを立ち上げます。 通常、この init プロセスは、最初の仕事として、まずディスクが正常かどう かの確認を行います。ディスクファイルシステムは繊細にできているので、も しハードウェアの故障や突然の電源遮断などでダメージを受けていた場合は、 Unix が完全に立ち上がってしまう前に、ファイルシステム修復の作業が必要で す。これについては、後ほど、ファイルシステムが壊れるというのはどういう ことかの章で説明します。 init の次の仕事は、プリントスプーラ (print spooler)やメールサーバ、 WWW サーバといった、いくつかのデーモン (daemon) を起動することです。このデ ーモンというのは、バックグラウンドで地道に動きながら、仕事が与えられる のを待っているプログラムのことです。こうした特別のプログラムは、競合す る複数のリクエストを上手に調整しつつ処理をしなければならないことがよく あります。そうしたプログラムがデーモンプログラムであるのは、たいていそ の方がプログラムを書きやすいからです。ひとつのリクエストをひとつのプロ グラムが処理することにして、同じプログラムを同時に実行しながら、相互に 絶対に干渉しあわないように工夫するよりも、ひとつのプログラムだけが常時 動いていて、すべてのリクエストを処理するようにしたほうが簡単だからです 。どういった種類のデーモンが起動するのかはシステムによって異なりますが 、たいていの場合、少なくともプリントスプーラ (プリンタの門番をしている デーモン)は起動されていることと思われます。 さらに次の仕事は、ユーザのための準備作業です。init は、 getty とよばれ るプログラムをスタートさせて、コンソールを監視します(もしくは、複数の getty を起動して、ダイヤルイン用のシリアルポートも監視することがありま す)。このプログラムは、コンソール上に login プロンプトを表示するもので す。すべてのデーモンが起動し、ターミナル毎に getty プロセスがスタートし たら、この時点でランレベル 2 (run level 2) に入ったことになります。ログ インやプログラムの実行は、このランレベルにおいて、可能になります。 とはいえ、まだすべてが完了したわけではありません。次の仕事は、ネットワ ーキング等のサービスをサポートする各種デーモンを起動することです。そし て、それらが終了すると、ランレベル 3 (run level 3) に入り、システムが完 全に使える状態になります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. ログインしたときに何が起こるのか? ログインするというのは、(getty にある名前を教えることで) 自分が誰なのか をコンピュータに認識させることです。その際、 (分かり易い名前の) login と呼ばれるプログラムが起動され、これがパスワードを受け取り、そのマシン を使う権限を持ったユーザであるかどうかの認証を行います。もし使う権限が なければ、ログインしようとしても拒否されます。権限があれば、login プロ グラムはいくつかの前提処理を済ましたうえで、シェル (shell) というコマン ドインタープリタ (command interpreter) をスタートさせます。(もちろん、 getty と login をひとつのプログラムとすることも可能ではあります。両者が 別々に分かれているのは、歴史的な理由からであり、ここでは特に取り上げる ようなものではありません。) ここでは、シェルが表示されるまでに、システム上で起こる事柄についてもう 少し詳しく説明します(後ほど、ファイルシステムのパーミッションについて解 説する際に、この章の知識が必要になります)。ユーザは、ログイン名とパスワ ードによって同定されます。ログイン名は、/etc/passwd というファイルを見 て、確認されます。このファイルは、一行ごとにそれぞれのユーザアカウント の情報が記述されたファイルです。 行ごとに複数あるフィールドのうち、アカウントパスワードの欄は、暗号化さ れています(場合によっては、この暗号化されたフィールドは、 /etc/shadow という別のファイルに保存され、より厳しいパーミッション設定がされること があります。これによって、パスワードのクラッキングをより困難にするので す)。アカウントパスワードとして入力した文字も、まったく同じ方法で暗号化 され、login プログラムにより正当なパスワードかどうかの確認がなされます 。この方法が安全であるといえるのは、平文パスワードを暗号化するのは簡単 でも、その逆は難しいという事実によっています。それゆえ、誰かが暗号化さ れた後のパスワードを見たとしても、それによってそのアカウントを使えるよ うにはなりません。 (逆に、自分のパスワードを忘れてしまったら、それをも う一度確認することもできなくなります。何か別のパスワードに変更する以外 にありません。) ログインに成功したら、利用している自分のアカウントに関連付けられたすべ ての権限を利用できるようになります。また、そのアカウントは、なんらかの グループ (group) に属していると認識される場合もあります。グループとは、 一定の名前を付けられたユーザの集団であり、システム管理者によって設定さ れます。グループには、その個々のメンバーの権限とは別に、グループ独自の 権限を設定することができます。また、ユーザは、複数のグループのメンバー となることもできます。(Unix の権限の仕組みの詳細については、パーミッシ ョンの章をご覧ください。) (注意すべきなのは、通常はユーザ名やグループ名というのは名前で呼んでいま すが、実際には、数字の ID として内部に保存されているということです。 passwd ファイルには、アカウント名とそのユーザ ID との対応関係が書かれて いて、/etc/group ファイルには、グループ名とその数字からなるグループ ID との対応関係が書かれています。アカウント名やグループ名を処理するコマン ドは、この両者の変換を自動的に行っています。) アカウントの情報欄には、そのユーザのホームディレクトリ (home directory) に関する情報も含まれています。ホームディレクトリとは、 Unix ファイルシ ステム上でそのユーザ個人のファイルが保管されている場所のことです。最後 に、アカウントの情報欄には、そのユーザのシェル (shell) に関する情報もあ ります。シェルとは、コマンドインタープリタのことであり、 login プログラ ムがユーザからのコマンドを受け取るために起動するものです。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. シェルからプログラムを起動したとき何が起こるか? シェルとは、ユーザが打ち込んだコマンドを解釈する Unix のインタープリタ です。これがシェルと呼ばれるのは、オペレーティングシステムのカーネルを 包み込んで、これをユーザから隠す働きをしているからです(訳注:木の実の比 喩であり、シェル (shell) とは殻を意味し、中核となる実(ここではカーネル) を包み込んでいることから、その名前があります)。シェルとカーネルとが別々 のプログラムとなっていて、両者が一連のシステムコール経由で通信するとい う仕組みは、Unix の重要な特徴となっています。このように両者を分離するこ とで、様々なシェルプログラムの実装が可能となり、ユーザの好みに合わせて いろいろなインターフェイスを選べるようになっています。 通常のシェルでは、ユーザがログインすると、(特にカスタマイズをしていない 限り) '$' というプロンプトが表示されます。ここではシェルの構文や、画面 を見ていればだいたい分かるような事柄には触れません。むしろ、こうした表 示の背景で何が起こっているのかを、コンピュータの視点から解説しようと思 います。 ブートが終了した後でまだ何らプログラムを実行していない時点におけるコン ピュータというのは、一連のプロセス全体がやるべき仕事を待っている状態で あると考えることができます。あらゆるプロセスがイベント (event) を待って いるわけです。ここでイベントとは、ユーザがキーを押すとかマウスを動かす とかいったことです。あるいは、マシンがネットワークに接続されているなら 、そのネットワーク越しに送られてくるデータパケットの着信などもイベント にあたります。 カーネルもそうしたプロセスのひとつです。ただし、カーネルは特殊なプロセ スでもあります。カーネルプロセス以外のユーザプロセス (user process) を いつ実行すべきか制御を行うのがカーネルプロセスであり、マシンのハードウ ェアに直接アクセスできるのも通常はカーネルプロセスだけだからです。実際 、ユーザプロセスが、キーボードの入力を読み込んだり、スクリーンに何かを 表示したり、ディスクに対して読み書きしたり等、メモリとのやりとり以外の すべての処理をする際には、カーネルに対してリクエストを送らなければなり ません。こうしたリクエストは、システムコール (system call) と呼ばれてい ます。 通常、I/O ポートへのアクセスはすべてカーネルを通じて行われるので、カー ネルはそうした処理をスケジューリングしたり、相互の干渉を防止したりする 機能を持っています。ただ、少数の特別なユーザプロセスは、カーネルを通さ ずに処理する権限を与えられています。たいていの場合、それは I/O ポートへ 直接アクセスできるという権限です。X サーバ (これは、大部分の Unix マシ ン上で、スクリーンへの画像表示に関する他のプログラムからのリクエストを 受け取って、処理しているプログラムのことです) が、その典型的な例です。 しかし、今のところ、まだ X サーバが立ち上がるところまで話が進んでいませ ん。まだ、文字端末上で、シェルプロンプトを見ている状態です。 シェルは、単なるユーザプロセスであり、何ら特別な権限を与えられているわ けではありません。シェルはキーストロークを待っており、(カーネル経由で) キーボードの I/O ポートを監視しています。カーネルはキーストロークを認識 すると、それをそのままスクリーン上に表示します。カーネルは 'Enter" キー が押されたことを認識すると、そのキーが押されるまでに入力されたテキスト 行をシェルに渡します。シェルは、それらのキーストロークをコマンドとして 解釈しようとします。 たとえば、Unix のディレクトリ表示コマンドを起動するために、'ls' という 文字をタイプして、Enter を押したとします。シェルは、あらかじめ組み込ま れているルールに従ってそれを解釈し、ユーザが '/bin/ls' ファイルにある実 行コマンドを起動したがっているのだと理解します。シェルはシステムコール を発して、カーネルに /bin/ls を新規の子プロセス (child process) として 起動するとともに、その子プロセスに対してカーネル経由でスクリーンとキー ボードへのアクセス権限を与えるよう要求します。そして、シェルはスリープ 状態に入り、ls コマンドが終了するのを待ちます。 /bin/ls コマンドが終了する際、コマンドは、 exit システムコールを発行し て、処理が終了したことをカーネルに伝えます。すると、カーネルが、スリー プしていたシェルを目覚めさせて、シェルが実行を再開できるようになったこ とを伝えます。シェルは、再度プロンプトを発し、次の入力がなされるのを待 ちます。 とはいえ、上記の 'ls' が実行されている最中に、他のプロセスを進行させる ことも可能です(非常に長いディレクトリのリストを表示しようとしているとし ましょう)。たとえば、'ls' の実行中に、別の仮想コンソールに切り替えて、 ログインし、ゲームの Quake で遊び始めることができます。あるいは、インタ ーネットに接続している状態だとすれば、 ls の実行中に、読者のマシンがメ ールの送受信をすることも可能です。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6. 入力デバイスや割り込みはどのように動作しているのか? キーボードというのは、非常にシンプルな入力デバイスです。というのも、キ ーボードは、(コンピュータの基準から見ると)ごく少ない量のデータを非常に ゆっくり生成するものだからです。キーを押したり離したりする際、そのイベ ント信号は、キーボードケーブルを伝わって、ハードウェア割り込み (hardware interrupt) を発生させます。 こうした割り込みを監視するのは、オペレーティングシステムの仕事です。あ らゆる種類の割り込みに対処するために、割り込みハンドラ (interrupt handler) というのが必要になります。これは、オペレーティングシステムの一 部であり、(キーストロークやキーリリースといった) 割り込みに関係するデー タのすべてを、それが処理されるまで保持しておくものです。 キーボード用の割り込みハンドラの実際の仕事というのは、キーの値をメモリ の底のほうにあるシステムエリアに格納することです。オペレーティングシス テムが、現在キーボードからの入力待ちと思われるプログラムに対して制御を 渡した際、そのプログラムがそこから値を読み出せるようにするわけです。 ディスクやネットワークカードのようなもっと複雑な入力デバイスの場合でも 動作方法は同じです。以前の章では、ディスクコントローラはバスを使ってデ ィスクリクエストが完了したことを伝えるという話をしました。その場合の実 際の動作というのは、ディスクが割り込みを発生されるということです。その とき、ディスクの割り込みコントローラは、発生した割り込みデータをメモリ 内に一旦コピーして、後からそのリクエストを発したプログラムが使えるよう にするのです。 割り込みには、どういう種類のものであれ、必ず関連する優先レベル (priority level) が付いています。低い優先レベルの割り込み(たとえば、キ ーボードイベント)は、高い優先レベルの割り込み(たとえば、クロックチック (clock tick) やディスクイベント) が処理されるのを待たなければなりません 。 Unix では、マシンの反応をスムーズにするために、迅速な処理を必要とす るイベントに対しては、高い優先順位が与えられるように設計されています。 オペレーティングシステムの起動時のメッセージのなかに、IRQ の数字に関す る表示があるのをご覧になったことがあると思います。また、よくあるハード ウェアの設定ミスのひとつとして、二つのデバイスが同じ IRQ を使うように設 定してしまっていたという事例があることを御存知だと思います。ただ、これ が何故設定ミスなのか、正確な理由は案外知られていません。 それは、つまりこういうことです。IRQ とは、"割り込みリクエスト (Interrupt Request)" の略です。オペレーティングシステムは、起動時に、ど のハードウェアがどの割り込み番号を使うのか知る必要があり、それによって 、適切な割り込みハンドラを個々のハードウェアに関連付けています。もし二 つの異なるデバイスが同一の IRQ を使おうとすると、割り込みが不適切なハン ドラに送られてしまう事態が生じます。その場合、通常は少なくともデバイス が反応しなくなってしまうか、あるいは、OS を混乱させてしまって、OS が固 まってしまうか、クラッシュしてしまうからです。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7. コンピュータはどうやって複数のことを同時に行うのか? 実際には、複数のことを同時に行っているわけではありません。コンピュータ は、その時々でひとつのタスク (もしくはプロセス (process) ) しか処理でき ません。しかし、コンピュータは、複数のタスクを瞬時に切り替えながら処理 できるので、人間の感覚からすると、複数のことを同時に行っているように錯 覚させることができるのです。こうした方法は、タイムシェアリング (timesharing, 時分割方式) と呼ばれています。 タイムシェアリングの管理も、カーネルの仕事のひとつです。カーネルにはス ケジューラ (scheduler) という部分があり、これが(カーネル以外の)全プロセ スに関する情報を内部に保持しています。 60 分の 1 秒ごとに、カーネル内の タイマーが進んで、クロック割り込みを生成します。スケジューラは、現在実 行中のプロセスを停止させて、それを適宜保留したうえで、別のプロセスに制 御を渡します。 60 分の 1 秒というのは、あまり長い時間とは思えないかもしれませんが、今 日のマイクロプロセッサからすると、数万のマシン命令を実行しうるだけの間 隔であり、それだけあれば、かなりの仕事がこなせます。それゆえ、多くのプ ロセスが存在したとしても、それぞれのプロセスは、その割り当て時間内に結 構な仕事量をこなせるわけです。 実際には、プログラムはその割り当て時間を全部使えない場合もあります。I/O デバイスから割り込みがかかると、カーネルは効率よく現在のプロセスを停止 させ、割り込みハンドラを実行して、そのあとで現在のプロセスを再開します 。高い優先順位の割り込みがつぎつぎに起こると、通常の処理がまったく行わ れなくなってしまいます。この異常事態は、スラッシング (thrashing) と呼ば れていていますが、幸なことに、現在の Unix ではめったに起こらなくなって います。 事実、プログラムの実行速度は、プログラムが取得するマシン時間の量によっ て制限されるということはほとんどありません (サウンドや 3-D グラフィック の生成など、いくつかの例外はありますが)。実行の遅延が起こる原因は、たい てい、プログラムがディスクやネットワーク上にあるデータを取得しようとし て待っている間に発生するのであり、ほとんどの原因がこれです。 多くのプロセスを順に同時並行的に処理できるオペレーティングシステムは、 "マルチタスク (multitasking) " OS と呼ばれます。Unix 系のオペレーティン グシステムは、もともとマルチタスク OS として設計されており、それを非常 に得意としています。Windows や Mac OS などよりもずっと効率よく処理でき ます。 Windows や Mac OS は、マルチタスク機能を後から組み込んでいるので 、 Unix に比べるとややおそまつです。この効率のよい、信頼できるマルチタ スク機能は、ネットワーク通信やウェブサービスの分野で Linux を優位に立た せる大きな要因となっています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8. コンピュータはどうやって複数のプロセスが干渉しあわないようにしている のか? カーネルのスケジューラは、プロセスの時間的な割り振りを担当しています。 同時に、オペレーティングシステムは、プロセスを空間的にも割り振って、そ れらが互いの作業メモリ領域に干渉しないようにしなければなりません。すべ てのプログラムが協調して動いてくれると仮定した場合でも、そのどれかひと つにバグがあり、それによって他のプログラムのメモリ領域が破壊されてしま うような事態は望ましくありません。この問題を解決するために、オペレーテ ィングシステムが行う対処方法は、メモリ管理 (memory management) と呼ばれ ています。 コンピュータ内の個々のプロセスには、そのコードを実行したり、変数を保存 したり、処理の結果を格納したりする場所として、独自のメモリ領域が必要で す。こうしたメモリ領域は、(プロセスの命令が保持される)読み出し専用領域 であるコードセグメント (code segment) と、(プロセスのすべての変数が保持 される) 書き込み可能な領域であるデータセグメント (data segment) から構 成されていると考えることができます。データセグメントは、文字どおり個々 のプロセスに固有の領域ですが、二つのプロセスが同一のコードを実行してい る場合、Unix は、メモリの利用効率の観点から、そうしたプロセスが、単一の コードセグメントを共有するよう調整を行います。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1. 仮想メモリ:簡易バージョン メモリは値段が高いので、効率よく利用することが大切です。ときには、マシ ンで実行中のすべてのプログラム全体をメモリに保持するだけの余裕がない場 合が生じます。特に、X サーバのような巨大なプログラムを実行しているよう な場合には、メモリ不足が生じることがあります。この問題に対処するために 、 Unix は、仮想メモリ (virtual memory) と呼ばれるテクニックを使います 。これは、プロセスのすべてのコードとデータをメモリ内に保持しようとする ものではありません。むしろ、比較的少量のワーキングセット (working set) だけを保持するようにして、残りのプロセス状態は、ハードディスク上のスワ ップスペース (swap space) という特別な領域に置いておきます。 注意して欲しいのは、前の段落で「ときには....生じます」と書いた部分は、 過去においては、「ほとんどいつも生じていました」と言い換えることができ るという点です。以前は、実行中のプログラムのサイズと比べてメモリのサイ ズが全然足りなかったので、スワッピングは頻繁に起こっていました。しかし 、メモリは今日ではかなり安価になっていて、ローエンドのマシンにすら、か なりのメモリが積まれるようになっています。64 MB 以上のメモリを積んだ現 在の個人用マシンの場合、X やよく利用するジョブが最初からコアにロードさ れたあとでも、そうしたプロセスをスワッピングなしで実行することが可能に なっています。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.2. 仮想メモリ:詳細バージョン 前章では、実際にちょっと話を単純化しすぎてしまいました。確かに、プログ ラムはメモリを、巨大で平板なアドレス領域であり物理メモリよりも大きなも のである、と認識していて、その幻想を支えるものとしてディスクスワッピン グが利用されているというのは本当です。しかし、現実には、ハードウェアは 異なる五種類ものメモリを持っていて、プログラムが最高速度で実行されるよ うチューニングしなければならない場合は、この五種類のメモリ間での違いは 、非常に重要な問題となるのです。マシン内で何が起こっているのかを本当に 理解するためには、これら全体がどういう仕組みで動いているのかを知らなけ ればなりません。 五種類のメモリとは、次のようなものです:プロセッサのレジスタ、内部(もし くは、オン・チップ)キャッシュ、外部(もしくは、オフ・チップ)キャッシュ、 メインメモリ、およびディスクです。これだけの種類のメモリが存在すること の理由は、単純です。スピードを上げるにはお金がかかるからです。上記五種 類のメモリは、アクセス時間の短い順番、コストの高い順番に並んでいます。 レジスタメモリは、最速かつ最も高価なものであり、一秒間に十億回くらいラ ンダムアクセスが可能ですが、ディスクは最も低速かつ安価であり、一秒間に 百回くらいのアクセスしかできません。 以下に、2000年初頭の典型的なデスクトップマシンにおける各種メモリのスピ ードの一覧表を記載します。スピードと容量は年々上昇し、価格は下がってい きますが、メモリ間でのそれらの比例関係は非常に安定していると考えること ができます。メモリが階層構造を持つのは、そうした比例関係が一定であるか らです。 ディスク Size: 13000MB Accesses: 100KB/sec メインメモリ Size: 256MB Accesses: 100M/sec 外部キャッシュ Size: 512KB Accesses: 250M/sec 内部キャッシュ Size: 32KB Accesses: 500M/sec プロセッサ Size: 28 bytes Accesses: 1000M/sec 最速のメモリだけを使ってすべてを構築することはできません。あまりに高価 なものになりすぎるからです。仮に高価でなかったとしても、高速なメモリは 揮発性です。つまり、電源を切ると、せっかくの成果が失われてしまいます。 したがって、コンピュータはハードディスクやその他の非揮発性のストレージ を内蔵して、電源を切った際にもデータを保持できるようにしなければなりま せん。また、プロセッサの速度とディスクの速度との間には、あまりに大きな 違いがあります。その中間にある三つのレベルのメモリ階層 (内部キャッシュ (internal cache)、外部キャッシュ (external cache) およびメインメモリ) は、基本的に、両者のギャップを埋めるために存在しています。 Linux とその他の Unix には、仮想メモリと呼ばれる機能が備わっています。 仮想メモリとは、オペレーティングシステムが実際に搭載しているメインメモ リ以上のメモリを持っているかのように振舞うということを意味しています。 実際の物理メインメモリは、より大きな「仮想」メモリ空間の窓、もしくはキ ャッシュのように振る舞い、仮想メモリの大部分は実際にはスワップエリアと 呼ばれるディスク上の領域に保持されます。ユーザプログラムからは見えない ところで、OS は、データブロックをメモリとディスクの間で移動させ、この幻 想を維持しています。その結果、仮想メモリは、実メモリよりもずっと大きい が、それほど遅くはないメモリとして機能するわけです。 仮想メモリが物理メモリと比べてどの程度低速になるかというのは、オペレー ティングシステムのスワッピングアルゴリズムが、どれだけプログラムによる 仮想メモリの利用方法に適合したものになっているかということで決まります 。幸なことに、一定の時間間隔で見ると、メモリの読み出しと書き込みは間を 置かずになされることが多いため、場所的に見た場合でも、メモリの読み書き はメモリ空間内の特定の場所に集中するという傾向があります。この傾向は、 ローカリティ (locality) 、もしくはより正式にはリファレンスのローカリテ ィ (locality of reference) と呼ばれています。これは都合のいいことです。 メモリへの参照 (reference) が仮想メモリ空間内の様々な場所にランダムに行 われるなら、通常は、新しい参照のたびごとにディスクに対する読み出しや書 き込みが行われなければならず、仮想メモリはディスクと同じくらい低速にな ってしまうでしょう。しかし、プログラムというのは一定の場所で読み書きを 行うという強い傾向 (locality) を示すものなので、メモリへの参照がある場 合でも、オペレーティングシステムはスワップを行うことが比較的すくなくて 済みます。 これは、経験則なのですが、最大公約数的にみて最も効率のよいメモリ利用パ ターンというのは非常にシンプルなものです。その方法は、LRU もしくは最長 時間未使用アルゴリズム ("least recently used" algorithm) と呼ばれていま す。仮想メモリシステムは、必要に応じて、ディスクブロックをメモリのワー キングセット (working set) として取り込みます。ワーキングセット用の物理 メモリが足りなくなったら、最長時間未使用のブロックをディスクに書き出し てしまいます。すべての Unix や、仮想メモリを使うその他のオペレーティン グシステムの大部分は、この LRU にいくらかの変更を加えたアルゴリズムを使 っています。 仮想メモリは、ディスクとプロセッサのスピードの違いを調整する第一の連環 となっています。これは、OS が明示的に管理しています。しかし、物理メモリ のスピードと、プロセッサがそのレジスタメモリにアクセスするスピードとの 間には、まだ大きなギャップがあります。外部と内部のキャッシュは、これを 埋めるものであり、そのために上記で述べた仮想メモリとよく似たテクニック を使っています。 物理メインメモリがディスクスワップ領域に対する一連の窓やキャッシュのよ うに振る舞っているように、外部キャッシュもメインメモリに対する窓のよう に振る舞います。外部キャッシュは、高速 (100M よりも速い秒間 250M アクセ ス)で、容量の小さいメモリです。ハードウェア (特に、コンピュータのメモリ コントローラ) は、LRU の方法を使って、メインメモリから取ってきた一連の データをもとにして、外部キャッシュ内のデータを管理します。歴史的な理由 で、キャッシュスワッピングの単位は、ページ (page) ではなくライン (line) と呼ばれています。 しかし、これで話が終わったわけではありません。内部キャッシュが、外部キ ャッシュの一部をさらにキャッシュすることで、アクセス速度の底上げの最終 段階を担当しています。この内部キャッシュは、さらに高速で容量の小さいメ モリです。事実、これはプロセッサチップのすぐ側に置かれています。 読者がプログラムを本当に速くしたいと思うなら、こうした細かい事柄を知っ ておくことが有益です。プログラムは、ローカリティが強いほど高速になりま す。キャッシュがより効果的に働くからです。それゆえ、プログラムを速くす る一番簡単な方法は、プログラムを小さくすることです。プログラムが多くの ディスク I/O のために動きが鈍くなったり、ネットワークイベントを待ったり しなくてもすむ場合、それは、通常、システム内で許容されている最大のキャ ッシュ効果をともなったスピードで実行されるはずだからです。 プログラム全体を小さくできない場合は、スピードに関係する部分をチューニ ングするようにして、強いローカリティを発揮するようにすれば報われるでし ょう。そうしたチューニングに関するテクニックの詳細は、この文書の範疇を 越えます。読者がそれらを必要とする頃には、コンパイラにかなり精通してい るはずなので、そうした方法はおのずと理解できるはずです。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.3. メモリ管理ユニット (memory management unit) 充分な容量のコアがあり、スワッピングを避けられるときでも、メモリ管理 (memory management) と呼ばれるオペレーティングシステムの一部は、重要な 役割を果たしています。確認しておきたいのは、プログラムは自分のデータセ グメントしか変更できないということです。すなわち、あるプログラムの中の 不具合のあるコードや悪意を持って作られたコードが、他のプログラムのデー タセグメントにデータを吐き出すことは出来ない仕組みになっているというこ とです。これを実現するために、メモリ管理機構では、データセグメントとコ ードセグメントの一覧が書かれたテーブルを保持しています。このテーブルは 、プロセスが追加のメモリ領域を要求したり、それまで使っていたメモリ領域 を開放する(通常、これはプロセス終了時に起こります)たびに、更新されるよ うになっています。 オペレーティングシステムのメモリ管理機構は、このテーブルを使って、 MMU もしくはメモリ管理ユニット (memory management unit) と呼ばれる、下位層 のハードウェアにある特別な箇所にコマンドを渡しています。現代のプロセッ サチップには、複数の MMU が内蔵されています。 MMU は、メモリ領域を保護 するための特別な機能を持っているので、越境的なメモリ参照は拒否されると ともに、その際には特殊な割り込みが発生するようになっています。 今までに、"Segmentation fault" や "core dumpd" といったメッセージを見た ことがあるなら、まさに、そうした越境的なメモリ参照が起こったということ を意味します。実行中のプログラムが自分以外のセグメントにメモリアクセス しようとすると、致命的な割り込みが起きるのです。これは、プログラムにバ グがあることを意味しています。MMU が残す core dump は、プログラマがその バグを追跡するのを支援するための診断情報なのです。 プロセスの相互干渉の防止は、プロセスがアクセスできるメモリ領域を分離す ること以外に、さらに別の観点からもなされています。読者は、上記以外にも 、ファイルへのアクセス制御が出来るようにして、バグのあるプログラムや悪 意を持ってつくられたプログラムがシステムの重要ファイルを破壊できないよ うにしたいと思うことでしょう。 Unix が、ファイルパーミッションという仕 組みを持っているのは、このためです。これについては、後ほど説明します。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9. コンピュータは情報をどのようにメモリに保持するのか? コンピュータ上では、すべてがビット列として保存されていることは御存知だ と思います(二進数というのは、一連の小さなオン・オフのスイッチと考えるこ とができます)。ここでは、そうしたビットによって、コンピュータ上で扱われ る文字や数字がどのように表されているのかを説明します。 説明に入る前に、まずコンピュータのワードサイズ (word size) について理解 する必要があります。ワードサイズとは、そのコンピュータ上で利用されてい る、情報をやり取りするための単位のことです。技術的に言うと、これはプロ セッサのレジスタ (registers) の幅のことであり、レジスタとは、プロセッサ が計算や論理演算を行う際にその情報を格納する場所です。コンピュータには ビットサイズがあると書かれている場合 (たとえば、それらを 32-bit や 64-bit コンピュータなどと呼んでいる場合)、それはコンピュータのワードサ イズのことを言っているわけです。 (386 や 486 および Pentium PC を含む) 大部分のコンピュータは、32 bit の ワードサイズとなっています。古い 286 のマシンは、16 ビットのワードサイ ズでした。旧式のメインフレームだと 36 bit ワードのものがよくあります。 いくつかのプロセッサでは (たとえば、旧 DEC 現 Compaq が出している Alpha などは)、64 ビットのワードサイズとなっています。64 bit ワードは、これか ら 5 年ほどの間には、もっと一般的なものになるでしょう。Intel は、 Pentium シリーズのチップを "Itanium" と呼ばれる 64 bit チップに置き換え よう計画しているからです。 コンピュータは、メモリを、ゼロからそのマシンのメモリサイズの上限に該当 する値までの番号を振った、一連のワードの並びとして見ています。このメモ リサイズの上限の値というのは、ワードサイズによって決まります。286 など の旧式のマシン用のプログラムが大容量メモリを管理するのに、かなり無理な こじつけを使わなければならないのはそのためです。ここではそうしたこじつ けの仕組みについては述べませんが、年輩のプログラマは、いまだにこれが原 因でうなされていたりします。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.1. 数字 整数は、プロセッサのワードサイズに応じて、ワードか、もしくはワードのペ アによって表現されています。32 bit マシンでは、ワードが最も基本的な整数 の表現単位となっています。 整数の計算は、二進数の計算と似ていますが、実際は少し異なります。純粋な 二進数では、下位ビットから順に 1, 次に 2, そして 4 等を表しますが、正負 の符合付き数字は、2 の補数 (twos-complement) という表記で表されます。最 上位ビットは、符合ビット (sign bit) であり、このビットを使って負の値が つくられます。そして、負の数字はすべて、正の数字のビットを全部逆転させ て、それに 1 を加えることで変換可能です。 32 bit マシン上で整数の値の幅 が、-2^31 から 2^31 -1 (ここで、^ の記号は、べき数の演算子を表し、2^3 = 8 です) までしかないのは、そのためです。32 bit 目のビットは、正負符合用 に使われるのです。 コンピュータ言語のなかには、符合なし演算 (unsigned arithmetic) ができる ものもあります。その場合は、ゼロと正の数だけを使った通常の二進数演算と なります。 大部分のプロセッサやコンピュータ言語のいくつかは、浮動小数点 (floating-point) 数での処理が可能です(この機能は、最近の(PC 用)プロセッ サチップなら、すべてに組み込まれています)。浮動小数点数を使うと、整数よ りも広い範囲の値を扱うことができるだけでなく、分数も表現できるようにな ります。浮動小数点計算を実現する方法は、様々な種類がありますし、ここで 解説するにはちょっと複雑すぎるのですが、大まかに言うと、いわゆる「科学 的な表記法」といわれるもの、すなわち 1.234 * 10^23 のような表記法と非常 によく似ています。数字をコーディングする際は、仮数 (mantissa) (すなわち 1.234) と、10 のベキ数としての指数部分 (すなわち 23) とに分離されます。 (つまり、ここでの仮数は、小数点以下に 3 桁あるので、この場合の計算結果 は、その数字のあとに 0 が 20 個付くわけです。) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.2. 文字 文字は、通常、ASCII (American Standard Code for Information Interchange) と呼ばれるコーディングにしたがった 7 bit の並びとして表現 されます。現在のマシンでは、128 個の ASCII 文字のそれぞれが、オクテット (octet) もしくは 8-bit のバイト (byte) の下位 7 bit を使って表されてい ます。オクテットは、メモリのワード単位にまとめられるので、たとえば 6 字 の文字列の場合、多くとも 2 メモリワード分の場所しか取りません。この ASCII 文字のコード表を見るには、Unix プロンプト上で `man 7 ascii' と打 ってください。 とはいえ、上記の段落は、ふたつの点で誤解を招くかもしれません。まず、ひ とつ目は、やや細かいことですが、オクテットという用語です。これは、正式 には間違ってはいませんが、実際にはほとんど使われていません。大部分の人 は、オクテットをバイト(byte) と呼び、バイトを 8 bit 長であると考えてい ます。厳密にいうと、バイトという用語は、もっと一般的な意味を持っていま す。たとえば、以前は 36-bit マシンで 9-bit バイトといった言い方もなされ ていたのです(もうこうした使い方は決してなされないとは思うのですが)。 ふたつ目のより重要な問題は、全世界で ASCII 文字が使われているわけではな いということです。事実、多くの国では、ASCII を使っていません。ASCII は 、アメリカ英語の場合には問題ないのですが、他の言語の利用者が必要とする アクセント付きの文字や特殊な記号の付いた文字の多くが欠落しているからで す。英国英語ですら、ポンド記号が欠けていることから、ASCII 文字では問題 が生じてしまうのです。 この問題を解決しようとする試みは、過去にいくつもなされてきました。それ らはすべて、ASCII では使われていない最上位 bit を使うというものであり、 それによって 256 文字セットをもうひとつ作ってしまおうというものです、そ れらのうち、もっとも広く利用されているのが Latin-1 と呼ばれるものです (正式には、ISO 8859-1 と呼ばれています)。これは、 Linux, HTML および X でのデフォルトの文字セットとなっています。 Microsoft Windows は、 Latin-1 に手を加え、正式な Latin-1 では歴史的な理由から空欄とされている 箇所に左右の二重引用記号などを追加しています。(これが、トラブルを引き起 こす原因になっているという事件の解説は、demoroniser のページを御覧ください。) Latin-1 は、英語、フランス語、ドイツ語、スペイン語、イタリア語、オラン ダ語、ノルウェー語、スウェーデン語、デンマーク語といった西ヨーロッパの 言語を扱うものです。しかし、Latin-1 は、どれひとつの言語においても満足 のゆく出来ではないために、その結果として、Latin-2 から Latin-9 までの一 連の文字セットが生まれ、これらを使って、ギリシャ語、アラビア語、ヘブラ イ語、エスペラント語、セルビア・クロアチア語なども扱っています。詳しく は、ISO alphabet soup のページを御覧ください。 究極の解決策が、Unicode (および、その双子の兄弟である ISO/IEC 10646-1: 1993) と呼ばれる膨大な標準規格です。Unicode は、冒頭の 256 箇所について は Latin-1 とまったく同じです。それ以降の 16 bit 空間には、ギリシャ、キ リル、アルメニア、ヘブライ、アラビア、デヴァナーガリー(訳注:サンスクリ ット・ヒンディーその他を含む現代インド諸語)、ベンガル、グルムキー(訳注 :パンジャブ地方の文字)、グジャラート、オーリヤ(訳注:インドの Orissa 州)、タミル、トゥルグ、カンナダ(訳注:インドの Mysore 州)、マラヤーナム (訳注:インド南西)、タイ、ラオス、グルジア、チベット、日本仮名、現代韓 国のハングル完全版、中国・日本・韓国の表意文字 (漢字) の統一セットとい った文字コードが含まれています。詳しくは、 Unicode ホームページ を御覧ください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 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 が あります。これは、ディスクとのやり取りを調整することで、常に整合性が保 たれることを保証し、システムが起動するときに復旧が可能になるようにする 仕組みです。これは、起動時の整合性チェックをかなり高速化することができ ます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11. コンピュータ言語はどのような仕組みで動いているのか プログラムを実行する仕組みについては、すでに説明しました。あらゆるプロ グラムは、最終的に、一連のバイト列、すなわちコンピュータのマシン言語 (machine language) で表現された命令として実行されなければなりません。し かし、人間は、こうしたマシン言語は上手く扱えません。そうしたことはもう 極めて稀になり、ハッカーの間でも黒魔術化しています。 カーネル自体の内部にあるハードウェアインターフェイスを直接サポートする 少量の部分を除くと、ほぼ全ての Unix コードは、今日では、高水準言語 (high-level language) で書かれています。(この用語の「高水準」という部分 は、古くさい言葉で、「低水準」のアセンブラ言語 (assembler languages と 区別する意味で使用されるものです。アセンブラ言語とは、基本的にマシン語 を若干分かり易くしたような言語のことです)。 高水準言語には、いろいろな種類があります。こうしたプログラム言語につい て話す際は、プログラムのソースコード (source code) (すなわち、人間が作 成したもので、変更が可能なコード) は、何らかの変換処理を経ることで、マ シンが実際に実行できるマシンコードに変換される必要があるのだということ を是非頭の片隅で覚えておいてください。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.1. コンパイル型の言語 コンピュータ言語の中で最も伝統的なのが、コンパイル型言語 (compiled language) です。コンパイル型言語は、その名の通りコンパイラ (compiler) と呼ばれる特別なプログラムによって、実行ファイルであるバイナリ形式のマ シンコードへと変換されます。一旦バイナリが生成されると、再度ソースファ イルを参照せずとも、直接そのバイナリを実行することができます。(大部分の ソフトウェアは、コンパイルされたバイナリ形式で配布され、その元になって いるコードを見ることはできないのが一般です。) コンパイル型言語は、パフォーマンスに優れ、OS リソースへのアクセスも最も 効率よく行うことができますが、プログラムするのが難しいという傾向があり ます。 C 言語は、Unix を記述している言語であり、(その派生言語である C++ ととも に) コンパイル言語のなかで圧倒的な重要性を持っています。FORTRAN もまた コンパイル型の言語であり、いまでもエンジニアや科学者の間では利用されて いますが、 C よりも古く、旧式なものです。Unix の世界では、それ以外のコ ンパイル言語は、メインストリームとしては利用されていません。それ以外で は、COBOL が、金融やビジネス用ソフトウェアとして広く利用されています。 以前は、いろいろなコンパイル型言語がありましたが、ほとんどはすたれたか 、研究用のツールとしてしか使われなくなっています。読者がコンパイル言語 を使う新人の Unix 開発者なら、使用言語は C か C++ であることがほとんど です。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.2. インタプリタ型言語 インタプリト型言語 (interpreted language) とは、ソースを解釈するプログ ラム (interpreter program) を必要とする言語です。そのプログラムが、実行 の際そのソースコードを読み込んで、演算やシステムコールが可能な形式に変 換する仕組みになっています。この場合、コードが実行されるたびに、( ソー スを解釈するプログラムが起動されて、) ソースコードの変換が行われなけれ ばなりません。 インタプリタ型言語は、コンパイル型言語よりも実行速度が遅く、下部階層に あるオペレーティングシステムやハードウェアへのアクセスも制限されること が多いの傾向があります。その反面、コンパイル型言語に比べてプログラムが 容易で、コードにエラーがあっても深刻な事態になりにくかったりします。 シェルや bc(1), sed(1), awk(1) を含む多くの Unix ユーティリティは、かな り小型のインタプリタ型言語です。BASIC も通常はインタプリタ型であり、 Tcl もそうです。歴史的に見て、最も重要なインタプリタ型言語は、LISP です (何世代もの開発者たちにより、大幅な改善がほどこされてきました)。現在で は、Emacs エディタ内で使用できるシェルと LISP が、おそらく最も重要な純 粋インタプリタ型言語であると思われます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.3. P-code 言語 1990 年以来、コンパイルとインタプリタの両方を使う一種のハイブリッド言語 の存在が重要になってきています。P-code 言語というのは、ソースコードがコ ンパクトなバイナリコードに変換されて、それが実際に実行されるという点で はコンパイル型言語と似ていますが、変換されたコードの形態がマシンコード ではないという点で異なります。それは、疑似コード (pseudocode) (もしくは p-code) であり、これは実際のマシンコードよりもシンプルかつパワフルなも のです。プログラムの実行時には、この p-code が (インタプリタにより) 変 換されます。 p-code は、コンパイル型言語のバイナリとほぼ同じくらいの実行速度を持ちま す (単純かつ小型で、スピーディな p-code インタープリタを作成することが できるからです)。つまり、p-code は、柔軟かつパワフルなインタプリタ型言 語の長所も保持しているわけです。 重要な p-code 言語としては、Python, Perl, および Java があります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12. インターネットはどのような仕組みで動いているのか インターネットがどのような仕組みで動いているのか理解しやすくするために 、ここでは、典型的なインターネットの操作、すなわち、Linux Documentation Project のウェブページ上にあるこの文書の最初のページをブラウザ上でクリ ックした場合に、どのようなことが起こっているのかを概観しましょう。例え ば、この文書は次の場所にあります。 ┌───────────────────────────────────────┐ │http://www.linuxdoc.org/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/index.html │ └───────────────────────────────────────┘ 上記は、LDP/HOWTO/Unix-and-Internet-Fundamentals-HOWTO/index.html とい うファイルが www.linuxdoc.org というホストの World Wide Web についての 公開ディレクトリ上に置かれているということを意味します。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.1. 名前と場所 ブラウザがまず初めにしなければならないことは、その文書が置かれたマシン との間にネットワークコネクションを確立することです。それをするには、第 一に、そのホスト (host) である www.linuxdoc.org のあるネットワーク上の 場所を探す必要があります(ここで「ホスト」とは、「ホストマシン」、もしく は「ネットワークホスト」の略です。 www.linuxdoc.org という名前は、ごく 一般的なホスト名 (hostname) です)。通信の際の場所の指定は、実際には、 IP アドレス (IP address) と呼ばれる番号が使用されます(IP アドレスという 用語の IP の部分については、後ほど説明します)。 IP アドレスを知るには、ブラウザはネームサーバ (name server) と呼ばれる プログラムに問い合わせをします。ネームサーバは自分のマシン上にあっても よいのですが、たいていの場合は専用のマシン上で動いているので、それに問 い合わせを送ることになっています。ISP と契約したとき、そのセットアップ 手順のなかで、ISP のネットワーク上にあるネームサーバの IP アドレスを、 手持ちのインターネットソフトウェアに指示するという手順がきっと含まれて いたはずです。 異なるマシン上で動くネームサーバは、お互いに通信をし、ホスト名解決 (す なわち、ホスト名を IP アドレスに変換する処理) に必要なすべての情報を交 換しあい、つねに最新の状態に保っています。ネームサーバは、 www.linuxdoc.org というホスト名解決の過程で三度か四度ネットワークごしに 存在する他のサイトに問い合わせをしますが、これは通常非常に高速に行われ ます(一秒以下です)。ネームサーバの詳細については、次章で概観します。 ネームサーバはブラウザに対して、www.linuxdoc.org の IP アドレスが 152.19.254.81 であることを教えます。これを知ることによって、読者のマシ ンは、www.linuxdoc.org との間で直接情報交換ができるようになります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.2. ドメインネームシステム (domain name system) プログラムとデータベースを連携させてホスト名を IP アドレスに変換する仕 組み全体は、DNS (Domain Name System) と呼ばれています。"DNS サーバ" と 呼ぶ場合、これは単に「ネームサーバ」と呼ぶのと同じ意味で使われます。こ こでは、そのシステム全体の仕組みについて説明します。 インターネットのホスト名は、ドットで区切られたいくつかの部分から構成さ れています。ドメイン (domain) とは、ホスト名の末尾部分が同一であるマシ ンの集合のことです。ドメインは、他のドメインの内部にも存在することがで きます。たとえば、www.linuxdoc.org というマシンは、 .org というドメイン の中の .linuxdoc.org というサブドメインに存在しています。 個々のドメインは、そのドメイン内の全てのマシンの IP アドレスを記憶して いる権威あるネームサーバ (authoritative name server) によって規律されて います。権威ある(もしくは「プライマリ」) ネームサーバは、故障に備えてバ ックアップされていることもあります。セカンダリネームサーバ (secondary name server) (もしくは、セカンダリ DNS) という記述がある場合、それはそ うしたバックアップホストのことを言っているわけです。こうしたセカンダリ サーバ群は、通常、プライマリサーバから数時間ごとに情報を受け取って更新 しているので、プライマリサーバ上でホスト名と IP との対応関係に変更が加 えられた場合は、自動的にそれがセカンダリ側にも伝えられるようになってい ます。 次の事柄は非常に重要です。すなわち、あるドメインのネームサーバは、他の ドメイン(および、それら自身のサブドメイン)にあるすべてのマシンの位置を 知る必要はないということです。ネームサーバが知らなければならないのは、 他のネームサーバの位置だけです。先ほどの例では、.org ドメインの権威ある ネームサーバは、.linuxdoc.org の権威あるネームサーバの IP アドレスを知 っていますが、linuxdoc.org 内にあるそれ以外のマシンのアドレスについては 知りません。 DNS システム内のドメインは、巨木を逆さまにしたような階層構造を持つよう 配列されています。頂点にあるのは、ルートサーバです。すべてのネームサー バは、ルートサーバの IP アドレスを知っています。ルートサーバの IP アド レスは、DNS ソフトウェアに組み込まれているからです。これらのルートサー バは、 .com や .org といったトップレベルドメインに関するネームサーバの IP アドレスを知っていますが、それらの内部にあるマシンのアドレスは知りま せん。個々のトップレベルドメインのサーバは、その直下のドメインのネーム サーバの場所を知っていて、以下そうした関係が続きます。 DNS は、非常に注意深く設計されていて、各マシンがこの木構造に関する必要 最小限の情報をやり取りするだけですむように、そして、局所的なサブツリー の変更は、一箇所だけの権威あるネームサーバの名前と IP アドレスとのマッ ピングを変更するだけで実施できるようになっています。 www.linuxdoc.org の IP アドレスを問い合わせた場合、実際には次のようなこ とが起こります。第一に、その問い合わせを受けたネームサーバ(x)が、ルート サーバに対して、.org に関するネームサーバがどこにあるのか教えるよう問い 合わせをします。それが分かると、次に(x は)、.org のサーバに対して 、.linuxdoc.org のネームサーバがどこにあるのか教えてくれるよう、問い合 わせをします。その答えが返ってきたら、今度は(x は)、 .linuxdoc.org のネ ームサーバに対して、www.linuxdoc.org というホストの IP アドレスを教える よう問い合わせます。 たいていの場合、ネームサーバは実際に上記のような面倒な仕事をする必要は ありません。ネームサーバは大量の情報をキャッシュしているからです。名前 の解決ができたとき、ネームサーバは入手した IP アドレスとの対応関係をし ばらくの間メモリ内に保持します。初めてのウェブサイトをサーフする際、「 ホストを探しています」というブラウザからのメッセージが表示されるのは一 番最初のページを開いた場合だけであるのは、上記のような仕組みになってい るからです。最終的には、名前とアドレスとのマッピングは時間切れとなり、 DNS はその名前を再度問い合わせなければならなくなります。この機能が重要 なのは、あるホスト名とアドレスとの結びつきが変更されたときに、無効にな った情報がいつまでも邪魔をするようなことがないということです。あるサイ トに関するキャッシュされた IP アドレスは、そのホストに到達できなくなっ た場合にも、破棄されます。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.3. パケットとルータ ブラウザが実行しようとしているのは、www.linuxdoc.org 上のウェブサーバに 対して次のようなコマンドを送信することです。 ┌──────────────────────────────────┐ │GET /LDP/HOWTO/Fundamentals.html HTTP/1.0 │ └──────────────────────────────────┘ 以下で、その際に何が起こるのかについて説明します。上記コマンドは、パケ ット(packet) というビットの塊に分割され、電報の場合のように、次のような 三つの重要事項を付け加えた上でラップされます。その重要事項とは、送信元 アドレス (source address) (読者のマシンの IP アドレス)、あて先アドレス (destination address) (これは、152.19.254.81)、およびそれがウェブに関す るリクエストであることを示すサービス番号 (service number) もしくはポー ト番号 (port number) (この場合では、80 番)です。 次に、読者のマシンはそのパケットを回線(ISP やローカルネットワークとのコ ネクション) 上に送信すると、そのパケットはルータ(router) と呼ばれる特別 なマシンに届きます。ルータは、インターネットの地図をメモリ内に記憶して います。これは必ずしも完全な地図ではありませんが、そのネットワークの周 辺地域については完全に記述しているので、インターネット上の他の周辺ルー タに至るべき方法は理解しています。 送信したパケットは、いくつかのルータを通過しながら、あて先へと到達しま す。ルータは非常に上手く出来ています。ルータは、パケットを受信した旨の 送達確認を他のルータから受け取るまでの経過時間を監視しています。また、 その経過時間に関する情報を使って、空いているリンク上にパケットを流すよ うトラフィックを調整しています。また、ルータはその情報を使用して、他の ルータやケーブルが原因でネットワークに支障が生じた際に、別のルータを探 すことで可能な限り埋め合わせをしようとします。 インターネットは核戦争に耐えられるように設計されたという噂が都会で暮ら す人々の間でささやかれていま。これは真実ではないのですが、しかし、イン ターネットの設計というのは、この不確かなことが多い世界において、頼りに ならないハードウェアからでも信頼に足るパフォーマンスを引き出してくれる だけの極めて優れた設計思想のもとに構築されています。このことは、まさに 次の事実によっています。すなわち、(電話回線網のように)少数の巨大で脆弱 な交換機に処理を集中させるのではなく、無数のルータにそうした処理の役割 を分散したということです。つまり、障害はあくまで局地的なものに留まるよ うになっていて、ネットワークはそうした障害を避けてルーティングすること ができるのです。 パケットが一旦あて先のマシンまで到達したなら、そのマシンはサービス番号 を使って、そのパケットをウェブサーバに渡します。ウェブサーバは、そのコ マンドパケットの送信元 IP アドレスを見ることで、どこに返答を返せばよい のかを知ることができます。上記の例で、そのウェブサーバがこの文書を送信 する際は、文書はいくつかのパケットへと分割されます。パケットのサイズは 、ネットワーク上の伝送方法やサービスの種類によって異なります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.4. TCP と IP こうした分割されたパケットを使う通信方法の処理のしかたを理解するには、 インターネットは実際にはふたつのプロトコルを使用していて、一方が他方の 上に積み重ねられているという通信の仕組みを知る必要があります。 下位層である IP (Internet Protocol) は、送信元アドレスからあて先アドレ スに個別のパケットを到達させるための方法は知っています(これらが IP アド レスと呼ばれるのはそのためです)。しかしながら、IP は信頼性に欠けていま す。パケットが途中で行方不明になったり消失したりしても、送信元とあて先 のマシンは決してそれに気づかないでしょう。ネットワーク用語では、IP はコ ネクションレス (connection less) なプロトコルです。送信者はパケットを受 信者に対して送るだけであり、受信側からの確認応答は期待できません。 しかし、IP は、高速かつ安価です。高速かつ安価であれば、信頼性がなくとも 問題ないという場合もあります。Doom や Quake をネットワーク上でプレイす るときは、弾丸のひとつひとつも IP パケットで送られています。弾丸の2,3 発消失してしまったとしても、特に問題はないはずです。 上位層の TCP (Transmission Control Protocol) は、信頼性のためのプロトコ ルです。二台のマシンが相互に交渉して TCP コネクションを張り (これは、IP を使って行われます)、受信側が受信したパケットについての到達確認を送信側 に送るようになっています。送信側が一定時間内にあるパケットの送達確認を 受け取らない場合は、そのパケットを再送します。さらに、送信側は、個々の TCP パケットに通し番号を振っているので、受信側ではそれを使って、パケッ トの到着順序が狂った場合でもそうしたパケットを並べ直すことができるよう になっています。(これは、ネットワーク上の経路が、コネクションの最中に変 動した場合には、よく起こる現象です。) TCP/IP パケットにはチェックサム (checksum) という情報も含まれていて、経 路上に障害が生じてデータが壊れてしまったりしていないかを検出できるよう になっています。 (チェックサムは、まずチェックサム自体を除いたパケット の残りの情報から算出されます。そうしておくと、パケットの残りの部分かチ ェックサム自体が(経路上で) 壊れてしまっていた場合は、(到達先で)そのパケ ットから再度チェックサムを算出して、もとのチェックサムと比較することで 、エラーの有無を高い確率で検出することができるわけです)。それゆえ、 TCP /IP とネームサーバというのは、使う側の視点からみると、ホスト名とサービ ス番号で区別された二台のホスト間でバイトストリームを伝達しあう方法とし ては、信頼に足るものと言ってもいいでしょう。また、ネットワークプロトコ ルを書く人からすると、このネットワーク階層以下で起こっているパケット分 割やパケットの再構成、エラーチェック、チェックサムの計算、再送といった 事柄をほとんど考慮しなくてもよくなります。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.5. HTTP : アプリケーションプロトコルの一例 ここで、先ほどの例題に戻りましょう。ウェブブラウザとサーバとは、アプリ ケーションプロトコル (application protocol) を使って会話をしています。 これは、TCP/IP 階層の上位で実行されるプロトコルであり、 TCP/IP をバイト 列のやりとりの手段として利用しているものです。上記の例でのプロトコルは 、 HTTP (Hyper-Text Transfer Protocol) と呼ばれるもので、そこで使われる コマンドの一例が GET で始まる文字列であることは既にご覧になった通りです 。 上記の GET コマンドが www.linuxdoc.org のウェブサーバにサービス番号 80 番で到達すると、ウェブサーバはこれを、80 番ポートで待機しているサーバデ ーモン (server daemon) へと渡します。大部分のインターネットサービスは、 サーバデーモンとして実装されていて、これらは、送られてくるはずのコマン ドを監視し、実行するために、特定のポートでただひたすら待ち受け状態を続 けています。 インターネットの設計に関する一大原則があるとするなら、それはすべての部 分ができる限りシンプルで人間が見て分かるような形にするということです。 HTTP とその親戚 (Simple Mail Transfer Protocol, SMTP という、ホスト間で 電子メールをやりとりするために使われるプロトコルなど) は、キャリッジ・ リターン(復帰)とライン・フィード(改行)で終了する、人間が読んでも意味の 分かるシンプルなテキストコマンドを使うという傾向があります。 こうした方法は、かなり非効率です。環境によっては、がちがちに固められた バイナリプロトコルを使ってスピードアップを図ることもできるはずです。し かし、経験が示すところによると、人間にとって説明や理解のしやすいコマン ドを使うことのメリットは、トリッキーで分かりにくいものをわざわざ作るこ とで得られる僅かな効率の上昇をはるかに上回るものなのです。 したがって、サーバデーモンが TCP/IP 経由で送り返す返答もテキスト形式で す。返答の最初の部分は、次のようなものとなります(ヘッダの一部は省略して います)。 ┌──────────────────────────────────┐ │HTTP/1.1 200 OK │ │Date: Sat, 10 Oct 1998 18:43:35 GMT │ │Server: Apache/1.2.6 Red Hat │ │Last-Modified: Thu, 27 Aug 1998 17:55:15 GMT │ │Content-Length: 2982 │ │Content-Type: text/html │ └──────────────────────────────────┘ 上記のようなヘッダの後に空行がひとつ入り、その後にウェブページのテキス トが続きます(それらの送信が終わった時点で、コネクションが切断されます) 。ブラウザは、それらを表示するだけです。ヘッダは、ブラウザに対してその 表示方法を指示するものです (特に、Content-Type というヘッダでは、返信デ ータが実際に HTML であるということをブラウザに伝えています)。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13. もっと詳しく知りたい人のために Reading List HOWTO という文書があります。これは参考文献の一覧であり、これらを読めば、本書 で触れたトピックについてもっと詳しく学ぶことができます。 How To Become A Hacker という文書 も参考になるかもしれません。 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14. 日本語訳について 現在のバージョンは、堀田倫英さんの翻訳をベースに Linux Japanese FAQ Project が作成しました。翻訳に関するご意見は JF プロジェクト 宛に連絡してください。 改訂履歴を以下に示します。 v1.1j, 15 January 1999 翻訳: 堀田倫英 (sim@remus.dti.ne.jp) 校正: 長谷川靖 (yaz-hase@qb3.so-net.ne.jp) 早川仁 (uv9h-hykw@asahi-net.or.jp) 中野武雄 (nakano@apm.seikei.ac.jp) 遠藤明 (akendo@t3.rim.or.jp) v1.4j, 8 December 1999 更新: 藤原輝嘉 (fujiwara@linux.or.jp) 校正: 高城正平 (takavoid@palette.plala.or.jp) v2.4j, 15 July 2001 更新: 千旦裕司 (ysenda@pop01.odn.ne.jp) 校正: 高城正平 (takavoid@palette.plala.or.jp) 伊藤祐一 (kade@@kadesoft.com) T. Hayashi (smiff@nifty.com)