[PR]100万円が無料で当たる!:今すぐ応募して現金を当てよう!


主要なシステムコールがわかりましたので、OSの設計に移ります。

DOS はリアルモードで実装する方法と、プロテクトモード(32bit)で 実装する方法の2通りが考えられます。
今回は後者、つまりカーネルをプロテクトモード(32bit)で実装して アプリケーション実行時に仮想86モードに切り替える方法をとることにしました。
もともとDOSは16bitのリアルモードで動くOSです。なぜプロテクトモードに 切り替えるのかという疑問が出てくるでしょう。
以下、特に断らない限り、リアルモードを16bit(モード)、 プロテクトモードを32bit(モード)と呼ぶことにします。

カーネルを16bitモードで実装すると、 32bitモードで実装する場合に比べ、 既存のドライバなどとの互換性は高くなります。
例えば、既存のメモリマネージャ(HIMEMやEMM386など)をそのまま使うことができます。
しかし、すべてのコードが16bitモードで実行されますので、 16bit境界やメモリ消費量を意識しながら開発することになります。

一方、カーネルを32bitモードで実装すれば、 16bit境界やメモリ消費量の悩みはなくなり、 WindowsやLinuxと同じ感覚で 開発をすすめることができます。
メモリマネージャについては、同等の機能を提供すれば良いでしょう。
また、実行には386以上のCPUが必要になりますが、 PCのCPUのほとんどは386以上ですから、この制限は あまり気にしなくても良いでしょう。
これが、今回32bitモードを選んだ理由です。

DOSアプリケーションは16bitのコードとして実行されます。
DOSの機能を提供するためには、カーネルの一部は16bitのコードとして 実装しておき、適宜32bitと切り替える必要があります。
そこで、どの機能を16bitで実装するか考えた結果、 次のようなコードは16bitで実装したほうが良いだろうという 結論になりました。

  • アプリケーションから参照できるDOSのワークエリア
  • アプリケーションによりフックできるソフトウェア割り込み
  • far callで提供するシステムコール

  • ソフトウェア割り込みやfar callのシステムコールについては、 エントリポイントさえ16bitであれば問題は起こりません。
    システムコール本体の実装は32bitで行ったほうが簡単でしょう。
    また、これら以外はいずれのモードでも良さそうですので、 なるべく32bitを使うことにします。
    これをふまえ、各機能を次のモードで実装することにします。

    機能モード補足説明
    INT 20h〜2Fh32bit DOS の機能を提供するソフトウェア割り込みです。 16bitのコードからフックされる可能性がありますので 16bitのエントリポイントを提供する必要があります。
    INT 31h32bit INT 31hはDPMIを提供する割り込みです。 16bitのコードから呼び出されることはまずありません。
    INT 67h32bit INT 67hはEMSやVCPIを提供する割り込みです。 16bitのコードから呼び出されますが、 32bitで実装しても問題ありません。 ただし16bit コードから far call されることがありますので、 16bitのエントリポイントとして INT 67h、IRET を用意します。
    その他のINT xxh16bit DOS 用の割り込みベクタ(INT 20h〜INT 3Fh)を含め、ソフトウェア割り込みは 16bitのコードからフックされる可能性があります。
    キャラクタデバイス16bit 念のため16bitで実装します。
    CD-ROMドライバなど、一部のドライバは 16bitのコードから直接呼び出される可能性があるためです。
    ブロックデバイス32bit おそらく、アプリケーションから呼び出される可能性は低いでしょう。
    OSの初期化コード両方 初期化コードはその一部を16bit、残りを32bitで 実装します。なるべく32bitを使うようにします。
    モード切り替えコード両方 32bitモードで実装するため、 16bitと32bitを切り替えるコードを実装する必要があります。
    特権命令のエミュレーション32bit DOSアプリケーションの多くは、本来OSが実行すべき ソフトウェア割り込みなどの特権命令を呼び出します。 アプリケーションがこれらの命令を実行すると、 CPUの設定によってはCPU例外の#GP(0)が発生します。 少なくとも CLI、STI、PUSHF、POPF、INT、IRET、IN、OUT、INSx、OUTSx、HLT については、命令をエミュレートするか、#GP(0)が 起こらないようにCPUを設定する必要があります。 なお、#GP(0)は一般保護違反などと呼ばれています。


    INT 20h〜2Fh、INT 67hは両方とも 16bitのエントリポイントを提供する必要があります。
    これは一見、同じことのようにおもえます。
    しかし、 INT 67hは呼び出されると直接32bitのコードを実行するのに対し、 INT 20h〜2Fhは呼び出された時は必ず一度16bitに切り替えます。
    具体的には、INT 20h〜2Fhは次の表のように実装します。

    ステップ116bitから呼び出されます。
    ステップ2#GP(0)により32bitに切り替わります。
    ステップ3フック対策としてINTをエミュレートし、16bitモードに戻します。
    ステップ4フックされていなければ再度32bitに切り替えてシステムコールを実行します。
    ステップ516bitの呼び出し元に戻ります。


    フック対策として1度16bitに戻す必要があるため、 システムコールを1回呼び出すたびに、 2回もモードの切り替えを行います。
    結果として、仮想86モードはリアルモードよりも遅くなります。
    後段の切り替えは仕方ないとしても、 前半の切り替えは単にINTをエミュレートしているだけで、 なんとかならないのかと考える方もいるでしょう。
    簡単のためここでは扱いませんが、 586クラス以降のCPUの大半が持つ VME(仮想86モード拡張)を使うと、 この前半のINT命令など、いくつかのモード切り替えをスキップできます。
    興味のある方はCPUのマニュアルなどをご参照ください。

    なお、 INTの2回目の切り替えでは 16bitから32bitへの意図的な切り替えが必要になります。
    この切り替えは、例えば予め決めたアドレスで HLT 命令など 特権命令を実行して#GP(0)を起こし、 例外処理でそれを検出すれば実現できます。


    2005-08-27: ChangeLog作成 (最終更新)
    Copyright (C) 2005 hoverkid, all rights reserved.
    [PR]衝撃!あなたの本当の裏の顔!:実は貴方はΟΔ県出身?ここで分かる真実