読者です 読者をやめる 読者になる 読者になる

yukke_longriders

ぼちぼちがんばる

LinuxでOS自作入門 3日目 (2)

OS自作

この記事はIS17er Advent Calendar 2016 - Adventarの16日目の記事として書かれました。


今回からOS本体を書き始めます。
とりあえず3日目の真っ黒の画面を出すというところまでできました。今回は苦戦しました。本ではやんわりと書かれていたことの意味を理解できていないことが原因です。
それでも今回分は一つの山場だったと思います。これから少しでも楽になると信じたい。それと写し間違えないように
この方にたどり着いたおかげで助かりました。takeisa memo: [OS作成]30日でできる!OS自作入門 3日目 (4)

はじめに

今回の大まかな流れです。
前回ブートセクタが完成したので、そこへ別に記述したプログラムと組み合わせてOSを作ってみようというものです。今後C言語のプログラムと組み合わせるための導入になります。
ブートセクタに真っ黒の画面を出力するプログラム(今回はこれがOS本体です)のアドレスへjmpする命令を書き足し、ブートセクタから生成されたOSイメージにそのプログラム(OS本体)を保存します。(この保存というのはこの単にファイルををwindows のようなOSのファイルに保存という意味です。)

とりあえずやってみる

本では最初に何もしないプログラムを作っていろいろ検証しているのですが、僕自身はとりあえずやってみようと思い後回しにしたので今の段階ではまだできていません。気になる方は上リンクのブログを参考にしてみてください。
本に書いてあることをそのまま利用します。
空の状態のディスクに対しファイルを保存してみると…

  • ファイル名は 0x002600 以降に入る
  • ファイルの中身は 0x004200 以降に入る

ということがわかります。ブートセクタの先頭アドレスは 0x8000 なので、 0x8000 + 0x4200 = 0xc200 にOS本体の中身が保存されていることがわかります。
まずOS本体になる真っ黒画面生成プログラムです。
os.s

.code16
.text
movb    $0x00, %ah
movb    $0x13, %al
int     $0x10
fin:
hlt
jmp   fin

このプログラムが 0xc200 に読み込まれるようにするためにリンカスクリプトを作ります。
os_lnk.ls

OUTPUT_FORMAT("binary")

SECTIONS {
	. = 0xc200;
	.text :{*(.text)}
}


ブートセクタです。
変更点は、fin:があった位置に jmp の命令が追加され、fin: は最後の方に移動しています。(ファイル名は付属のday3のディレクトリの英字に対応させています。)jmpで飛ぶアドレスには$は必要ないです。
ipl3g.ls

.text
.code16
jmp     entry
.byte   0x90
.ascii  "HELLOIPL" # ブートセクタの名前(8byte)
.word   512 # 1セクタの大きさ(512固定) (16 * 512bit?)
.byte   1 # クラスタのセクタ数(1固定)
.word   1 # FAT開始のセクタ
.byte   2 # FATの個数(2固定)
.word   224 # ルートディレクトリ領域のエントリ数 (224推奨)
.word   2880 # ドライブのセクタ数(2880固定)
.byte   0xf0 # メディアのタイプ(0xf0固定)
.word   9 # FAT領域のセクタ数(9固定)
.word   18 # 1トラックのセクタ数(18固定)
.word   2 # ヘッドの数(2固定)
.int    0 # パーティション数(未使用0)
.int    2880 # ドライブのセクタ数
.byte   0, 0, 0x29 # ?
.int    0xffffffff # ボリュームシリアル番号
.ascii  "HELLO-OS   " # ディスクの名前(11byte)
.ascii  "FAT12   " # フォーマットの名前(8byte)
.skip   18, 0x00 # 18byte空ける

# プログラム本体
entry:
movw	$0, %ax # レジスタの初期化
movw	%ax, %ss
movw	$0x7c00, %sp
movw	%ax, %ds

# ディスクを読む
movw	$0x0820, %ax
movw	%ax, %es
movb	$0, %ch # シリンダ0
movb	$0, %dh # ヘッド0
movb	$2, %cl # セクタ2

readloop:
movw    $0, %si # 失敗回数のカウンター

retry:
movb	$0x02, %ah # ディスクの読み込み
movb	$1, %al # 1セクタ
movw	$0, %bx
movb	$0x00, %dl # Aドライブ
int 	$0x13 # ディスクBIOS呼び出し
jnc     next # エラーがなければnextへ
addw    $1, %si
cmpw    $5, %si # 
jae     error # ( 5 <= si) then error

movb    $0x00, %ah
movb    $0x00, %dl # Aドライブ
int     $0x13 # ドライブのリセット
jmp     retry

next:
movw    %es, %ax # アドレスを0x0020 進める
add    $0x20, %ax
movw    %ax, %es 
# addw    $0x512, %bx
add    $1, %cl # セクタ
cmp    $18, %cl # 
jbe     readloop  # (18 >= cl) then readloop
movb    $1, %cl
add   $1, %dh # ヘッド
cmp    $2, %dh
jb      readloop # (2 > dh) then readloop
movb    $0, %dh
add    $1, %ch # シリンダ
cmp    $10, %ch
jb      readloop # (10 > ch) then readloop

movb    %ch, (0x0ff0) # iplがどこまで読んだかをメモ
jmp     0xc200

error:
movw	$msg, %si

putloop:
movb	(%si), %al
addw	$1, %si
cmpb	$0, %al
je  	fin
movb	$0x0e, %ah # 一文字表示ファンクション
movw	$15, %bx # カラーコード
int 	$0x10 # ビデオBIOS呼び出し
jmp 	putloop

fin:
hlt
jmp     fin

msg:
.byte	0x0a, 0x0a # 改行 改行(ascii)
.ascii	"load error" # 出力
.byte	0x0a # 改行
.byte	0

これらを利用して実行していきます。

$ gcc ipl3g.s -nostdlib -Tipl.ls -o ipl.bin
$ mformat -f 1440 -B ipl.bin -C -i os.img
$ gcc -nostdlib os.s -Tos_lnk.ls -o os.sys
$ mcopy os.sys -i os.img ::

まず前回同様、ipl3g.s からバイナリipl.binを作り、OSイメージファイルos.imgを作ります。
そして今回作ったOS本体 os.s からシステムファイル os.sys を生成します。
最後にシステムファイルをOSイメージファイルに保存します。
ここで出てくる formatmcopy は mtoolsコマンド でMS-DOSフォーマットのフロッピーディスクを操作するコマンドです。
mcopy でファイルを保存します。
以上で完成したOSイメージを今までと同じように実行します。

$ qemu-system-x86_64 -fda os.img

すると
f:id:yukke42:20161216043036p:plain
やりました!真っ黒な画面が出てきました。
今日の分はこれで終わりです。

最後に

今日はいろいろ悩みました。OSイメージを"OS" ではなくプログラムの何かとぼんやりととらえていたためにこれに保存するという意味が全く呑み込めませんでした。そのおかげで少しすっきりした気がします。
今までは16bitのOSでしたが、次は32bitのOSになります。説明は後回しに100行くらい追加されているようです…3日目はまだまだ続きます…