借り初めのひみつきち

仮ブログです。

最小ステップで作る UEFI OS v0.4.1?

昔の CPU は世代が進むごとに動作周波数が向上していき、それに伴って動作も高速になっていました。

しかし、やがて動作周波数をあげるのが困難になり、別の方法で性能向上する必要が出てきました。
そこで偉い人は考えました。
超高速な CPU を1個作るより、そこそこ高速な CPU を2個並べた方が簡単で性能出るんじゃね?

そんなわけで、かつてはマルチプロセッサーというのはサーバーなど一部のハイエンドにしか存在しませんでしたが、現代の CPU はローエンドでも2コア以上のマルチコア構成が普通です。
シングルプロセッサー対応の OS は CPU の性能の半分も使い切れていません。SMP の対応が必要です。

APIC の SMP 対応

マルチプロセッサではローカル APIC の IPI (Inter Processor Interrupt) が特に重要になります。
SMP 構成のシステムでは CPU が起動時に BSP (BootStrap Processor) と AP (Application Processor) に自動的に役割が分かれます。
通常は BSP のみが動作し、 AP は起動時に AP に役割が決まった瞬間そのまま再び起こされるまで眠りにつきます。

シングルプロセッサーシステムから SMP システムに移行するには AP に対して IPI を発行して起こす必要があります。
具体的なやり方は SDM Vol. 3 に書いてあって、各 AP に対して INIT IPI を発行して 10ms 待った後に(この間に AP 用の BIOS が起動するらしいです)Startup IPI を発行します。これで AP が起動します。

AP が起動した時どのアドレスが実行されるかは Startup IPI に指定するベクタ番号で決まります。実際のところこの値は割り込みベクタの番号ではなく実際の開始アドレスは 12bit シフトした値になります。
AP は起動直後リアルモードにいるので OS を実行するために速やかにロングモードに移行する必要があります。
処理を簡単にするため、ロングモードの動作に必要なほとんどのパラメーターは BSP からコピーした値を使います。

UEFI には Multi Processor に関する規格が存在しているので、この方法を使わなくても動作するかもしれません・・・

スケジューラの SMP 対応

前回までのコンテキストスイッチは SMP を一切考慮しておらず、スレッドリストを最初から順番に読み込んで切り替える実装でした。
シングルプロセッサ構成ならこの方法でもとりあえず動作しますが、マルチプロセッサ構成では現在実行中のスレッドがプロセッサコアごとに異なるために次に実行するべきスレッドの同期が取れません。下手をすると複数のプロセッサコアで同じスレッドの実行を取り合ってメモリ破壊する可能性もあります。

そこでいったん FIFO のキューにスレッドを全て入れて、スレッド切り替えの際にはキューから取り出して次に実行するスレッドを決めるように変更しました。この部分をもっと賢くする余地はまだたくさんあります。

実行するスレッドがなくなったときにはアイドルスレッドが必要ですが、複数のコアで同じアイドルスレッドを実行するのは色々まずいので、アイドルスレッドを特別扱いしてコアごとにアイドルスレッドを分離しました。
これ以外にもコアごとに分けて管理する必要があるものがあるので結構複雑になっています。なお、 SMP 起動前と起動後で結構変わる部分があって複雑になるためコア数が決定するまでスレッドサブシステムは動作しないように変更されました。

なお、 Lazy FPU Switch は現在の実装では SMP 環境と適合しないので OFF になっています。タイミングによっては SSE レジスタの値が大きく壊れます。

SMP 環境では予期せぬタイミングで他のプロセッサコアからメモリアクセスされる可能性があるので、シングルプロセッサーの時よりも排他制御に気を配る必要があります。

とりあえず実機ではそれなりに動作するようになりましたが、 qemu では謎のエラーが頻発していて原因不明です。

f:id:neriring16:20181118204258p:plain