どうも、PiBVTです。今回は、ブートローダーから起動したxv6側でCompatibility modeからProtected modeへ移行する部分を解説していきたいと思います。 前編ではカーネルのmain関数の直前までで、後編はmain関数からの解説となります。
Long mode から Protected mode への移行手順
前回の記事でも紹介したのですが、以下のような手順でLong modeからProtected modeへと移行します。 Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3 (3A, 3B, 3C & 3D): System Programming Guide より
この手順を間違うことなく行うことで、Protected modeへと移行することができます。
xv6のブートシーケンス
xv6をBIOSから起動すると、以下のようなステップで起動します。
今回はすでにカーネル全体をロードし、Compatibility modeで動作しているため、bootasm.Sとbootmain.cは必要ありません。 xv6をmakeした際に作られるELF形式のカーネル本体であるkernelはbootasm.Sとbootmain.cを含まず、xv6.imgなるイメージディスクを作成する段階で特定番地に配置されます。 つまり、kernelをメモリー上に展開してエントリーポイントにジャンプすれば、一番最初にentry.Sが実行されるわけです。
entry.Sの先頭にProtected modeへの移行コードを書けば、残りの部分は手を入れる必要は原理上無いということです。実際にはこんな感じになります。
Compatibility mode から Protected mode への移行
モード移行用コードを追加したentry.Sは以下のようになっています。解説に必要ない部分は省略しています。
gist8bea376bbd0925830513b500697f7ffa
9行目からが呼び出される部分です。
12~18行目でブートローダ側で設定したGDTのデータ・セグメントを設定しています。
21~23行目で、Step.2に該当する操作を行います。Step2ではCR0のPGビットを0クリアすることで、ページングを無効化します。4本あるControl Registerはこのようになっています。
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3 (3A, 3B, 3C & 3D): System Programming Guide より
PGビットはCR0の第31ビットなので、CR0を読み出し0x7fffffffでandをとり再びCR0に書き戻します。
26~27行目で、Step.3に該当する操作を行います。CR3に新しく設定する legacy page-table-directory base addressの物理アドレスを入れるだけです。既存のentry.Sに同様の操作をしている部分があったので、それをそのまま流用しました。
ここで重要なのが、ページングには従来のページングと、32bitで4GB以上の物理メモリ空間を利用するためのPAE(Physical Address Extension)が有効化された場合のページングの2種類が存在することです。 この二者間ではページングテーブルの構造が大きく異なり、Long modeではPAEが有効なページングを利用しています。そして、これはCR4のPAEビットを0クリアするまで有効です。
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3 (3A, 3B, 3C & 3D): System Programming Guide より
First of all, long mode uses PAE paging and therefore you have the page-directory pointer table (PDPT), the page-directory table (PDT) and the page table (PT). There's also another table which now forms the root (instead of the PDPT or the PDT) and that is page-map level-4 table (PML4T).
そのため、PAEを無効化しない限りは他の設定が正しくとも全く動作しません。この設定はPage Size Extensionの設定でCR4を操作するときに行います。
30~33行目で、Step.4に該当する操作を行います。IA32_EFERのLMEビットを0クリアすることでIA32e modeを無効化します。
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3 (3A, 3B, 3C & 3D): System Programming Guide より
IA32_EFERのMSR numberは0xC0000080なのでECXレジスタに0xC0000080を代入し、rdmsrで読み出し、andでLMEビットを0クリアし、wrmsrで再び書き戻しています。
35~39行目で、ページングを有効化する前にPSE(Page Size Extension)とPAE無効化の設定を行います。PSEの設定は既存のentry.Sをそのまま流用し、PAEはPAEビットをandによって0クリアします。その後、CR4に書き戻します。
42~44行目で、Step.5に該当する操作を行います。CR0のPG,WP,PEビットをorで1に設定し、CR0に書き戻すことでページングを有効化します。
その後、スタックポインタの設定など既存のentry.Sに存在する部分が続き、main.cのmain関数へとジャンプします。
まとめ
以上の設定終了後、main関数へとジャンプした時点でProtected modeが有効となります。PAE等、Real modeからProtected modeへと移行したときに設定する場合は気にすることのない部分も気にしなければならないため、このコードが完成するまで非常に大変な道のりでした。
ここまででProtected modeへ移行は完了したので、後編ではxv6のBIOS依存部分をUEFI用に書き直した部分を解説したいと思います。
ソースコード等
xv6本体