VMモデル

VMとは?

狭義のOSはユーザに対して直接サービスを提供する事はありません。 狭義のOSの役割はハードウェアを特定のモデルに従って抽象化し、サービスを提供するためのアプリケーションソフトの設計・作成・試験を直接ハードウェアにアクセスするのと比較して容易にすることになります。 見方を変えてみると、一般的なアプリケーションソフトには以下の特性が求められ、そのほとんどがOSの支援なしでは実現が難しいです。だからこそ、特殊なハードウェア以外についてはOSが作成され、利用されてきたのです。

正確さ

皆さんはアプリケーションソフトを使用していて同じ事をしたつもりなのに異なった応答があってとまどったことはないでしょうか?

これはアプリケーションソフトの問題かもしれませんし、それをサポートするOSの問題かもしれません。同じ事をしたら同じ結果が出る。マニュアルに書いてある動作を実行したら、想像通りの動作となる。 それが、アプリケーションの正確さです。

OSに求められる事の例を挙げると次の様になります。OSは相互に依存しない機能を実行した場合、それらの実行順序に依らず一定の結果を出すこと要求されます。 つまり、相互に依存しないα、β、γの処理があり、α、β、γの順序で処理をして結果がAになったのであれば、γ、α、βの順序で処理をしても結果がAにならなければなりません。

こうして見ると、OSが無い方が正確さを高めやすい様に思えますが、ハードウェアを扱う場合にOSがあった方が圧倒的に正確さを保証しやすいのです。 例えば、HDDへの書き込みを考えてみましょう。HDDへの書き込みは以下の様になります。

  1. ヘッドと呼ばれる読みとり装置をディスクの円周の内側か外側にずらす(書き込み位置へ円周方向の移動をする)
  2. 移動が終わるまで待つ
  3. ヘッドが読みとり位置に来るまで回転を待つ。(書き込み位置へ回転方向の移動をする)
  4. 書き込みをする
ディスク書き込みのイメージ

OSがある場合はこれらの処理は一つの処理として実施されます。これをαとしましょう。しかし、OSが無かった場合は4つの処理をしなければなりません。これをα1~α4としましょう。 そして、α2とα3はタイミングを計る必要があります。

最初に上げた例をOS無しで実施した場合α1,α2、α3,α4、β、γの処理をすることになります。 α1,α2、α3,β、α4,γという順序で処理を実施してしまったらどうなるでしょうか?βのタイミングでディスクに書き込みをしなければいけないはずが、βの処理をしているため、少し遅れて書き込みをすることになります。 最悪のケースでは書き込み予定の位置から離れた部分に書き込みを行ってしまうでしょう。

可搬性

皆さんはMaxt○r社のHDDだと動作して、Sea○ate社のHDDだと動作しないアプリケーションソフトに出くわした事があるでしょうか?

私は10年以上PCを使用していますが、そういった経験はありませんでした。 なぜ、こんな事が可能かというと、OSがMaxt○r社のHDDなどの具体的なディスクから、ディスクの共通の属性を抜き出した抽象ディスクというVMを作成し、アプリケーションソフトが具体的なMaxt○r社のディスクなどではなくOSの提供する抽象的なディスクを対象にして作られているからです。 この事を応用すればFTPでサーバにアクセスし、FTPサーバのディスクも抽象的なローカルディスクとして扱う事が可能なのです。もちろんこの場合でもアプリケーションソフトには何ら変更は不要となります。 つまり、OSがあることによってアプリケーションソフトはハードウェアに依存せず、AのハードからBのハードへ移植が容易となります。このことを可搬性が高いと言います。

可用性

皆さんはアプリケーションソフトを起動中にアプリケーションソフトが停止してしまった事があるでしょうか?

私は学生時代にでたらめなアプリケーションを作って実行した時にアプリケーションソフトの停止を何度も体験し、社会人となってからはw○rd2000やex○l2000のそれに苦しめられました。 ですが、前述の二つの出来事には明らかな差があります。学生時代の時にはアプリケーションソフトが停止してしまうと、OSを強制的に再起動させないと他のアプリケーションも使用できませんでした。 しかし、社会人になってからの場合は原因のアプリケーションさえ強制終了させれば、他のアプリケーションはそのまま使用できました。 もちろん、正しいOSが取るべき立場は後者の方で、OSはアプリケーションソフトによって障害が発生した場合でも他のアプリケーションは使用可能であるべきです。 この様にサービスが利用したい時に利用できる事を可用性が高いと言います。

頑丈さ(ロバストネス)

私はレンタルDVDショップでDVDを借りたときに汚れだらけだと気分が滅入りますが、それでも私のDVD-ROMドライブはそのDVDで映画を再生してくれます。

CD-ROMへのアクセスは表面についたほこり等で時々失敗します。しかし、極端にひどくない場合は皆さんはそれを気にすることはないでしょう。 それはCD-ROMドライバが読みとりミスをアプリケーションには見せず、内部的に読みとりのリトライをしているからです。 そうしてOSは具体的な失敗をするデバイスから、完全な故障で無い限り失敗をしない抽象デバイスを提供してアプリケーションのベースを支えています。

こういった想定外・仕様外の動作が起こった際に正常に思える動作ができることを頑丈さが高いと言います。

統合性(インテグリティ)

私は正規にwindowsを購入したにもかかわらず、ライセンス認証に失敗したことがあります。その際に私はwindowsのレジストリを編集してライセンス認証済みにできないかと考えました。しかし、できませんでした。

もし、ライセンスを持たない人がライセンス認証済みにできたとしたらどうでしょうか?microsoft社は謂われのない損失を被ります。 こうした様に、保護されたデータを変更する権限を持っていない者(実際にはアプリケーションソフト)が被保護データを変更できてしまうことは社会的・法的・道徳的に見て問題となります。 そのため、ほとんどのOSは認証という機構を提供し、保護データを持つアプリケーションに対して、データが不正な手段で変更されない事を約束します。

経済性

皆さんは車輪の再発明をしたいでしょうか?高度な技術になればなるほど車輪の再発明には人材・資金・時間が必要になり、不毛です。

一つのハードウェアを操作するアプリケーションが二つあったとします。 それぞれ別々にアプリケーションを開発するよりも、共通部分を作成し、それを土台にしてそれぞれのアプリケーションを開発する方が人材・資金・時間の節約になるのは自明です。 OSはその土台にあたります。

効率性

皆さんはハードウェアの利用効率を計測した事があるでしょうか?私は厳密な測定をしたことはありません。 ハードウェアの利用効率の測定は行き過ぎると不毛であることが予測されるからです。

効率が良い、効率が悪いといった事が言われますが、効率とは目的に応じて変わります。例えば、テキストエディタを考えて見ましょう。 テキストエディタの効率の良さはユーザーの効率の良さとなります。そして、入力した文字が表示される前にキーを打ち続けられるユーザーはあまりいません。 つまり、テキストエディタの効率の良さは入力から出力までの応答特性(ターンアラウンドタイム)となります。

今度はコンビニエンスストアチェーンでの一日の売り上げの集計をするアプリケーションを考えて見ましょう。 複数の店舗の情報を集計するのですが、一つの店舗の情報を入力してから出力するまでの時間ではなく、全ての店舗の結果が出力されるまでの時間が重要となります。 つまり、そうしたアプリケーションの効率の良さは単位時間に出力される量(スループット)となります。

上記の二つの意味での効率をOSは目的に応じて高める必要があり、特に後者のケースにおいてアプリケーションはOSに恩恵をこうむっています。 ハードウェアの動作する時間尺度は様々であり、CPUの10-9[sec]での時間尺度から、プリンター出力の1[sec]での時間尺度までかなりのばらつきがあります。 このことはプリンター出力をしている間はそれをしているアプリケーションではなく、他のアプリケーションでCPUを使用した方がスループットを高められる事を意味します。 つまり、OSは複数のアプリケーションの間でハードウェアの使用権限を上手に配分することによって、スループットを高まるという機能を果たします。

機能性

OSがなかったとしたら、アプリケーションの機能を実現するのにとても手間がかかり素人プログラマやオープン開発は不可能だったでしょう。

これを書きながら私はACPIの仕様書のファイルを開いています。APPENDIXを除いても550ページにもなります。もし、OSを利用せずに電源管理をしようと思ったらこの仕様書に目を通さなければなりません。

また、VBE3.0(グラフィックボード・ディスプレイの規格)の仕様書のファイルも開いてみましょう。これも95ページあります。もし、OSを利用せずにGUIを実現しようと思ったらこの仕様書に目を通さなければなりません。

OSは直接的にはアプリケーションの機能性を高めることをしませんが、以上の様にアプリケーションの機能性を高めるための開発を容易にします。 ただし、その一方でOSを使うせいでできなくなる事もあります。 例えば、Intel社のCPUは実行セグメントとデータセグメントを分離できるため、バッファオーバーランによって任意のコードが実行されるという事はあり得ないはずです。 しかし、OSが実行セグメントとデータセグメントを分離させていなかったため問題となってしまっていました。

拡張性

OSはこの特性について直接アプリケーションを支援することはありません。しかし、ほとんどの場合この特性は他の特性をベースにしています。 例えば、互換性が高ければ複数のアプリケーションを組み合わせて一つのサービスとし易いため、拡張性が高いと言えるでしょう。

互換性

互換性というとあまり皆さんになじみが無いかもしれません。互換性とはAというパーツをBというパーツに置き換えても動作可能であるという意味です。 実際的な見方をすると、互換性の基礎となるのはパーツ同士のインターフェースの規定に他なりません。OSは互換性についてアプリケーションに対して最低限の保証をします。 それはOSの提供・規定するインターフェースを使用していれば同じOS上でのアプリケーションはデータを送受信することが可能であるといった内容です。

具体的な例を挙げると、ABIというバイナリレベルでのインターフェースの規定があります。 これはソフトウェアが制御(関数コール等)をする際にCPUをどう使うかを規定しているものです。 関数コール後にレジスタ(EAX,EBX,ECX,EDX)が変更される可能性があり、戻り値はEAXに設定される等

別のOS間同士のインターフェースについてはOSが提供する機能というよりも、ミドルウェア、あるいはインターフェース規約が提供する機能となります。

適時性

皆さんは2009年に新しい上りレート128k/下りレート128kの携帯電話端末が発売されたと聞いたら買いたくなるでしょうか? 私は機能・性能以外でよほどのメリットが無い限り買う気にはなれないでしょう。

上記の様にアプリケーションにも発売される適切な時期と寿命があります。 一見するとOSはこの性質についてアプリケーションを支援していないように見えますが、いくつかの点で支援をしています。 一つだけ例を挙げると、OSがなかったとするとアプリケーションの開発者は未発売・未発表のハードウェアに対して情報が得られないため、サポートできない事になります。 その結果、どういう事が起こるかというと発売されたばかりのハードウェアを購入するとアプリケーションが使えないことになり、ユーザは最新のハードウェアのメリットを享受できません。

実証性(テスト容易性)

皆さんがアプリケーションを作成した経験があれば、一度はデバッガを使った事があるでしょう。 アプリケーションの自己診断コードはそれ自体でアプリケーション自身の状態を変更してしまいます。そのため、アプリケーションが自分を診断することはかなりの難しさを伴います。 それを解決するのがデバッガです。

OSはデバッガ向けにいくつかのサービスを提供し、それによってアプリケーションのテスト容易性を高める支援を行っています。


長々とアプリケーションに求められる特性とOSとの関係を説明しましたが、最良のOSでもそれらを完璧に満足させることはできません。可搬性と効率性について一つ具体例を挙げて説明してみましょう。 Intel社のCPUの命令にSSE2というものがあります。これを使用すれば効率よく浮動小数点演算ができますが、比較的古いCPUではサポートされていません。 つまり可搬性を高めると効率性が下がり、効率性を上げると可搬性が下がってしまうことになります。

もちろん、SSE2をサポートしているかどうかを判断し、サポートしていない場合はそれを使わない実装をすれば、可搬性を損なうことなく、効率性を高めることが可能です。しかし、その場合は統合性と経済性、適時性が損なわれます。
仮想メモリ

OSはアプリケーションに対して仮想メモリを提供し、アプリケーションはあたかも自分が専用のメモリを持っているとみなせます。実際の設計レベルを説明すると複雑になって理解しづらいですので、単純化したモデルで説明します。

フラットモデル

フラットモデルの場合、OSは単一の仮想メモリをアプリケーションに提供します。その中に実行コード、ヒープ、スタックが配置されます。

実行セグメント、データセグメント分離モデル

実行セグメント、データセグメント分離モデルの場合、OSは二つの仮想メモリを提供します。 実行セグメントの中に実行コードが配置され、データセグメントの中にヒープとスタックが配置されます。

実行セグメント、データセグメント、スタックセグメント分離モデル

実行セグメント、データセグメント分離モデルの場合、OSは二つの仮想メモリを提供します。 実行セグメントの中に実行コードが配置され、データセグメントの中にヒープとスタックが配置されます。

実行セグメント、データセグメントのモデルと動作はほとんど変わりません。

なぜ、複数のモデルがあるのか?

フラットモデルには実行コード、ヒープ、スタックがメモリとして同等に扱われます。 そのため、ヒープへの書き込みの様に実行コードの書き換えが可能である上、ヒープやスタックにあるデータを実行コードとして実行することが可能です。 従って、バッファーオーバーランの様な攻撃に弱くなります。 (CPUによってはこの弱点を克服するため、ページテーブルのエントリに実行禁止属性を用意して、スタックやヒープのデータが実行コードとして解釈されることを予防しているものもあります。)

実行セグメント、データセグメント分離モデルでは実行セグメントとデータセグメントの管理が必要になります。 そのため、フラットモデルと比較して手間がかかることと、実行コードはまとめて近接したアドレスに配置し、データについてもまとめて近接したアドレスに配置しなければならない制約があります。 しかし、メリットとして実行セグメント・データセグメントを最初に配置した位置から変更してもアプリケーションに影響がないこと。実行セグメント、データセグメントが独立して扱えると言った事が挙げられます。

実行セグメント、データセグメント、スタックセグメント分離モデルはおおよその特徴は実行セグメント、データセグメント分離モデルと差がありません。 差がつくのはシングルスレッドのアプリケーションの場合、またはOSベースのマルチスレッドの場合です。 このモデルではスタックセグメントを最初に配置した位置から変更してもアプリケーションに影響が無いため、先ほどの二つのケースではスタックが不足した場合は動的に拡張し、余りすぎた場合は動的に縮小する事が可能です。 しかし、C言語などでスタックのアドレスを取った場合にリニアアドレス(データセグメント、スタックセグメント共通のアドレス)のポインタではなく、スタックセグメント情報を含んだポインタを使用しなければならないため、あまり使われていないようです。 ガベージコレクションベースの言語の場合はスタック配置した変数のアドレスを取ることが無いため、ガベージコレクションベースの言語には相性が良いのかもしれません。

仮想CPU

OSはアプリケーションに対して仮想CPUを提供し、アプリケーションはあたかも自分が専用のCPUを持っていると見なせます。 また、OSレベルスレッドを提供する場合、アプリケーションに対して複数の仮想CPUを提供する事が可能となります。

仮想CPUモデル(x86)

ここではIntelのx86系プロセッサの場合の仮想CPUモデルを説明します。また、簡単化のため、セグメントの切り替え、ページテーブルの切り替え等は割愛します。

ファイルシステム

ファイルシステム(抽象リソースロケーションと抽象インターフェース)

ほとんどのOSでファイルシステムが提供されています。 実ハードウェアには対応するものはありませんが、ほとんどのアプリケーションはファイルシステムというディレクトリ型のデータベースが提供されている前提で作成されているため、OSが提供する仮想ハードウェアと見なすのはそれほどこじつけではありません。

ファイルシステムをデーターベースと表現した場合、データーベースでのエントリに対応するファイルシステムのそれはファイルとなります。 一般的にファイルはHDDなどのディスク上に記憶されたデータとそれのヘッダ情報のとして使用されていますが、しかし、ファイルの意味はそれだけではありません。 特殊な例を挙げれると、linuxでは/dev/zeroというファイルがあります。このファイルは読み込むと無限に0が読み込めます。しかし、HDDの中に無限に0が書き込まれている訳ではありません。 このように広い意味でとらえた場合、ファイルはアプリケーションと抽象デバイス、アプリケーションと別アプリケーション、アプリケーションとOS間を結ぶ抽象的なインターフェースとなります。 そして、"/dev/zero"といったパスはインターフェースを特定するための識別子であり、パスにインターフェイスの集合を配置する事をマウントと呼びます。 その事によって、OSはインターフェースの位置透過性(ファイルのパスを変更しても、ファイルを使用できる)を実現しています。

説明が抽象的すぎるので、具体例を考えてみましょう。まず、フォーマット済みのHDDを新しく追加したとします。しかし、そのままではアプリケーションからアクセスできません。 そのため、OS、または特別なアプリケーションはHDDのインターフェースをファイルシステムにマウントします。 そうして、アプリケーションはインターフェースが配置された後にパスを指定して、ファイルの書き込みを実施できるようになります。

実際にはファイルシステムは階層化されている事が多く、例えば(勝手に作った例ですが)、HDDに書き込むデータとそのヘッダのフォーマットを管理する物理層、ローカルアクセス・リモートアクセスを隠蔽するロケーション層、暗号化を行うユーティリティ層といった構成を取っています。

仮想IO

仮想IO(仮想非タイミングクリティカルデバイス、仮想専有デバイス、バッファリング)

OSの機能としてはあまり見栄えしない部分となりますが、実はこれがOSの主たる役割となります。例えば、アプリケーションがHDDにファイルを書き込む場合、アプリケーションはHDDのヘッドが移動するのを待つ命令を実行するのでしょうか? そんなことはありません。アプリケーションはOSにHDDへの書き込みを依頼してOSが実際の書き込みを実施します。その場合、OSはアプリケーションの処理を停止し、書き込みが終わってからアプリケーションの処理を再開させます。 つまり、HDD等のデバイス固有の処理タイミング管理はOSが全て引き受けて、アプリケーションは全くそれを気にする必要がありません。 また、複数のアプリケーションが一つのHDDに対して別ファイルの書き込みを依頼した場合についても、どのアプリケーションの書き込みが先に行われるかをアプリケーション自身は気にする必要がありません。に

また、OSは統一されたデバイスアクセスのモデルを提供します。デバイスの操作方法はデバイスによってだいぶ異なりますが、通常のOSの場合はデバイスをキャラクタデバイス、ブロックデバイスに分類して統一的なアクセス方法を提供します。 そうすることによって、アプリケーションはランダムアクセスの可否だけを気にすればデバイスを扱えるようになります。(理想的にはそうなのですが、実際にはそこまで成功していません) 低レベルな視点から見た場合、デバイス操作はどこに(デバイスのレジスタ、デバイスのポート等)、何を(アドレス、データ、実行コマンド等)、どの方向で(IN、OUT)、どのタイミングで(割り込みベース、クロックベース、チック)という事を意識しなければなりませんが、OSはそれらを隠蔽して抽象的なデバイスをアプリケーションに提供します。

そして、OSはアプリケーションに対してデバイスの処理速度すらできる限り隠蔽しようとします。 例えば、プリンタの様な低速デバイス(1GクロックのCPUは10-9[sec]のスケールで動作しているので、10-0[sec]のスケールで動作するプリンタは非常に遅くみえる)に対してはスプーリングを行い、アプリケーションを待たせるような事はしません。 HDDへのアクセスも低速(10-1~-2[sec])であるため、メモリにバッファリングを行って処理速度を隠蔽しようとします。