8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

『作って理解するOS』の開発環境で作成する『30日でできる!OS自作入門』(1日目~4日目)

Last updated at Posted at 2019-10-15

記事の概要

『作って理解する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"を表示させるだけです。

prog/src/day2/boot.s
	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コードで表現しています。

  1. siレジスタに.msgのアドレスを格納
  2. siレジスタの指すアドレスに格納されている文字データをalレジスタに格納
  3. siレジスタの値に1を足して、次のアドレスに移行
  4. BIOSコールで文字表示後、.putloopに戻り2と3を繰り返す
  5. alレジスタに0が格納された場合、cmp命令後、je .fin により.finへ移行
アドレスマップ.png
  • 無限ループ

『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を指定しています。

2日目1.png

バッチファイルを使用したくない場合は以下を実行してください。

(各自の環境に合わせて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番地へ読み込む」までに相当する部分を以下に作成しました。

prog/botebote/day3a/boot.s
	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で以下のように結果を確認できます。

3日目1.png

C言語による開発

ブートセクタ

3日目の項目8以降では、C言語を用いての開発を行っています。
『30日OS』の最大の難所と言われる個所ですが、ここで『30日OS』の開発ツールは使用せずに『理解するOS』の開発環境を利用すると、とても簡単になります。

『理解するOS』をお持ちの方は15章の4以降のboot.sをお使いください。
もしくは、オススメしませんが『30日OS』のサンプルプログラムasmhead.nasを元に私が作成した以下のboot.sでも同様のことができます。

prog/botebote/day3b/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データを埋めただけのカーネルセクタですが、いずれここにフォントアドレスの取得などの初期処理を追加します。

prog/botebote/day3b/kernel.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		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の階層に置きます。
申し訳ありませんが、上記サイトからコピーしただけなので、説明できるほど理解していません。

prog/botebote/day3b/har.ld
/* 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形式でコンパイルする
  • -m32 : 32bitのバイナリを生成する
  • -nostdlib : 標準ライブラリをリンクしない
  • -T : 読み込むリンカスクリプトのファイル名を指定
  • -o : 出力ファイル名を指定

アセンブラ関数をCプログラムで使用

3日目の項目10では「Cプログラムからアセンブラ関数を呼び出す」ことをしています。
具体的には、以下のCPU停止HLT命令を実行するアセンブラ関数io_hltを利用しています。

prog/botebote/day3b/naskfunc.nas
; naskfunc
; TAB=4

section .text
		GLOBAL	io_hlt

io_hlt:	; void io_hlt(void);
		HLT
		RET

『30日OS』では、この関数io_hltを用いた、以下のCプログラムを作成しています。
無限ループの中でCPU停止命令を呼び出すだけの処理です。

prog/botebote/day3b/bootpack.c
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を作成します。

prog/botebote/day4a/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
prog/botebote/day4a/bootpack.c
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を書き込むことで白画面表示になります。

4日目1.png

縞画面表示

本の通りに簡単な修正をするだけなので、結果のみを以下に示します。

4日目3.png

『30日OS』では、ポインタを様々な方法で利用して、同様の画面表示を行っています。

16種の色番号のカスタマイズ

初期設定では0が黒で、15が白になるのように、あらかじめ0から15までの番号と色の対応が決められています。
VGAレジスタのビデオDAコンバータの設定を変更することで、番号に対応する色を自由に決めることができます。

以下の記事に具体的なプログラムが紹介されているのでご参照ください。
30日でできる!OS自作入門(4日目)[Ubuntu16.04/NASM]

本の通りにプログラムを打つだけでいいはずなのですが、私の環境では設定に失敗してしまいました。
色の設定値を定めた配列table_rgbが読み出せませんでした。
table_rgbを呼び出せず、何もない0x00を読みだして設定してしまいます。
つまり16種全ての色番号のRGB設定が0になります。黒一色だけしか表示できないので、画面は真っ暗になります。

bootpack.cの一部
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命令相当 */
}

そこで大変不格好なことですが、数値を直接代入するプログラムを作成しました。

bootpack.cの一部
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]

4日目4.png

4日目5.png

(追記)

32ビットモードへの移行

『30日OS』では5日目から8日目にかけて、3日目で作成したブートセクタについて32ビットモードへ移行する仕組みを解説しています。

『理解するOS』では14章と15章で詳しく解説しています。
詳細はそちらを参照いただくとして、ここでは私が作成したboot.sとkernel.sがどのようにして32ビットモードへ移行しているかを簡単に説明します。

prog/botebote/day3b/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	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』にはない設定です。

カーネルセクタの読み出し

外部記憶装置からカーネルセクタを読みだします。
カーネルセクタは上位アドレスに読み込みたいのですが、ブートモードではまだ下位アドレスしか使用できません。
そのため、まずはブートセクタの終わりの位置にカーネルセクタを読み込みます。

32ビットモード移行

セレクタ初期化、カーネル部コピー

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?