借り初めのひみつきち

仮ブログです。

UEFI自作OS日記 v0.6 再始動

しばらく開発の停滞していた moe ですが・・・

github.com

思うところあってリファクタリングしながら再実装しています。

f:id:neriring16:20190812101940p:plain

既存のコードを再利用できるところは再利用していますが、完全に新規に書き直してる部分もあります。

これだけだと面白くないので具体的な違いをいくつか紹介します。

コンテキストスイッチを setjmp から専用の関数に変更しました。

moe は最低限のコストで最低限の物を作るというコンセプトだったので setjmp/longjmp の仕組みを流用することでコンテキストスイッチが簡単に実装できる予定でした。
しかし、実際に実装してみるとうまく動かすために工夫が必要で、無理やり setjmp/longjmp を使い続ける理由がないと判断して新しいバージョンでは完全に変更しています。

ウィンドウシステムの廃止

カーネルレベルでウィンドウ対応しているのは minimal と言えるのかどうかという問題もあり、将来ユーザーランドでウィンドウシステムを実装するとき邪魔になりそうだったのでウィンドウシステムに依存する処理をいったんリセットしました。

SSE の使用禁止

前述の問題と関連して、カーネルがコンテキストの怪しい部分で SSE を使用してしまうと SSE レジスタが破壊されてしまう懸念があり、最近のコンパイラは意図せず SSE 命令を生成している事があるので、根本的な対策としてカーネルでは SSE を完全に使用しないようにしました。

初期化時の 32bit コードの削除

SMP の初期化処理でリアルモードからロングモードに遷移する際、いったん 32bit モードを経由していたコードを見直して 32bit セグメントに依存しないようになりました。

この問題についてもう少し詳しく説明すると、ロングモードには 64bit モードだけではなく 16bit モードや 32bit モードも存在しています。

しかし、実は筆者は遥か昔のおれんじぺこを作っていた時代から 16bit モードから直接 64bit モードに遷移する方法が成功したことがありませんでした。ロングモードに 16bit モードは存在しないのではないかと疑った事があるレベルです。
そのため、今までは初期化時にいったん 32bit モードを経由したあと 64bit モードに遷移していました。

この問題の解決のヒントが少し前に開発していた PC エミュレーターにありました。

実は x86 の命令は 16bit モードと 32bit モードの2種類あるという単純なものではなく、デフォルトオペランドサイズによって 16bit モードと 32bit モードが切り替わる他に、 66 や 67 (名前はないけど有名なプレフィックス) でも切り替わり、命令によってはひとつのオペコードで最大4種類の命令になります。

例えば AB というオペコードは 8086 では STOSW ですが、 386 以降は以下の4種類の命令になります。

f:id:neriring16:20190812234346p:plain

STOSW 命令の場合 66 プレフィックスがあると STOSW/STOSD とニーモニックが変化しますが、 67 プレフィックスの有無はアセンブラレベルでは区別がつきません。アセンブラによっては特殊な表記で区別する場合もあります。

同様に FF /5 というオペコードは JMP FAR を指しますが、 66 プレフィックスや 67 プレフィックスによってニーモニックは変化しません。
JMP FAR 命令の場合は 67 プレフィックスの有無はメモリアドレスの指定方法が 16bit か 32bit かで変化し、アセンブラレベルで容易に区別できます。
問題は 66 プレフィックスの有無ですが、アセンブラレベルで正確に記述するのは容易ではありません。しかし、 16bit で動作するか 32bit で動作するかという重大な違いがあります。

16bit ロングモードから 64bit モードに遷移するときに 32bit の JMP FAR 命令を実行するには、 66 プレフィックスのついた命令を実行する必要があります。

この問題に気づくのに何年もかかってしまいました。

このように、 x86 の命令はひとつのオペコードに対して最大4種類の命令がありますが、アセンブラレベルでは識別が困難な命令も多く存在しています。