]>
The Unix and Internet Fundamentals HOWTO Eric Raymond
esr@thyrsus.com
JF Project 日本語訳
JF@linux.or.jp
2.4 12 June 2001 esr Where to find more. 1.0 29 October 1998 esr Initial revision. この文書では、PC 系のコンピュータや Unix ライクなオペレーティングシステム、 およびインターネットに関する実用的な基礎知識について、技術的な専門用語を 使わずに解説しています。
<!--Introduction-->はじめに <!--Purpose of this document-->この文書の目的 この文書は、Linux やインターネットを使い倒すことで学んでいるユーザを支援 するためのものです。実践から学ぶというのは、特定のスキルを身につける方法 としては申し分ないのですが、(体系的ではないため)ごく基本的なはずの知識の 各所に穴が生じてしまいがちです。そして、そうした欠落があると、自分で何か をあらたに考え出したり、トラブルを効果的に解決したりする際に困難に直面す るものです。現実に何が起こっているのかを頭の中で思い描くことができないから です。 本書では、Linux とインターネットに関する全体の仕組みについて、明確かつ平易な 言葉で説明するつもりです。本書の解説は、PC 系ハードウェア上で Unix か Linux を 使っている人を対象にしています。ただ、ここでは、両者をあわせて単に 'Unix' と呼びます。本書での説明の大部分は、各種プラットフォームや様々な種類の Unix に共通するものだからです。 また、本書では、読者が Intel プロセッサを搭載した PC を使っているものと 仮定しています。Alpha プロセッサや PowerPC 、もしくはそれ以外の Unix マシンを動かしている場合、細かな点に多少違いはありますが、それでも 基本的なコンセプトは同じです。 同じことをくどくど述べることはしないので、注意して読んでください。 別の言い方をすると、重複した説明がないので、どこを読んでも新しい 知識を得られるということでもあります。最初はざっと読んで、学んだ ことを消化した後で、さらに何度か読み返すという方法をおすすめします。 この文書は随時更新されています。ユーザのフィードバックをもとにして セクションを追加していくつもりなので、これからもちょくちょく本書に 目を通してもらえればと思います。 <!--New versions of this document-->この文書の新バージョン Unix and Internet Fundamentals HOWTO の新バージョンは、定期的に comp.os.linux.helpnews: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 本書には、 ポーランド語の翻訳があります。 <!--Feedback and corrections-->フィードバックと訂正 この文書に関する質問やコメントがあれば、遠慮なく Eric S. Raymond <esr@thyrsus.com> までメールを送ってください。どのような提案や 批判でも歓迎します。特に、本書での個々のコンセプトをより詳しく説明して いるサイトへのハイパーリンクは大歓迎です。この文書に間違いを見つけた 場合は、是非著者に知らせてください。次のバージョンで訂正します。 よろしくお願いします。 <!--Related resources-->関連リソース ハッキングの方法が知りたくてこの文書を読んでいるなら、ついでに How To Become A Hacker FAQ (山形訳 中谷訳) も読んでください。役に立つ情報への リンクが記載されています。 <!--Basic anatomy of your computer--> コンピュータ解剖学入門 読者のコンピュータには、プロセッサチップが内蔵されていて、これが実際の 計算処理を実行しています。また、内部メモリも内蔵されています ( DOS/Windows なひとたちは、''RAM'' と呼びますが、Unix なひとたちは ''コア(core)'' と呼んだり もします。この Unix 用語は、まだ RAM がドーナツ型のフェライトコアで 出来ていた頃の名残です)。そして、プロセッサとメモリは、 マザーボードmotherboard 上に搭載されていて、このマザーボードがコンピュータ の心臓部となっています。 コンピュータには、スクリーンとキーボードもついています。また、ハード ドライブやフロッピディスクも内蔵されています。スクリーンとディスクには コントローラカードが必要です。こうしたカードはマザーボードに差し込まれ、 コンピュータによるデバイス制御を補助しています(キーボードは非常にシンプルな デバイスなので、専用のカードを必要としません。 コントローラはキーボードの 筐体内に埋め込まれています)。 こうしたデバイスの動作原理の詳細については、後ほど説明します。ここでは まず、これらが互いにどういった仕組みで動作しているのかを簡単に説明します。 ケース内に収められたコンピュータのパーツはすべて、 バス (bus) bus によって繋がっています。物理的にいうと、バスとは、コントローラカードを 差し込む部分です(ビデオカードやディスクコントローラ、場合によっては セカンド(ディスク)コントローラなど)。いわばバスとは、プロセッサやスクリーン、 ディスク等、すべてのパーツ間でデータを高速にやり取りするための通路です。 ( PC 関連の話題で 'ISA' や 'PCI', 'PCMCIA' といった用語を目にするけれど、 それが何なのかよく分からないというかたがいるかもしれません。これらは、 バスの形式のことです。ISA というのは、おおまかに言うと、1980 年代に IBM が 販売していた初期の PC で使用されていたのと同一のバスのことで、現在では すたれつつあります。PCI とは、Peripheral Component Interconnection の略で あり、現代の PC および Macintosh で使用されているバスです。PCMCIA とは、 物理コネクタを小型化した ISA の改良版であり、ラップトップで使用されて います。) プロセッサはすべての機器を統括するパーツなのですが、これは実際には 他のパーツを直接見ることはできず、バスを経由して会話するようになっています。 バス以外に、プロセッサが直接かつ高速にアクセスできるサブシステムは、メモリ (コア)だけです。それゆえ、プログラムを実行しようとするなら、プログラムは コア内(メモリ内)に読み込まれる必要があります。 コンピュータがプログラムやデータをディスクから読み出す場合、実際の 動作としては、まずプロセッサが、バスを使って、ディスクからの読み出しリクエスト をディスクコントローラに対して送信します。しばらくして、ディスクコントローラ は、バスを使って、データを読み出してメモリ内のある場所に置いたということを プロセッサに伝えます。それによって、プロセッサは、バスを使って、 そのデータを見ることができるようになるわけです。 キーボードとスクリーンも、バス経由でプロセッサとコミュニケーションを 取っているのですが、その方法はもう少し単純です。これについては後述します。 コンピュータに電源を入れた際に何が起こるかを理解するには、いまのところ、 これで充分です。 <!--What happens when you switch on a computer?--> コンピュータの電源を入れた時に何が起こるのか? プログラムが動いていないコンピュータは、役立たずな電子部品のかたまり にすぎません。電源を入れた際、コンピュータがまず最初に行うべきことは、 オペレーティングシステムと呼ばれる 特別なプログラムをスタートさせることです。オペレーティングシステムの 仕事は、コンピュータハードウェアの制御という極めて煩雑な処理をやってのける ことで、その他のプログラムの仕事を支援することです。 オペレーティングシステムを起動する処理のことは、ブート (boot) と呼ばれています(ブートとは、 もともと「編み上げ靴のつまみ革(bootstrap)」のことであり、靴のつまみ革を 引っ張って自分自身を持ち上げる(困難な事柄の例え)という言い方がオペレーティング システムの起動処理に似ていることから名付けられました)。ブートの手順は コンピュータチップのひとつである BIOS (Basic Input/Output System の略) に書き込まれているので、(オペレーティングシステムが起動する前でも) コンピュータはブート方法を知ることができるようになっています。 BIOS チップは、コンピュータに対して、ある決まった場所にある 特別のプログラムを探すように指示します。これは通常、一番若い番号が 付けられたハードディスク (ブートディスク (boot disk)) 上にある、ブートローダ (boot loader) とよばれる プログラムです (Linux 上では、このブートローダは LILO とよばれています)。 そして、ブートローダがメモリに読み込まれ、起動されます。ブートローダの仕事は、 オペレーティングシステムそのものを起動することです。 このローダによるオペレーティングシステムの起動というのは、 カーネルkernel を探して、それをメモリにロードし、スタートさせることにより 実行されます。読者が Linux を起動すると、 スクリーンに "LILO" という文字と、 その後にドットがいくつか表示されるのが分かると思います。その時は、 ブートローダがカーネルをロードしているのです (ひとつのドットの表示は、 ディスクブロック (disk block) ひとつ分のカーネルコードがロードされたことを意味しています)。 (読者は、BIOS がなぜ直接カーネルをロードしないのか、なぜわざわざブート ローダを使って 2 段階の処理をするのかについて不思議に思うかもしれません。 これは、BIOS というのが、あまり賢くないからなのです。実際コイツはとても頭 が悪いので、ブートが終われば Linux は全く BIOS を使いません。BIOS はもともと ディスクもあまり積んでいない原始的な 8 ビット PC のために書かれたもので、 ディスクにアクセスしてカーネルを直接ロードするような処理は事実上できない のです。また、Unix では都合の悪いような作業があったとしても、ブートローダと いう処理過程を介在させることで、ディスク上の別の場所から異なる オペレーティングシステムを起動させることも可能になります。) いったんカーネルが始動すると、カーネルは周辺装置の検出を行い、 ハードウェアを残らず認識して、プログラムを実行する準備を整えます。その際、 カーネルは、通常のメモリではなく、I/O ポート (I/O ports) という場所とやりとりをします。I/O ポートとは、特別なバス アドレスであり、デバイスコントローラカードはたいていこのポートを監視しながら カーネルからのコマンドを待っています。この時、カーネルは、I/O ポートを ランダムに探すわけではありません。どこを探せば何があるのか、コントローラ が存在する場合はどういう反応があるのかといった情報が、カーネルのなかに あらかじめぎっしりと組み込まれているからです。このプロセスは、 自動検出 (autoprobing) 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) に入り、システムが完全に使える状態に なります。 <!--What happens when you log in?--> ログインしたときに何が起こるのか? ログインするというのは、(getty にある名前を教える ことで) 自分が誰なのかをコンピュータに認識させることです。その際、 (分かり易い名前の) login と呼ばれるプログラムが 起動され、これがパスワードを受け取り、そのマシンを使う権限を持ったユーザである かどうかの認証を行います。もし使う権限がなければ、ログインしようとしても 拒否されます。権限があれば、login プログラムはいくつかの前提処理を 済ましたうえで、シェル (shell) というコマンド インタープリタ (command interpreter) をスタートさせます。(もちろん、 gettylogin をひとつの プログラムとすることも可能ではあります。両者が別々に分かれているのは、 歴史的な理由からであり、ここでは特に取り上げるようなものではありません。) ここでは、シェルが表示されるまでに、システム上で起こる事柄について もう少し詳しく説明します(後ほど、ファイルシステムのパーミッションについて 解説する際に、この章の知識が必要になります)。ユーザは、ログイン名と パスワードによって同定されます。ログイン名は、/etc/passwd というファイルを見て、確認されます。このファイルは、 一行ごとにそれぞれのユーザアカウントの情報が記述されたファイルです。 行ごとに複数あるフィールドのうち、アカウントパスワードの欄は、 暗号化されています(場合によっては、この暗号化されたフィールドは、 /etc/shadow という別のファイルに保存され、より 厳しいパーミッション設定がされることがあります。これによって、パスワード のクラッキングをより困難にするのです)。アカウントパスワードとして入力した 文字も、まったく同じ方法で暗号化され、login プログラム により正当なパスワードかどうかの確認がなされます。この方法が安全である といえるのは、平文パスワードを暗号化するのは簡単でも、その逆は難しいと いう事実によっています。それゆえ、誰かが暗号化された後のパスワードを 見たとしても、それによってそのアカウントを使えるようにはなりません。 (逆に、自分のパスワードを忘れてしまったら、それをもう一度確認することも できなくなります。何か別のパスワードに変更する以外にありません。) ログインに成功したら、利用している自分のアカウントに関連付けられた すべての権限を利用できるようになります。また、そのアカウントは、 なんらかの グループ (group) group に属していると認識される場合もあります。 グループとは、一定の名前を付けられたユーザの集団であり、システム管理者に よって設定されます。グループには、その個々のメンバーの権限とは別に、 グループ独自の権限を設定することができます。また、ユーザは、複数のグループ のメンバーとなることもできます。(Unix の権限の仕組みの詳細については、 パーミッションの章をご覧ください。) (注意すべきなのは、通常はユーザ名やグループ名というのは名前で呼んでいます が、実際には、数字の ID として内部に保存されているということです。 passwd ファイルには、アカウント名とそのユーザ ID と の対応関係が書かれていて、/etc/group /etc/group ファイルには、グループ名とその数字からなる グループ ID との対応関係が書かれています。アカウント名やグループ名を処理する コマンドは、この両者の変換を自動的に行っています。) アカウントの情報欄には、そのユーザの ホームディレクトリ (home directory)home directory に関する情報も含まれています。ホームディレクトリとは、 Unix ファイルシステム上でそのユーザ個人のファイルが保管されている 場所のことです。最後に、アカウントの情報欄には、そのユーザの シェル (shell)shell に関する情報もあります。シェルとは、コマンドインタープリタのことであり、 login プログラムがユーザからのコマンドを受け取るために 起動するものです。 <!--What happens when you run programs from the shell?--> シェルからプログラムを起動したとき何が起こるか? シェルとは、ユーザが打ち込んだコマンドを解釈する 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 の実行中に、読者のマシンがメールの送受信を することも可能です。 <!--How do input devices and interrupts work?--> 入力デバイスや割り込みはどのように動作しているのか? キーボードというのは、非常にシンプルな入力デバイスです。というのも、 キーボードは、(コンピュータの基準から見ると)ごく少ない量のデータを 非常にゆっくり生成するものだからです。キーを押したり離したりする際、 そのイベント信号は、キーボードケーブルを伝わって、 ハードウェア割り込み (hardware interrupt) hardware interrupt を 発生させます。 こうした割り込みを監視するのは、オペレーティングシステムの仕事です。 あらゆる種類の割り込みに対処するために、割り込みハンドラ (interrupt handler)interrupt handler というのが必要になります。これは、 オペレーティングシステムの一部であり、(キーストロークやキーリリース といった) 割り込みに関係するデータのすべてを、それが処理されるまで 保持しておくものです。 キーボード用の割り込みハンドラの実際の仕事というのは、キーの値をメモリの 底のほうにあるシステムエリアに格納することです。オペレーティングシステムが、 現在キーボードからの入力待ちと思われるプログラムに対して制御を渡した 際、そのプログラムがそこから値を読み出せるようにするわけです。 ディスクやネットワークカードのようなもっと複雑な入力デバイスの場合でも 動作方法は同じです。以前の章では、ディスクコントローラはバスを使ってディスク リクエストが完了したことを伝えるという話をしました。その場合の実際の動作と いうのは、ディスクが割り込みを発生されるということです。そのとき、ディスクの 割り込みコントローラは、発生した割り込みデータをメモリ内に一旦コピーして、 後からそのリクエストを発したプログラムが使えるようにするのです。 割り込みには、どういう種類のものであれ、必ず関連する 優先レベル (priority level)priority level が付いています。低い優先レベルの割り込み(たとえば、 キーボードイベント)は、高い優先レベルの割り込み(たとえば、クロックチック (clock tick) やディスクイベント) が処理されるのを待たなければなりません。 Unix では、マシンの反応をスムーズにするために、迅速な処理を必要とする イベントに対しては、高い優先順位が与えられるように設計されています。 オペレーティングシステムの起動時のメッセージのなかに、IRQ IRQ の数字に関する 表示があるのをご覧になったことがあると思います。また、よくあるハード ウェアの設定ミスのひとつとして、二つのデバイスが同じ IRQ を使うように 設定してしまっていたという事例があることを御存知だと思います。ただ、 これが何故設定ミスなのか、正確な理由は案外知られていません。 それは、つまりこういうことです。IRQ とは、"割り込みリクエスト (Interrupt Request)" の略です。オペレーティングシステムは、起動時に、どのハードウェア がどの割り込み番号を使うのか知る必要があり、それによって、適切な割り込み ハンドラを個々のハードウェアに関連付けています。もし二つの異なるデバイスが 同一の IRQ を使おうとすると、割り込みが不適切なハンドラに送られてしまう 事態が生じます。その場合、通常は少なくともデバイスが反応しなくなってしまう か、あるいは、OS を混乱させてしまって、OS が固まってしまうか、クラッシュ してしまうからです。 <!--How does my computer do several things at once?--> コンピュータはどうやって複数のことを同時に行うのか? 実際には、複数のことを同時に行っているわけではありません。 コンピュータは、その時々で ひとつのタスク (もしくはプロセス (process) ) しか処理できません。しかし、コンピュータは、複数のタスクを瞬時に 切り替えながら処理できるので、人間の感覚からすると、複数のことを 同時に行っているように錯覚させることができるのです。こうした方法は、 タイムシェアリング (timesharing, 時分割方式) timesharing と呼ばれています。 タイムシェアリングの管理も、カーネルの仕事のひとつです。カーネルには スケジューラ (scheduler) scheduler という部分があり、 これが(カーネル以外の)全プロセスに関する情報を内部に保持しています。 60 分の 1 秒ごとに、カーネル内のタイマーが進んで、クロック割り込みを 生成します。スケジューラは、現在実行中のプロセスを停止させて、 それを適宜保留したうえで、別のプロセスに制御を渡します。 60 分の 1 秒というのは、あまり長い時間とは思えないかもしれませんが、 今日のマイクロプロセッサからすると、数万のマシン命令を実行しうるだけの 間隔であり、それだけあれば、かなりの仕事がこなせます。それゆえ、多くの プロセスが存在したとしても、それぞれのプロセスは、その割り当て時間内に 結構な仕事量をこなせるわけです。 実際には、プログラムはその割り当て時間を全部使えない 場合もあります。I/O デバイスから割り込みがかかると、カーネルは効率よく 現在のプロセスを停止させ、割り込みハンドラを実行して、そのあとで 現在のプロセスを再開します。高い優先順位の割り込みがつぎつぎに起こると、 通常の処理がまったく行われなくなってしまいます。この異常事態は、 スラッシング (thrashing) と呼ばれていていますが、 幸なことに、現在の Unix ではめったに起こらなくなっています。 事実、プログラムの実行速度は、プログラムが取得するマシン時間の 量によって制限されるということはほとんどありません (サウンドや 3-D グラフィックの生成など、いくつかの例外はありますが)。実行の遅延が 起こる原因は、たいてい、プログラムがディスクやネットワーク 上にあるデータを取得しようとして待っている間に発生するのであり、 ほとんどの原因がこれです。 多くのプロセスを順に同時並行的に処理できるオペレーティングシステムは、 "マルチタスク (multitasking) " OS と呼ばれます。Unix 系のオペレーティング システムは、もともとマルチタスク OS として設計されており、それを非常に 得意としています。Windows や Mac OS などよりもずっと効率よく処理できます。 Windows や Mac OS は、マルチタスク機能を後から組み込んでいるので、 Unix に比べるとややおそまつです。この効率のよい、信頼できるマルチタスク 機能は、ネットワーク通信やウェブサービスの分野で Linux を優位に立たせる 大きな要因となっています。 <!--How does my computer keep processes from stepping on each other?--> コンピュータはどうやって複数のプロセスが干渉しあわないようにしているのか? カーネルのスケジューラは、プロセスの時間的な割り振りを担当しています。 同時に、オペレーティングシステムは、プロセスを空間的にも割り振って、それらが 互いの作業メモリ領域に干渉しないようにしなければなりません。 すべてのプログラムが協調して動いてくれると仮定した場合でも、 そのどれかひとつにバグがあり、それによって他のプログラムのメモリ領域が 破壊されてしまうような事態は望ましくありません。この問題を解決するために、 オペレーティングシステムが行う対処方法は、メモリ管理 (memory management) memory management と呼ばれて います。 コンピュータ内の個々のプロセスには、そのコードを実行したり、変数を 保存したり、処理の結果を格納したりする場所として、独自のメモリ領域が 必要です。こうしたメモリ領域は、(プロセスの命令が保持される)読み出し専用 領域である コードセグメント (code segment) code segment と、(プロセスのすべての変数が保持される) 書き込み可能な領域である データセグメント (data segment) data segment から構成されていると考えることができます。データセグメントは、文字どおり 個々のプロセスに固有の領域ですが、二つのプロセスが同一のコードを 実行している場合、Unix は、メモリの利用効率の観点から、そうした プロセスが、単一のコードセグメントを共有するよう調整を行います。 <!--Virtual memory: the simple version--> 仮想メモリ:簡易バージョン メモリは値段が高いので、効率よく利用することが大切です。ときには、マシンで 実行中のすべてのプログラム全体をメモリに保持するだけの余裕がない場合が 生じます。特に、X サーバのような巨大なプログラムを実行しているような場合 には、メモリ不足が生じることがあります。この問題に対処するために、 Unix は、仮想メモリ (virtual memory) virtual memory と呼ばれるテクニックを 使います。これは、プロセスのすべてのコードとデータをメモリ内に保持 しようとするものではありません。むしろ、比較的少量の ワーキングセット (working set) working set だけを保持するようにして、 残りのプロセス状態は、ハードディスク上の スワップスペース (swap space)swap space という特別な領域に置いておきます。 注意して欲しいのは、前の段落で「ときには....生じます」と書いた部分は、過去に おいては、「ほとんどいつも生じていました」と言い換えることができるという点 です。以前は、実行中のプログラムのサイズと比べてメモリのサイズが 全然足りなかったので、スワッピングは頻繁に起こっていました。しかし、 メモリは今日ではかなり安価になっていて、ローエンドのマシンにすら、かなりの メモリが積まれるようになっています。64 MB 以上のメモリを積んだ現在の 個人用マシンの場合、X やよく利用するジョブが最初からコアにロードされた あとでも、そうしたプロセスをスワッピングなしで実行することが可能に なっています。 <!--Virtual memory: the detailed version--> 仮想メモリ:詳細バージョン 前章では、実際にちょっと話を単純化しすぎてしまいました。確かに、プログラム はメモリを、巨大で平板なアドレス領域であり物理メモリよりも大きなもの である、と認識していて、その幻想を支えるものとしてディスクスワッピングが 利用されているというのは本当です。しかし、現実には、ハードウェアは異なる 五種類もの メモリを持っていて、プログラムが最高速度で実行されるようチューニングしなけれ ばならない場合は、この五種類のメモリ間での違いは、非常に重要な問題となるの です。マシン内で何が起こっているのかを本当に理解するためには、これら 全体がどういう仕組みで動いているのかを知らなければなりません。 五種類のメモリとは、次のようなものです:プロセッサのレジスタ、内部(もしくは、 オン・チップ)キャッシュ、外部(もしくは、オフ・チップ)キャッシュ、 メインメモリ、およびディスクです。これだけの種類のメモリが存在することの理由 は、単純です。スピードを上げるにはお金がかかるからです。上記五種類のメモリは、 アクセス時間の短い順番、コストの高い順番に並んでいます。レジスタメモリは、 最速かつ最も高価なものであり、一秒間に十億回くらいランダムアクセスが可能です が、ディスクは最も低速かつ安価であり、一秒間に百回くらいのアクセスしか できません。 以下に、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)internal cache外部キャッシュ (external cache)external cache およびメインメモリ) は、基本的に、両者のギャップを埋めるために存在しています。 Linux とその他の Unix には、仮想メモリと呼ばれる機能が備わっています。 仮想メモリとは、オペレーティングシステムが実際に搭載しているメインメモリ 以上のメモリを持っているかのように振舞うということを意味しています。 実際の物理メインメモリは、より大きな「仮想」メモリ空間の窓、もしくは キャッシュのように振る舞い、仮想メモリの大部分は実際にはスワップエリア と呼ばれるディスク上の領域に保持されます。ユーザプログラムからは見えない ところで、OS は、データブロックをメモリとディスクの間で移動させ、 この幻想を維持しています。その結果、仮想メモリは、実メモリよりも ずっと大きいが、それほど遅くはないメモリとして機能するわけです。 仮想メモリが物理メモリと比べてどの程度低速になるかというのは、 オペレーティングシステムのスワッピングアルゴリズムが、どれだけ プログラムによる仮想メモリの利用方法に適合したものになっている かということで決まります。幸なことに、一定の時間間隔で見ると、メモリの 読み出しと書き込みは間を置かずになされることが多いため、場所的に見た 場合でも、メモリの読み書きはメモリ空間内の特定の場所に集中するという 傾向があります。 この傾向は、ローカリティ (locality) 、もしくはより正式には リファレンスのローカリティ (locality of reference) locality と呼ばれています。 これは都合のいいことです。メモリへの参照 (reference) が仮想メモリ空間 内の様々な場所にランダムに行われるなら、通常は、新しい参照のたびごとに ディスクに対する読み出しや書き込みが行われなければならず、仮想メモリは ディスクと同じくらい低速になってしまうでしょう。しかし、プログラムと いうのは一定の場所で読み書きを行うという強い傾向 (locality) を 示すものなので、メモリへの参照がある場合でも、オペレーティングシステムは スワップを行うことが比較的すくなくて済みます。 これは、経験則なのですが、最大公約数的にみて最も効率のよいメモリ利用 パターンというのは非常にシンプルなものです。その方法は、LRU もしくは 最長時間未使用アルゴリズム ("least recently used" algorithm) と 呼ばれています。仮想メモリシステムは、必要に応じて、ディスクブロックを メモリの ワーキングセット (working set) working set として取り込みます。ワーキングセット 用の物理メモリが足りなくなったら、最長時間未使用のブロックをディスクに 書き出してしまいます。すべての Unix や、仮想メモリを使うその他の オペレーティングシステムの大部分は、この LRU にいくらかの変更を加えた アルゴリズムを使っています。 仮想メモリは、ディスクとプロセッサのスピードの違いを調整する第一の 連環となっています。これは、OS が明示的に管理しています。しかし、 物理メモリのスピードと、プロセッサがそのレジスタメモリにアクセスする スピードとの間には、まだ大きなギャップがあります。外部と内部のキャッシュ は、これを埋めるものであり、そのために上記で述べた仮想メモリとよく似た テクニックを使っています。 物理メインメモリがディスクスワップ領域に対する一連の窓やキャッシュのように 振る舞っているように、外部キャッシュもメインメモリに対する窓のように 振る舞います。外部キャッシュは、高速 (100M よりも速い 秒間 250M アクセス)で、 容量の小さいメモリです。ハードウェア (特に、コンピュータのメモリコントローラ) は、LRU の方法を使って、メインメモリから取ってきた一連のデータをもとにして、 外部キャッシュ内のデータを管理します。歴史的な理由で、キャッシュ スワッピングの単位は、ページ (page) ではなくライン (line) と呼ばれています。 しかし、これで話が終わったわけではありません。内部キャッシュが、 外部キャッシュの一部をさらにキャッシュすることで、アクセス速度の底上げの 最終段階を担当しています。この内部キャッシュは、さらに高速で容量の小さいメモリ です。事実、これはプロセッサチップのすぐ側に置かれています。 読者がプログラムを本当に速くしたいと思うなら、こうした細かい事柄を知って おくことが有益です。プログラムは、ローカリティが強いほど高速になります。 キャッシュがより効果的に働くからです。それゆえ、プログラムを速くする一番簡単な 方法は、プログラムを小さくすることです。プログラムが多くのディスク I/O の ために動きが鈍くなったり、ネットワークイベントを待ったりしなくても すむ場合、それは、通常、システム内で許容されている最大のキャッシュ効果を ともなったスピードで実行されるはずだからです。 プログラム全体を小さくできない場合は、スピードに関係する部分をチューニング するようにして、強いローカリティを発揮するようにすれば報われるでしょう。 そうしたチューニングに関するテクニックの詳細は、この文書の範疇を越えます。 読者がそれらを必要とする頃には、コンパイラにかなり精通しているはずなので、 そうした方法はおのずと理解できるはずです。 <!--The Memory Management Unit--> メモリ管理ユニット (memory management unit) 充分な容量のコアがあり、スワッピングを避けられるときでも、 メモリ管理 (memory management) と呼ばれるオペレーティング システムの一部は、重要な役割を果たしています。確認しておきたいのは、 プログラムは自分のデータセグメントしか変更できないということです。 すなわち、あるプログラムの中の不具合のあるコードや悪意を持って作られた コードが、他のプログラムのデータセグメントにデータを吐き出すことは 出来ない仕組みになっているということです。これを実現するために、 メモリ管理機構では、データセグメントとコードセグメントの一覧が書かれた テーブルを保持しています。 このテーブルは、プロセスが追加のメモリ領域を要求したり、それまで使っていた メモリ領域を開放する(通常、これはプロセス終了時に起こります)たびに、 更新されるようになっています。 オペレーティングシステムのメモリ管理機構は、このテーブルを使って、 MMUMMU もしくは メモリ管理ユニット (memory management unit) memory management unit と呼ばれる、下位層のハードウェアにある特別な箇所にコマンドを渡しています。 現代のプロセッサチップには、複数の MMU が内蔵されています。 MMU は、メモリ領域を保護するための特別な機能を持っているので、越境的な メモリ参照は拒否されるとともに、その際には特殊な割り込みが発生するように なっています。 今までに、"Segmentation fault" や "core dumpd" といったメッセージを見た ことがあるなら、まさに、そうした越境的なメモリ参照が起こったということを 意味します。実行中のプログラムが自分以外のセグメントにメモリアクセス しようとすると、致命的な割り込みが起きるのです。これは、プログラムに バグがあることを意味しています。MMU が残す core dumpcore dump は、プログラマがそのバグを追跡するのを支援するための 診断情報なのです。 プロセスの相互干渉の防止は、プロセスがアクセスできるメモリ領域を分離する こと以外に、さらに別の観点からもなされています。読者は、上記以外にも、 ファイルへのアクセス制御が出来るようにして、バグのあるプログラムや悪意を 持ってつくられたプログラムがシステムの重要ファイルを破壊できないように したいと思うことでしょう。 Unix が、ファイルパーミッションという 仕組みを持っているのは、このためです。これについては、後ほど説明します。 <!--How does my computer store things in memory?--> コンピュータは情報をどのようにメモリに保持するのか? コンピュータ上では、すべてがビット列として保存されていることは 御存知だと思います(二進数というのは、一連の小さなオン・ オフのスイッチと考えることができます)。 ここでは、そうしたビットによって、コンピュータ上で扱われる 文字や数字がどのように表されているのかを説明します。 説明に入る前に、まずコンピュータのワードサイズ (word size) word size について 理解する必要があります。ワードサイズとは、そのコンピュータ上で利用されて いる、情報をやり取りするための単位のことです。技術的に言うと、これは プロセッサのレジスタ (registers) 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 などの 旧式のマシン用のプログラムが大容量メモリを管理するのに、かなり無理なこじつけ を使わなければならないのはそのためです。ここではそうした こじつけの仕組みについては述べませんが、年輩のプログラマは、いまだにこれが 原因でうなされていたりします。 <!--Numbers-->数字 整数は、プロセッサのワードサイズに応じて、ワードか、もしくはワードのペア によって表現されています。32 bit マシンでは、ワードが最も基本的な整数の 表現単位となっています。 整数の計算は、二進数の計算と似ていますが、実際は少し異なります。 純粋な二進数では、下位ビットから順に 1, 次に 2, そして 4 等を 表しますが、正負の符合付き数字は、2 の補数 (twos-complement) twos-complement という表記で表されます。最上位ビットは、符合ビット (sign bit)sign bit であり、このビットを使って負の値がつくられます。そして、負の数字はすべて 、正の数字のビットを全部逆転させて、それに 1 を加えることで変換可能です。 32 bit マシン上で整数の値の幅が、-2^31 から 2^31 -1 (ここで、^ の 記号は、べき数の演算子を表し、2^3 = 8 です) までしかないのは、そのため です。32 bit 目のビットは、正負符合用に使われるのです。 コンピュータ言語のなかには、符合なし演算 (unsigned arithmetic) unsigned arithmetic ができるものもあります。その場合は、ゼロと正の数だけを使った 通常の二進数演算となります。 大部分のプロセッサやコンピュータ言語のいくつかは、浮動小数点 (floating-point)floating-point 数での処理が可能です(この機能は、最近の(PC 用)プロセッサ チップなら、すべてに組み込まれています)。浮動小数点数を使うと、整数よりも 広い範囲の値を扱うことができるだけでなく、分数も表現できるように なります。浮動小数点計算を実現する方法は、様々な種類がありますし、 ここで解説するにはちょっと複雑すぎるのですが、大まかに言うと、 いわゆる「科学的な表記法」といわれるもの、すなわち 1.234 * 10^23 のような 表記法と非常によく似ています。数字をコーディングする際は、 仮数 (mantissa)mantissa (すなわち 1.234) と、10 のベキ数としての指数部分 (すなわち 23) とに分離されます。(つまり、ここでの仮数は、小数点以下に 3 桁あるので、 この場合の計算結果は、その数字のあとに 0 が 20 個付くわけです。) <!--Characters-->文字 文字は、通常、ASCII (American Standard Code for Information Interchange) と呼ばれるコーディングにしたがった 7 bit の並びとして表現されます。 現在のマシンでは、128 個の ASCII 文字のそれぞれが、オクテット (octet)octet もしくは 8-bit のバイト (byte) の下位 7 bit を使って表されています。オクテットは、 メモリのワード単位にまとめられるので、たとえば 6 字の文字列の場合、 多くとも 2 メモリワード分の場所しか取りません。この ASCII 文字のコード表を 見るには、Unix プロンプト上で `man 7 ascii' と打ってください。 とはいえ、上記の段落は、ふたつの点で誤解を招くかもしれません。まず、 ひとつ目は、やや細かいことですが、オクテットという用語です。 これは、正式には間違ってはいませんが、実際にはほとんど使われていません。 大部分の人は、オクテットを バイト(byte) 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 ホームページ を御覧ください。 <!--How does my computer store things on disk?--> コンピュータはどのようにディスクに情報を保存するのか? Unix 上のハードディスクには、いろいろな名前がついたディレクトリやファイルが 階層的に並んでいるのをご存知だと思います。通常はそれ以上深く探求する必要は ないのですが、ディスクがクラッシュして内部のファイルをなんとか復活させる 必要が生じた場合には、その水面下で何が起こっているのかを知っていることは 非常に有益になります。残念ながら、ディスクの仕組みについて普段目にしている ファイルのレベルから段々詳しく説明するような分かりやすい方法がないので、 ここでは、ハードウェアレベルの説明から始めて、じょじょに日常的な操作の 方に話をもっていくようにしたいと思います。 <!--Low-level disk and file system structure--> 低レベルでのディスクとファイルシステム構造 ディスクの表面というは、データが保持される場所なわけですが、ここは、 ダーツの的のように幾つかの構成部分に分割されています。同心円状の トラックが何本も走っていて、それらのおのおのが、パイを切り分ける ときのようにセクタに分けられています。ディスクの外周に近いトラックの ほうが中央のスピンドルに近いトラックよりも領域が広いので、外周の トラックは、内周のトラックよりも数多くのセクタに分割されています。 個々のセクタ (もしくは、ディスクブロック (disk block)disk block とも言います) は、同一サイズとなって います。これは、現在の Unix 系 OS では、一般的には、1 バイナリ k (1024 8-bit ワード) となっています。それぞれのディスクブロック には、固有のアドレス、言いかえると ディスクブロック番号(disk block number)disk block number が付けられています。 Unix は、ディスクをディスクパーティション (disk partition) disk partition へと分割します。個々のパーティションは、連続したディスクブロック から成る一定のディスク領域であり、そうしたパーティションは、 他のパーティションとは全く独立した領域として、ファイルシステムか スワップスペースとして扱われます。パーティションを分割する もともとの理由は、まだディスクのアクセス速度が今よりずっと遅く、 エラーも多かった時代において、ディスククラッシュに対処するため でした。各パーティションの間に境界を設けることによって、 ディスクの一部が、その中にランダムに発生する不良個所によって、 アクセスできなくなったり、破壊されたりする可能性を軽減していた のです。現在、パーティション分割は、より重要性を増しています。すなわち、 (悪意を持った侵入者が重要なシステムファイルを書き換えてしまう ことを防止するために) 特定のパーティションを読み出し専用にしたり、 この文書では触れないような各種の手段によって、ネットワーク越しに 共有可能に設定したりできるからです。最も若い番号が付いたパーティション は、たいてい、ブートパーティション (boot partition) boot partititon として 特別な扱いを受けます。ブートパーティションとは、起動すべきカーネルを 置くことができるパーティションのことです。 個々のパーティションは、(仮想メモリを実装する ために利用される) スワップスペース (swap space)swap space か、ファイルを保持するために利用される ファイルシステム ( file system)file system かのどちらかです。スワップスペースのパーティション は、連続した一連のディスクブロックとして扱われます。それとは異なり、 ファイルシステムには、ファイル名をディスクブロックに対応付ける仕組みが 必要です。ファイルは時間の経過とともにサイズが増減したり中身が変わったり するので、ファイルのデータブロックというのは、連続して並んでいる わけではなく、該当するパーティション全体に散らばっていることが よくあります(オペレーティングシステムは、使用中でないディスクブロックを それがパーティション中のどこにあるかには関係なく使うように出来ている からです)。このようにディスクブロックが各所に散らばった場合の 症状は フラグメンテーション (fragmentation) と呼ばれています。 <!--File names and directories--> ファイル名とディレクトリ 各ファイルシステム内では、ファイル名とディスクブロックとの対応関係は、 i-nodei-node と呼ばれる構造体 (structure) を媒介にして処理されています。こうした事柄に関する 情報は、ファイルシステムの先頭部分 (小さな番号のディスクブロック群) に 保存されています(実際に最小の番号を持つディスクブロック群は、ここでは説明は しませんが、情報の整理とラベリングの目的で利用されます)。ひとつの i-node が ひとつのファイルの情報を記述するようになっています。(ディレクトリを 含む) ファイルのデータブロックは、i-node よりも上の領域 (大きな番号を もつディスクブロック上)に置かれています。 個々の i-node には、必ずそれが記述するファイルに属するディスク ブロックの番号のリストが含まれています。(これは、実際には小さなファイル の場合にしか当てはならないのですが、例外に関する詳細はここでは 重要性を持っていません。) 注意してほしいのは、i-node には、ファイル名に ついての情報は含まれないということです。 ファイル名は、ディレクトリ構造体 (directory structure) directory structures のなかにあります。すなわち、 ディレクトリ構造体が、ファイル名を i-node に対応付けているわけです。 Unix では、ひとつのファイルが正式なファイル名を複数持つことが 可能になっている(これは、 ハードリンク (hard links)hard links とも言います) のは、このためです。 それらは複数のディレクトリエントリであり、全部が同一の i-node をポイントしています。 <!--Mount points-->マウントポイント 最も単純な構成では、Unix ファイルシステムの全体は、たったひとつの ディスクパーティションに格納することができます。こうした構成を 小さな個人用の Unix システム上でご覧になることがあるとしても、 この構成は一般的なものであるとはいえません。むしろ、複数の ディスクパーティションにまたがって構成されるのが普通です。 それゆえ、たとえば、小さなパーティション上にカーネルを置いて、 少し大きめのパーティションに OS のユーティリティを置き、 さらに非常に大きなパーティションをユーザのホームディレクトリに するといった構成が考えられます。 システムの起動直後にアクセスするパーティションは、 ルートパーティション(root partition) root partition だけです。起動の際は、(ほぼ、必ず)ここから起動されるという場所です。 ここには、ファイルシステムのルートディレクトリがあり、他のすべての 起点となる最上位の階層(top node)です。 複数のパーティションを持つファイルシステム全体へのアクセスを可能にするには、 ファイルシステム内にあるそれ以外のパーティションを、このルートパーティション に付け加えていかなければなりません。起動プロセスのだいたい中ほどで、Unix はこうしたルート以外のパーティションをアクセス可能にします。その際、Unix は、そうしたパーティションのおのおのを、ルートパーティションにある ディレクトリ上に マウント(mount)mount します。 たとえば、/usr というディレクトリが Unix 上にある 場合、おそらくここはマウントポイントであり、インストールされたプログラム のうち、システムの起動に必要のないものを置いているパーティションが マウントされる場所になっているはずです。 <!--How a file gets looked up--> ファイルの問い合わせの仕組み ここまでの説明で、上部構造から下部構造を眺める準備ができました。読者が ファイルを開くとき (たとえば、/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 が ディレクトリではなく、そのファイルに関連付けられたディスクブロックの リストを保持しています。 <!--File ownership, permissions and security--> ファイルの所有者、パーミッション、セキュリティ プログラムが、事故や他人の悪意ある行為によって、本来アクセスできない はずのデータ領域に侵入するのを防ぐために、Unix には、 パーミッション (permission) permission という機能があります。この機能はもともと、 昔まだ Unix が主に高価なミニコンピュータとして大勢で共有して利用されて いた頃、同一マシン上の複数のユーザが相互に干渉しないようにすることで、 時分割方式をサポートするために設計されました。 ファイルのパーミッションを理解するには、 ログインしたときに何が起こるか?のセクションにあった ユーザとグループの説明を思い出す必要があります。個々のファイルには、 それを所有するユーザとグループとがあります。それらは、当初 そのファイルの作成者のユーザ名やグループ名が付けられますが、 chown (1)chown (1) や chgrp (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 をもとにして、名前が付けられていることでしょう。 The string `-rw-r--r--' represents the permission bits for the file. The very first dash is the position for the directory bit; it would show `d' if the file were a directory. After that, the first three places are user permissions, the second three group permissions, and the third are permissions for others (often called `world' permissions). On this file, the owning user `esr' may read or write the file, other people in the `users' group may read it, and everybody else in the world may read it. This is a pretty typical set of permissions for an ordinary data file. ]]> 文字列 "-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 bitsetuid bit と呼ばれる特別なパーミッションを表しています。 setuid bit というのは、一般に、特殊なプログラムに対して付与される パーミッションです。すなわち、root の権限を、一定の制限付きで一般ユーザにも 与える必要のあるプログラムがそれに該当します。setuid bit が 実行プログラムにセットされた場合、ユーザは、そのプログラムファイルの所有者と 同じ権限を行使できるようになるのですが、他方で、ユーザがその所有者であるか 否かに関らず、そのプログラムはユーザ自身の名で実行されるというものです。 root アカウント自体と同様、setuid されたプログラムも便利ではありますが、 危険です root が所有者である setuid されたプログラムを破壊したり 変更したりできるひとならだれでも、そのプログラムを使って root 権限 を持ったシェルを起動することができるからです。それゆえ、大部分の Unix システムでは、何か書き込むためにそのファイルを開いた時点で、 setuid bit が自動的に無効になるようになっています。Unix セキュリティ に対する攻撃の多くが、setuid プログラムを破壊するために、setuid プログラムのバグを悪用しようとしています。したがって、セキュリティ意識 を持っているシステム管理者は、そうしたプログラムに特に慎重になって いて、出来たばかりの(バグが除去されていない)プログラムはインストール したがりません。 これまでパーミッションについて解説したことの詳細に関して、いくつか 重要なことがあります。すなわち、所有グループとパーミッションは、 そのファイルが作成された際にどのような方法で割り当てられるのか ということです。ユーザは複数のグループのメンバーとなることが できるので、このグループの割り当ては問題ではあるのですが、 そのひとつは、(/etc/passwd ファイル内の そのユーザのエントリで指定されている)ユーザの デフォルトグループ (default group)default group が割り当てられ、ユーザによって 作成されたファイルは、通常そのグループが所有することになります。 The story with initial permission bits is a little more complicated. A program that creates a file will normally specify the permissions it is to start with. But these will be modified by a variable in the user's environment called the umaskumask. The umask specifies which permission bits to turn off when creating a file; the most common value, and the default on most systems, is -------w- or 002, which turns off the world-write bit. See the documentation of the umask command on your shell's manual page for details. ]]> パーミッション bit の先頭 bit の話は、もうすこし複雑です。なんらかの ファイルの作成を伴うプログラムは、通常、そのファイルの初期設定として のパーミッションを指定します。しかし、それらは、 umaskumask と呼ばれるユーザの環境変数によって変更されてしまいます。 umask は、ファイルを作成する際にどのパーミッション bit を 無効にするかを指定するものです。最も一般的な値であり、 たいていのシステム上のデフォルトとなっているのは、------w- もしくは、 002 です。これは、誰でも書き込みが出来るという権限を無効にする ものです。これに関する詳しい説明は、読者の shell の man ページ にある umask コマンドに関する文書を御覧ください。 ディレクトリグループの初期設定というのも、やや複雑です。Unix のなかには、 新規ディレクトリのグループには、それを作成したユーザのデフォルトグループ を当てるものがあります(これは、System V 系の慣習です)。それ以外にも、 作成されたディレクトリの親ディレクトリの所有グループが割り当てられる ものもあります(これは、BSD の慣習です)。Linux を含む現在の Unix のなかには、 set-group-ID をそのディレクトリに設定する(chmod g+s)ことによって、 後者のように動作するよう設定できるものがあります。 <!--How things can go wrong-->調子が悪いというのは どういうことなのか 以前、ファイルシステムは非常に繊細であるということを漠然といいましたが、 いまの時点では、ファイルに到達するには、ディレクトリと i-node を 参照しながら、どれだけ長いか分からない連鎖を辿っていかなければならない ということが分かっていると思います。では、ハードディスクに不良箇所 が発生した場合、これはどうなるでしょうか? 幸運に恵まれれば、ファイルデータのいくつかをダメにするだけで済みます。 そうでない場合は、ディレクトリ構造や i-node 番号を破壊してしまって、 システム内の特定のサブツリー以下がお釈迦になってしまうかもしれません。 さらに、ファイル構造自体が壊れてしまって、雑多なアクセスが 同一のディスクブロックや i-node に集中してしまったりするかも しれません。そうした不具合は、通常の操作を続けていると、 どんどん広がっていって、最初の不良箇所にあったデータが システム全体にばら撒かれてしまうかもしれません。 幸いにも、この手の偶発事故は、ディスクのハードウェアの信頼性が高まった ことで、ほとんど生じなくなりましたが、それでも、やはり、Unix では、 定期的にファイルシステムの整合性チェックを行なって、何も異常がない ことを確認する必要性があります。現在の Unix は、個々のパーティション に対して、起動時のマウント直前にすばやく整合性のチェックを行うように なっています。何回かに一度は、もう数分時間がかかる完全なチェックを 行なう仕組みになっています。 こうしたことを読んで、Unix は恐ろしく複雑で不具合が起きやすいかのよう に思えたなら、こう考えてください。これらの起動時のチェックは、 通常はよくあるような問題を検出・修正して、それが本当の大惨事に いたるのを防いでいるのです。こうした機能を持たない他のオペ レーティングシステムの場合、確かに起動は高速になりますが、実際に 手動で復旧しなければならなくなった時は、問題がこじれてしまって 深刻な状態になっていることがあります(とはいえ、これは、そもそも Norton Utility かそれに類するツールを持っている場合ですが...)。 最近の Unix の設計上のトレンドのひとつに、 journalling file systemsjournalling file systems があります。 これは、ディスクとのやり取りを調整することで、常に整合性が 保たれることを保証し、システムが起動するときに復旧が 可能になるようにする仕組みです。これは、起動時の整合性チェックを かなり高速化することができます。 <!--How do computer languages work?--> コンピュータ言語はどのような仕組みで動いているのか プログラムを実行する仕組み については、すでに説明しました。あらゆるプログラムは、最終的に、 一連のバイト列、すなわちコンピュータの マシン言語 (machine language)machine language で表現された命令として実行されなければなりません。 しかし、人間は、こうしたマシン言語は上手く扱えません。 そうしたことはもう極めて稀になり、ハッカーの間でも 黒魔術化しています。 カーネル自体の内部にあるハードウェアインターフェイスを直接サポートする少量の 部分を除くと、ほぼ全ての Unix コードは、今日では、 高水準言語 (high-level language) high-level language で書かれています。(この用語の「高水準」という部分は、古くさい言葉で、 「低水準」の アセンブラ言語 (assembler languages assembler languages と区別する意味で 使用されるものです。アセンブラ言語とは、基本的にマシン語を若干分かり易く したような言語のことです)。 高水準言語には、いろいろな種類があります。こうしたプログラム言語について 話す際は、プログラムのソースコード (source code) source code (すなわち、人間が作成したもので、変更が可能なコード) は、何らかの変換処理を 経ることで、マシンが実際に実行できるマシンコードに変換される必要がある のだということを是非頭の片隅で覚えておいてください。 <!--Compiled languages--> コンパイル型の言語 コンピュータ言語の中で最も伝統的なのが、コンパイル型言語 (compiled language)compiled language です。コンパイル型言語は、その名の通り コンパイラ (compiler)compiler と呼ばれる特別なプログラムによって、実行ファイルである バイナリ形式のマシンコードへと変換されます。一旦バイナリが生成されると、 再度ソースファイルを参照せずとも、直接そのバイナリを実行することが できます。(大部分のソフトウェアは、コンパイルされたバイナリ形式で配布され、 その元になっているコードを見ることはできないのが一般です。) コンパイル型言語は、パフォーマンスに優れ、OS リソースへのアクセスも最も効率 よく行うことができますが、プログラムするのが難しいという傾向があります。 C 言語は、Unix を記述している言語であり、(その派生言語である C++ とともに) コンパイル言語のなかで圧倒的な重要性を持っています。FORTRAN もまたコンパイル 型の言語であり、いまでもエンジニアや科学者の間では利用されていますが、 C よりも古く、旧式なものです。Unix の世界では、それ以外のコンパイル言語は、 メインストリームとしては利用されていません。それ以外では、COBOL が、 金融やビジネス用ソフトウェアとして広く利用されています。 以前は、いろいろなコンパイル型言語がありましたが、ほとんどはすたれたか、 研究用のツールとしてしか使われなくなっています。読者がコンパイル言語を 使う新人の Unix 開発者なら、使用言語は C か C++ であることがほとんどです。 <!--Interpreted languages--> インタプリタ型言語 インタプリト型言語 (interpreted language) interpreted language とは、ソースを解釈する プログラム (interpreter program) を必要とする言語です。そのプログラムが、 実行の際そのソースコードを読み込んで、演算やシステムコールが可能な形式に 変換する仕組みになっています。この場合、コードが実行されるたびに、( ソースを解釈するプログラムが起動されて、) ソースコードの変換が行われ なければなりません。 インタプリタ型言語は、コンパイル型言語よりも実行速度が遅く、下部階層にある オペレーティングシステムやハードウェアへのアクセスも制限されることが多いの 傾向があります。その反面、コンパイル型言語に比べてプログラムが容易で、 コードにエラーがあっても深刻な事態になりにくかったりします。 シェルや bc(1), sed(1), awk(1) を含む多くの Unix ユーティリティは、かなり 小型のインタプリタ型言語です。BASIC も通常はインタプリタ型であり、 Tcl もそうです。歴史的に見て、最も重要なインタプリタ型言語は、LISP です(何世代もの開発者たちにより、大幅な改善がほどこされてきました)。 現在では、Emacs エディタ内で使用できるシェルと LISP が、おそらく 最も重要な純粋インタプリタ型言語であると思われます。 <!--P-code languages-->P-code 言語 1990 年以来、コンパイルとインタプリタの両方を使う一種のハイブリッド言語 の存在が重要になってきています。P-code 言語というのは、ソースコードが コンパクトなバイナリコードに変換されて、それが実際に実行されるという点では コンパイル型言語と似ていますが、変換されたコードの形態がマシンコードでは ないという点で異なります。それは、疑似コード (pseudocode) pseudocode (もしくは p-codep-code) であり、これは実際のマシンコードよりもシンプルかつパワフルなものです。 プログラムの実行時には、この p-code が (インタプリタにより) 変換されます。 p-code は、コンパイル型言語のバイナリとほぼ同じくらいの実行速度を持ちます (単純かつ小型で、スピーディな p-code インタープリタを作成することが できるからです)。つまり、p-code は、柔軟かつパワフルなインタプリタ型言語 の長所も保持しているわけです。 重要な p-code 言語としては、Python, Perl, および Java があります。 <!--How does the Internet work?--> インターネットはどのような仕組みで動いているのか インターネットがどのような仕組みで動いているのか理解しやすくするために、 ここでは、典型的なインターネットの操作、すなわち、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 についての公開ディレクトリ 上に置かれているということを意味します。 <!--Names and locations-->名前と場所 ブラウザがまず初めにしなければならないことは、その文書が置かれた マシンとの間にネットワークコネクションを確立することです。それをするには、 第一に、そのホスト (host) host である www.linuxdoc.org の あるネットワーク上の場所を探す必要があります(ここで「ホスト」とは、 「ホストマシン」、もしくは「ネットワークホスト」の略です。 www.linuxdoc.org という名前は、ごく一般的な ホスト名 (hostname)hostname です)。通信の際の場所の指定は、実際には、 IP アドレス (IP address) IP address と呼ばれる番号が使用されます(IP アドレスという用語の IP の部分 については、後ほど説明します)。 IP アドレスを知るには、ブラウザは ネームサーバ (name server) name server と呼ばれるプログラムに問い合わせをします。ネームサーバは自分のマシン上に あってもよいのですが、たいていの場合は専用のマシン上で動いているので、 それに問い合わせを送ることになっています。ISP と契約したとき、 そのセットアップ手順のなかで、ISP のネットワーク上にある ネームサーバの IP アドレスを、手持ちのインターネットソフトウェアに指示する という手順がきっと含まれていたはずです。 異なるマシン上で動くネームサーバは、お互いに通信をし、ホスト名解決 (すなわち、ホスト名を IP アドレスに変換する処理) に必要なすべての情報を交換しあい、つねに最新の状態に保っています。 ネームサーバは、www.linuxdoc.org というホスト名解決の過程で三度か 四度ネットワークごしに存在する他のサイトに問い合わせをしますが、 これは通常非常に高速に行われます(一秒以下です)。ネームサーバの 詳細については、次章で概観します。 ネームサーバはブラウザに対して、www.linuxdoc.org の IP アドレスが 152.19.254.81 であることを教えます。これを知ることによって、 読者のマシンは、www.linuxdoc.org との間で直接情報交換ができる ようになります。 <!--The Domain Name System--> ドメインネームシステム (domain name system) プログラムとデータベースを連携させてホスト名を IP アドレスに変換する 仕組み全体は、DNS (Domain Name System) と呼ばれています。"DNS サーバ" と呼ぶ場合、これは単に「ネームサーバ」と呼ぶのと同じ意味で使われます。 ここでは、そのシステム全体の仕組みについて説明します。 インターネットのホスト名は、ドットで区切られたいくつかの部分から 構成されています。ドメイン (domain) domain とは、ホスト名の末尾部分が同一 であるマシンの集合のことです。ドメインは、他のドメインの内部にも 存在することができます。たとえば、www.linuxdoc.org というマシンは、 .org というドメインの中の .linuxdoc.org というサブドメインに 存在しています。 個々のドメインは、そのドメイン内の全てのマシンの IP アドレスを記憶して いる 権威あるネームサーバ (authoritative name server)authoritative name server によって規律されています。権威ある(もしくは「プライマリ」) ネームサーバは、故障に備えてバックアップされていることもあります。 セカンダリネームサーバ (secondary 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 アドレスは、そのホストに到達できなくなった場合にも、破棄されます。 <!--Packets and routers--> パケットとルータ ブラウザが実行しようとしているのは、www.linuxdoc.org 上のウェブサーバ に対して次のようなコマンドを送信することです。 GET /LDP/HOWTO/Fundamentals.html HTTP/1.0 以下で、その際に何が起こるのかについて説明します。上記コマンドは、 パケット(packet)packet というビットの塊に分割され、電報の場合のように、 次のような三つの重要事項を付け加えた上でラップされます。その 重要事項とは、送信元アドレス (source address) source address (読者のマシンの IP アドレス)、あて先アドレス (destination address) destination address (これは、152.19.254.81)、およびそれがウェブに関するリクエストであることを示す サービス番号 (service number) service number もしくは ポート番号 (port number)port number (この場合では、80 番)です。 次に、読者のマシンはそのパケットを回線(ISP や ローカルネットワークとの コネクション) 上に送信すると、そのパケットは ルータ(router)router と呼ばれる特別なマシンに届きます。ルータは、インターネットの 地図をメモリ内に記憶しています。これは必ずしも完全な地図ではありませんが、 そのネットワークの周辺地域については完全に記述しているので、 インターネット上の他の周辺ルータに至るべき方法は理解しています。 送信したパケットは、いくつかのルータを通過しながら、あて先へと到達します。 ルータは非常に上手く出来ています。ルータは、パケットを受信した旨の 送達確認を他のルータから受け取るまでの経過時間を監視しています。また、 その経過時間に関する情報を使って、空いているリンク上にパケットを流す ようトラフィックを調整しています。また、ルータはその情報を使用して、 他のルータやケーブルが原因でネットワークに支障が生じた際に、 別のルータを探すことで可能な限り埋め合わせをしようとします。 インターネットは核戦争に耐えられるように設計されたという噂が都会で暮らす 人々の間でささやかれていま。これは真実ではないのですが、しかし、インター ネットの設計というのは、この不確かなことが多い世界において、頼りにならない ハードウェアからでも信頼に足るパフォーマンスを引き出してくれるだけの 極めて優れた設計思想のもとに構築されています。 このことは、まさに次の事実によっています。 すなわち、(電話回線網のように)少数の巨大で脆弱な交換機に処理を集中 させるのではなく、無数のルータにそうした処理の役割を分散したという ことです。つまり、障害はあくまで局地的なものに留まるようになっていて、 ネットワークはそうした障害を避けてルーティングすることができるのです。 パケットが一旦あて先のマシンまで到達したなら、そのマシンはサービス番号を 使って、そのパケットをウェブサーバに渡します。ウェブサーバは、そのコマンド パケットの送信元 IP アドレスを見ることで、どこに返答を返せばよいのかを 知ることができます。上記の例で、そのウェブサーバがこの文書を送信する際は、 文書はいくつかのパケットへと分割されます。パケットのサイズは、ネットワーク 上の伝送方法やサービスの種類によって異なります。 <!--TCP and IP-->TCP と IP こうした分割されたパケットを使う通信方法の処理のしかたを理解するには、 インターネットは実際にはふたつのプロトコルを使用していて、一方が他方の 上に積み重ねられているという通信の仕組みを知る必要があります。 下位層である IP IP (Internet Protocol) は、送信元アドレスからあて先アドレスに個別のパケットを到達させるための 方法は知っています(これらが IP アドレスと呼ばれるのはそのためです)。 しかしながら、IP は信頼性に欠けています。パケットが途中で行方不明に なったり消失したりしても、送信元とあて先のマシンは決してそれに 気づかないでしょう。ネットワーク用語では、IP は コネクションレス (connection less) なプロトコルです。 送信者はパケットを 受信者に対して送るだけであり、受信側からの確認応答は期待できません。 しかし、IP は、高速かつ安価です。高速かつ安価であれば、信頼性がなくとも 問題ないという場合もあります。Doom や Quake をネットワーク上でプレイする ときは、弾丸のひとつひとつも IP パケットで送られています。弾丸の2,3 発 消失してしまったとしても、特に問題はないはずです。 上位層の TCPTCP (Transmission Control Protocol) は、信頼性のためのプロトコルです。 二台のマシンが相互に交渉して TCP コネクションを張り (これは、IP を使って 行われます)、受信側が受信したパケットについての到達確認を送信側に 送るようになっています。送信側が一定時間内にあるパケットの送達確認を 受け取らない場合は、そのパケットを再送します。さらに、送信側は、個々の TCP パケットに通し番号を振っているので、受信側ではそれを使って、パケットの 到着順序が狂った場合でもそうしたパケットを並べ直すことができるように なっています。(これは、ネットワーク上の経路が、コネクションの最中に 変動した場合には、よく起こる現象です。) TCP/IP パケットにはチェックサム (checksum) という情報も含まれていて、 経路上に障害が生じてデータが壊れてしまったりしていないかを検出できる ようになっています。 (チェックサムは、まずチェックサム自体を除いたパケットの残りの情報から算出 されます。そうしておくと、パケットの残りの部分かチェックサム自体が(経路上で) 壊れてしまっていた場合は、(到達先で)そのパケットから再度チェックサムを算出 して、もとのチェックサムと比較することで、エラーの有無を高い確率で検出する ことができるわけです)。それゆえ、 TCP/IP とネームサーバというのは、使う側の視点からみると、ホスト名とサービス 番号で区別された二台のホスト間でバイトストリームを伝達しあう方法としては、 信頼に足るものと言ってもいいでしょう。また、ネットワークプロトコルを書く人から すると、このネットワーク階層以下で起こっているパケット分割やパケットの再構成、 エラーチェック、チェックサムの計算、再送といった事柄をほとんど考慮しなくても よくなります。 <!--HTTP, an application protocol--> HTTP : アプリケーションプロトコルの一例 ここで、先ほどの例題に戻りましょう。ウェブブラウザとサーバとは、 アプリケーションプロトコル (application protocol) application protocol を使って 会話をしています。これは、TCP/IP 階層の上位で実行されるプロトコルであり、 TCP/IP をバイト列のやりとりの手段として利用しているものです。上記の例での プロトコルは、 HTTPHTTP (Hyper-Text Transfer Protocol) と呼ばれるもので、そこで使われるコマンドの一例が GET で始まる文字列である ことは既にご覧になった通りです。 上記の GET コマンドが www.linuxdoc.org のウェブサーバにサービス番号 80 番で 到達すると、ウェブサーバはこれを、80 番ポートで待機している サーバデーモン (server daemon)server daemon へと渡します。大部分のインターネットサービスは、 サーバデーモンとして実装されていて、これらは、送られてくるはずのコマンドを 監視し、実行するために、特定のポートでただひたすら待ち受け状態を続けています。 インターネットの設計に関する一大原則があるとするなら、それはすべての部分が できる限りシンプルで人間が見て分かるような形にするということです。HTTP と その親戚 (Simple Mail Transfer Protocol, SMTPSMTP という、ホスト間で電子メールをやりとりするために使われるプロトコルなど) は、キャリッジ・リターン(復帰)とライン・フィード(改行)で終了する、人間が 読んでも意味の分かるシンプルなテキストコマンドを使うという傾向があります。 こうした方法は、かなり非効率です。環境によっては、がちがちに固められた バイナリプロトコルを使ってスピードアップを図ることもできるはずです。 しかし、経験が示すところによると、人間にとって説明や理解のしやすいコマンド を使うことのメリットは、トリッキーで分かりにくいものをわざわざ作ること で得られる僅かな効率の上昇をはるかに上回るものなのです。 したがって、サーバデーモンが 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 であるということをブラウザに伝えています)。 <!--To Learn More--> もっと詳しく知りたい人のために Reading List HOWTO という文書があります。これは参考文献の一覧 であり、これらを読めば、本書で触れたトピックについてもっと詳しく学ぶ ことができます。 How To Become A Hacker という文書も参考になるかもしれません。 日本語訳について 現在のバージョンは、堀田倫英さんの翻訳をベースに Linux Japanese FAQ Project が作成しました。翻訳に関するご意見は JF プロジェクト <JF@linux.or.jp> 宛に連絡してください。 改訂履歴を以下に示します。 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)