64bit mode

base version : v0.1.0

基本的にosdev.orgが詳しい

詳細はIntel 64 Architectures Software Developer’s Manual Volume 3 9.8.5 Initializing IA-32e Mode(→参考資料)を参照。

64bitへの遷移

以下の順で遷移していく

  1. プロテクトモード
  2. page structure tablesの設定
  3. 各種bitを立ててIA-32eモードに入る
  4. トランポリンコードを経て、C言語コードへ

1. プロテクトモード

grubから処理が移った時点で既に32bitプロテクトモードである。

まずはcpuid命令を用いて64bitモードが存在するかどうかを確認する。コードはOSDev.orgにある物を参考にした。kernel/boot/boot.s#L73

2. page structure tablesの設定

x86のページングはいろいろあるが、ネイティブ64bit向けのIA-32eページングを使う。
IA-32eページングでは、各種テーブルのエントリ数が32bitの時の半分の512個になっている事だけ注意する事。1ページが4KBで、アドレスが64bit(=8byte)なんだから、4KB/8byte = 512というわけ。

ちなみに、このコードを書いた時のQEMUでは1GBページが使えないので、4KBページを使用した。2MBページならQEMUでも使える。1GBページの方がテーブルの設定が省略できて楽なんだけど、仕方ない。

コードは、kernel/boot/boot.s#L101

3. 各種bitを立ててIA-32eモードに入る

kernel/boot/boot.s#L209でIA-32eモードに突入する。ただし、このIA-32eモードはあくまでcompatibilityモードであり、32bitのままである事に注意。

逆に、IA-32e compatibilityモードではx86の各種命令セットが使えるが、IA-32e 64bit modeになると使えない命令が出てくる。
例えば、「絶対far jump」は使えない。

さて、ここから即座に64bit modeに突入したい所だが、gdt(→セグメンテーション<執筆中>)をhigher address(4GBよりも上の領域)に置きたい都合上、一手間踏む必要がある。

compatibilityモードではまだ32bitモードなので、仮想メモリ空間も32bitまで。だから、gdtをhiger addressに置きたければ、一度32bit領域においたtemporary gdtを読み、次に64bit modeに入った所でhigher addressのgdtを読まなければいけない。(compatibilityモードでhigher addressのgdtを読むという手抜きはできない。)

同様に、compatibility modeから64bit modeへのfar jumpでは、higher addressにジャンプする事はできない。higher addressでコードを走らせたければ、一度トランポリンコードを挟まなければいけない。

だから、kernel/boot/boot.s#L220で設定しているgdtのアドレスは32bitに収まるアドレスだし、直後にtrampolinというラベルにfar jmpしているが、これも32bitアドレス領域内に収まる場所に位置している。

さて、ここで読み込むgdtのcode segmentにはLフラグが立っており、これを読み込む事で64bit modeへ移行する事になる。つまり、kernel/boot/boot.s#L221まではIA-32e compatibilityモードで、kernel/boot/boot.s#L242から64bitモードになる。

4. トランポリンコードを経て、C言語コードへ

トランポリンコードでは既に64bit空間にいるので、64bitメモリアクセスし放題である。

gdtのポインタをhigher addressに設定した(→kernel/boot/boot.S#L258)上で、lgdtを呼び、gdtの位置をずらす。次に、higher addressに存在するmain関数へとジャンプするため、スタックに詰み(→kernel/boot/boot.S#L254)、lretqを呼んで、トランポリンはおしまい。

Q. なんでgdtを64bit空間に置くの?

A.将来的に、前のメモリ領域はアプリケーション、後ろのメモリ領域はカーネル、といった形で分離する予定なのだが、その時に32bitで収まるくらいの前の方にgdtがあると邪魔だから

Q. なんでgdtの位置をずらすのにポインタへのadd演算だけで済ませているの?ポインタをずらしてもそこにgdtは存在しないのでは?

A. ページテーブルの設定を上手くやっているから問題ない。物理メモリの最初の4GBは仮想メモリの最初の4GBへマッピングすると同時に、kLinearAddrOffsetだけずらした位置にも4GB分マッピングしている。よって、kLinearAddrOffsetだけ足すだけと、全く同じデータにアクセスできるのだ。(どうやってマッピングしてるか知りたければ、kernel/boot/boot.S#L103移行のページテーブルの設定を読む事。ただし解析するのが辛い割に得られる物は少ないと思う。たぶん)

Q. boot/boot.S#L261でどうしてljmpではなくlretqを使っているの?

A. 64bit modeではljmpによる絶対ジャンプが使えないから。代わりにlretqを使うというのは、ちょっとセコいように見えるかもしれないが、64bit modeで絶対ジャンプする上での定石。

ちなみに、compatibility modeではljmpが使える。→kernel/boot/boot.S#L221

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中