RamenOSに64ビットモードを実装した
要約
RamenOSに64ビットモードを実装しました.64ビットモード実装の際は,4階層ページングの実装,PAE(Physical Address Extension)の有効化,IA-32eモードの有効化,64ビットコードセグメントへのfar JMP,各種アドレスの変更,IDTの64ビットモード対応化,一部アセンブリコードの64ビットモード対応化が必要となります.デバッグにはQEMU以外にBochsも便利です.
はじめに
RamenOSが64ビットモードで動作するようになりました.64ビットモードでは,RAXやRBXなどの64ビットのレジスタが使用できたり,4GB以上のメモリを使用できたりします.64ビットモードでは4階層ページングが必須となります.また,32ビットモードでは使用可能だった一部の命令が64ビットモードでは使用不可になります.この記事では,64ビットモードへの移行方法を説明します.
32ビットページングと4階層ページングの違い
重要な点は以下の通りです.これらはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 4.5の情報を基にしています.
PML4とPDPTの追加,CR3レジスタに保存されるアドレス変更
4階層ページングでの追加点:PML4(Page Map Level 4)とPDPT(Page-Directory Pointer Table)の追加
| 変更点 | 32ビットページング | 4階層ページング |
|---|---|---|
CR3レジスタに保存される内容 |
ページディレクトリ(以下PD)のアドレス | PML4のアドレス |
CR3レジスタにはアドレスに加えて,各種フラグも保存します.
PML4の各エントリはPDPTのアドレスを保存し,PDPTの各エントリはPDのアドレスを保存します.
PD等のエントリサイズ,総エントリ数変更
| 変更点 | 32ビットページング | 4階層ページング |
|---|---|---|
| PD,ページテーブル(以下PT)のエントリの大きさ | 4バイト | 8バイト |
| 1つのPDまたはPTの総エントリ数 | 1024個 | 512個 |
PD,PT自体の大きさは変更ありません.また,PML4,PDPTのエントリの大きさ,総エントリ数もそれぞれ8バイト,512個です.
仮想アドレスの解釈変更
4KBページの場合です.詳しくはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter4.5 Figure 4-8を参照してください.
32ビットページングの場合
| 第nビット | 対応する変換表等 |
|---|---|
| 22 - 31 | PD |
| 12 - 21 | PT |
| 0 - 11 | 4KBページのオフセット |
4階層ページングの場合
| 第nビット | 対応する変換表等 |
|---|---|
| 39 - 47 | PML4 |
| 30 - 38 | PDPT |
| 21 - 29 | PD |
| 12 - 20 | PT |
| 0 - 11 | 4KBページのオフセット |
例として,仮想アドレス0xFFFF80000000を考えます.
| 第nビット | 0xFFFF80000000の第nビット |
解釈 |
|---|---|---|
| 39 - 47 | 0b111111111 = 0x1FF | PML4の第0x1FF番目のエントリを指します |
| 30 - 38 | 0b111111110 = 0x1FE | PDPTの第0x1FE番目のエントリを指します |
| 21 - 29 | 0 | PDの第0番目のエントリを指します |
| 12 - 20 | 0 | PTの第0番目のエントリを指します |
| 0 - 11 | 0 | 4KBページの先頭を指します |
ソースコード
この記事は,以下のツリーを基にしています. github.com
やり方
paging_64.asmを作成し,32ビット保護モード移行後のコードにインクルードする
ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
%include "paging_64.asm"
インクルードの方法の詳細はこちらを参照してください.
ページングの設定をする
ramen/paging_64.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
基本的に32ビットページングを実装する場合と変わりません.32ビットページングの実装については以下の記事を参照してください.
64ビットコードセグメントを用意する
ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
64ビットモードを含むIA-32eモードには,64ビットモードと互換モードの2つが存在します.Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5.3 に記述されている内容を表にしました.
| コードセグメントのLフィールドの値 | コードセグメントのDフィールドの値 | モード | 既定のオペラントの大きさ(ビット) | 既定のアドレスの大きさ(ビット) |
|---|---|---|---|---|
| 0 | 0 | 互換 | 16 | 16 |
| 0 | 1 | 互換 | 32 | 32 |
| 1 | 0 | 64ビット | 32 | 64 |
| 1 | 1 | 予約(使用不可) | - | - |
GDTの各ビットが表す意味については以下の記事を参照してください.
64ビットモードを実装するために,GDTに新たにエントリを追加します.尚,IA-32eモードで使用されるGDTのエントリのフィールドは以下の通りです.これ以外のフィールドは無視されます.
- Gフィールド
- Dフィールド
- Lフィールド
- AVLフィールド
- Pフィールド
- DPLフィールド
- Typeフィールド
また,Sフィールドと,Typeフィールドの最上位ビットは常に1でなければなりません.そして,Base Addressは0と見做されます.セグメントのLimit値はチェックされません.詳しくは,Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 5.2.1, Figure 5-2と,Chapter 5.3.1 を参照してください.
64ビットモードに移行する
ramen/paging_64.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
詳しい移行方法についてはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5 を参照してください.
ページングを無効にする
Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5 によれば,IA-32eモードへの移行の際は予めページングを有効にしている必要があるとされています.しかし,実際の移行の際は,最初にページングを無効にします.結局無効にするため,RamenOSでは最初からセグメンテーション方式のままIA-32eモードに移行します.もしページングを有効にしている場合,この時点でページングを無効にしてください.尚,ページングを無効にする命令は,物理アドレスと仮想アドレスが同一のメモリ上に配置してください.でないと,ページングを無効にした瞬間に命令ポインタが無効なアドレスを指してしまいます.
物理アドレス拡張を有効にする
CR4レジスタの第5ビットを1にします.CR4レジスタの各ビットの意味はCPU Registers x86-64 - OSDev Wikiで確認してください.
CR3レジスタにPML4のアドレスを保存する
32ビットページングの場合と同様です.
IA-32eモードを有効にする
MOV ECX, 0xC0000080 RDMSR OR EAX, 0x00000100 WRMSR
IA32_EFERの第8ビットを1にします.IA32_EFERの各ビットの意味はIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 4, Chapter 2.1 Table 2-2で確認してください.
このレジスタを操作するには,RDMSR命令とWRMSR命令を使用します.これらの命令は,IA32_EFERを含むMSR (Model-Specific Register)を操作します.MSRの一覧はIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 4, Chapter2にあります.
| 使用するレジスタ | RDMSR |
WRMSR |
|---|---|---|
EDX |
MSRの上位32ビットが書き込まれる |
MSRの上位32ビットに書き込む内容 |
EAX |
MSRの下位32ビットが書き込まれる |
MSRの下位32ビットに書き込む内容 |
ECX |
読み込むMSRののインデックス |
書き込むMSRのインデックス |
IA32_EFERの場合,ECXには0xC0000080を指定します.
4階層ページングを有効にする
32ビットページングの場合と同様,CR0レジスタの第31ビットを1にします.
64ビットコードセグメントにジャンプする
先程用意したセグメントにfar JMPします.これは32ビット保護モードに移行する際と同様です.
これで,64ビットモードに移行することが出来ます.
カーネルへのジャンプのアドレス,スタックポインタの初期値を変更する
ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
共に仮想アドレスに変更します.
JMP命令では,64ビットの即値アドレスを引数に取ることが出来ず,代わりにレジスタに64ビットのアドレスを保存し,そのレジスタを引数に取る必要があります.詳しくは,Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 2, Chapter 3.2のJMP命令のページを確認してください.
SIMDを無効にする
ramen/cargo_settings.json at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
OSでは,SIMDを無効にするべきです.詳細はこちらで確認してください.尚,SIMDを無効にしない場合,大きな配列へのアクセスなどでOSがクラッシュする場合があります.
IDTを64ビットモードに対応させる
ramen/descriptor_table.rs at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
64ビットモードでは,IDTの各エントリの大きさは16バイトとなります.各ビットの意味は以下の通りです.尚,括弧内の数値は使用するビット数です.これはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 6.14.1の情報を基にしています.
| Reserved (32) | |||||||||||||||||||||||||||||||
| Offset 63..32 (32) | |||||||||||||||||||||||||||||||
| Offset 31..16 (16) | P (1) | DPL (2) | 0 (1) | TYPE (4) | 0 (5) | IST (3) | |||||||||||||||||||||||||
| Segment Selector (16) | Offset 15..0 (16) | ||||||||||||||||||||||||||||||
ISTの値は,Interrupt Stack Tableを利用する場合に用いられますが,今回は使用しないため,値を0にします.
また,LIDT命令で指定するアドレスをIDTの仮想アドレスに変更する必要があります.そして,引数が指すメモリアドレスには,以下の情報を格納しなければなりません.
| 32ビットモード | 64ビットモード |
|---|---|
| 16ビットのlimit値と32ビットのIDTのアドレス | 16ビットのlimit値と64ビットのIDTのアドレス |
割り込みハンドラを修正する
ramen/asm.rs at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
| 異なる点 | 32ビットモード | 64ビットモード |
|---|---|---|
ES,DS,SS |
使用する | 使用しない |
PUSH,POP |
16ビットレジスタ,32ビットレジスタに対して使用可能 | 16ビットレジスタ,64ビットレジスタに対して使用可能 |
PUSHAD,POPAD |
使用可能 | 使用不可能 |
| 割り込みからの復帰 | IRETD |
IRETQ |
64ビットモードでは,割り込みハンドラで汎用レジスタ(RAX,RDI等8つのレジスタと,64ビットモードで新たに追加されたR8,R9,... R15の8つのレジスタで,計16個)を保存・復元します.
デバッグ方法
デバッグ方法についてはこの記事の最後でも紹介しています.
Bochsのデバッガ
Bochsはエミュレータですが,QEMUよりもデバッグがしやすいです.但し,QEMUよりも速度が遅いため,使い分けが必要です.
デバッガを有効にする方法
Gentooの場合,debuggerのUSEフラグを有効にしてください.
# /etc/portage/package.use/bochs(package.useがディレクトリの場合)あるいは /etc/portage/package.use(package.useがディレクトリではない場合) app-emulation/bochs debugger
それ以外のディストリビューションでは,パッケージマネージャでインストールしたBochsのデバッガが有効になっていない場合,ソースコードからビルドする必要があります.詳しくは以下のリンクで確認して下さい.
bochsrc
ramen/bochsrc at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub
Bochsを利用する場合,bochsrcファイルが必要となります.floppya: 1_44=にはフロッピーのイメージファイルを指定して下さい.
作成したbochsrcファイルがあるディレクトリでbochs -qを実行すると,Bochsが起動します.
pbreak, vbreak
pbreak 物理アドレス vbreak セグメント:オフセット
設定したアドレスに到達した場合,処理を中断します.後述するtrace onと組み合わせると便利です.中断した処理を再開させるには,continue命令を使用して下さい.
trace on
このコマンドは,実行される全ての機械語命令をコンソールに表示します.出力するとその分処理速度が遅くなるため,pbreakやvbreak等と併用するのがおすすめです.
show int
このコマンドは,発生した割り込み,例外をコンソールに表示します.trace onでもこれらは表示されますが,割り込みや例外を確認する場合はこちらの方が表示する内容が少ないため高速です.
page
page 仮想アドレス
このコマンドは,仮想アドレスに対応する物理アドレスを表示します.