%@ Register tagprefix="pt" tagname="header" src="../header.ascx" %>
<%@ Register tagprefix="pt" tagname="footer" src="../footer.ascx" %>
<%@ Register tagprefix="pt" tagname="wiki" src="../wiki.ascx" %>
この文書はコンピュータアーキテクチャ的な側面からCooSの特徴について知りたい方のために用意しました。OSについてあまり詳しく知らない方でも読んでいただけるように、既存のOSについても解説しています。一方、技術的に細かい話や、具体的にプログラムがどう処理しているかといった話はありません。
CooSが既存のOSと比較して大きく違うところは、次のような点です。
次に、それぞれの点についてみていきます。
まず、既存のOSはどうなっているかを説明します。
既存のOSでは、プロセスごとに独立したアドレス空間を持ちます。プロセスごとに独立しているということは、あるプロセスP1が扱うアドレスA1と、別のプロセスP2が扱うアドレスA2には何の関係もないということです。これは、A1とA2が必ず違う値になるなどという意味ではなく、たとえA1とA2が同じ値であっても両者には何の関係もないということです。
次の図はP1の立場でとらえたメモリと、P2の立場でとらえたメモリの例です。メモリブロックが重なったりしていますが、そもそも両者のアドレス空間は違うものなので、問題ありません。”どうやって” アドレス空間を分離しているかを次に説明します。

Intel PentiumなどのIA-32アーキテクチャのコンピュータでは、ページングという処理によって、あるアドレスA (リニアアドレス)を物理メモリの別のアドレス(物理アドレス)に対応づけることができます。たとえば、プロセスが0x00010000というリニアアドレスにアクセスすると、ページングによって物理アドレスの0x4000へのアクセスとして処理されたりします。
次の図はP1とP2のリニアアドレスが物理アドレスにマッピングされる例を示しています。

この例は物理的に4GBのメモリを搭載したコンピュータ想定していますが、もし2GBのコンピュータであった場合は中央の物理アドレスを示す空間が小さくなります。その場合でも、P1とP2のリニアアドレス空間は小さくなりませんが、箱の外(2GB空4GBの領域)にリニアアドレスをマッピングすることはできなくなります。
これは、プロセス同士をお互いに保護するために役に立っています。図において、もしP1が別のプロセスであるP2の領域にアクセスしようとしても、P1のリニアアドレス空間にはP2のメモリ領域は存在しないので、触れることすらできません。物理アドレスを直接指定できればアクセスできそうですが、物理アドレスを操作できるのはOSだけであり、普通のプログラムが物理アドレスを指定してアクセスすることはできません。
普通、プログラムは、自分が使っているメモリは自分以外は読み書きできないという想定の下で開発されています。もし、あるプロセスP2が間違ってP1のメモリを読み書きできてしまうと、P2の動作によってP1が影響を受けてしまいます。影響の仕方によっては誤動作したり、セキュリティに影響が出たりしますので、プロセス同士が互いに干渉できないようにするということは大変重要なことです。
このように、保護という観点からするととても優れているページングですが、二つのプロセスが協調して動作しようとするときにとても邪魔なものへと変貌してしまいます。
たとえば、あるツールのプロセスがインターネットからウェブページを受信したので、ブラウザのプロセスにそれを渡そうとする状況を思い浮かべてみてください。ウェブページのデータはツールのプロセスのアドレス空間にしかありませんので、なんとかしてブラウザのプロセスのアドレス空間にもマップしたいところです。
しかし、あるプロセスの任意のメモリ領域を他のプロセスと共有することは、一般には不可能です。なぜなら、OSのメモリ管理の単位は4KBなどなのですが、プロセスの中ではさらに細かい単位でC言語ライブラリなどが再管理しており、この細かい単位ではページングの動作を制御できないからです。
では既存のOSではどうしているのかというと、単純に、相手のアドレス空間内に新しく領域を確保してデータをコピーしています。ブラウザのプロセスはそのデータを読み取るだけですから、わざわざ複製を作る必要はないのですが、他に方法がないので仕方なくこうしているのです。
さらにもし、ブラウザのプロセスがデータを変更して変更結果をツールのプロセスが受け取りたいとすると、もっと大変です。ブラウザのプロセスが変更したメモリを、今度はツールのプロセスの元々のデータ領域に上書きして変更結果を反映させます。(このような相互通信のためのデータ処理をマーシャリング(Marshaling)といいます。)
このような状況は、説明のためにわざわざ作り出した特殊な状況ではありません。たとえば、Microsoft WordにはMicrosoft Excelのワークシートを埋め込むことができますが、とても遅くなると思います。これはWordとは別のアドレス空間でExcelが起動しているために、前述のようなオーバーヘッドが加わっていることも一因なのです。ほかにも、(最近はやり方変えたのでマシになりましたが、)インターネットエクスプローラなどは他のプロセスから利用されることの多いプログラムであり、説明したような状況で動いていることが多いのです。
CooSでは、今まで保護のために必要と書いておきながら、ページングをばっさり切り捨ててしまいます。次の図はCooSでのアドレス空間です。リニアアドレスから物理アドレスへの変換はしませんので、リニアアドレス=物理アドレスです。(ですから、搭載メモリ量によってリニアアドレス空間が制限されます。)

このようにすると、前述の問題点はクリアできることは分かると思います。別のプロセスに自分のデータを渡したい場合は、そのアドレスを教えてあげればよいのです。アドレス空間は共有していますので、あるデータのアドレスは相手のプロセスにとっても同じデータを指すアドレスです。相手のプロセスはそのアドレスを使ってデータを自由に変更することができます。
さて、一方、保護のほうはどうなってしまうのでしょうか。あるプロセスが別のプロセスにアクセスできてしまうのは問題です。
CooSでは、ハードウェアの仕組みを利用しては、プロセス間の保護を行っていません。しかし、ソフトウェアが事前にいろいろチェックすることによって、プロセスが他のプロセスに対してアクセスしないことを保証しています。(既存のOSではこのようなアプローチは不可能でした。) このような保証が可能なので、わざわざハードウェアの仕組みを使ってプログラムの実行中に再チェックをする必要がないということになります。
このチェックについて詳しく知りたい場合は、CLI仕様のコードベリファイア(Code Verifier)について調べてください。
前項で説明しませんでしたが、既存のOSでも、アドレス空間を共有することはできなくはありません。(そのようなOSが存在するかは知りませんが。) これにはページングの機能の一つである、ページ単位でのアクセス制御を使います。
しかし、データを保護しようとするとかならず制約が出てきてしまい、CooSのように他のプロセスのメモリに自由にアクセスできるようにすることはできないと考えられます。
CPUには算術演算といった基本的な命令から、特権命令と呼ばれる特殊な命令まで、多種多様な命令が存在します。このうち、算術演算命令や条件分岐命令などは実行してもコンピュータ全体に影響を与えることはありませんので、特に制限をする必要はありません。しかし、中には、実行に失敗すると瞬時にコンピュータが暴走したりする、危険なものもあります。(Ctrl+Alt+Delを押して……というような優しい暴走ではありません。まず手始めにコンピュータが一切反応しなくなります。)
この節の説明はIntel PentiumなどのIA-32アーキテクチャに限った説明です。他のアーキテクチャではどうなってるのかよく知りませんが、まあ、似たようなもんだろうと思います。
そのような危険な命令をアプリケーションプログラムがほいほい実行すると迷惑きわまりないので、CPUはそれらの命令を実行できるプロセスを制限しています。制限には何段階かあり、その段階のことを特権レベル(Priviledge Level)といいます。特権レベル0が一番強い権利を持ち少ない制約を受け、特権レベル3がもっとも弱い権利を持ち大きい制約を受けます。(数字の大きさとは反対であることに注意してください。) とくに、特権レベル0であるのことを指して”特権モード”(Priviledged Mode)と呼びます。
既存のOSでは、アプリケーションが特権を必要とする処理をしなければならない場合は、いったんOSを呼び出して処理を代行してもらうことにしています。イメージとしては関数呼び出しのような感じです。OSは常に特権モードで動作していますので、特権命令を実行して必要な処理をしたあと、呼び出し元のプログラムに結果を返します。
しかし、このような特権レベルの変化を伴う関数呼び出し(システムコールといいます)は、通常の関数呼び出しに比べて非常に大きなオーバーヘッドがかかります。ネットワーク通信やディスクの読み書きなどは必ずシステムコールを伴いますので、ハイパフォーマンスを実現するためにはシステムコールを如何に減らすかという工夫が必要になってしまいます。
Microsoft の IIS というプログラムは、バージョン6から、HTTPに関する一部のソフトウェアがカーネルモードドライバとして実装されたと発表されました。
これは、前述したようなネットワークドライバとのオーバーヘッドと、前節のデータのコピーを回避するために、アプリケーションの一部をOSの一部として動作させるという極めてMSらしい解決方法だと思います。
この例からも、プログラムが連係して動作するときに、いまのプロセス保護が邪魔になっている現状が分かると思います。
CooSでは、ページングと同様に、特権レベルについても利用していません。CooSではアプリケーションも含めたすべてのプログラムが特権モードで動作します。
こちらの代替策も、ページングと同様に、ハードウェアではなくソフトウェアが特権命令の実行についてチェックすることで、アプリケーションが特権命令を自由に実行できないようにしています。
CooSに関する質問の中でかなり多いものが、セキュリティに関するものです。とくに、ページングや特権レベルによる保護をしないということに関連して、「それでセキュリティが確保できるの?」というものが多いです。
しかし、これはよく考えると自ずと答えが分かります。ページングや特権レベルによるセキュリティは、所詮メモリアクセスと命令実行だけでしかありません。既存のOSといえども、ユーザの識別と認証、ファイルへのアクセス制御、セキュリティで保護された操作への許可などは、すべてソフトウェアでチェックしています。CooSでもそれは一緒なのですから、同じレベルの安全性は実現可能なのです。
僕は、セキュリティのようなミスの許されない品質に関して、どこまでシステマティックなアプローチが取れるかが重要だと考えています。プログラマが注意して安全なコードを書くのではなくて、検証ツールが安全でないコードを指摘できなければならないのです。
既存のプログラム開発方法ではそれは不可能でしたが、CLIでは(少なくとも既存の手法よりは)そういったことがやりやすくなっています。ですから、CooSは他のOSよりもセキュアになれる可能性があると考えています。
CooSの面白いところは、過去のしがらみから抜け出して、最新の技術だけに注力できることです。――しかも、アプリケーションの互換性を確保したままで。
いままでのOS開発では、新しい技術を採用すると既存のソフトウェアがほとんど動かなくなってしまい、OSとしての根本的な価値がなくなってしまうという問題がありました。逆に過去の資産との互換性を重視すると、結局どこかにあるOSのクローンになってしまうというジレンマがあります。
僕が開発の方針を検討するときに、まず後者は除外しました。Windowsは大変便利ですし、似たようなものを作るくらいならWindowsを使った方が良いに決まっています。
前者については、僕は独特の考え方をしました。つまり、既存のソフトウェアすべてが動かないからダメなのではなくて、開発ツールが動かないからダメなのだと考えました。僕自身が開発者ですからたぶん当たっていると思うのですが、ソフトウェア開発は慣れた環境でないとやりたくありません。変な草の根OSのために、不便なコンソールで、文法に制約のあるヘンテコ言語をわざわざ覚えて開発するなんて、少なくとも僕にとっては暇つぶし以外の何者でもないのです。
これを証明するように、新規OSはまず最初に開発環境の拡充と、協力してくれる開発者を募集します。たとえばLinuxでクロスコンパイルできるようにしたり、APIセットに互換性を持たせたりします。
CooSでは、すべての開発を大変優れた環境で行うことができます。WindowsであればMicrosoft謹製のVisual Studio.NETがありますし、Linuxでも洗練されたビルドツールが豊富にあります。またCLIの標準クラスライブラリと互換を保っていますので、新しい仕組みを覚えたり、プログラミング言語を習得する必要もありません。そういうことなら、手順が簡単ならちょっと試してみようという気になる――なりませんか?(笑)
僕はMicrosoftの.NETという枠組みは、現時点で実用になった技術としては最高のものだと思っています。(理想的な技術ではないでしょうが、実際に使えるものの中では一番良いということです。) そうしたときに、その技術だけを純粋に動かすとどうなるのか、それがCooSのおもしろさです。
こんな研究が面白いかなと思っています。まあ、もうちょぃまともに動作するまでは机上の空論なのですが。
CooSの型システムは――CLIのそれももちろん含みますが――Javaの型システムを含んでいます。つまり、Javaクラスの情報を損なうことなく、CooSのクラスとして表現できるということです。
現状ではCooSはCLIのアセンブリ形式しかロードできませんが、Javaのクラスローダを実装すればJavaのクラスを扱えるようになります。そうすると、CLIのクラスとJavaのクラスが連携するといったことも可能になり、なかなか面白いと思います。
開発はC#で、System.Reflection.Emit あたりを利用することになると思います。
現状のガーベジコレクタのアルゴリズムは、速度や停止性についての研究は進んでいるのですが、実際のOSに使えるかとかというとかなり微妙です。
CLIには「固定されたメモリ領域」があり、そのようなメモリブロックに関しては明示的に解放されるまでは移動させることができません。メモリコンパクションをするようなアルゴリズムがCLI仕様の条件下でもそれなりに効率よく動かせるかどうかというのはよく分かっていません。
また、既存のアルゴリズムではOSとしての動作に注意が払われていません。たとえば、あるプロセスが終了したときに、そのプロセスがオープンしたファイルオブジェクトをすべて破棄しないと、プロセスが終了してもファイルが閉じられていないということになります。しかし、単純にStop-the-world型のGCをそのまま使用した場合では、プロセスが終了するたびにコンピュータが一時停止してしまいます。GCの回収動作を改善することで対応可能なのか、対応できない場合はどのようにすれば回避可能なのでしょうか。