linuxカーネル読書がしたい(ブート編4)
前回に引き続き読書を続けて行く.main.cの読書の続きから.
main.c(リアルモードでのカーネルコードモジュール)
・0ページ目にブートヘッダをコピー
・early-bootコンソールの初期化(コンソールの準備)
・ヒープの初期化
・CPUの検証
・BIOSをどのCPUモードで使うかの設定
・メモリレイアウトの検出
・キーボードの初期化
・(もし定義されていれば)APM情報の照会
・(もし定義されていれば)EDD情報の照会
↓このページはここから
・ビデオモードの設定
・プロテクトモードへの移行
↑ここまで
set_video()
まずはヒープ領域のリセット.具体的にはHEAP = _end.(ヒープの先頭アドレス)
store_mode_params()
後々カーネルが使う為にビデオモードパラメータをストアする.BIOSに問い合わせることでなされるが,行列パラメータは除かれ,(テキスト解像度)80x25モードと直接セットする.というのも,あるBIOSからはとんでもな値が来てしまうことがあるからである.
テキスト解像度は画面上にテキストを何文字表示できるかの数字を表している.以下のURLの最初の画像がわかりやすい.
http://www008.upp.so-net.ne.jp/pocketbsd/dump.html
store_cursor_position()
カーソル位置のストア.
ah=0x03を指定してint 0x10を実行して,カーソル位置とサイズを取得.
返り値は以下の通りである.
ch:カーソルが始まるスキャンラインのスタート位置
cl:カーソルが始まるスキャンラインの終わりの位置
dh:カーソルの行(0x00は一番上)
dl:カーソルの列(0x00は一番左)
これにより,boot_params.screen_info.orig_xにはdl,yにはdhを代入する.
chの6bit目が立っている,もしくはchの下位5bit > clの下位5bitの時
boot_params.screen_info.flagsにVIDEO_FLAGS_NOCURSORを付加する.
===== store_cursor_position ここまで =====
store_video_mode()
ビデオモードのストア.しかしどこでもページは0と仮定しているためここでビデオモードのストアは少し愚行かもしれないとされている.
ahに0x0fを指定してint 0x10を実行して現在のビデオモードを取得する.返り値は以下の通りである.
al:ディスプレイ(ビデオ)モード
bh:アクティブ(ビデオ)ページ
alのモードについての詳細は以下のURLに記載されている.
http://www.delorie.com/djgpp/doc/rbinter/it/10/0.html
今回はalの下位7bitをboot_params.screen_info.orig_video_modeに取得し,bhをboot_params.screen_info.orig_video_pageに取得する.
===== store_video_mode ここまで =====
↑の関数で取得したビデオモードが0x07の時にはビデオセグメントを0xb000にして,それ以外の時には0xb800にセットする.
後者はCGA,EGA,VGAなどの時であるとコメントされている.
fsレジスタを0にセットする.
fs:0x485の2バイトをfont_sizeに指定し,boot_params.screen_info.orig_video_pointsに代入.(BIOSエリアから持って来ている?)
xをfs:0x44aからの2バイト,yをfs:0x484からの1バイト+1(CGAの時は25固定)にセット.
強制的にx,yをセットする時には↑のあとでセットし直す.
boot_params.screen_info.orig_video_cols = x
boot_params.screen_info.orig_video_lines = y
===== store_mode_params ここまで =====
save_screen()
スクリーン情報をセーブする関数.セーブ対象を設定しているstore_mode_params()の後で呼ぶべきとされている.
つい先ほど↑のstore_mode_paramsで得たx, yとカーソルのx, yをセーブ用の構造体に保存する.
ここでスクリーン分+512のヒープ領域があるかの確認をする.この確認はboot.hに定義されているheap_freeであるが,非常にシンプルなので以下に載せる.(malloc - freeのfreeではない)
212 static inline bool heap_free(size_t n) 213 { 214 return (int)(heap_end-HEAP) >= (int)n; 215 }
ついでにその直後で呼ばれるGET_HEAPの内部で動く__get_heapも以下に載せる
200 static inline char *__get_heap(size_t s, size_t a, size_t n) 201 { 202 char *tmp; 203 204 HEAP = (char *)(((size_t)HEAP+(a-1)) & ~(a-1)); 205 tmp = HEAP; 206 HEAP += s*n; 207 return tmp; 208 }
つまり現在のヒープの指し示されたアドレスからアライメントされた分を足したアドレスを返し,ヒープの指すアドレスを指定されたサイズ分増やしておくものである.
このGET_HEAPでスクリーン情報を保存したアドレスをdataに格納する.
ビデオセグメントの先頭からスクリーン分の情報をヒープ領域のdataアドレスへコピーする.(copy_from_fsは事実上のmemcpyをしている)
===== save_screen ここまで =====
probe_cards(int unsafe)
ビデオドライバを探し,モードリストを作らせる.unsafeには0か1が指定される.
video_cardsのアドレスはsetup.ldに定義されている.card_info構造体の定義を以下に載せる.video.hより.
72 struct card_info { 73 const char *card_name; 74 int (*set_mode)(struct mode_info *mode); 75 int (*probe)(void); 76 struct mode_info *modes; 77 int nmodes; /* Number of probed modes so far */ 78 int unsafe; /* Probing is unsafe, only do after "scan" */ 79 u16 xmode_first; /* Unprobed modes to try to call anyway */ 80 u16 xmode_n; /* Size of unprobed mode range */ 81 };
probe関数があるなら,探索させてnmodes(これまで探索されたモード数)を格納する.ないならnmodesを0に指定する.
===== probe_cards ここまで =====
unsafe=0でcard_probeを呼び出しnmodesを探索させる.その後のfor文は以下のようになっている.
329 for (;;) { 330 if (mode == ASK_VGA) 331 mode = mode_menu(); 332 333 if (!set_mode(mode)) 334 break; 335 336 printf("Undefined video mode number: %x\n", mode); 337 mode = ASK_VGA; 338 }
modeはset_videoの先頭で定義されたmode = boot_params.hdr.vid_modeから.
ASK_VGAであればmode_menu()して,そこから得たmodeをセットして0が返って来たらfor文を抜け,そうでなければ知らないビデオモードとし,先頭のmode_menu()の呼びだしに戻るものである.
mode_menu()
kbd_flushはキーボードがpending(保留中)でなければget_char()し続けるもの.
さきほどprobeしたカード情報をdisplay_mode()で表示させて選んだものを返す.もし"scan"と入力した時にはunsafe=1としてprobe_cardsを呼び出す.
#define H(x) *1;
となっている.code32のスタート地点のポインタと,ブートパラメータのポインタをdsレジスタに乗せた値の2つが引数となっている.
アセンブラコードを読んだ要約は以下
ーーーーーーーーーー
コードセグメントアドレスをラベル2に足しこむ(addl %ebx, 2f??)
ブートデータセグメントをcx,ブートTSS(Task State Segment)をdiに読み込む
cr0にプロテクトモードフラグをセット
in_pm32にljmpl
フラット32-bitモードのためにデータセグメントをセット.具体的には先ほどのブートデータセグメントをds, es, fs, gs, ssにコピー.
コードセグメント分espを足す.
TSSを選択するレジスタからタスクレジスタをロードする.(LTR命令)
ecx,edx,ebx,ebp,ediを0クリア
LDT(Local Descriptor Table)をロード.(LLDT命令)
LTRとLLDTはIntel VTを円滑にするために用いられる?
32-bitエントリポイントへジャンプ
ーーーーーーーーーー
これで実際に32-bitプロテクトモードへジャンプした
===== go_to_protected_mode ここまで =====
リアルモードでの動作を4回に渡って読書していったが,知らない単語が多かったため進むのに手こずってしまった.加えて読書の精度は著しく低いため,後日再び2周目の読書をした方がいいと思った.
参考
http://www.hdmi-navi.com/edid/
https://e2e.ti.com/group/jp/b/microcontroller/archive/2016/07/20/q-a-23-nmi
http://wiki.osdev.org/CMOS#Reading_from_the_CMOS
http://www.wiki.os-project.jp/?A20
http://wiki.osdev.org/Global_Descriptor_Table
*1:x)-'a'+10)の意味?
===== mode_menu ここまで =====
mode_menuで選んだモードがboot_params.hdr.vid_modeになる.
vesa_store_edid()
EDID情報をカーネルの為にセーブする.EDID(Extended Display Identification Data)とはディスプレイなどのシンク機器が内蔵するその機器の情報や対応する能力などが記述された「機器固有の識別データ」である.(参考URLより一部抜粋)
===== vesa_store_edid ここまで =====
ここで再びstore_mode_paramsでモードパラメータをセーブする.
do_restoreフラグがセットされていたらセーブしたスクリーンをレストアする.
restore_screen()
スクリーンをレストアする.
===== restore_screen ここまで =====
===== set_video おわり =====
go_to_protected_mode()
プロテクトモードへの移動をする.
realmode_switch_hook()
リアルモードへのスイッチフックが有効ならそれを呼び出し,そうでないなら全ての割り込みを無効にする処理を行う.
boot_params.hdr.realmode_swtchに関数が入っているならlcallwで呼び出す.そうでないならcliを実行して割り込みを禁止する.elseについて,0x70ポートに0x80を出力しているが,コメントによればNMIを無効にしているようである.NMI(Non Maskable Interrupt)とはマスクできない割り込みであり,本当に緊急で割り込みを入れなければならない時に使うものである.
outb 0x80, 0x70とは,CMOSのI/Oポートに0x80のビットをセットすることでNMIを無効にしている.このビットが0の時にはNMIは有効であるという.
NMIを無効にしたら,0x80ポート(DELAY_PORT)にalの値を書き込んでI/O delayを行う.
===== realmode_switch_hook ここまで =====
次にA20ゲートを有効にする.A20は1MiB以上のメモリを使う時に有効でなければならないアドレスバスゲート.マシン起動直後は互換性のためにA0〜A19までに制限されていることが多いようである.有効にできなければブートが出来ないとしてdie()を呼び出す.
reset_coprocessor
FPUのIGNNE#がアサートされていたらリセットする.
I/OポートのFPU部分(この関数では0xf0, 0xf1)に0を出力する.
===== reset_coprocessor ここまで =====
mask_all_interrrupts()
PICでの割り込みを無効にする.PICはPeripheral Interface Controllerの略.I/Oポート0x21,0xa1はそれぞれpic1,pic2のポート.
===== mask_all_interrupts ここまで =====
setup_idt
IDTをセットアップする.IDT(Interrupt Descriptor Table)は割り込みと割り込みハンドラを結びつける8バイトのテーブル.
===== setup_idt ここまで =====
setup_gdt
GDTをセットアップする.GDT(Global Descriptor Table)はCPUにメモリセグメントを伝えるために使われるテーブル.
===== setup_gdt ここまで =====
protected_mode_jump(u32 entrypoint, u32 bootparams)
実際にプロテクトモードへジャンプする関数.ソースはpmjump.Sに記述されている.呼ばれる時には,
protected_mode_jump(boot_params.hdr.code32_start, (u32)&boot_params + (ds() << 4