同じことは、ディスクの「家計簿情報」にも言える。例えば、あるプログラムが今 特定のディレクトリからファイルを読んだとすると、後にも同じディレクトリから ファイルを読むことは極めてありうる。
ディスクキャッシュ cache は主記憶の一部で、ディスクの一部のブロックを保持 している。あるブロックが要求されると、まずそれがキャッシュにあるか検査し もしあれば低速なディスク操作を避けるようにする。ブロックがキャッシュにな ければ、ディスクから読み込むと同時にキャッシュにもそれを置く。キャッシュ が一杯なら、古いブロックが捨てられる。通常ある種の「least recently used」 (訳注:訳語を忘れた(^^;。「最近一番使われていない」、という位の意味」) 算法が使われる。これは長時間使われていないブロックを捨てる方法である。
高級なキャッシュでは、しばしばある種の「先読み」が行われる。今ファイルの 第Nブロックを読んでいるとすると、直ぐにN+1番目のブロックが必要になるだろ う。そこでCPUの空時間を利用して、ファイルシステムはこのブロックも読んで おく。時にはN+1番のブロックは使われないかもしれない。この場合先読みは無 駄になる;しかし平均的には、このアプローチで節約できる時間は偶に起きる無 駄を上回る。
ディスクを読んでいるだけで書き込まなければ、キャッシュの内容はディスク上 の内容と同一である。書き込みを始めると、そうはいかない。キャッシュの書き 込み操作には2つの一般的な方法がある:
[Windowsを使っていたころは、危なくて遅延書込みを使えなかった。ディスク の異常が、アプリケーションがシステムをクラッシュさせる毎に段々悪くなって 行ったからだ。今ではOS/2を使っており、通常遅延書込みを生かしている。アプ リケーションがおかしくなっても、普通システム全体が落ちることがないからだ。]
驚いたことに、OS/2をきちんと遮断せず、電源を切ってしまう人がいるらしい。 こういった人は遅延書込みを生かしてはいけない。(本当は、こういった人を 計算機がある部屋に入れてはいけない - でもこれは別の話である。) 遅延書き 込みは、保安が速度より重要な場合もオフにすべきである。
システムがロックアップして遮断操作が困難な時(訳注:PMは比較的脆弱なので、 いかれたPMアプリを走らすと偶にこうなる)、Ctrl-Alt-Delを使ってシステムを 止めるべきである。(一度でうまくいかなければ、何秒か待って再度試みられた い。2度目は普通うまくいく。) これは正しい遮断ではないが、少なくとも キャッシュをフラッシュ(訳注:遅延書き込みで溜まっているセクタを書き出す こと)してくれる。
理屈はこの位にして、ユーザが制御できるキャッシュのパラメタを見ていこう。
DISKCACHE=3328,LW,128,AC:CDパラメタの意味は以下のようである。
註釈:多くのOS/2ユーザが、DOSとの互換性のためにFAT区画をおいている。 しかしその場合でももっとも重要なファイルはHPFS区画においている。実際 にはFATのファイルをそんなに頻繁には使わないということである。このよう な場合DISKCACHEは小さめにしておくか、止めてしまうかするのがいいかもし れない。FATの操作は遅くなるが、それより主記憶が空くので他の動作が速く なる。
IFS=F:¥OS2¥HPFS.IFS /CACHE:2048 /CRECL:64 /AUTOCHECK:EFCACHEパラメタはキャッシュサイズをキロバイト単位で指定する。CRECLパラメ タはキャッシュを行う最大レコードサイズをキロバイト単位で指定する。(こ れより大きなデータを一度に読み書きした場合、キャッシュはバイパスされる。) AUTOCHECKは不正な遮断が起きた後で検査すべき区画を指定する。全てのHPFS区 画を指定すべきだ。OS/2をいかれたファイルシステムで動かそうとするのは、 危険であるから。
その行に/F:2のようなパラメタがあるかも知れない。これはCHKDSKの検査レベル を指定する。デフォルトは2であり、これはほとんどの場合最適である。
CACHEコマンドをパラメタなしで実行すると、現在のパラメタを表示する。典型 例を挙げよう:
DiskIdle: 1000 milliseconds MaxAge: 30000 milliseconds BufferIdle: 10000 milliseconds Cache size: 2048 kbytes 3 Lazy write worker(s) are enabled. 1 Read ahead worker(s) are enabled.キャッシュサイズと「ワーカ」の数は意味が明らかだろう。3つのタイミング 値は更に説明しないとよく判らないだろう。
Disk <--> cache <--> buffers <--> application software
オリジナルの8086は通常のメモリに比べ特に速いわけではなかった。そこで キャッシュは必要なかった。(内部的には命令パイプラインを持っており、 それをキャッシュの一種として考えることもできる。しかしそれはわずかに 数バイトに過ぎない(訳注:プリフェッチキュー prefetch queue))。後の 80x86ファミリは高速で、内部キャッシュを持っている。即ち、キャッシュは CPUチップの一部として実装されたのである。CPUが新しいほど、チップ上の キャッシュは大きい。
極めて高速なCPUでは、チップには載せられないくらい大きなキャッシュが 必要である。(おっけぇー、正確には必要というわけではない; でもあるとなしとでは大違いである。) マザーボードのメーカは今では「2 次キャッシュ」を載せており、これはチップ上のキャッシュ(訳注:「1次 キャッシュ」)を大きくる効果がある。チップ上のキャッシュと違うのは、 2次キャッシュはCPUチップでなくマザーボードに載っていることである。 (訳注:内蔵キャッシュの方が、ずっと速い)
[註釈:ハードメーカはしばしば販売している金物を真のマルチタスクソフト ウェアでテストしていない。つまり金物はメーカが思うほど速く動作しないこ とがありうる。その結果、OS/2は時々2次キャッシュを搭載した金物に導入で きないことがある。一旦2次キャッシュを不可にしてOS/2を導入し、その後2 次キャッシュを生かす(BIOS設定で出来る)とうまくいく。]
ディスクキャッシュと違い、CPUキャッシュはソフトウェアでは制御されない。 金物だけで制御されている。必要な金物は全部PCが持っており、ほとんどの ユーザは気にしないでいい。
主記憶を増やすとシステムが遅くなることがある。(普通はメモリを増やすと システムが速くなる。) 何が起きているかというと、追加したメモリがキャッ シュされないのである。この問題を解決するには主記憶全体をキャッシュでき るよう金物を設定する必要がある。典型的には2次キャッシュの容量を増やす ことになる(訳注:TAG RAMも増やさないといけない。)
ページングは別のハードウェアレベルでのメモリ管理法である。これはディス クスワップ(後述する)を主な目的としている。ページングの金物には普通あ る種の保護機構も含まれる。例えばあるページを読みだし専用にする、などで ある。しかしセグメンテーションほど完全な保護はできない。
80x86ファミリで初めてセグメンテーションの金物を備えたのは80286であった (時々8086にもセグメンテーションがあったと言われるが、それは言葉の濫用 というものである。セグメントによるアドレッシングに似たものはあったが、 それは真のセグメンテーションではない。メモリ保護の金物がなかったから だ。) 80386から、セグメンテーションとページングが備わった。OS/2は、 この2つの金物機能を利用するので、OS/2は80386以降を要求するのである。 (訳注:16bit OS/2は80286で動いた)
セグメンテーションの背景にある考えは実の所8086よりずっと古い;しかし金 物のコストの所為で、長年の間それを実現できたのはわずかな計算機だけであっ た。(ページングはもっと乱暴で単純なので、金物設計者からもっと優遇され てきた。) 80286での極めて重要な発明は、設計者がセグメンテーションの 金物をCPUのチップ上においたことである。これで安価で量産できた。
80386のアドレス変換を行うハードは、2段階の変換を行う点で、いささか普 通ではない。プログラム - 即ち機械コード - の中ではアドレスは一対の値 (セグメント番号、セグメント中のオフセット)で表現される。(多くの場 合、機械コードにはセグメント番号を明示的に含めない。デフォルトとして 「現行のセグメント」を利用できるからだ。しかしプログラマはどのセグメ ントを操作対象にしているのか、常に認識していなければいけない。) セ グメンテーションのハードはこのようなアドレスを所謂「リニア・アドレス」 に変換する。次に、ページングのハードがこのリニア・アドレスを一対の値 (ページ番号、ページ中のオフセット)に変換する。このハードには各ペー ジの物理アドレスを示す表があり、この表を参照して最終的に物理アドレス を生成する。命令フェッチの中で、あるいは読み込みアドレスとして主記憶 に渡されるのは、この物理アドレスである。
ちょっと見にはセグメンテーションの金物とページングの金物とは同じ動作を しているように見える。そうだとしたら、両方の金物を備えるのは無駄なこと である。1段階変換で十分同じことができるであろう。しかしながら、幾つか 重要な違いがある:
セグメンテーションの金物も、ページングの金物も参照用の表を主記憶に置い ている。これではいかにもオーヴァヘッドが大きいように思われる。もし、ア ドレス変換が毎度毎度余計なメモリ参照を引き起こすなら、まさにそのとおり だろう。実際にはどちらの金物も、(高速なメモリに)自前のキャッシュを持っ ているので、システムがまともに動く。
スワップはディスクファイルを主記憶の延長のように扱う技術である。これは 以下のように動作する。プログラムのメモリはいくつかの仮想ページと呼ばれ るものから構成される。ページングの金物が仮想ページを物理ページにマップ する。ページ表(ページングの為のアドレス変換表)の各項目には物理ページ 番号だけではなく、いくたりかのフラグが含まれる。そのフラグの一つが、 「物理ページが存在しない」ことを示す。
ソフトウェアが主記憶に物理ページが存在するメモリページだけを使用してい る限り、特別なことは何も起きない。しかし、もしページングの金物が「ペー ジがない」ことを検出すると、「ページフォールト page fault」割り込みが 発行される。割り込みルーチンがこの条件を処理することになる。
ページフォールトには少なくとも2つの原因がありうる。明らかな原因として、 プログラミングの誤りでソフトウェアが不正なアドレスを要求した場合がある。 もちろん、これに対してユーザができることは何もない。単にそのソフトウェア を捨てるしかない。もう少し明らかでない原因として、アドレスは正当だが、ス ワップのソフトウェアがまだ必要なページを主記憶にロードしていないこともあ る。この場合はプログラムは暫時停止され、ディスクからページが読み込まれる ると実行が再開される。
結局、これにより主記憶の実効的な大きさがスワップファイルと呼ばれる特殊 なファイルの大きさ分拡大されることになる。ページング及びスワップの面倒 を見るためのシステムソフトウェアが、主記憶とスワップファイルとの入れ替 えを必要に応じて行う。
OSによっては、スワップファイルの大きさは固定である。OS/2では、CONFIG.SYS にスワップファイルの大きさの初期値を入れるが、スワップファイルは必要に 応じ大きくなったり小さくなったりする。この柔軟性には、それなりの代価が かかる:スワップファイルが拡大する時に、余計なオーヴァヘッドが生ずるの で、システムが突然遅くなる。このオーヴァヘッドを避けるには、スワップファ イルの初期サイズをほとんど拡大が起きることのない位大きくすればよい。
セグメンテーションと保護は80286になって導入された。しかし、設計者はそ こで互換性の問題にぶつかった:ほとんどの現存するソフトウェアは8086用 で、従って80286は現存する8086用のソフトウェアを実行できなければならな かった。そこで使われた解決法はCPUに2つの実行モードを持たせることであっ た。「リアルモード」では、CPUは8086のように動き、セグメンテーションの 金物は停止される。「プロテクトモード」(訳注:protected mode。一般に 流布した表現を採用した)では、新しい保護機能が動作する。
結局の所、80286用のソフトウェアは大して出なかった。DOS/Windows市場が 余りにも優勢だったので、ほとんどの人は80286を8086としてのみ使った。 先進的な機能はほとんど無駄になってしまった。
80386では新たなひねりが加えられた。「リアルモード」「プロテクトモード」 は存在するが、プロテクトモードで特殊なセグメント形式を定義することが可 能になった。それは8086のイミュレータとして働く。この機能のおかげでリア ルモードに戻さなくても、プロテクトモードのOSを走らせ同時にこれら過去の アプリケーションを全て動かすことができた。これこそOS/2がDOS/Windowsア プリケーションを走らす時に使っている「仮想86モード」である。
この機能があれば、最早リアルモードはほとんどいらない。CPUは依然として リアルモードで起動するが、OS/2の初期化ルーチンがほとんど即座にプロテク トモードに変更する。
偶にOS/2のDOSイミュレーション機能をもってしても動かないDOSアプリケーショ ン - 普通はゲーム - がある。こうなるとCPUをリアルモードにして「純粋の」 DOSを走らすしかない。OS/2は「ハイバネーション」機能をもっており、これが 可能である。事実上それは機械を再起動するに等しい。OS/2はもはや動作して いない(訳注:「専用DOS」のことだと思う)。
その印象は誤解に基づいている。この章の後の方で説明するように、実際の所 32ビットソフトウェアは等価な16ビット版より低速でメモリをより多く消費す る。
オリジナルの8086では、ほとんどの内部レジスタは16ビット幅であった。CPU は32ビットのアドレスを使ったが、それは16ビットのセグメントと16ビットの オフセットとに分解された。セグメント基底はほとんどの命令で暗黙のうちに 決められたので、この32ビットアドレスを16ビットアドレスと呼ぶのが一般的 だった。(問題を面倒にしたのは、主記憶のアドレスがたった20ビット幅であっ たことだ(訳注:セグメント基底を16倍し、これにオフセットを加えてアドレ スを生成した。従って20ビット幅しかなかった。)。)
80386以降のモデルでは、多くの内部レジスタが32ビット幅に拡張された。アド レスは48ビット幅になった:16ビットのセグメント番号と32ビットのオフセッ トである。(ここでも多くの人々はこれを48ではなく32ビットアドレスと呼ん だ。) 加えて、これらの新しいCPUは幾つかの新しい命令とアドレッシング モードを持つようになった。
ここで、またしても上位互換性の問題が現れてくる。レジスタの大きさを変え ずに8086のソフトウェアを正しく実行するにはどうするか。これを解決するた めに、全てのコードセグメントに2つの特殊なフラグが用意された。そのフラ グはセグメントディスクリプタ(訳注:segment discriptor。セグメント記述 子。セグメントの属性を記述するもの)に保存される。(セグメントディスク リプタはセグメンテーションの金物がアドレス変換に用いる表の項目である。) フラグの一つはレジスタを32ビットで用いるか、低位16ビットだけ用いるかを 指定する。もう一つはアドレスのオフセットを16ビットでとるか32ビットでと るか指定する。これはセグメント毎に指定され、新旧両方のソフトウェアの混 在を可能にする。16ビットの手続きを32ビットコードセグメントから呼ぶこと も、その反対もできる。
(実際、特殊な「エスケープ」コードすら存在し、16ビットセグメントの中に 隔離された32ビット命令を入れたり、逆をしたりできる)
本当に32ビットデータレジスタが必要だろうか? 私の経験(何年にも亙り、 たくさんのプログラムを書いてきた)からすると、16ビットでほとんど常に 十分だ。32ビット変数を必要とする場面は少しあるが、そんなにしばしばあ るわけではない。
一方、16ビットレジスタを使うと、プログラマは桁あふれの危険性を意識し なければいけないし、プログラムをそれなりにデザインしなければいけない。 世の中には良いプログラマを遥かに上回る数の駄目なプログラマがおり、こ の問題を考慮しないプログラムは数多い。実力のないプログラマにとっては、 32ビットデータレジスタへの移行は誤りの蓋然性を減らすことになるし、そ れは多分良い事であろう。
アドレスに関しては、事態は若干複雑である。16ビットのオフセットでは、 64キロバイトのセグメントが最大である。これは大量のメモリとは言えない。 もっと大きなセグメントを必要とする場面がある。
よろしい、これで32ビットソフトウェアのなんたるかをある程度説明するこ とにはなるが、何故こんなに多くの人々が、16ビットアプリケーションから 32ビット版へ「グレードアップ」したがるのかは説明できない。何が魅力な のだろうか?
それは歴史的な偶然による。80x86での32ビットサポートは、OSがCPUのプロ テクトモードを活かしはじめた頃にほぼ一致して出現した。プロテクトモー ドへの移行は前進であった。多くのPCユーザが「一般保護違反」に悩んでい たし、一つアプリケーションがいかれても、システム全体を道連れにしてし まうという状況から逃れたかった。プロテクトモードOSは、救いであった。 「プロテクトモード」と「32ビットソフトウェア」の違いについてソフトウェ ア販売店は良く知らず、馬鹿げた宣伝のお陰でほとんどのユーザがそれらを 混同してしまった。 (訳注:16ビットで80x86の保護機構を活かしたOSはいくつかある。OS/2 1.x がそうだったし、BTRONの80286版(1B)もそうである。MINIXもそうだったか も。)
残念ながら、セグメンテーションは高くついた。それは洗練されたアドレス変 換を高速で(容認できないほどのオーヴァヘッドをもたらさないように)行え る金物を必要とした。当時の技術でもこれは可能だったが、計算機の総コスト を引き上げる事になった。恐らく金物設計者はコストの上積みは何があっても 避けなければならないと決めたのだろう;実際の製品は極わずかしか出なかっ た。
80286が出現した時、技術がついに追いついた;漸く、何年もそこにあったア イデアを、容認できる程度のコストで実現したのだ。
何とも哀れな事に、セグメンテーションの機能を利用した者は、ほとんどいな かった。いくつか理由はあった。
アドレス空間は、アドレスに線形演算が適用可能なら、リニア(線形)である。 例えば、ポインタp1とp2について、p1+p2もp1-p2も意味のあるアドレスでなけ ればならない。
セグメント化されたアドレスは、定義上線形ではない。セグメント化メモリで はp1+p2には意味がないし、p1-p2はp1とp2とが共に同じセグメントのポインタ である場合に限り意味を持つ。セグメント化アドレス空間は、ある種のアドレ ス演算を厳しく制約する。
強く型付けられた(訳注:データの型を厳密に扱う、ということ)言語では、 このような制限は意味のあるものである。実際、セグメント化アドレス空間は モジュール性の概念に力をいれている、より近代的なプログラミング言語(Ada, Modula-2, Oberonなど)には最適である。モジュールとセグメントの間には、 極めて自然な対応関係をつけられる。
ところが、現在のOSは、より旧式の言語であるCがほぼ全盛だった頃デザインさ れた。Cはセグメント化メモリモデルとはあまり相性がよくない。Cは セグメンテーションの金物が違反としてトラップをかけかねない操作を許してい る(実行コードへのポインタとデータへのポインタの混用、ポインタへの無制限 な線形演算など)。セグメンテーションが防ごうとするものは、センスのあるプ ログラマなら不適当だとして避けるようなものだ、ということに、同意してくれ ると思う。コンパイラは言語の標準が許すものなら、なんでも許してしまう。ど んなに馬鹿げたことでも。そうでなければ、標準に合致しないコンパイラなのだ。
結果として、Cプログラマは一般にリニアアドレス空間を好む。またこの伝統は、 C++プログラマにも引き継がれているようだ。これはOS/2に大きな影響を与えた。 なんとなればOS/2のプログラムのうちCやC++で書かれたものがあまりに多いから だ。(訳注:日本に至っては、Algol系プログラマは絶滅寸前ではないだろうか。)
80x86でリニアアドレス空間を使えるのであろうか。そう、セグメンテーション は止められない。しかしそれがないように見せかける方法がある。それは、プロ グラム全体(実行コードもデータもひっくるめて)を一つの巨大なセグメントに 押し込み、セグメントレジスタを全て同じセグメントを指すように設定する、と いうトリックである。見方を替えれば、複数のセグメントを使ってはいるが、そ れらがぴったり重なっていて、同一の物理メモリにあるようにするのである。こ れが80x86(より正確には80386以降)での「フラットメモリ」に他ならない。
以上の議論から判ると思うが、私はフラットメモリモデルが好きではない。実際、 私はセグメンテーションの利点を投げ捨ててしまうのは、狂気の沙汰だと思って いる。私がこんなことを言えるのも、OS/2のソフトウェアを販売していないから だ。ソフトウェア屋だったら、こんな贅沢は言えない;客を「気違い」なんて言 おうものなら、商売あがったりになるのが落ちだ。フラットメモリモデルこそ、 ほとんどのOS/2プログラマのお望みのもので、人は望んだものしか得られない。 (訳注:実行コードを実行中に書替えるという技法があるが、同時に、おかしな ポインタ操作によって、コードを壊してしまうこともある。コードセグメントと データセグメントとをハードウェアレベルで区別すれば、コードをプログラム自 体が破壊することを検出/予防できる。このような利点を著者は考えているのだ と思う。)