OS自作入門 onLinux 3日目

文化祭関連の準備で忙しい。。。
10月が終われば大分楽になりそう。



3日目には手こずらされました。
何かって、まずasmhead.nasの内容が難解なのと、
筆者作の.hrbフォーマットの構造を知らずに適当に書いてたことかな。

でも、なんとか頑張ってGASに移植できました。
勿論、筆者作のedimg.exeや.hrb形式や.bim形式は一切利用しませんでした。

ちょっと4日目の内容に食い込んでますが、ここまでやらないと
成功したか確認できなかったので。

f81a6253.png

自作本中ではカラーコード15(白)でしたが、適当に10にしたら緑でした。

続きからどうぞ。
====

.equ CYLS, 10

.text
.code16
        jmp entry

        .byte 0x90
        .ascii "HELLOIPL"                # ブートセクタの名前
        .word 512                        # 1セクタの大きさ
        .byte 1                                # クラスタの大きさ
        .word 1                                # FATがどこから始まるか
        .byte 2                                # FATの個数
        .word 224                        # ルートディレクトリのサイズ
        .word 2880                        # このドライブの大きさ
        .byte 0xf0                        # メディアのタイプ
        .word 9                                # FAT領域の長さ
        .word 18                        # 1トラックにいくつのセクタがあるか
        .word 2                                # ヘッドの数
        .int 0                                # 必ず0
        .int 2880                        # ドライブのサイズ
        .byte 0, 0, 0x29
        .int 0xffffffff                        # ボリュームシリアル番号
        .ascii "HELLO-OS   "                # ディスクの名前
        .ascii "FAT12   "                # フォーマットの名前
.skip 18, 0x00                                # 18バイト空ける

# プログラム本体
entry:
        movw $0, %ax                        # レジスタ初期化
        movw %ax, %ss
        movw $0x7c00, %sp
        movw %ax, %ds
        movw %ax, %es
        
        movw $0x0820, %ax
        movw %ax, %es
        movb $0x00, %ch                        # シリンダ0
        movb $0x00, %dh                        # ヘッド0
        movb $0x02, %cl                        # セクタ2

readloop:
        movw $0x00, %si                        # 失敗回数のカウンタ
retry:
        movb $0x02, %ah                        # ディスク読み込み
        movb $0x01, %al                        # 1セクタ
        xorw %bx, %bx
        movb $0x00, %dl                        # Aドライブ
        int $0x13
        jnc next
        addw $0x01, %si
        cmpw $0x05, %si                        # 5回失敗したらエラー
        jae error
        movb $0x00, %ah
        movb $0x00, %dl                        # Aドライブ
        int $0x13                        # ドライブのリセット
        jmp retry

next:
        movw %es, %ax
        addw $0x0020, %ax                # アドレスを0x0200進める
        movw %ax, %es
        addb $0x01, %cl
        cmpb $18, %cl                        # 18セクタまで読み込む
        jbe readloop

        movb $0x01, %cl
        addb $0x01, %dh
        cmpb $0x02, %dh                        # 裏ヘッダを読み込む
        jb readloop
        movb $0x00, %dh
        addb $0x01, %ch
        cmpb $CYLS, %ch                        # CYLSシリンダまで読み込む
        jb readloop

_load_fin:
        movb $CYLS, (0x0ff0)

        jmp 0xc200

error:
        movw $load_err, %si
        call print
_error_fin:
        hlt
        jmp _error_fin

# %si = string adress
print:
        movb (%si), %al
        addw $1, %si
        cmpb $0, %al
        je _print_fin
        movb $0x0e, %ah                        # 一文字表示BIOSコール
        movw $15, %bx                        # カラーコード
        int $0x10                        # ビデオBIOS呼び出し
        jmp print
_print_fin:
        ret

.data
load_err:        .string "load error\n"

IPL(ipl.s)です。前回からの変更点は、FDイメージを10シリンダまで
読み込んでいることと、最後に0xc200にジャンプしていることです。
詳細は自作本をご覧ください。

また、_load_finでCYLSの値、つまり読み込んだシリンダ数を
0x0ff0に格納しています。0x0ff0はシリンダ数を格納するようになっています。

.equ BOTPAK,        0x00280000
.equ DSKCAC,        0x00100000
.equ DSKCAC0,        0x00008000

.equ CYLS,                0x0ff0
.equ LEDS,                0x0ff1
.equ VMODE,                0x0ff2
.equ SCRNX,                0x0ff4
.equ SCRNY,                0x0ff6
.equ VRAM,                0x0ff8

.text
.code16
head:
        movb $0x13, %al
        movb $0x00, %ah
        int $0x10

        movb $0x08, (VMODE)
        movw $320, (SCRNX)
        movw $200, (SCRNY)
        movl $0x000a0000, (VRAM)

        movb $0x02, %ah
        int $0x16
        movb %al, (LEDS)

# 32ビットプロテクトモードへ移行
        # PICへの割り込み禁止
        movb $0xff, %al
        outb %al, $0x21
        nop
        outb %al, $0xa1
        cli                                # CPUレベルでも割り込み禁止

        # A20信号線をON
        call waitkbdout
        movb $0xd1, %al
        outb %al, $0x64
        call waitkbdout
        movb $0xdf, %al
        outb %al, $0x60                        # enable A20
        call waitkbdout

.arch i486                                # 32bitネイティブコード
        lgdtl (GDTR0)                        # 暫定GDTを設定
        movl %cr0, %eax
        andl $0x7fffffff, %eax                # bit31を0に(ページング禁止)
        orl $0x00000001, %eax                # bit0を1に(プロテクトモード移行)
        movl %eax, %cr0
        jmp pipelineflash
pipelineflash:
        movw $1*8, %ax                        # 読み書き可能セグメント32bit
        movw %ax, %ds
        movw %ax, %es
        movw %ax, %fs
        movw %ax, %gs
        movw %ax, %ss

# bootpackの転送
        movl $bootpack, %esi
        movl $BOTPAK, %edi
        movl $512*1024/4, %ecx
        call memcpy

# ブートセクタの転送
        movl $0x7c00, %esi
        movl $DSKCAC, %edi
        movl $512/4, %ecx
        call memcpy

# 残り
        movl $DSKCAC0+512, %esi
        movl $DSKCAC+512, %edi
        movl $0x00, %ecx
        movb (CYLS), %cl
        imull $512*18*2/4, %ecx
        subl $512/4, %ecx
        call memcpy

# start bootpack
        movl $BOTPAK, %ebx
        movl $0x11a8, %ecx
        addl $3, %ecx
        shrl $2, %ecx
        jz skip
        movl $0x10c8, %esi
        addl %ebx, %esi
        movl $0x00310000, %edi
        call memcpy
skip:
        movl $0x00310000, %esp
        ljmpl $2*8, $0x00000000

###########################
# function

waitkbdout:
        inb $0x64, %al
        andb $0x02, %al
        inb $0x60, %al
        jnz waitkbdout
        ret

memcpy:
        movl (%esi), %eax
        addl $4, %esi
        movl %eax, (%edi)
        addl $4, %edi
        subl $1, %ecx
        jnz memcpy
        ret

##########################
# GDT
.align 8
GDT0:
.skip 8, 0x00
        .word 0xffff, 0x0000, 0x9200, 0x00cf        # 読み書き可能セグメント32bit
        .word 0xffff, 0x0000, 0x9a28, 0x0047        # 実行可能セグメント32bit

        .word 0x0000

GDTR0:
        .word 8*3-1
        .int GDT0

.align 8
bootpack:

asmhead.nasを改良して、head.sとしました。
大きな変更点は、
.arch i486で32bitコードになる
・#start bootpackの部分でレジスタに即値を代入している
・far jump(ljmp)で第2セグメントの0x00番地にジャンプしている

さて、1つ目はGASの仕様なので置いておきます。
2つ目と3つ目ですが、ともにhrbフォーマットに関わることです。
hrbフォーマットは以下のようになっています。

[ .hrbファイルの構造 ]

+ 0 : stack+.data+heap の大きさ(4KBの倍数)
+ 4 : シグネチャ "Hari"
+ 8 : mmarea の大きさ(4KBの倍数)
+12 : スタック初期値&.data転送先
+16 : .dataのサイズ
+20 : .dataの初期値列がファイルのどこにあるか
+24 : 0xe9000000
+28 : エントリアドレス-0x20
+32 : heap領域(malloc領域)開始アドレス

参考URL:http://blogs.dion.ne.jp/kazuu/archives/3051835.html

元々レジスタに代入しているのは、hrbのヘッダ部分の値です。
これは固定値なので、即値代入します。値は自作本P.170の通りです。

そして、0x00番地へのジャンプについてですが、元々は0x1bにジャンプしています。
第2セグメントはbootpack.hrbがあるわけですが(そうなるようhead.sでコピーした)、
bootpack.hrbの0x1bとは、ヘッダ部分です。

0xe9 0x90 0x00 0x00 0x00
⇒ jmp [エントリーアドレス]


つまり、bootpack.hrbのエントリーポイント、自作本ならばbootpack.cの
HariMain関数に飛んでいます。
ならば、始めからbootpack.hrbの中身に飛ばせばいいんです。
バイナリフォーマットなら、0x00番地から実行データなので、
直接0x00番地に飛ばします。

OSNAME=os

ASRC=./src/asm
CSRC=./src/c
OBJ=./obj
LS=./ls

IMG=$(OSNAME).img
OSSYS=$(OBJ)/$(OSNAME).sys
IPL=$(OBJ)/ipl.bin

BINOPT=-nostdlib -Wl,--oformat=binary
QEMUOPT=-m 32 -localtime -vga std -fda

$(IMG) : $(OSSYS) $(IPL)
    mformat -f 1440 -C -B $(IPL) -i $(IMG) ::
    mcopy $(OSSYS) -i $(IMG) ::

$(OSSYS) : $(ASRC)/head.s $(ASRC)/func.s $(CSRC)/bootpack.c
    gcc $(ASRC)/head.s -nostdlib -T$(LS)/head.ls -o $(OBJ)/head.bin
    gcc $(CSRC)/*.c $(BINOPT) -c -o $(OBJ)/boot.o
    as $(ASRC)/func.s -o $(OBJ)/func.o
    ld -o $(OBJ)/boot.bin -e Main --oformat=binary $(OBJ)/boot.o $(OBJ)/func.o
    cat $(OBJ)/head.bin $(OBJ)/boot.bin > $(OSSYS)

$(IPL) : $(ASRC)/ipl.s
    gcc $(ASRC)/ipl.s -nostdlib -T$(LS)/ipl.ls -o $(IPL)

run        : $(IMG)
    qemu $(QEMUOPT) $(IMG)
debug    : $(IMG)
    qemu -s -S $(QEMUOPT) $(IMG) -redir tcp:5555:127.0.0.1:1234 &
img        :;    make $(IMG)
clean    :;    rm $(OBJ)/*

Makefileです。今回は色々追加しました。

ファイル名はちょっと個人的にいくつか変えているので、対応を以下に示します。
ipl10.nas ⇒ ipl.s
asmhead.nas ⇒ head.s
naskfunc.nas ⇒ func.s
bootpack.c そのまま
asmhead.bin ⇒ head.bin
bootpack.hrb ⇒ boot.bin
haribote.sys ⇒ os.sys
haribote.img ⇒ os.img

$(IMG)、つまりイメージファイルの作成ルールでmcopyコマンドを使っています。
これはedimgにあたるもので、FDイメージにファイルを書き込みます。

$(OSSYS)の作成ルールのコマンド4行目で、自分でld(リンカ)にオブジェクトファイルを
通しています。
-eオプションでエントリポイントを選択できます。デフォルトは勿論mainですよね。
ここで、オブジェクトファイルを通す順番も変えてはいけません。
なぜなら、boot.oを最初に通さなければ、boot.oの始めに入ってくる
エントリポイントのMain関数がboot.binの0x00に来ないからです。


あとは、debugコマンドを入れておきました。
これで、gdb経由でqemuデバッグできます。この詳細は後日。
正直、これにはとても助けられました^^;


make runを実行すると、以下のようにファイルが作成されます。

head.s ⇒ head.bin
bootpack.c ⇒ boot.o
func.s ⇒ func.o
boot.o + func.o ⇒ boot.bin
head.bin + boot.bin ⇒ os.sys

ipl.s ⇒ ipl.bin

ipl.bin + os.sys ⇒ os.img

そして、qemuにos.imgを通します。



ちょっと複雑になったので、ソースをアップしておきます。
os03.tar.gz ⇒ こちら

説明が不足しているかもしれないので、質問は遠慮なくどうぞー。