マイクロカーネル

マイクロカーネル（英: microkernel）とはオペレーティングシステム (OS) の設計思想、及びそのようなOSのカーネル部の名称である。OSが担う各種機能のうち、必要最小限のみをカーネル空間に残し、残りをユーザーレベルに移すことで全体の設計が簡素化でき、結果的にカスタマイズ性が向上し、性能も向上できるというOSの設計手法のことである。カーネル本体が小規模な機能に限定されるので「マイクロカーネル」と呼ばれるが、必ずしも小さなOSを構成するとは限らない。 マイクロカーネルの出現に伴い、従来型のOSを「モノリシックカーネル（一枚岩のカーネルという意）」と呼ぶようになった。マイクロカーネルの思想を更に推し進めたナノカーネルも提唱されている。 純粋なマイクロカーネルでは、まずカーネル空間で提供される機能を、メモリ空間の仮想化、プロセス制御、プロセス間通信に限定し、割り込みなども全て通信にマップする。その上でファイルシステムやデバイスドライバといった準カーネル機能をそれらのアプリケーションとして実装し、ユーザー空間で動作させる。場合によってはそれらの機能セットをサーバと呼ばれる単位で複数動作させる。 このような形態を持つ事のメリットは、 OS開発効率の向上（機能拡張、デバッグなどを容易に行える） システム全体を止めずにカーネル以外のOSのアップデートを行うことができる 必要な機能のみを提供するアプリケーションに特化したOSを構築することが容易になる などである。また一般にマイクロカーネルでは資源の抽象度が高く、マルチプロセッサ対応やネットワーク透過の通信が自然に実装できるため、大規模な資源利用には有利である。 反面あらゆる場所でプロセス間通信を利用する為、機能相互のオーバーヘッドが大きく、モノリシックカーネルと同等の機能を実装した場合、数パーセントから数倍のロスが出る場合がある。特にコンテキストスイッチのコストを避けるため、必要な機能をカーネル空間に移し、見た目上モノリシックカーネルのように動作させる場合もある(co-location)。 初期のOSのカーネルは、コンピュータのメモリ容量が小さかったこともあって、比較的小さかった。コンピュータの能力が成長するに従い、カーネルが制御しなければならないデバイスの数も増えていった。それでも例えば当初のUNIXのカーネルは、デバイスドライバやファイルシステムを含んでいても一般に小さかった。アドレス空間が16ビットから32ビットに拡大すると、カーネル設計がハードウェアアーキテクチャに束縛されることがなくなり、カーネルの成長が始まった。 UNIXカーネルの肥大化の端緒となったのが Berkeley Software Distribution (BSD) である。それまでのOSの役割に加え、BSDでは複数のファイルシステムを追加し、完全なTCP/IPプロトコルスタックを備え、ネットワーク上で既存のプログラムを動作可能にする各種仮想デバイスが追加されている。UNIXはファイルという概念でリソースを統一し、ファイルを扱う小さなプロセスをパイプでつないで相互通信させることにより、コンパクトかつ高機能なOS設計に成功した。しかしその後の利用環境の変化に伴い、必ずしもファイルとして扱えないリソースが増加している。例えばネットワーク通信はソケットという概念で行われるが、ソケットはファイルではない。結果的に、後付けで似て非なるコードがOSを肥大化させるという事態が発生したといえる。このような成長が何年も続き、カーネルのソースコードは数百万行にまで肥大化した。結果として、バグが潜在している可能性が高まり、保守の困難さが増していった。 マイクロカーネルはそのような「UNIXの再設計」という観点から始まっている。理論上、マイクロカーネル設計はカーネルの一部だったサービス群をユーザー空間に切り出すことでコードの管理を容易にするとされていた。また、カーネルモードで動作するコードの量を減らすことで、セキュリティと安定性が増すと考えられた。例えば、ネットワークサービスがバッファオーバーランでクラッシュしても、マイクロカーネルは影響を受けないので、システム全体としては機能し続けることができる。 OSがたくさんのコードから成る事の直接的な問題点は、 カーネル自体が占めるメモリ領域（カーネル空間のフットプリント）が大きくなる 移植性やメンテナンスに不利 といったものである。さらに、計算機資源の増大に伴いマルチプロセッサや仮想メモリといった、UNIX開発時に想定していなかった実装が出現しており、それらを含めたOS概念のリファインが必要と考えられた。 そこで、「大規模な資源」と「高速な通信」を念頭において「相互通信するプロセスによる協調動作」というUNIXの発展理念に至ったのがマイクロカーネルである。UNIXではファイルを中心概念としていたが、マイクロカーネルではプロセスと通信に主眼を移し、割り込みやI/Oといったあらゆる資源が徹底的に抽象化されている。 そのような背景で1980年代に開発されたマイクロカーネルは、既存の一体型カーネルを新たなシステムへ適応させるいくつかの試みを伴っていた。従来型カーネルに含まれていたデバイスドライバ、プロトコルスタック、ファイルシステムや他の低レベルシステムを一度に新たに開発するため、膨大な作業を伴い、慎重なコード管理が必要とされた。ユーザー空間のプログラムとして実装されたサービスは通常のユーザープログラムのように動作する。そのためそれらサービスの扱いが容易になっただけでなく、残ったカーネル部分は副作用を気にせずに最適化することができる。さらに言えば、共通の中核部の上に全く新たなOSを構築することも可能になり、OS研究にも寄与した。 なお、この時古くなった計算機資源の再利用も考慮されていた。つまり、移植性が高くコンパクトなOSを使えば、旧式のシステムであってもネットワーク上に接続して資源として活用する事ができるという考え方である。 1980年代には初期の Local Area Network が登場しており、マイクロカーネルは非常に有望な技術とされていた。カーネルの機能をユーザー空間に分散させる機構を使えば、システムをネットワーク上に分散させることも可能だからである。Machなどの初期のマイクロカーネルは、性能に問題があることが判明したが、それよりも利点が大きいとみなされ、1990年代末までOS研究の中心となった。実際に実装されたMachでは、当初の予想通りコード量の削減には成功した。抽象度が高く、見通しの良いマイクロカーネルはトレンドとしては妥当であり、この時は将来有望な物と見なされている。しかし、その間にコンピュータの性能がネットワーク性能に比べて格段に向上し、マイクロカーネルの性能問題は開発の容易さという利点を考慮しても無視できなくなった。既存システムを性能向上させる様々な試みがなされたが、オーバーヘッドが常に問題となり、性能向上のためにはユーザー空間のプログラムとなったサーバをカーネルに戻す必要があった。2000年までにMachのような大規模な試みはなされなくなったが、OPENSTEPが採用したXNUというMachカーネル改良版は Darwin や macOS で採用されている。2012年現在、Machベースの GNU Hurd も機能しており、それを採用した Arch Linux と Debian のテスト版も進行中である。 マイクロカーネルの大規模な研究は下火になったが、研究開発は続けられた。初期のマイクロカーネル設計における性能問題の多くは方針の根本的の問題ではなく、たまたまある目的のために実装されたシステムを可能な限り多くのサービスに使おうとする設計者の願望に起因していたと見られるようになった。アセンブリコードを使ったり、通常はソフトウェアでサポートすることをプロセッサのアーキテクチャ独自の機能に頼るなどのより実用的なアプローチを通して、性能が劇的に改善されたマイクロカーネルが登場するようになった。このような新世代のマイクロカーネルを従来の多くのシステムサービスを含んだ実装と区別するため、ナノカーネル (nanokernel) という呼称も使われた。しかし、従来型のマイクロカーネルからナノカーネルへの移行がほぼ完了すると、ナノカーネルとは呼ばれなくなっている。 マイクロカーネルと密接に関連する方式としてエクソカーネルがある。エクソカーネルはハイパーバイザと共通点が多いが、極小性を主張することはなく、仮想機械サポートに特化している。実際、L4マイクロカーネルファミリーはハイパーバイザの実装によく使われている。 マイクロカーネル自体ではなく、それに付随して開発された機能やマイクロカーネルの設計思想がマイクロカーネル以外のOSへ影響を及ぼした事例がある。インタフェースの明確な定義がもたらした高い移植性や、カーネルへの依存性を軽減したOS機能の実装がきっかけとなっている。Machの仮想記憶サーバは4.4BSDのカーネルへ移植され、その子孫を含めて旧来の仮想記憶実装を置き換えた。ユーザプロセスとしてファイルシステムのドライバを実装したFUSEは実装の容易さや障害への強さなどマイクロカーネルの技術的なメリットだけでなく、Linux上での初期のZFSサポートのように技術以外の問題（この場合はライセンスの非互換性）を回避する手段ともなっている。 プロセス間通信 (IPC) とは、プロセス間の通信を可能にする何らかの機構であり、メッセージ送信がよく使われる。共有メモリも厳密にはプロセス間通信機構の一種だが、マイクロカーネルでIPCといえば一般にメッセージを指す。IPCにより、OSをサーバといういくつかの小さなプログラムで構築することができ、IPCを使ってサーバを他から呼び出すことができる。周辺ハードウェアのサポートもそのように実装でき、サーバにはデバイスドライバ、ネットワークのプロトコルスタック、ファイルシステム、グラフィックスなど様々なものがある。 IPCには同期式と非同期式がある。非同期IPCはネットワーク通信に似ている。送信側はメッセージを発行して処理を継続する。受信側はメッセージの到着を定期的にチェックするか、何らかの検出機構で通知される。非同期IPCではカーネルがメッセージ用のバッファとキューを管理しており、バッファオーバーフローもカーネルが扱う。また、メッセージは2回コピーされる（送信側からカーネル、およびカーネルから受信側）。同期IPCでは、送信側か受信側はもう一方がIPC可能となるまでブロックされる。バッファリングも複数回のコピーも不要だが、明示的に両者が待ち合わせる必要があるため、プログラミングに巧妙さを必要とする。多くのプログラマは非同期送信と同期受信を好む。 第一世代のマイクロカーネルは一般に同期IPCも非同期IPCもサポートしていたが、その性能は悪かった。ヨッヘン・リートケ はその原因がIPC機構の設計と実装にあることを示した。彼はL4マイクロカーネルでIPCコストを10分の1以下にした。IPCは送信も受信もシステムコールでサポートされ、同期式のみであり、レジスタでなるべく多くのデータを渡すようにしている。さらにリートケは direct process switch という概念を導入し、IPCの際に送信側から受信側へ直接（不完全な）コンテキストスイッチを行うようにした。そのためL4では、メッセージの一部または全部をレジスタ群で渡す場合、レジスタ内のメッセージは全くコピーせずに転送することができる。さらにスケジューラを呼び出すオーバーヘッドを排除している。これは特に遠隔手続き呼出し (RPC) 的にサーバを呼び出す際のIPCに適している。もう1つの最適化として lazy scheduling と呼ばれるものがあり、IPC中にスケジューリングキューを走査せずに済むようするため、IPC中はスレッドをレディキューでブロックしたままにする。スケジューラが呼び出されると、そのようなスレッドを適当な待ちキューに移す。 クライアント・サーバ型システムでは多くの通信が基本的に同期式であり、非同期なプリミティブを使っていたとしても、通常はクライアントがサーバを呼び出し、応答を待つことになる。その方が実装も効率的になるため、L4以降のマイクロカーネルでは同期IPCプリミティブのみを提供するようになった。非同期IPCは補助スレッドを使って同期IPCプリミティブ上で実装できる。しかし、L4を採用した商用製品では非同期通信サポートのために非同期通知機構が必要であることが判明し、それを追加している。このシグナルのような機構はデータを転送するものではなく、カーネルによるバッファリングが不要である。 同期IPCでは通信の一方が応答可能になるまでもう一方がブロックされる。したがって無制限にIPCを使用すると容易にデッドロック状態が発生する。また、クライアントが要求を送って応答を待ち合わせないようにすると、容易にDoS攻撃のような状態になる。したがって同期IPCでは長期に渡るブロックを防ぐ手段を提供しなければならない。多くのマイクロカーネルはIPC呼び出しにタイムアウトを提供しており、ブロック時間を制限している。実際にはタイムアウト時間の設定は難しく、結果としてクライアントのタイムアウトは無制限、サーバのタイムアウトはゼロとすることが多い。結果としてタイムアウト時間を任意に設定できるようにせず、相手が応答可能でない場合にIPCを即座に失敗させるかどうかを示すフラグだけを用意する傾向になってきた。それによってタイムアウト時間としてゼロか無制限かを選択することになる。L4やMINIXの最近のバージョンもそのようになっている（QNXや以前のL4ではタイムアウト時間を任意に設定できる）。 マイクロカーネルでのサーバはデーモンのようなものだが、一部のサーバは物理メモリ空間の一部にアクセスできる特権を与えられている。それによりデバイスドライバのサーバがハードウェアと直接やりとりできるようになっている。 汎用マイクロカーネルにおける基本的サーバとしては、ファイルシステムサーバ、デバイスドライバサーバ、ネットワークサーバ、ディスプレイサーバ、ユーザインタフェースデバイスのサーバなどがある。これらのサーバ群でUnix系のモノリシックカーネルの提供するサービスをほぼカバーしている。必須サーバは立ち上げ時に起動してサービスを開始する。サーバはユーザーアプリケーションと同様の環境で動作し、その開発もアプリケーションと似ている。カーネル開発で必要となる頻繁なビルドとリブートは不要である。 さらに、サーバで障害が発生してもシステム全体がクラッシュすることはなく、単にそのサーバを再起動すればよい。しかし、サーバで障害が発生することでシステム状態の一部が失われることがあるため、アプリケーション側もサーバ障害に対応する必要がある。例えば、TCP/IP接続を担うサーバがあるとする。このサーバを再起動すると、アプリケーションから見てコネクションが失われることになるが、これは通常のネットワークでも起き得る障害である。しかしアプリケーションが障害発生を予期していないサービスの場合、アプリケーションのコードを修正する必要がある。QNXではサーバの再起動への対応として QNX High Availability Toolkit が提供されている。 全サーバを再起動可能にするため、トランザクションやレプリケーションやチェックポインティング（英語版）といったデータベース的技法を取り入れているマイクロカーネルもある。例えば ChorusOS があり、電気通信業界の高信頼アプリケーション用にそのような機能を備えている。 Direct Memory Access (DMA) を頻繁に行うデバイスドライバは、カーネルのデータ構造を含めた物理メモリの任意の位置に書き込める必要がある。そのようなドライバは信頼されていなければならないが、必ずしもカーネル内に含まれている必要はない。実際、カーネルの一部だからといってデバイスドライバがより信頼できるというわけではない。 デバイスドライバをユーザー空間で動作させたとしても、ドライバが発生しうる障害のダメージが低減されることはないが、バグのあるドライバによってシステムの安定性が影響を受けにくくするという効果はある（悪意あるドライバの場合はその限りではない）。（デバイスではなく）ドライバのコード自体が不正なメモリアクセスを行う場合、メモリ管理ハードウェアが捉えることになる。さらにDMAを行わないデバイスでは、ユーザー空間で動作させることでドライバを信頼されないもの（特権が不要なもの）とすることができる。最近ではIOMMUを備えたコンピュータが増えており、IOMMUを使ってデバイスの物理メモリアクセスを制限できることが多い。これもユーザーモードのドライバを信頼されないものとすることができる。 ユーザーモードのドライバは実際にはマイクロカーネルより古くから存在する。1967年の Michigan Terminal System (MTS) で初めて（ファイルシステムも含めた）ユーザー空間のドライバがサポートされた。歴史的には、ドライバをカーネルに含めることはあまり問題とされなかった。周辺デバイスの数は少なく、特権が必須だったためであり、カーネルに含めることで性能問題を防いでいた。Unixもそのスタイルを踏襲し、Linuxや Windows XP 以前のWindowsも同様だった。デバイスの種類が豊富になってくると、ドライバのコード量も増大するようになり、カーネル全体に占める割合も増えていった。 マイクロカーネル上で任意のOSサービス群を構築しなければならない場合、マイクロカーネルはいくつかの中核機能を提供しなければならない。最小でも以下が含まれる。 アドレス空間を扱う何らかの機構 - メモリ保護に必要 CPU割り当てのための何らかの実行抽象化 - スレッドまたは scheduler activations が典型的 プロセス間通信 - 独立したアドレス空間で動作するサーバ群を呼び出すのに必要 このような最小設計は Per Brinch Hansen の Nucleus とIBMのVM（英語版）のハイパーバイザがさきがけである。そして、リートケの「極小原則」として次のように定式化された。 ある概念をマイクロカーネル内で実現することが許されるのは、それをカーネルの外に移した場合、すなわち競合する実装が可能となることでシステム必須の機能が妨げられる場合だけである。 他のあらゆるものはユーザーモードのプログラムとして実装できるが、デバイスドライバをユーザープログラムとして実装する場合、一部のプロセッサアーキテクチャではI/Oハードウェアにアクセスするための特別な特権を必要とする。 極小原則とも関連しマイクロカーネル設計で重要な観点として機構と方針の分離があり、極小のカーネル上に任意のシステムを構築できるようにするのに必要である。方針をカーネル内で実装すると、ユーザーレベルでは上書きできず、マイクロカーネルの可能性が一般に制限される。ユーザーレベルのサーバで実装された方針はサーバを置換することで（あるいは同様のサービスを提供する複数のサーバからアプリケーションが選択することで）変更できる。 効率化のため、ほとんどのマイクロカーネルはスケジューラとタイマー管理を含んでおり、極小原理と機構と方針の分離の原則に反している。 マイクロカーネルを使ったシステムの立ち上げ（ブート）では、カーネル内に含まれないデバイスドライバが必要になる。一般に必要なデバイスドライバをカーネルと共にパッケージ化してブートイメージを作成する必要があり、カーネルはデバイスドライバを配置して起動する手順をサポートしている必要がある。例えばL4ではそのようなブート方式になっている。一部のマイクロカーネルは重要なデバイスドライバを（極小原則に反して）カーネルに含めており、LynxOSや元々のMINIXが挙げられる。ブートを単純化するため、ファイルシステムまでもカーネルに含める場合がある。マルチブート対応のブートローダからブートできるマイクロカーネルのシステムもある。その場合、静的リンクされたサーバ群をロードするか、OSイメージをマウントすることで立ち上げを続行する。 マイクロカーネルにおいては、IPCシステムと仮想記憶管理の設計が重要であり、ページフォールト処理やスワッピングをユーザーモードのサーバで安全に実装できるようにすることが肝要である。全サービスがユーザーモードのプログラムで実行されるので、それらプログラム間の効率的通信手段が必須である。これはモノリシックカーネルに比べて遥かに重要である。効率化するには、IPCシステムのオーバーヘッドを小さくするだけでなく、CPUスケジューリングともうまくやりとりしなければならない。 多くの主流のプロセッサにおいて、サービスを得るのにかかるコストはモノリシックカーネルよりもマイクロカーネルの方が本質的に高い。モノリシックなシステムでは、サービスを得るのに1回のシステムコールを使用し、その際に2回の「モード切り換え」（プロセッサのリングまたはCPUモードの変更）を必要とする。マイクロカーネルを採用したシステムでは、サービスを得るにはサーバにIPCメッセージを送り、別のIPCメッセージで結果を得る必要がある。このときドライバがプロセスとして実装されていればコンテキストスイッチが必要であり、プロシージャとして実装されていれば関数呼び出しが必要である。さらに実際にデータをサーバに渡して結果のデータを戻してもらうには、データをコピーするオーバヘッドが余分にかかる。モノリシックカーネルでは、クライアント（ユーザー空間）の持つバッファにあるデータに直接アクセス可能である。 したがってマイクロカーネルシステムには性能問題が潜在している。実際、MachやChorusOSといった第一世代のマイクロカーネルを採用したシステムの性能は低かった。しかしヨッヘン・リートケはMachの性能問題は設計と実装がまずかったためであることを示し、特にMachがページキャッシュ（英語版）を使いすぎていることを指摘した。リートケは自身のL4マイクロカーネルを注意深く設計・実装することで、特に極小原則に従えばIPCコストをMachの10分の1以下にできることを証明した。L4のIPC性能は様々なアーキテクチャ上で最速を誇った。 そういった結果により、第一世代のマイクロカーネルの性能の低さは第二世代のL4などのマイクロカーネルには当てはまらないことを示したが、マイクロカーネルをベースにしたシステムが高性能を発揮できることを証明したわけではなかった。それでもモノリシックなLinuxサーバをL4上に移植したものは、通常のLinuxに対して数パーセントのオーバーヘッドを示しただけだった。しかし、単一サーバシステムはマイクロカーネルの利点を生かしているとは言い難く、OSの機能を複数のサーバに分離したときの性能が重要だとされた。 複数サーバの商用システムはいくつか存在しており、例えばリアルタイムオペレーティングシステムのQNXやIntegrity（英語版）がある。そういったシステムとモノリシックなシステムとの包括的性能比較は公表されたことがない。さらに言えばそれら商用システムでは性能が最重要課題ではないようで、割り込み応答時間の保証や頑健性のための単純さが強調されている。複数サーバの高性能OSを構築する試みとしてIBMの Sawmill Linux というプロジェクトがあった。しかし、このプロジェクトは完了することなく中止された。 同じころ、ギガビット・イーサネットのような高速デバイスのデバイスドライバをユーザーレベルで実装したものが、カーネル内のデバイスドライバと同等の性能を発揮することが示されている。これは複数サーバの高性能システムが構築可能であることを示唆している。 マイクロカーネルはセキュリティ面で優れているとよく言われてきた。セキュリティの観点では、マイクロカーネルの極小原則は、全てのコードについて必要とされる機能を提供するのに必要な権限だけを持つべきであるという最小権限の原則の直接的帰結である。極小性は、システムのトラステッド・コンピューティング・ベース（英語版） (TCB) を極小に保つべきであることを要求する。カーネル（ハードウェアの特権モードで実行されるコード）は常にTCBの一部であり、セキュリティ指向設計ではそれを極小化するのが自然である。 結果として、マイクロカーネル設計は高度なセキュリティを要求される用途で採用されており、例えばKeyKOS（英語版）、EROS（英語版）、軍用システムなどがある。実際、コモンクライテリア (CC) の最高保証レベル（評価保証レベル (EAL) 7）では、対象の評価が「単純」であること、複雑なシステムの真の信頼性を確立するのに実際的不可能性を確認できることを明示的に要求している。 近年のマイクロカーネル研究開発は、カーネルAPIの形式仕様とそのAPIのセキュリティ属性の形式的証明に集中している。第一の例として、EROSでの隔離機構の数学的証明があり、EROS API を単純化したモデルに基づいている。より最近の例として、L4のバージョンの一種である seL4 のプロテクションモデルについて自動検証された包括的な証明がなされている。 それにより「第三世代マイクロカーネル」と呼ばれるものが生まれている。これはセキュリティ指向のAPI、ケイパビリティによるリソースアクセス制御、最重要概念としての仮想化、カーネル資源管理への革新的アプローチ、形式的分析に適した設計、などを特徴としつつ、性能向上を目標としたものである。例として、Coyotos（英語版）、seL4、Nova、Fiasco.OCなどがある。 seL4の場合、実装についての完全な形式的検証がなされており、形式仕様と実装が一致していることが数学的に証明されている。それにより、APIについて証明された属性が実際のカーネルでも保持されていることが保証でき、CC EAL7 をも越える保証の程度となっている。 ナノカーネル (nanokernel) またはピコカーネル (picokernel) という用語は歴史的には次のような意味で使われてきた。 カーネルコードの量、すなわちハードウェアの特権モードで実行されるコードの量が極めて小さいカーネル。カーネルが極めて小さいことを強調する際に「ピコカーネル」という呼称も使われた。「ナノカーネル」という用語を生み出したのは Jonathan S. Shapiro で、The KeyKOS NanoKernel Architecture (1992) という論文で初めて使っている。それはMachへの冷笑的反応であった。彼はMachについて、本質的に非構造的であり、モノリシックであるのにマイクロカーネルと主張しているだけで、置き換えようとしているものより性能が悪いと評した。その用語が他でも使われ、さらにピコカーネルという呼称が生まれた背景には、従来のマイクロカーネルが「カーネルが小さい」という点から大きく外れていったことを示唆している。その後、ナノカーネルとピコカーネルはマイクロカーネルと同じ意味で使われるようになった。 OSの下にある仮想化層。より正確には「ハイパーバイザ」と呼ばれる。 カーネルの最下層部を形成している ハードウェア抽象化層。例えば Adeos があり、通常のOSにリアルタイム機能を提供するのに使われる。 また、小さくないカーネルをナノカーネルと呼ぶ用例も少なくとも1つあり、その場合はクロック間隔としてナノ秒単位までサポートしているカーネルを指す。