カーネル
カーネル(kernel)は、階層型に設計されたオペレーティングシステム (OS) の中核となる部分で、アプリケーションとハードウェアの架け橋である。具体的には、システムのリソースや、ハードウェアとソフトウェアの連携を管理する。その他、通信制御も行う事が多い。
オペレーティングシステムの基本コンポーネントとして、カーネルはメモリ、CPU、入出力を中心としたハードウェアを抽象化し、ハードウェアとソフトウェアがやり取りできるようにする。また、ユーザープログラムのための機能として、プロセスの抽象化、プロセス間通信、システムコールなどを提供する。
これらのタスクはカーネルによって方式が異なり、設計も実装も異なる。モノリシックカーネルは全てを一つの仮想アドレス空間に格納されたコードで実行して性能を向上させようとする。マイクロカーネルはサービスの大部分をユーザー空間で実行し、コードの保守性とモジュール性を向上させようとする。多くのカーネルはこの二つのカテゴリのいずれか、あるいは中間である。
全てではないが、多くのオペレーティングシステム (OS) はカーネルを内包する。ハードウェアとソフトウェアの間の通信を管理するソフトウェアとしてのカーネルは、性能、メモリ効率、セキュリティ、プロセッサのアーキテクチャなどが複雑に絡んだ問題への妥協的解答である。
多くの場合、ブートローダーがカーネルを特権モードのプロセスとして起動する。しかし、初期化が完了すると、カーネルはいわゆるプロセスとしては存在せず、ディスクアクセスなどの高い特権レベルを必要とする処理を必要としたときにユーザプログラムから呼び出される機能の集合体として存在することになる。カーネルの処理の流れはユーザープロセスの処理の流れの延長上にあり、システムコールによってカーネルに処理が渡り、終了するとユーザーに戻っていく。初期化時のコンテキストはそのまま消えるようにする設計もあるが、「アイドルプロセス」とか「collects」と呼ばれる、プロセッサが何もすることがない時に実行されるコードに流用される設計とすることもある。省電力のため、プロセッサが「休む」ような命令を繰り返すようなコードとすることも多い。
カーネル開発はプログラミングの中でも複雑で難しいタスクのひとつと考えられる。オペレーティングシステムの中核部であるということは、高い性能を要求される最重要なソフトウェアであり、正しく設計し実装することは難しい。カーネルはユーザプログラムの互換性や移植性を考慮する必要などから、設計が制限されることもあり、そのことがさらに開発を難しくしている。
カーネルの仕事はコンピュータのリソースを管理し、他のプログラムがそれらのリソースを使って動作できるようにすることである。典型的なリソースとしては以下のものがある。
CPU(プロセッサ)。コンピュータの中心となる部分で、プログラムの実行を分担する。カーネルは、多数のプログラムの中からプロセッサ(群)を割り当てるべきものを選択する。基本的にプロセッサは一度に1つのプロセスしか実行できない(複数実行できる場合、カーネルは複数のプロセッサとして認識する)。
メモリ。メモリにはプログラムとデータの両方が格納される。一般にプログラムを実行するには、プログラムとデータの両方がメモリ上になければならない。複数のプログラムがメモリへのアクセスを要求すると、実際に搭載している以上のメモリが必要とされる場合がある。カーネルは各プロセスにメモリを割り当て、全体としてメモリが不足した場合の対処を決定する。
コンピュータには何らかの入出力デバイスがある(キーボード、HDD、USBなど)。カーネルはアプリケーションから入出力要求を受け付け、適切なデバイス(あるいはデバイスの一部、例えばやウィンドウなど)に対して入出力を実行し、デバイスを使用するための便利な方法を提供する(一般に、アプリケーションがデバイスの実装の詳細を知らなくとも済むように抽象化する)。
リソース管理に必要な重要な観点は、実行領域(アドレス空間)の定義とその領域内のリソースへのアクセスを調停する保護機構である。
カーネルはまた、一般にプロセス同士の同期と通信の手段も提供しており、プロセス間通信 (IPC) と呼ぶ。
カーネルは自前でそれらの機能を実装していることもあるし、何らかのプロセスに委任していることもあるが、後者の場合はプロセス間で機能へのアクセスを可能にするIPCを提供する必要がある。
最後に、カーネルはそれら機能群へのアクセスを要求する手段をプログラムに提供しなければならない。
カーネルの主な仕事はアプリケーションの実行を許可し、ハードウェア抽象化などの機能によってそれをサポートすることである。
プロセスは、アプリケーションがアクセスできるメモリの範囲を定義する。カーネルのプロセス管理は、ハードウェアの持つメモリ保護機構を考慮しなければならない。
カーネルはアプリケーションを実行するためアドレス空間を設定し、アプリケーションのコードを含むをメモリにロードし、プログラムのためのコールスタックを設定し、そのプログラムの所定の位置に制御を渡すことで実行を開始する。
マルチタスク可能なカーネルは、ユーザーから見て実際にそのコンピュータが同時実行できるプロセス数よりも、多数のプロセスが同時並行して実行されているかのように見せかける。一般にシステムが同時並行して実行できるプロセス数は、そのシステムの持つCPU数に等しい(同時マルチスレッディングをサポートしている場合はその限りではない)。
プリエンプティブ・マルチタスクシステムでは、カーネルは各プログラムにタイムスライス(そのプログラムがCPU上で実行される連続時間)を与え、プロセスからプロセスへと高速に切り換えていくので、ユーザーから見ればそれらのプロセスが同時並行して実行されているように見えるのである。カーネルは次に実行すべきプロセスを決定し、タイムスライスの長さを決定するスケジューリングアルゴリズムを持つ。一般にプロセスには優先度が設定される。カーネルはそれらのプロセス間の通信手段も提供する。これはプロセス間通信 (IPC) と呼ばれ、パイプ、共有メモリ、メッセージ、RPC、ソフトウェア割り込みなどがある。
他に協調型マルチタスクもあり、各プロセスは自らカーネルに制御を戻すまで割り込まれずに実行を続けることができる。制御をカーネルに戻すことを "yielding" と呼び、プロセス間通信の際や何らかのイベントを待つ際に行われ、そのときにカーネルが別のプロセスを動作させる。古い Windows や Mac OS はこの方式だったが、コンピュータの性能向上に伴ってプリエンプティブ方式に切り換えた。
オペレーティングシステムは、マルチプロセッシング(SMPやNUMA)をサポートすることもある。この場合、複数のプログラムやスレッドが複数のプロセッサ上で動作する。そのようなシステムでカーネルを動作させる場合、「リエントラント(再入可能)」あるいは「割り込み可能」になるよう大幅な改造が必要となる。これはつまり、何か処理をしている最中に他からも要求を受け付けるということである。この改造ができれば、異なるプロセッサ上で動作するプログラムが同時にカーネルを呼び出しても大丈夫になる。カーネルは複数のプロセッサからのメモリアクセスを同期させる方法(スピンロックなど)も提供しなければならない。これはメモリ管理とプロセス管理にまたがる問題である。
カーネルはシステムの全メモリへの無制限のアクセスが可能で、ユーザープロセスの要求に応じて安全なメモリアクセスを提供しなければならない。このための第一歩はページング方式やセグメント方式による仮想アドレッシングである。仮想記憶方式では、カーネルは物理アドレスを別のアドレス、つまり仮想アドレスに変換する。これにより、各プログラムは(カーネル以外では)仮想空間上唯一のコードに見え、プログラムが互いに他のプログラムを破壊することを防止する。
多くのシステムで、あるプログラムの仮想アドレスはメモリ上にないデータを指していることがある。仮想アドレッシングによるインダイレクション層は、本来なら主記憶 (RAM) になければならないデータをハードディスクなどの補助記憶装置に退避させることを可能にする。結果としてOSは物理的な容量以上のメモリをプログラム群に提供可能となる。RAMにないデータがあるプログラムで必要になった場合、CPUはカーネルにそれを知らせ(ページフォールト)、(必要なら)カーネルが使われていないメモリブロックの内容をディスクに退避させ、必要なデータをそのメモリブロックに復帰させる(ページ置換アルゴリズム)。すると、プログラムは要求を行った時点から処理を再開させることができる。これをデマンドページングと呼ぶ。
仮想アドレッシング方式では、仮想空間をカーネル用の部分(カーネル空間)とアプリケーション用の部分(ユーザー空間)に分けることが出来る。アプリケーションはカーネル用メモリにアクセスできないので、アプリケーションにバグがあったとしてもカーネルにダメージを与えることはない。この根本的な分離は多くの汎用カーネルで実際に使われているが、別な方式を採用したカーネルの研究も行われている(例えば、Singularity)。
メモリ管理のもうひとつの機能として、カーネル内の各モジュールやデバイスドライバが使用するメモリの割り当てがある(動的メモリアロケーション)。
実際に何らかの作業をするには、OSはコンピュータに接続された周辺機器にアクセスする必要があり、周辺機器はその開発元などが書いたデバイスドライバを通して制御される。デバイスドライバはOSがハードウェアデバイスとやりとりするためのプログラムであり、OSに対して何らかのハードウェアを制御・通信するための情報を提供する。ドライバはアプリケーションにとっても重要で不可欠である。ドライバの設計目標は抽象化である。ドライバの機能はOSの定めたインタフェースからデバイス固有のインタフェースに変換することである。理論上、デバイスは適当なドライバがあれば正しく動作する。デバイスドライバは、ビデオカード、サウンドカード、プリンター、スキャナー、モデム、LANカードなどに対応して存在する。一般的なデバイスドライバの抽象化レベルを次に示す。
直接インタフェースする。
高度なインタフェースを使用する(ビデオBIOSなど)。
低レベルなデバイスドライバを使用する(ディスクドライバを使用するファイルシステムドライバなど)
ハードウェアと共にシミュレーションを行う。すなわち、実際には全く異なる何かを行う(廃れたデバイスの代わりに最新のデバイスを使用するなど)。
OSがハードウェア資源に直接アクセスできるようにする。
ドライバとして基本的な部分だけを実装。
ドライバ以外のソフトウェアとのインタフェースを実装(例えば、TWAIN)。
何らかの高度な言語を実装(例えばPostScript)。
例えば、ユーザー向けに何かを画面に表示する場合、アプリケーションがカーネルに要求し、その要求がディスプレイドライバに送られ、ディスプレイドライバが実際の文字やピクセルの描画を行う。
カーネルは使用可能なデバイスの一覧を保持しなければならない。この一覧は、事前に知られている場合(例えば、組み込みシステムでは利用可能なハードウェアが変われば、カーネルを書き換える)、ユーザーが設定する場合(古いPCや個人用に設計されていないシステムなど)、OSが実行時に検出する場合(プラグアンドプレイ)がある。プラグアンドプレイのシステムでは、デバイス管理は最初に様々なバス(PCIやUSB)上をスキャンして実装されたデバイスを検出し、対応するドライバを探す。
デバイス管理は各OS固有の部分であり、カーネルの設計によってドライバの扱い方は異なるが、一般にカーネルはドライバが物理的にデバイスにアクセスするための入出力ポートやメモリ空間を用意する必要がある。デバイスへのアクセスはコンテキストスイッチを引き起こしたり、CPUを浪費したりすることになり易く、性能オーバヘッドの元となるため、デバイス管理の設計は重要である。
意味のある作業を実行するには、ユーザプログラムはカーネルの提供する全サービスにアクセスできなければならない。これはカーネルによって実装が異なるが、多くは標準CライブラリやAPIが提供され、そこから対応するカーネル機能が呼び出される。
カーネル機能を呼び出す方法は主にCPUがどのような機能を提供しているかに依存する。カーネル空間とユーザー空間が分離されている場合、ユーザープロセスが直接カーネルを呼び出すことはできない。例えば以下のような技法を採用する。
例外処理や割り込みを明示的に発生する命令(トラップ命令、ソフトウェア割り込み)を使用。多くのハードウェアで実装されている技法である。
コールゲートを使用。x86で採用されている。
特別なシステムコール命令を使用。最近のx86で実装された。
メモリ上のキューを使用。仮想空間の所定の位置にユーザープロセスが要求を投入できるキューを用意し、カーネルがそこから定期的に要求を読み取って実行する。即時性が要求されない場合で、プロセスから複数の要求を行う場合に便利である。
カーネル設計において重要な観点として、障害(フォールトトレラント性)と悪意ある動作(セキュリティ)からの保護(プロテクション)サポートがある。この2つは通常明確には区別されず、明確に区別しようとするとリングプロテクションでは対応できなくなる。
カーネルが提供する機構または方針は、いくつかの基準で分類できる。
静的(コンパイル時に決定)か動的(実行時に決定)か
それらが満足する保護原理による分類(デニング)
オープンな機構によるものか、方針と密に結合しているか
などである。
階層型プロテクションは、一般に「CPUモード」でサポートされる。ハードウェアサポートによる単純で効率的な方法は、MMUにメモリアクセスの度にその妥当性をチェックさせるもので、その機構をケイパビリティベースドアドレッシングと呼ぶ。ただし、多くの商用コンピュータアーキテクチャではMMUがケイパビリティをサポートしていない。
代替手法は、階層型プロテクションでケイパビリティをシミュレートするものである。この場合、保護されたオブジェクトはアプリケーションがアクセスできないアドレス空間になければならない。カーネルもそのようなメモリ空間のケイパビリティのリストを保持する。ケイパビリティによって保護されたオブジェクトにアプリケーションがアクセスしたい場合、システムコールを行い、カーネルが実際のアクセスを代行する。これにはアドレス空間の切り替えを必要とするため、オブジェクト間で複雑なやりとりが必要なシステムでは性能が低下するが、現代のOSはアクセス頻度が低いオブジェクトや性能を要求されないオブジェクトについてはこの方式を採用している。保護機構をより高い階層でシミュレートする方式も可能だが(例えば、直接サポートされていないハードウェアについてのページテーブルを操作してケイパビリティをシミュレートするなど)、性能上の問題がある。言語ベースの保護を選択するシステムでは、ハードウェアサポートがなくても問題にならない。
カーネル設計における重要な点として、セキュリティの機構と方針を実装する抽象化レベルの選択がある。カーネルのセキュリティ機構は、高度なセキュリティをサポートする上で重要である。
1つの方式として、ファームウェアとカーネルでフォールトトレラント性をサポートする方式があり、その上に悪意ある動作に対するセキュリティ方針を構築し(必要に応じて暗号機構を追加する)、一部の責任をコンパイラに委任する。コンパイラやアプリケーションレベルへのセキュリティ方針の責任委譲の方式を一般に「言語ベースのセキュリティ」と呼ぶ。
現代の主流のOSの多くは重要なセキュリティ機構が欠如しているため、アプリケーションの抽象化レベルでの適切なセキュリティ方針実装ができないことがある。一般にカーネルサポートがどうであれ、アプリケーションで任意のセキュリティ方針を実装可能だとされているが、間違いである。
現代の一般的コンピュータは、ハードウェアが強制した規則を使ってプログラムのデータへのアクセスを許可している。プロセッサは動作を監視し、規則に違反したプログラムを停止させる(例えば、カーネル空間のメモリを読み書きしようとしたユーザプロセスを停止させるなど)。ケイパビリティをサポートしていないシステムでは、プロセスは相互に隔離されたアドレス空間で動作する。ユーザプロセスがカーネルを呼び出すことは、上述したシステムコールの技法を使って統制されている。
代替手法として言語ベースの保護(プロテクション)がある。言語ベースのプロテクションシステムでは、カーネルは信頼されている言語コンパイラが生成したコードのみ実行を許可する。そしてその言語は、セキュリティに違反するようなコードをプログラマが書けないように設計されている。
この方式には次のような長所がある。
アドレス空間を分離する必要がない。アドレス空間の切り替えは低速な操作であり、オーバーヘッドになっているため、現代のOSではその切り替えをなるべく減らすような最適化に多大な労力を費やしている。言語ベースのプロテクションシステムではそのような切り替えが全く不要であり、全コードを同一アドレス空間に置いても安全に運用可能である。
柔軟性がある。プログラミング言語でプロテクション機構を表現できるよう設計すれば、この方式ではそれらを実装することが可能である。言語ベースのプロテクションを実現するのにハードウェアを新たに設計する必要はない。
一方、次のような短所がある。
アプリケーションの起動に時間がかかる。アプリケーションを起動する際に正しいコンパイラで生成されたものか、あるいはソースコードやバイトコードから再コンパイルが必要でないかをチェックする必要がある。
型システムが固定される。従来のシステムでは、アプリケーションは型安全性でない操作を頻繁に実行する。言語ベースのプロテクションシステムではそのような操作は許されないので、アプリケーションを書き換える必要があり、場合によっては性能が低下することになる。
言語ベースのプロテクションを採用したシステムとしては、JXやマイクロソフトのSingularityがある。
エドガー・ダイクストラは論理的観点から、バイナリセマフォにおける不可分なロックとアンロック操作だけで、プロセス間の任意の協調作動を実現できることを証明した。しかしそのような方式は一般に安全性や効率性が欠如しており、メッセージパッシング方式の方が柔軟性が高い。他の方式もいくつかあり、現代のカーネルでは共有メモリやRPCなどのシステムをサポートしていることが多い。
入出力デバイスを並行して協調作動する他のプロセス群から一様に扱えるようにするというカーネルの考え方は、Per Brinch Hansen が提唱し実装したのが最初である(似たような考え方は1967年にも示唆されていた)。Hansen はその説明で、「共通の」プロセス群を「内部プロセス」、入出力デバイスを「外部プロセス」と呼んでいる。
物理メモリと同様、アプリケーションがコントローラのポートやレジスタに直接アクセスすることを許可すると、コントローラが不正作動したり、システムがクラッシュすることになる。それに加えて、デバイスの複雑さに応じて対応するプログラムは非常に複雑化することがあり、しかも複数の異なるコントローラを使うことがある。そのため、デバイスを管理するためのより抽象化されたインタフェースを提供することが重要である。この抽象化を提供するのは一般にデバイスドライバや Hardware Abstraction Layer (HAL) である。アプリケーションは必要なら頻繁にデバイスへのアクセスを要求する。カーネルはシステムに接続されたデバイスの一覧を何らかの方法で保持しなければならない。これはBIOSや各種システムバスの機能(PCI/PCIeやUSB)を使ってなされる。あるアプリケーションがあるデバイス操作を要求すると(例えばディスプレイに文字を表示する)、カーネルは対応するドライバ(例えばビデオドライバ)に要求を送らなければならない。するとそのドライバがデバイスに対して必要な処理を行う。マイクロカーネルの場合、この際にプロセス間通信 (IPC) が使われる。
もちろん、上述したタスク群や機能群の提供方法は設計や実装の面で様々である。
「機構と方針の分離」の原則は、マイクロカーネルとモノリシックカーネルの哲学の間でかなり大きな相違がある。ここで、「機構」は様々な「方針」の実装を可能とするものであり、「方針」は特定の「操作のモード」である。例えば「機構」面では、ユーザーがログインしようとしたとき認証サーバを呼び出してアクセスを認めるべきか否かを決定するということが考えられる。一方「方針」面では、認証サーバがパスワードを要求し、データベース内の暗号化されたパスワードと照合するかもしれない。機構が汎用的であれば、機構と方針が同一モジュールに統合されている場合よりも方針の変更(例えば、パスワードの代わりにセキュリティトークンを使うなど)がより容易になる。
最小のマイクロカーネルでは非常に基本的な方針のみが含まれ、その機構はカーネル上で動作させるもの(OSの残りの部分やアプリケーション群)自身がどのような方針(メモリ管理、高度なプロセススケジューリング、管理など)を採用するか決定することを可能にする。一方モノリシックカーネルは方針の大部分をカーネル内に含む傾向があり、結果としてその上の部分の自由度は制限される。
Per Brinch Hansen は機構と方針の分離のための主張を展開した。すなわち、この分離が不適切であることが既存のOSで本質的技術革新が見られないことの主要因だとし、コンピュータアーキテクチャにおける共通課題だとした。モノリシック設計は、従来の商用システムで一般的な保護技法である「カーネルモード」と「ユーザーモード」に分離するアーキテクチャ(いわゆるリングプロテクション)から生まれた。そのアーキテクチャでは、保護(プロテクション)を必要とするモジュールを可能な限りカーネルに含めようとする。このようなモノリシック設計と特権モードの関係が機構と方針の分離における重要な問題として再注目されている。実際「特権モード」のアーキテクチャ技法は保護機構とセキュリティ方針を融合させる傾向があるが、これとは大きく異なるアーキテクチャ技法であるケイパビリティベースドアドレッシングではその2つを明確に区別し、自然にマイクロカーネル設計が可能となる。
モノリシックカーネルはカーネルの全コードを同じアドレス空間(カーネル空間)で実行するが、マイクロカーネルでは多くのサービスをユーザー空間で実行しようとし、コードベースの保守性とモジュール性を向上させようとしている。多くのカーネルは明確にどちらかに分類できるわけではなく、その中間の実装とも言うべきハイブリッドカーネルになっている。さらに特殊な設計としてナノカーネルやエクソカーネルが研究されているが、広く使われるまでには至っていない。エクソカーネルの例としてXenハイパーバイザがある。
モノリシックカーネルでは、全OSサービスはひとつのカーネル空間内に存在し、カーネルスレッド上で実行される。この手法は強力なハードウェアアクセスを提供する。UNIXの開発者ケン・トンプソンは、モノリシックカーネルの方がマイクロカーネルより実装が容易だとしている。主な欠点はシステム構成要素間の依存関係の複雑さである。例えば、デバイスドライバにバグがあっただけでシステム全体がクラッシュするし、大きなカーネルは保守が非常に困難である。
Unix系OSが伝統的に採用してきたモノリシックカーネルは、OS中核機能とデバイスドライバを全て含んでいた。デバイスドライバ、スケジューラ、メモリ管理、、ネットワークのプロトコルスタックなど、多くのプログラムが必要とするがライブラリとしてユーザー空間で実行することができない機能は、全てカーネル空間に置かれた。それら全サービスへのアクセスを可能にするため、数多くのシステムコールがアプリケーションに対して提供されている。
必要とされないサブシステムを伴って最初からロードされるモノリシックカーネルは、より汎用的な意味ではあるが、特定ハードウェア向けに設計されたものよりもチューニングが可能である。LinuxやFreeBSDなどの現代のモノリシックカーネルはUnix系OSであり、実行時にモジュールをロードする機能を備えており、必要に応じて容易に機能を拡張でき、同時にカーネル空間で動作するコード量をなるべく最小に抑えることができる。モノリシックカーネルには次のような長所がある。
関係するソフトウェアが少ないので、より高速である。
カーネルは1つのソフトウェアであるため、ソースコード量もコンパイル後の実行ファイルの大きさも小さくなる。
コードが少ないのでバグも少なく、結果としてセキュリティ問題も比較的少ない。
モノリシックカーネルはシステムコールの延長で動作する部分がほとんどである。システムコールは一般にテーブル構造で保持されるインタフェースであり、ディスク操作などのカーネル内サブシステムへのアクセスを行う。プログラム内でライブラリルーチンを呼び出すと、その中で要求をチェックしてコピーし、システムコールに渡す。したがって、それほど重い呼び出しではない。Linuxカーネルはモノリシックだがかなり小さくできる。これは、ローダブル・カーネル・モジュール機能と、カスタマイズが容易なためである。実際、フロッピーディスク1枚にカーネルだけでなく多数のユーティリティを搭載し、それだけで完動するOSとすることもできる(最も有名な例として muLinux がある)。このカーネルを小型化できる能力があるため、Linuxは組み込みシステムで急速に採用が増えている(組み込みLinux)。
このようなカーネルはOSの中核機能とデバイスドライバから成り、実行時にモジュールをロードする機能を備えている。それらによって、下層のハードウェアについての豊富で強力な抽象化を提供する。それらは単純なハードウェア抽象化の小さなセットを提供し、サーバと呼ばれるアプリケーションを使ってさらなる機能を提供する。この特定の手法でハードウェア上の高度な仮想インタフェースを定義し、プロセス管理、並行性管理、メモリ管理といったスーパーバイザモードで動作するいくつかのモジュールでOSサービスを実装し、システムコールでそれらを呼び出せるようにしている。しかし、このような設計には以下のような短所や制約がある。
カーネル内のコーディングは難しい。標準Cライブラリが使えず、デバッグにはGNUデバッガなどのソースレベルのデバッガを必要とするためである。そのため、開発中はコンピュータを頻繁にリブートする必要がある。これは単に開発者だけの問題ではない。デバッグが難しいということはバグをつぶすのが難しいということであり、カーネル内にバグが残存しやすいということでもある。
カーネル内のバグは重大な副作用を引き起こす。カーネル内の関数はどれも特権状態で動作するので、全く無関係なデータ構造を(カーネル空間内でもユーザー空間内でも)容易に壊すことができる。モジュール群は同一アドレス空間で動作するので、バグによってシステム全体をダウンさせることがある。
カーネルは肥大化しやすく、肥大化すると保守が困難になる。
コードの結合度が強く、モジュール化して分離したとしても、その分離を正しく行うのは困難である。
移植性が低い。動作させるアーキテクチャごとに書き直しが必須となる。