記事の概要
『作って理解するOS』の開発環境を流用して『30日でできる!OS自作入門』を読み進めていきます。
本記事では1日目から4日目までの進捗を紹介します。
OSの技術的詳細については『30日でできる!OS自作入門』および『作って理解するOS』をご参照ください。
背景
『作って理解するOS』がとても良い本だったので、監修者である川合氏の『30日でできる!OS自作入門』を購入しました。こちらも評判がとても良い本です。
ただ、同書は2006年刊行ということもあり、フロッピーディスクの使用を想定しており、最近の開発環境にはそぐわないと思いました。
そこで『作って理解するOS』(以降『理解するOS』)の開発環境を用いて『30日でできる!OS自作入門』(以降『30日OS』)のOSに似たものを作成するのに挑戦してみます。
そのため、本記事は『理解するOS』を既に読んでいる方を対象に作成しています。
1日目
1日目の内容
アセンブラの解説をしています。
まず最初に大量の0と1が並ぶ機械語だけのコードを書いて"hello, world"を表示させます。
次に同じ動作をするプログラムをアセンブラで書くことで、人間の直感に沿って作成できるプログラムの利便性を示し、プログラムが最終的には機械語と同じことをしているのが分かります。
2日目
2日目の内容
2日目はブートセクタを作成します。
PCの電源ON時に、BIOSは外部記憶装置の先頭1セクタをメモリの0X7C00番地に読み込み、起動プログラムIPLを実行します。
ここで読み込んだ先頭の1セクタをブートセクタと呼びます。
なお、『30日OS』では外部記憶装置としてフロッピーディスクの使用を想定していますが、『理解するOS』はUSBを使用します。
2日目には、他に以下のことをしています。
- アセンブラ命令の解説
- BIOSコールの1つ、文字の出力を行うビデオサービスの解説
2日目の実装
以下が作成したBOOTセクタ、boot.sです。『30日OS』のhello.asmに相当するプログラムです。
IPLとしての動作は何もせず、1日目のプログラムと同様に"hello, world"を表示させるだけです。
ORG 0x7C00
entry:
jmp ipl
;----------------------------------------
; BPB
;----------------------------------------
times 90 - ($ - $$) db 0x90
;----------------------------------------
; IPL
;----------------------------------------
ipl:
;----------------------------------------
; セグメントレジスタ初期化
;----------------------------------------
cli
mov ax, 0
mov ss, ax
mov sp, 0x7C00
mov ds, ax
mov es, ax
sti
;----------------------------------------
; 文字出力
;----------------------------------------
mov si, .msg
.putloop:
mov al, [si]
add si, 1
cmp al, 0
je .fin
mov ah, 0x0E
mov bx, 15
int 0x10
jmp .putloop
.fin:
;----------------------------------------
; 無限ループ
;----------------------------------------
jmp $
;----------------------------------------
; メッセージ
;----------------------------------------
.msg:
db 0x0A
db 0x0A
db "hello, world"
db 0x0A
db 0
;----------------------------------------
; ブートフラグ0x55 0xAA 設定
;----------------------------------------
times 510 - ($ - $$) db 0
db 0x55, 0xAA
解説
本記事は『理解するOS』を既に読んでいて、『30日OS』をお持ちの方を前提にしている為、プログラムの詳細についての解説は行いません。
ですが、本を読んだだけでは分かりにくいと思った点、本と異なることをした点については、補足説明をしていきたく思います。
- 疑似命令:データ定義 db, dw
dbは1バイトのデータを現在番地に置くことを意味します。
dwは2バイトデータです。
例えばプログラムでは、.msgの後にdbで文字データをメモリ内に配置しています。
.msg直後のdb 0x0Aにより.msgの番地0x7Fに0x0Aのデータが配置されます。
次のdb 0x0Aで次番地0x80に0x0Aのデータが配置され、db "hello, world"で複数バイトの文字データが1バイトずつ各番地に順に配置されています。
カンマで区切って複数のデータをまとめて配置することもできます。つまり
db 0x0A
db 0x0A
は
db 0x0A, 0x0A
と書くこともできます。
- 文字の出力
BIOSコール int 0x10 でalレジスタに格納したデータが画面に出力されます。
mov ah, 0x0E
mov bx, 15
int 0x10
コンパイル時に出力されるboot.lstの該当箇所は以下のようになっています。
出力させたい文字データ.msgは、メモリの0x7F番地以降に格納されているのが分かります。
48 ;----------------------------------------
49 ; メッセージ
50 ;----------------------------------------
51 0000007F 0A .msg: db 0x0A
52 00000080 0A db 0x0A
53 00000081 68656C6C6F2C20776F- db "hello, world"
53 0000008A 726C64
54 0000008D 0A db 0x0A
55 0000008E 00 db 0
以下の図に、メモリ上のデータ配置とアセンブラの説明を示します。
0x0Aは改行コードです。文字はASCIIコードで表現しています。
- siレジスタに.msgのアドレスを格納
- siレジスタの指すアドレスに格納されている文字データをalレジスタに格納
- siレジスタの値に1を足して、次のアドレスに移行
- BIOSコールで文字表示後、.putloopに戻り2と3を繰り返す
- alレジスタに0が格納された場合、cmp命令後、je .fin により.finへ移行
- 無限ループ
『30日OS』では.fin以降、HLT命令でCPUを停止させて節電していますが、ここでは jmp $ に置き換えています。
$は現在の番地を意味します。つまり、現在の番地から現在の番地へと飛ぶので、永遠に現在の番地に留まり続けます。
- ブートフラグ
ブートセクタは510バイト目に0x55 0xAAが来るようにしないといけません。
このプログラムでは、times 510 - ($ - $$) db 0 で複数バイトの0を挿入してから、0x55 0xAAを置いています。
$$は先頭番地を意味します。($ - $$) は先頭番地から現在番地までのバイト数を示すので、510からこの数を引いた分だけ1バイトデータ0で埋めれば、ちょうど510バイト目に0x55 0xAAが来てくれます。
動作確認
コンパイルにはnasmを使用します。
また、動作確認はQEMUの仮想環境で行います。ここで『理解するOS』の開発環境を流用します。
開発環境の構築方法、バッチファイルの中身と使用方法については同書の12章をご参照ください。
『理解するOS』ではソースファイルはprog以下のフォルダに置きます。
今回に私が作成するOSは、オリジナルのHARIBOTE OSから名前をいただき、BOTEBOTE OSと命名します。
そこでprog以下にboteboteフォルダを作成し、ソースファイルを全てそこに置くことにします。
boot.sはprog/botebote/day2の階層に置きました。この階層まで移動し、
mk
によるコンパイル後、
boot
を実行すれば、仮想環境QEMUで以下のように結果を確認できます。
この時、BOOTドライブの外部記憶装置にはHDDを指定しています。
バッチファイルを使用したくない場合は以下を実行してください。
(各自の環境に合わせてpathを記入)/nasm.exe boot.s -o boot.img -l boot.lst
"C:\Program Files (x86)\qemu\qemu-system-i386.exe" -rtc base=localtime -drive file=boot.img,format=raw -boot order=c
フロッピーディスクを起動ディスクにした場合を試したい場合は以下を実行してください。
"C:\Program Files (x86)\qemu\qemu-system-i386.exe" -fda boot.img
3日目
3日目の内容
3日目はIPLからOSを実行します。
IPLの役割はOSを起動させることです。
具体的には、IPLは次に起動するプログラムを呼び出します。
3日目は以下の段階を踏んで学習します。
- 第2セクタをメモリの0x8200番地へ読み込む
- エラー時に再読み込みする
- 18セクタ分をメモリの0x8200番地へ読み込む
- 10シリンダ分をメモリの0x8200番地へ読み込む
- ブートセクタからOSを実行させる
- OSは何もしないプログラム
- OSは画面をグラフィックモードに切り替える
- C言語で開発する
- C言語で書いたプログラムを呼び出すための処理追加(ここでは解説しない)
- Cプログラムからアセンブラ関数を呼び出す
3日目には、他に以下のことをしています。
- 外部記憶装置CHS方式の解説
- BIOSコールの1つ、ディスクサービスINT 0x13の解説
外部記憶装置の読み込みの実装
3日目の内容の「10シリンダ分をメモリの0x8200番地へ読み込む」までに相当する部分を以下に作成しました。
BOOT_LOAD equ 0x7C00
BOOT_SIZE equ (1024*8)
SECT_SIZE equ 512
SECT_MAX equ (BOOT_SIZE/SECT_SIZE)
HEAD_MAX equ 1
CYLN_MAX equ 1
ORG BOOT_LOAD
entry:
jmp ipl
;----------------------------------------
; BPB
;----------------------------------------
times 90 - ($ - $$) db 0x90
;----------------------------------------
; IPL
;----------------------------------------
ipl:
;----------------------------------------
; セグメントレジスタ初期化
;----------------------------------------
cli
mov ax, 0x0000
mov ss, ax
mov sp, BOOT_LOAD
mov ds, ax
mov es, ax
sti
mov [BOOT.DRIVE], dl
;----------------------------------------
; セクタ2の読み出し
;----------------------------------------
mov ax, (BOOT_LOAD + SECT_SIZE) >> 4 ;0x7E0
mov es, ax ;データの読み込み先0x7E00番地
mov ch, [BOOT.CYLN + 0] ;シリンダ0
mov ch, [BOOT.CYLN + 1] ;シリンダ0
shl cl, 6
or cl, [BOOT.SECT] ;セクタ2
mov dh, [BOOT.HEAD] ;ヘッド0
.readloop:
mov si, 0
.retry:
mov ah, 0x02 ;セクタ読み出し命令
mov al, 1 ;読み込みセクタ数
mov bx, 0 ;データの読み込み先オフセット
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jnc .next_read
;----------------------------------------
; 読み出し失敗時
;----------------------------------------
add si, 1
cmp si, 5 ;読み込み失敗時、最大5回やり直し
jae .error
mov ah, 0x00
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jmp .retry
;----------------------------------------
; 残りのセクタ読み出し
;----------------------------------------
.next_read:
mov ax, es
add ax, 0x0020 ;読み込み先番地を512バイト加算
mov es, ax
add cl, 1
cmp cl, SECT_MAX
jbe .readloop
;----------------------------------------
; 残りのヘッダ読み出し
;----------------------------------------
mov cl, 1
add dh, 1
cmp dh, HEAD_MAX
jb .readloop
;----------------------------------------
; 残りのシリンダ読み出し
;----------------------------------------
mov dh, 0
add ch, 1
cmp ch, CYLN_MAX
jb .readloop
;----------------------------------------
; stage 2へ
;----------------------------------------
jmp stage_2
;----------------------------------------
; エラー処理:再起動
;----------------------------------------
.error:
int 0x19
ALIGN 2, db 0
BOOT:
.DRIVE: dw 0
.CYLN: dw 0
.HEAD: dw 0
.SECT: dw 2
;----------------------------------------
; ブートフラグ0x55 0xAA 設定
;----------------------------------------
times 510 - ($ - $$) db 0
db 0x55, 0xAA
stage_2:
;----------------------------------------
; 文字出力
;----------------------------------------
mov si, .msg
.putloop:
mov al, [si]
add si, 1
cmp al, 0
je .fin_put
mov ah, 0x0E
mov bx, 15
int 0x10
jmp .putloop
.fin_put:
;----------------------------------------
; 無限ループ
;----------------------------------------
jmp $
;----------------------------------------
; メッセージ
;----------------------------------------
.msg: db 0x0A, 0x0D
db 0x0A, 0x0D
db "stage 2..."
db 0x0A, 0x0D
db 0
;----------------------------------------
; 8kバイト
;----------------------------------------
times BOOT_SIZE - ($ - $$) db 0
- 読み込み範囲
『30日OS』は外部記憶装置にフロッピーディスクを想定していましたが、ここではHDDを想定しています。そのため、読み込み範囲が異なります。
フロッピーディスクの場合、片面に18セクタで1シリンダとして80シリンダ分のメモリ領域を持ちます。表をヘッダ0として、裏のヘッダ1も使えます。よって合計で
2ヘッダ×80シリンダ×18セクタ×512バイト
=1,474,560バイト=1,440KB
というフロッピーディスクの容量になります。
今回の場合、ブートサイズを8KBとしました。プログラムの最後に
times BOOT_SIZE - ($ - $$) db 0
により8KBをデータ0で埋めています。
よってセクタ数は8KB/512バイト=16セクタ、ヘッダとシリンダは1個としています。
プログラムは『30日OS』と同様であり、複数のヘッダとシリンダも読み込めるように作ってあります。
また、読み込みセクタ数は1個ずつにしています。
mov al, 1 ;読み込みセクタ数
複数個を一度に読み込まない理由は、処理するセクタ数の制限0xFFを超える場合を想定してのことだそうです。
ですが、次回の記事で説明しますが、カーネルセクタの読み込み時においては複数セクタを設定しないと読み込みに失敗してしまいました。
また、読み込み先をブートセクタ(0x7C00~0x7DFF)の直後、0x7C00+512=0x7E00番地に変更しています。
読み込み先の番地はes*16+bxで計算するので、esレジスタに0x07E0(0x7E00を4ビット右シフト)、bxレジスタに0を格納しています。
- セクタ2のプログラム
読み込みが成功してたかを画面上で確認できるように、セクタ2にメッセージを表示するプログラムを置きました。
読み込みに成功すると、"stage 2..."と画面表示されます。
動作確認
boot.sはprog/botebote/day3aの階層に置きました。この階層で以下を実行すれば、
mk
boot
仮想環境QEMUで以下のように結果を確認できます。
C言語による開発
ブートセクタ
3日目の項目8以降では、C言語を用いての開発を行っています。
『30日OS』の最大の難所と言われる個所ですが、ここで『30日OS』の開発ツールは使用せずに『理解するOS』の開発環境を利用すると、とても簡単になります。
『理解するOS』をお持ちの方は15章の4以降のboot.sをお使いください。
もしくは、オススメしませんが『30日OS』のサンプルプログラムasmhead.nasを元に私が作成した以下のboot.sでも同様のことができます。
BOOT_LOAD equ 0x7C00
BOOT_SIZE equ (1024*8)
BOOT_END equ (BOOT_LOAD + BOOT_SIZE)
SECT_SIZE equ 512
SECT_MAX equ (BOOT_SIZE / SECT_SIZE)
HEAD_MAX equ 1
CYLN_MAX equ 1
KERNEL_LOAD equ 0x10_1000
KERNEL_SIZE equ (1024*8)
BOOT_SECT equ (BOOT_SIZE / SECT_SIZE)
KERNEL_SECT equ (KERNEL_SIZE / SECT_SIZE)
ORG BOOT_LOAD
entry:
jmp ipl
;----------------------------------------
; BPB
;----------------------------------------
times 90 - ($ - $$) db 0x90
;----------------------------------------
; IPL
;----------------------------------------
ipl:
;----------------------------------------
; セグメントレジスタ初期化
;----------------------------------------
cli
mov ax, 0x0000
mov ss, ax
mov sp, BOOT_LOAD
mov ds, ax
mov es, ax
sti
mov [BOOT.DRIVE], dl
;----------------------------------------
; セクタ2の読み出し
;----------------------------------------
mov ax, (BOOT_LOAD + SECT_SIZE) >> 4 ;0x7E0
mov es, ax ;データの読み込み先0x7E00番地
mov ch, [BOOT.CYLN + 0] ;シリンダ0
mov ch, [BOOT.CYLN + 1] ;シリンダ0
shl cl, 6
or cl, [BOOT.SECT] ;セクタ2
mov dh, [BOOT.HEAD] ;ヘッド0
.readloop:
mov si, 0
.retry:
mov ah, 0x02 ;セクタ読み出し命令
mov al, 1 ;読み込みセクタ数
mov bx, 0 ;データの読み込み先オフセット
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jnc .next_read
;----------------------------------------
; 読み出し失敗時
;----------------------------------------
add si, 1
cmp si, 5 ;読み込み失敗時、最大5回やり直し
jae .error
mov ah, 0x00
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jmp .retry
;----------------------------------------
; 残りのセクタ読み出し
;----------------------------------------
.next_read:
mov ax, es
add ax, 0x0020 ;読み込み先番地を512バイト加算
mov es, ax
add cl, 1
cmp cl, SECT_MAX
jbe .readloop
;----------------------------------------
; 残りのヘッダ読み出し
;----------------------------------------
mov cl, 1
add dh, 1
cmp dh, HEAD_MAX
jb .readloop
;----------------------------------------
; 残りのシリンダ読み出し
;----------------------------------------
mov dh, 0
add ch, 1
cmp ch, CYLN_MAX
jb .readloop
;----------------------------------------
; stage 2へ
;----------------------------------------
jmp stage_2
;----------------------------------------
; エラー処理:再起動
;----------------------------------------
.error:
int 0x19
ALIGN 2, db 0
BOOT:
.DRIVE: dw 0
.CYLN: dw 0
.HEAD: dw 0
.SECT: dw 2
BOOT_TRANS:
.DRIVE: dw 0
.CYLN: dw 0
.HEAD: dw 0
.SECT: dw 2
;----------------------------------------
; ブートフラグ0x55 0xAA 設定
;----------------------------------------
times 510 - ($ - $$) db 0
db 0x55, 0xAA
FONT:
.seg: dw 0
.off: dw 0
BOOT_INFO:
.CYLS: db 0
.LEDS: db 0
.VMODE: dw 0
.SCRNX: dw 0
.SCRNY: dw 0
.VRAM: dd 0
ALIGN 16, db 0
;----------------------------------------
; GLOBAL DISCRIPTER TABLE
;----------------------------------------
GDT0:
dq 0 ;ヌルセレクタ
.ds: dw 0xFFFF, 0x0000, 0x9200, 0x00CF ; 読み書き可能セグメント32bit
.cs: dw 0xFFFF, 0x0000, 0x9A00, 0x00CF ; 実行可能セグメント 32bit
.gdt_end:
;----------------------------------------
; セレクタ
;----------------------------------------
SEL_CODE equ .cs-GDT0
SEL_DATA equ .ds-GDT0
;----------------------------------------
; GDT
;----------------------------------------
GDTR0:
dw GDT0.gdt_end - GDT0 -1 ;8*3-1
dd GDT0
stage_2:
;----------------------------------------
; 文字出力
;----------------------------------------
mov si, .msg
.putloop:
mov al, [si]
add si, 1
cmp al, 0
je .fin_put
mov ah, 0x0E
mov bx, 15
int 0x10
jmp .putloop
.fin_put:
;---------------------------------------
; ドライブ情報を取得
;---------------------------------------
mov ax, 0
mov es, ax
mov di, ax
mov ah, 0x08
mov dl, [BOOT.DRIVE]
int 0x13
jc .error
mov al, cl
and ax, 0x3F
shr cl, 6
ror cx, 8
inc cx
movzx bx, dh
inc bx
mov [BOOT.CYLN], cx
mov [BOOT.HEAD], bx
mov [BOOT.SECT], ax
;----------------------------------------
; BIOSフォントデータ取得
;----------------------------------------
mov ax, 0x1130
mov bh, 0x06
int 0x10
mov [FONT.seg], es
mov [FONT.off], bp
;----------------------------------------
; 画面モード切替
;----------------------------------------
mov al, 0x12
mov ah, 0x00
int 0x10
;----------------------------------------
; 画面モード情報取得
;----------------------------------------
mov word [BOOT_INFO.VMODE], 8
mov word [BOOT_INFO.SCRNX], 320
mov word [BOOT_INFO.SCRNY], 200
mov dword [BOOT_INFO.VRAM], 0x000a_0000
;----------------------------------------
; LED状態取得
;----------------------------------------
mov ah, 0x02
int 0x16
mov [BOOT_INFO.LEDS], al
;----------------------------------------
; A20ゲート有効化
;----------------------------------------
cli ;割り込み無効化
;キーボード無効化
;call .waitkbdout
.waitkbdout1:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout1
mov al, 0xAD
out 0x64, al
;出力ポート書き込みコマンド
;call .waitkbdout
.waitkbdout2:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout2
mov al, 0xD1
out 0x64, al
;A20ゲート有効化
;call .waitkbdout
.waitkbdout3:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout3
mov al, 0xDF
out 0x60, al
;キーボード有効化
;call .waitkbdout
.waitkbdout4:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout4
mov al, 0xAE
out 0x64, al
.waitkbdout5:
;call .waitkbdout
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout5
sti ;割り込み有効化
;----------------------------------------
; LBA方式からCHS方式への変換
;----------------------------------------
mov al, [BOOT.HEAD]
mul byte [BOOT.SECT]
mov bx, ax
mov dx, 0
mov ax, BOOT_SECT
div bx
mov [BOOT_TRANS.CYLN], ax
mov ax, dx
div byte [BOOT.SECT]
movzx dx, ah
inc dx
mov ah, 0x00
mov [BOOT_TRANS.HEAD], ax
mov [BOOT_TRANS.SECT], dx
mov al, [BOOT.DRIVE]
mov [BOOT_TRANS.DRIVE], al
;----------------------------------------
; カーネルセクタの読み出し
;----------------------------------------
mov ax, BOOT_END >> 4 ;0x9C0
mov es, ax ;データの読み込み先0x7E00番地
mov ch, [BOOT_TRANS.CYLN + 0] ;シリンダ0
mov ch, [BOOT_TRANS.CYLN + 1] ;シリンダ0
shl cl, 6
or cl, [BOOT_TRANS.SECT] ;セクタ1
mov dh, [BOOT_TRANS.HEAD] ;ヘッド0
.readloop_kernel:
mov si, 0
.retry_kernel:
mov ah, 0x02 ;セクタ読み出し命令
mov al, SECT_MAX;1 ;読み込みセクタ数
mov bx, 0 ;データの読み込み先オフセット
mov dl, [BOOT_TRANS.DRIVE] ;ドライブ番号
int 0x13
jnc .fin_read_kernel
;----------------------------------------
; 読み出し失敗時
;----------------------------------------
add si, 1
cmp si, 5 ;読み込み失敗時、最大5回やり直し
jae .error
mov ah, 0x00
mov dl, [BOOT_TRANS.DRIVE] ;ドライブ番号
int 0x13
jmp .retry_kernel
.fin_read_kernel:
;----------------------------------------
; プロテクトモード移行
;----------------------------------------
cli
lgdt [GDTR0]
mov eax, cr0
and eax, 0x7FFF_FFFF
or eax, 0x0000_0001
mov cr0, eax
;----------------------------------------
; セグメント間ジャンプ
;----------------------------------------
[BITS 32]
DB 0x66
jmp SEL_CODE:pipelineflush
;----------------------------------------
; エラー処理:再起動
;----------------------------------------
.error:
int 0x19
;----------------------------------------
; メッセージ
;----------------------------------------
.msg: db 0x0A, 0x0D
db 0x0A, 0x0D
db "stage 2..."
db 0x0A, 0x0D
db 0
;----------------------------------------
; キーボードコントローラ
;----------------------------------------
.waitkbdout:
mov al, 0x64
and al, 0x02 ;書き込み可能かを確認
jnz .waitkbdout
ret
pipelineflush:
;----------------------------------------
; セレクタ初期化
;----------------------------------------
mov ax, SEL_DATA ;1*8
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
;----------------------------------------
; カーネル部コピー
;----------------------------------------
mov esi, BOOT_END ;転送元
mov edi, KERNEL_LOAD ;転送先
mov ecx, (KERNEL_SIZE)/4
cld
rep movsd
;----------------------------------------
; カーネル部に移行
;----------------------------------------
jmp KERNEL_LOAD
;----------------------------------------
; 8kバイト
;----------------------------------------
times BOOT_SIZE - ($ - $$) db 0
このboot.sが何をしているかについては追記をご参照ください。
ここでは、C言語での開発手順のみを紹介いたします。
まず、どちらかのboot.sをprog/botebote/day3bの階層に置きます。
カーネルセクタ
次にカーネルセクタを作成します。
カーネルセクタはOSの主要機能を担うプログラムの集合です。次回の記事で更に説明する予定です。
以下のkernek.sをprog/botebote/day3bの階層に置きます。
8kB分の0x00データを埋めただけのカーネルセクタですが、いずれここにフォントアドレスの取得などの初期処理を追加します。
BOOT_LOAD equ 0x7C00
BOOT_SIZE equ (1024*8)
BOOT_END equ (BOOT_LOAD + BOOT_SIZE)
SECT_SIZE equ 512
SECT_MAX equ (BOOT_SIZE / SECT_SIZE)
HEAD_MAX equ 1
CYLN_MAX equ 1
KERNEL_LOAD equ 0x10_1000
KERNEL_SIZE equ (1024*8)
BOOT_SECT equ (BOOT_SIZE / SECT_SIZE)
KERNEL_SECT equ (KERNEL_SIZE / SECT_SIZE)
ORG KERNEL_LOAD
[BITS 32]
kernel:
ALIGN 4, db 0
FONT_ADR: dd 0
times KERNEL_SIZE - ($ - $$) db 0x00
hrbファイルの作成
C言語のプログラムからhrbファイルを作成します。
この作業にはLinuxを使用しています。WindowsおよびCygwinで同様のことを試したのですが失敗してしまいました。
お手数ですが、VirtualBoxなどの仮想マシンでUbuntuをご利用ください。
この節は以下の記事を参照して書きました。記事の作成者様には心から御礼申し上げます。
30日でできる!OS自作入門(3日目)[Ubuntu16.04/NASM]
『30日でできる!OS自作入門』をLinuxでやってみる 3.2日目
『30日でできる!OS自作入門』のメモ .hrb実行形式
以下のリンカスクリプトhar.ldをprog/botebote/day3bの階層に置きます。
申し訳ありませんが、上記サイトからコピーしただけなので、説明できるほど理解していません。
/* https://vanya.jp.net/os/haribote.html#hrb */
OUTPUT_FORMAT("binary");
SECTIONS
{
.head 0x0 : {
LONG(64 * 1024) /* 0 : stack+.data+heap の大きさ(4KBの倍数) */
LONG(0x69726148) /* 4 : シグネチャ "Hari" */
LONG(0) /* 8 : mmarea の大きさ(4KBの倍数) */
LONG(0x310000) /* 12 : スタック初期値&.data転送先 */
LONG(SIZEOF(.data)) /* 16 : .dataサイズ */
LONG(LOADADDR(.data)) /* 20 : .dataの初期値列のファイル位置 */
LONG(0xE9000000) /* 24 : 0xE9000000 */
LONG(HariMain - 0x20) /* 28 : エントリアドレス - 0x20 */
LONG(0) /* 32 : heap領域(malloc領域)開始アドレス */
}
.text : { *(.text) }
.data 0x310000 : AT ( ADDR(.text) + SIZEOF(.text) ) {
*(.data)
*(.rodata*)
*(.bss)
}
/DISCARD/ : { *(.eh_frame) }
}
このリンカスクリプトを以下のコマンドで使用すると、Cプログラムからhrbファイルを作成できます。
なお、以降のコマンドは全て、ファイルの置いてある階層
prog/botebote/day3b において実行しています。
gcc -march=i486 -m32 -nostdlib -T har.ld bootpack.c -o bootpack.hrb
コマンドオプションは以下を用いています。
- -march=cpu-type
- cpu-type
- i386 : Original Intel i386 CPU形式でコンパイルする
- i486 : i486形式でコンパイルする
- cpu-type
- -m32 : 32bitのバイナリを生成する
- -nostdlib : 標準ライブラリをリンクしない
- -T : 読み込むリンカスクリプトのファイル名を指定
- -o : 出力ファイル名を指定
アセンブラ関数をCプログラムで使用
3日目の項目10では「Cプログラムからアセンブラ関数を呼び出す」ことをしています。
具体的には、以下のCPU停止HLT命令を実行するアセンブラ関数io_hltを利用しています。
; naskfunc
; TAB=4
section .text
GLOBAL io_hlt
io_hlt: ; void io_hlt(void);
HLT
RET
『30日OS』では、この関数io_hltを用いた、以下のCプログラムを作成しています。
無限ループの中でCPU停止命令を呼び出すだけの処理です。
extern void io_hlt(void);
void HariMain(void)
{
for (;;) {
io_hlt();
}
}
アセンブラ関数を使用する場合、hrbファイルの作成方法を少し修正します。
まずはnasmfunc.asmのバイナリファイルを作成します。
nasm -g -f elf nasmfunc.asm -o nasmfunc.o
そして、nasmfunc.oも含めてbootpack.cをコンパイルして、bootpack.hrbを作成します。
gcc -march=i486 -m32 -nostdlib -T har.ld -g bootpack.c nasmfunc.o -o bootpack.hrb
imgファイル作成
ここでの作業もUbuntu上で行っています。
まずはboot.sとkernel.sからバイナリファイルを作成します。
nasm boot.s -o boot.bin -l boot.lst
nasm kernel.s -o kernel.bin -l kernel.lst
次にddコマンドを用いて、bootpack.hrbをkernel.sに結合します。
dd if=bootpack.hrb of=kernel.bin count=10 bs=512 seek=1 conv=notrunc
コマンドオプションは以下を用いています。
(Linux基本コマンドTips(163)を参照)
- if=ファイル : コピーファイルの指定
- of=ファイル : コピー先ファイルの指定
- count=個数 : コピーファイルの先頭からibsで指定したサイズ×個数までをコピーする
- bs=バイト数 : 1回に読み書きするサイズ
- seek=個数 : ディフォルト設定では、コピー先ファイルの、指定した個数分のセクタを飛ばした位置からコピーを開始する
- conv
- notrunc : 出力ファイルを切り詰めない
kernel.sの第1セクタには初期処理を入れる予定なので、第2セクタ以降にbootpack.hrbを挿入します。
seek=1に設定することで、1セクタ飛ばした位置からコピーを始めてくれます。
最後にboot.binとkernel.binを結合して、boot.imgを作成します。
cat boot.bin kernel.bin > boot.img
以上でC言語での開発は完了です。
以下を実行すれば、32ビットモードに移行し、画面が真っ暗になるのが確認できます。
boot
4日目
4日目の内容
4日目はC言語からメモリ0x000A_0000番地以降のVRAM領域に書き込みを行い、カラー画面表示を実現しています。
4日目は以下の段階を踏んで学習します。
- VRAMへ書き込むアセンブラ関数を呼び出して画面出力
- 白画面表示
- 縞画面表示
- ポインタを利用してVRAMへ書き込み画面出力
- ポインタ使用方法の解説
- キャスト使用方法の解説
- ポインタ初期値を使用した場合
- 配列を使用した場合
- 16種の色番号を初期設定から変更
- 四角形の描画
- Windows風画面の描画
4日目には、他に以下のことをしています。
- ポインタの解説
- VGAレジスタ、ビデオDAコンバータの解説
4日目の実装
白画面表示
prog/botebote/day4aにboot.s、kernel.s、har.ldを置きます。
そして、以下のbootpack.cとnaskfunc.nasを作成します。
; naskfunc
; TAB=4
section .text
GLOBAL io_hlt,write_mem8
io_hlt: ; void io_hlt(void);
HLT
RET
write_mem8: ; void write_mem8(int addr, int data);
MOV ECX,[ESP+4]
MOV AL,[ESP+8]
MOV [ECX],AL
RET
extern void io_hlt(void);
extern void write_mem8(int addr, int data);
void HariMain(void)
{
int i;
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, 15);
}
for (;;) {
io_hlt();
}
}
以下を実行してboot.imgを作成します。
$ nasm -g -f elf nasmfunc.asm -o nasmfunc.o
$ gcc -march=i486 -m32 -nostdlib -T har.ld -g bootpack.c nasmfunc.o -o bootpack.hrb
$ nasm boot.s -o boot.bin -l boot.lst
$ nasm boot.s -o boot.bin -l boot.lst
$ dd if=bootpack.hrb of=kernel.bin count=10 bs=512 seek=1 conv=notrunc
$ cat boot.bin kernel.bin > boot.img
動作確認には以下を実行します。
boot
白い画面が表示されます。
初期設定では15は白色に設定されているので、15を書き込むことで白画面表示になります。
縞画面表示
本の通りに簡単な修正をするだけなので、結果のみを以下に示します。
『30日OS』では、ポインタを様々な方法で利用して、同様の画面表示を行っています。
16種の色番号のカスタマイズ
初期設定では0が黒で、15が白になるのように、あらかじめ0から15までの番号と色の対応が決められています。
VGAレジスタのビデオDAコンバータの設定を変更することで、番号に対応する色を自由に決めることができます。
以下の記事に具体的なプログラムが紹介されているのでご参照ください。
30日でできる!OS自作入門(4日目)[Ubuntu16.04/NASM]
本の通りにプログラムを打つだけでいいはずなのですが、私の環境では設定に失敗してしまいました。
色の設定値を定めた配列table_rgbが読み出せませんでした。
table_rgbを呼び出せず、何もない0x00を読みだして設定してしまいます。
つまり16種全ての色番号のRGB設定が0になります。黒一色だけしか表示できないので、画面は真っ暗になります。
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
0x00, 0x00, 0x00, /* 0:黒 */
0xff, 0x00, 0x00, /* 1:明るい赤 */
0x00, 0xff, 0x00, /* 2:明るい緑 */
0xff, 0xff, 0x00, /* 3:明るい黄色 */
0x00, 0x00, 0xff, /* 4:明るい青 */
0xff, 0x00, 0xff, /* 5:明るい紫 */
0x00, 0xff, 0xff, /* 6:明るい水色 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:明るい灰色 */
0x84, 0x00, 0x00, /* 9:暗い赤 */
0x00, 0x84, 0x00, /* 10:暗い緑 */
0x84, 0x84, 0x00, /* 11:暗い黄色 */
0x00, 0x00, 0x84, /* 12:暗い青 */
0x84, 0x00, 0x84, /* 13:暗い紫 */
0x00, 0x84, 0x84, /* 14:暗い水色 */
0x84, 0x84, 0x84 /* 15:暗い灰色 */
};
set_palette(0, 15, table_rgb);
return;
/* static char 命令は、データにしか使えないけどDB命令相当 */
}
そこで大変不格好なことですが、数値を直接代入するプログラムを作成しました。
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 割り込み許可フラグの値を記録する */
io_cli(); /* 許可フラグを0にして割り込み禁止にする */
io_out8(0x03c8, start);
/*for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}*/
//0:
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
//1
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
//2
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0x00 / 4);
//3
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0x00 / 4);
//4
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0xff / 4);
//5
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0xff / 4);
//6
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0xff / 4);
//7
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0xff / 4);
io_out8(0x03c9, 0xff / 4);
//8
io_out8(0x03c9, 0xc6 / 4);
io_out8(0x03c9, 0xc6 / 4);
io_out8(0x03c9, 0xc6 / 4);
//9
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
//10
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x00 / 4);
//11
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x00 / 4);
//12
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x84 / 4);
//13
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x84 / 4);
//14
io_out8(0x03c9, 0x00 / 4);
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x84 / 4);
//15
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x84 / 4);
io_out8(0x03c9, 0x84 / 4);
io_store_eflags(eflags); /* 割り込み許可フラグを元に戻す */
return;
}
色設定変更プログラムの説明は『30日OS』のP88にありますが、0x03c9に設定する数値を4で割っている理由だけは分かりませんでした。
その理由は以下で説明されていました。
OS自作入門 4日目 【Linux】| VRAMとカラーパレット
最大輝度256に対して、今回のビデオモード320×200ドットの画面に合わせて輝度64にするためのようです。
四角形の描画とWindows風画面の描画
本の通りにプログラムを打ち込むだけなので、結果のみを以下に示します。
(ただし色の設定だけは先述の修正をしています。)
以下の記事に具体的なプログラムが紹介されているのでご参照ください。
30日でできる!OS自作入門(4日目)[Ubuntu16.04/NASM]
(追記)
32ビットモードへの移行
『30日OS』では5日目から8日目にかけて、3日目で作成したブートセクタについて32ビットモードへ移行する仕組みを解説しています。
『理解するOS』では14章と15章で詳しく解説しています。
詳細はそちらを参照いただくとして、ここでは私が作成したboot.sとkernel.sがどのようにして32ビットモードへ移行しているかを簡単に説明します。
BOOT_LOAD equ 0x7C00
BOOT_SIZE equ (1024*8)
BOOT_END equ (BOOT_LOAD + BOOT_SIZE)
SECT_SIZE equ 512
SECT_MAX equ (BOOT_SIZE / SECT_SIZE)
HEAD_MAX equ 1
CYLN_MAX equ 1
KERNEL_LOAD equ 0x10_1000
KERNEL_SIZE equ (1024*8)
BOOT_SECT equ (BOOT_SIZE / SECT_SIZE)
KERNEL_SECT equ (KERNEL_SIZE / SECT_SIZE)
ORG BOOT_LOAD
entry:
jmp ipl
;----------------------------------------
; BPB
;----------------------------------------
times 90 - ($ - $$) db 0x90
;----------------------------------------
; IPL
;----------------------------------------
ipl:
;----------------------------------------
; セグメントレジスタ初期化
;----------------------------------------
cli
mov ax, 0x0000
mov ss, ax
mov sp, BOOT_LOAD
mov ds, ax
mov es, ax
sti
mov [BOOT.DRIVE], dl
;----------------------------------------
; セクタ2の読み出し
;----------------------------------------
mov ax, (BOOT_LOAD + SECT_SIZE) >> 4 ;0x7E0
mov es, ax ;データの読み込み先0x7E00番地
mov ch, [BOOT.CYLN + 0] ;シリンダ0
mov ch, [BOOT.CYLN + 1] ;シリンダ0
shl cl, 6
or cl, [BOOT.SECT] ;セクタ2
mov dh, [BOOT.HEAD] ;ヘッド0
.readloop:
mov si, 0
.retry:
mov ah, 0x02 ;セクタ読み出し命令
mov al, 1 ;読み込みセクタ数
mov bx, 0 ;データの読み込み先オフセット
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jnc .next_read
;----------------------------------------
; 読み出し失敗時
;----------------------------------------
add si, 1
cmp si, 5 ;読み込み失敗時、最大5回やり直し
jae .error
mov ah, 0x00
mov dl, [BOOT.DRIVE] ;ドライブ番号
int 0x13
jmp .retry
;----------------------------------------
; 残りのセクタ読み出し
;----------------------------------------
.next_read:
mov ax, es
add ax, 0x0020 ;読み込み先番地を512バイト加算
mov es, ax
add cl, 1
cmp cl, SECT_MAX
jbe .readloop
;----------------------------------------
; 残りのヘッダ読み出し
;----------------------------------------
mov cl, 1
add dh, 1
cmp dh, HEAD_MAX
jb .readloop
;----------------------------------------
; 残りのシリンダ読み出し
;----------------------------------------
mov dh, 0
add ch, 1
cmp ch, CYLN_MAX
jb .readloop
;----------------------------------------
; stage 2へ
;----------------------------------------
jmp stage_2
;----------------------------------------
; エラー処理:再起動
;----------------------------------------
.error:
int 0x19
ALIGN 2, db 0
BOOT:
.DRIVE: dw 0
.CYLN: dw 0
.HEAD: dw 0
.SECT: dw 2
BOOT_TRANS:
.DRIVE: dw 0
.CYLN: dw 0
.HEAD: dw 0
.SECT: dw 2
;----------------------------------------
; ブートフラグ0x55 0xAA 設定
;----------------------------------------
times 510 - ($ - $$) db 0
db 0x55, 0xAA
FONT:
.seg: dw 0
.off: dw 0
BOOT_INFO:
.CYLS: db 0
.LEDS: db 0
.VMODE: dw 0
.SCRNX: dw 0
.SCRNY: dw 0
.VRAM: dd 0
ALIGN 16, db 0
;----------------------------------------
; GLOBAL DISCRIPTER TABLE
;----------------------------------------
GDT0:
dq 0 ;ヌルセレクタ
.ds: dw 0xFFFF, 0x0000, 0x9200, 0x00CF ; 読み書き可能セグメント32bit
.cs: dw 0xFFFF, 0x0000, 0x9A00, 0x00CF ; 実行可能セグメント 32bit
.gdt_end:
;----------------------------------------
; セレクタ
;----------------------------------------
SEL_CODE equ .cs-GDT0
SEL_DATA equ .ds-GDT0
;----------------------------------------
; GDT
;----------------------------------------
GDTR0:
dw GDT0.gdt_end - GDT0 -1 ;8*3-1
dd GDT0
stage_2:
;----------------------------------------
; 文字出力
;----------------------------------------
mov si, .msg
.putloop:
mov al, [si]
add si, 1
cmp al, 0
je .fin_put
mov ah, 0x0E
mov bx, 15
int 0x10
jmp .putloop
.fin_put:
;---------------------------------------
; ドライブ情報を取得
;---------------------------------------
mov ax, 0
mov es, ax
mov di, ax
mov ah, 0x08
mov dl, [BOOT.DRIVE]
int 0x13
jc .error
mov al, cl
and ax, 0x3F
shr cl, 6
ror cx, 8
inc cx
movzx bx, dh
inc bx
mov [BOOT.CYLN], cx
mov [BOOT.HEAD], bx
mov [BOOT.SECT], ax
;----------------------------------------
; BIOSフォントデータ取得
;----------------------------------------
mov ax, 0x1130
mov bh, 0x06
int 0x10
mov [FONT.seg], es
mov [FONT.off], bp
;----------------------------------------
; 画面モード切替
;----------------------------------------
mov al, 0x12
mov ah, 0x00
int 0x10
;----------------------------------------
; 画面モード情報取得
;----------------------------------------
mov byte [BOOT_INFO.VMODE], 8
mov word [BOOT_INFO.SCRNX], 320
mov byte [BOOT_INFO.SCRNY], 200
mov dword [BOOT_INFO.VRAM], 0x000a_0000
;----------------------------------------
; LED状態取得
;----------------------------------------
mov ah, 0x02
int 0x16
mov [BOOT_INFO.LEDS], al
;----------------------------------------
; A20ゲート有効化
;----------------------------------------
cli ;割り込み無効化
;キーボード無効化
;call .waitkbdout
.waitkbdout1:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout1
mov al, 0xAD
out 0x64, al
;出力ポート書き込みコマンド
;call .waitkbdout
.waitkbdout2:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout2
mov al, 0xD1
out 0x64, al
;A20ゲート有効化
;call .waitkbdout
.waitkbdout3:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout3
mov al, 0xDF
out 0x60, al
;キーボード有効化
;call .waitkbdout
.waitkbdout4:
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout4
mov al, 0xAE
out 0x64, al
.waitkbdout5:
;call .waitkbdout
mov al, 0x64
and al, 0x02 ;書き込み可能を待機
jnz .waitkbdout5
sti ;割り込み有効化
;----------------------------------------
; LBA方式からCHS方式への変換
;----------------------------------------
mov al, [BOOT.HEAD]
mul byte [BOOT.SECT]
mov bx, ax
mov dx, 0
mov ax, BOOT_SECT
div bx
mov [BOOT_TRANS.CYLN], ax
mov ax, dx
div byte [BOOT.SECT]
movzx dx, ah
inc dx
mov ah, 0x00
mov [BOOT_TRANS.HEAD], ax
mov [BOOT_TRANS.SECT], dx
mov al, [BOOT.DRIVE]
mov [BOOT_TRANS.DRIVE], al
;----------------------------------------
; カーネルセクタの読み出し
;----------------------------------------
mov ax, BOOT_END >> 4 ;0x9C0
mov es, ax ;データの読み込み先0x7E00番地
mov ch, [BOOT_TRANS.CYLN + 0] ;シリンダ0
mov ch, [BOOT_TRANS.CYLN + 1] ;シリンダ0
shl cl, 6
or cl, [BOOT_TRANS.SECT] ;セクタ1
mov dh, [BOOT_TRANS.HEAD] ;ヘッド0
.readloop_kernel:
mov si, 0
.retry_kernel:
mov ah, 0x02 ;セクタ読み出し命令
mov al, SECT_MAX;1 ;読み込みセクタ数
mov bx, 0 ;データの読み込み先オフセット
mov dl, [BOOT_TRANS.DRIVE] ;ドライブ番号
int 0x13
jnc .fin_read_kernel
;----------------------------------------
; 読み出し失敗時
;----------------------------------------
add si, 1
cmp si, 5 ;読み込み失敗時、最大5回やり直し
jae .error
mov ah, 0x00
mov dl, [BOOT_TRANS.DRIVE] ;ドライブ番号
int 0x13
jmp .retry_kernel
.fin_read_kernel:
;----------------------------------------
; プロテクトモード移行
;----------------------------------------
cli
lgdt [GDTR0]
mov eax, cr0
and eax, 0x7FFF_FFFF
or eax, 0x0000_0001
mov cr0, eax
;----------------------------------------
; セグメント間ジャンプ
;----------------------------------------
[BITS 32]
DB 0x66
jmp SEL_CODE:pipelineflush
;----------------------------------------
; エラー処理:再起動
;----------------------------------------
.error:
int 0x19
;----------------------------------------
; メッセージ
;----------------------------------------
.msg: db 0x0A, 0x0D
db 0x0A, 0x0D
db "stage 2..."
db 0x0A, 0x0D
db 0
;----------------------------------------
; キーボードコントローラ
;----------------------------------------
.waitkbdout:
mov al, 0x64
and al, 0x02 ;書き込み可能かを確認
jnz .waitkbdout
ret
pipelineflush:
;----------------------------------------
; セレクタ初期化
;----------------------------------------
mov ax, SEL_DATA ;1*8
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
;----------------------------------------
; カーネル部コピー
;----------------------------------------
mov esi, BOOT_END ;転送元
mov edi, KERNEL_LOAD ;転送先
mov ecx, (KERNEL_SIZE)/4
cld
rep movsd
;----------------------------------------
; カーネル部に移行
;----------------------------------------
jmp KERNEL_LOAD
;----------------------------------------
; 8kバイト
;----------------------------------------
times BOOT_SIZE - ($ - $$) db 0
ドライブ情報を取
『30日OS』にはない設定です。
BIOSフォントデータ取得
『30日OS』にはない設定です。
『30日OS』では自作したフォントを用いて文字表示していますが、今回は『理解するOS』のBIOSフォントを利用する方法を採用します。
BIOSコール0x10でフォントを取得しています。
画面モード切替
axレジスタに0x0013を設定して320×200ドット16色モードを選択します。
『理解するOS』では0x0012を設定して640×480ドット16色モードを使用しており、画面サイズが異なるのでプログラムを流用する際には気を付けないといけません。
また、メモリのアドレスBOOT_INFO(メモリ番地0x204)に、この画面モードの縦横サイズなどを保存しておきます。
boot.lstを確認すれば、どのメモリ番地にBOOT_INFOのが保存されているかが分かります。
135 BOOT_INFO:
136 00000204 00 .CYLS: db 0
137 00000205 00 .LEDS: db 0
138 00000206 0000 .VMODE: dw 0
139 00000208 0000 .SCRNX: dw 0
140 0000020A 0000 .SCRNY: dw 0
141 0000020C 00000000 .VRAM: dd 0
A20ゲート有効化
CPUには32本のアドレスバスがあり、0x0000_0000から0xFFFF_FFFFまでの番地が利用できますが、初期状態ではA0からA19までの下位アドレスしか使用できません。
そこでキーボードコントローラから信号を送り、A20からA31を使用できるように有効化します。
最初に学習した時は、ここでキーボードコントローラを使う理由が分かりませんでした。
どうやらコンピュータの歴史において、IBMがキーボードの未使用のピンを利用したという経緯があるようです。
LBA方式からCHS方式への変換
『30日OS』にはない設定です。
カーネルセクタの読み出し
外部記憶装置からカーネルセクタを読みだします。
カーネルセクタは上位アドレスに読み込みたいのですが、ブートモードではまだ下位アドレスしか使用できません。
そのため、まずはブートセクタの終わりの位置にカーネルセクタを読み込みます。