2014年9月30日火曜日

[OS作成]30日でできる!OS自作入門 3日目 (7)

32bitモードへの移行と、C言語で書いたOS本体の実行を実装する。

3日目では、32bitモードへの移行に関する詳細な解説は無く、5日目以降になっている。
C言語とアセンブリ言語で作成したオブジェクトファイルの扱いにおいては、 筆者独自のツールを使っており、標準ツールに置き換えるには、どのようにすれば良いのか、今ひとつ分からない。

他の人のブログを参考にして、乗り切ることにしよう。

コードを読んでいて、どうして1MB以上のメモリにアクセスするようにするために、キーボード関連のコードが出てくるのかと思っていたら、ここに 書いてあった。
---引用---
これに対する互換性のために、IBMのエンジニアはA20アドレスライン(8086はA0からA19までのアドレスラインしか持たない)にキーボードコントローラを通して信号を送り、A20互換モードを有効化/無効化できるようなメカニズムを提供した。なぜキーボードコントローラなのかと不思議に思うだろう。答えは未使用のピンがあったからである。

きっと有名な仕様なんだろうな。こういう話は面白い。

Ubuntu14.04の64bit版を使っているので、gccやldはデフォルトで64bit版のコードに対応するため、 gccやldに32bit用のコードを生成するオプションを付ける必要があった。
こちらのブログでは、hrbファイルのヘッダファイルの固定値を直接レジスタに代入していたが、なるべくオリジナルのコードのままにしたかったので、bootpack以降に、ヘッダに相当するデータを定義して、それを参照するようにした。

以下にMakefile, boot.s, tinyos.c のソースを示す。

Makefile

image_file=tinyos.img

ipl.bin: ipl.s ipl_lnk.ls
 gcc -nostdlib -o $@ -Tipl_lnk.ls ipl.s
 gcc -Tipl_lnk.ls -c -g -Wa,-a,-ad ipl.s > ipl.lst

tinyos.sys: boot.s boot_lnk.ls
 gcc boot.s -nostdlib -o $@ -Tboot_lnk.ls -o boot.bin
# gcc boot.s -m32 -nostdlib -o $@ -Tboot_lnk.ls -o boot.bin
 gcc -Tboot_lnk.ls -c -g -Wa,-a,-ad boot.s > boot.lst
 gcc *.c -m32 -c -nostdlib -Wl,--oformat=binary -o tinyos.o
# gcc *.c -m32 -S -c -nostdlib -Wl,--oformat=binary -o tinyos.o
# gcc -c -g -Wa,-a,-ad tinyos.s > tinyos.lst
 ld -m elf_i386 -o tinyos.bin -e Main --oformat=binary tinyos.o
 cat boot.bin tinyos.bin > $@

image_file: ipl.bin tinyos.sys
 mformat -f 1440 -B ipl.bin -C -i ${image_file} ::
 mcopy tinyos.sys -i ${image_file} ::

img:
 make image_file

run:
 qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file}

debug:
 qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file} \
  -gdb tcp::10000 \
  -S

clean:
 rm tinyos.sys

boot.s

.set BOTPAK,  0x00280000 # bootpackのロード先
.set DSKCAC,  0x00100000 # ディスクキャッシュの場所
.set DSKCAC0, 0x00008000 # ディスクキャッシュの場所(リアルモード)

// BOOT_INFO
.set CYLS, 0x0ff0 # シリンダ数
.set LEDS,0x0ff1 # LEDの状態
.set VMODE, 0x0ff2 # ビデオモード
.set SCRNX, 0x0ff4 # 解像度X
.set SCRNY, 0x0ff6 # 解像度Y
.set VRAM, 0x0ff8 # VRAMの開始アドレス

.text
.code16

 // ビデオモードを変更する
 movb $0x00, %ah # VGA Graphics 320x200x8bit
 movb $0x13, %al
 int $0x10

 // 画面の状態を記録する
 movb $8, (VMODE)
 movw $320, (SCRNX)
 movw $200, (SCRNY)
 movl $0x000a0000, (VRAM)

 // LEDの状態を記録する
 movb $0x02, %ah
 int $0x16
 movb %al, (LEDS)

 // PICが割り込みを受け付けないようにする
 movb $0xff, %al
 outb %al, $0x21
 nop   # outは連続して使用しない
 outb %al, $0xa1
 cli   # CPUでも割り込み禁止

 // A20互換モードを無効にして1MB以上のアドレスにアクセスできるようにする
 call waitkbdout
 movb $0xd1, %al
 outb %al, $0x64
 call waitkbdout
 movb $0xdf, %al # A20を有効にする
 outb %al, $0x60
 call waitkbdout

 // プロテクトモードに移行する
.arch i486
 lgdt (GDTR0)
 movl %cr0, %eax
 andl $0x7fffffff, %eax # ページング禁止
 orl $0x00000001, %eax # プロテクトモード移行
 movl %eax, %cr0
 jmp pipelineflush
pipelineflush:
 movw $1*8, %ax
 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 # 4で割っているのは4バイト単位で処理するため
 call memcpy

 // ディスクイメージを本来の位置へ転送する

 // ブートセクタ
 movl $0x7c00, %esi
 movl $DSKCAC, %edi
 movl $512/4, %ecx
 call memcpy
 
 // 残り
 movl $DSKCAC0+512, %esi
 movl $DSKCAC+512, %edi
 movl $0, %ecx
 movb (CYLS), %cl   # 読み込んだシリンダ数
 imull $512*18*2/4, %ecx # 1シリンダあたりのバイト数/4を掛ける
 sub $512/4, %ecx      # IPL分を引く
 call memcpy

 // bootpackを起動する
 movl $BOTPAK, %ebx
 movl 16(%ebx), %ecx
 addl $3, %ecx
 shrl $2, %ecx
 jz skip  # 転送するものがない
 movl 20(%ebx), %esi  # .dataのアドレス
 addl %ebx, %esi
 movl 12(%ebx), %edi # .data転送先
 call memcpy
skip: 
 movl 12(%ebx), %esp  # スタック初期値
 ljmpl $2*8, $0x0000001b
 
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

.align 16
 
GDT0:
 // GDTの構成
 // short limit_low, base_low
 // char base_mid, access_right
 // char limit_high, base_high
 
 // null selector
 .skip 8, 0x00
 // base=0x00000000 limit=0xcfffff access_right=0x92
 .word 0xffff, 0x0000, 0x9200, 0x00cf
 // base=0x00280000 limit=0x47ffff access_right=0x9a
 .word 0xffff, 0x0000, 0x9a28, 0x0047
 .word 0x0000
 
GDTR0:
 .word 8 * 3 - 1 # GDTのサイズ?
 .int GDT0

.align 16
bootpack:
 # + 0 : stack+.data+heap の大きさ(4KBの倍数)
 .int 0x00
 # + 4 : シグネチャ "Hari"
 .ascii "Tiny"
 # + 8 : mmarea の大きさ(4KBの倍数)
 .int 0x00
 # +12 : スタック初期値&.data転送先
 .int 0x00310000
 # +16 : .dataのサイズ
 .int 0x11a8
 # +20 : .dataの初期値列がファイルのどこにあるか
 .int 0x10c8
 # +24 +28 のセットで 1bからの命令が E9 XXXXXXXX (JMP)になり、C言語のエントリポイントにJMPするようだ
 # +24 : 0xe9000000
 .int  0xe9000000
 # +28 : エントリアドレス-0x20
 .int 0x00
 # +32 : heap領域(malloc領域)開始アドレス
 .int 0x00

tinyos.c

void Main(void) {
 fin:
  __asm__("hlt\n\t");
  goto fin;
}

gdbを使い、正しく動いているのか確認してみた。
bootpackへのジャンプ
 ljmpl $2*8, $0x0000001b
の実行前に 0x00280000番地からメモリをダンプして bootpackの内容になっていることを確認後、 ステップ実行して、0x1bにジャンプし、 0番地からのメモリをダンプしてみたが、 bootpackの内容にはなっていなかった。
ljmplで0x1bにジャンプしても、0x00280000番地が0番地に割り当てられていないようである。

いろいろ調べてみたが、原因が分からず、今だC言語で書いた関数の呼び出しができない。

Ubuntuの64bit環境だと、いろいろと手間取ることが多いので、Debianの32bit環境で試してみよう。

参考

2014年9月22日月曜日

[OS作成]30日でできる!OS自作入門 3日目 (6)

やっとOS本体のコードを実行できた。
前回まで動かなかった原因は、1セクタ分しかメモリに読み込んでおらず、 OS本体が読み込まれていなかったことが原因だった。 こんなコードを書いていた。
 .set MAX_SECTOR, 18 # 最大セクタ数
..略..
 add $1, %cl
 cmp MAX_SECTOR, %cl ←◆ここ
 jbe readloop # セクタは 1 〜 MAX_SECTOR
これでMAX_SECTOR(18)とCLレジスタを比較しているつもりだったが、 これは間違いだった。 即値と比較するには、以下のコードのように識別子の頭に$を付けなければならなかった。 ($を付けないと、間接アドレシングになるのかな?)
 .set MAX_SECTOR, 18 # 最大セクタ数
..略..
 add $1, %cl
 cmp $MAX_SECTOR, %cl ←◆$を付ける
 jbe readloop # セクタは 1 〜 MAX_SECTOR
同様にして、以下の識別子を参照している箇所は、即値となるように$を付けた。
 .set MAX_RETRY, 5 # 再読み込み最大回数
 .set MAX_SECTOR, 18 # 最大セクタ数
 .set MAX_HEAD, 2 # 最大ヘッド数
 .set MAX_CYLINDER, 10 # 最大シリンダ数
イメージを作成して、qemuで動作確認すると、画面は黒くなり、ビデオモードの変更を確認できた。
ここまでのコードは以下の通り。

ipl.s

/*
 ipl.s
*/
 .text 
 .code16
 jmp entry
 .byte 0x90
 .ascii "TINY_IPL" # ブートセクタの名前
 .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  # ?
 .int 2880  # ドライブのセクタ数
 .byte 0, 0, 0x29 # ?
 .int 0xFFFFFFFF # ボリュームシリアル番号
 .ascii "TINY-OS    " # ディスクの名前
 .ascii "FAT12   " # フォーマットの名前
 .space 18

// プログラム
 .set MAX_RETRY, 5 # 再読み込み最大回数
 .set MAX_SECTOR, 18 # 最大セクタ数
 .set MAX_HEAD, 2 # 最大ヘッド数
 .set MAX_CYLINDER, 10 # 最大シリンダ数

entry:
 movw $0, %ax
 movw %ax, %ss
 movw $0x7C00, %sp
 movw %ax, %ds

 // 2セクタから1セクタ分読み込む
 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 # Read sector(s) into memory
 movb $1, %al  # 1セクタ読み込む
 movw $0, %bx  # ES:BX Data buffer(0x8200に読み込む)
 movb $0x00, %dl # Aドライブ
 int $0x13  # BIOS interrupt call
 jnc next  # 読み込みOK でnextへ

 add $1, %si
 cmp $MAX_RETRY, %si
 jae error  # SI >= MAX_RETRY でerrorへ

 movb $0x00, %ah # Reset disk system
 movb $0x00, %dl # Aドライブ
 int $0x13
 jmp retry

next:
 movw %es, %ax # ES = ES + 0x20(512バイト)
 add $0x20, %ax
 movw %ax, %es
 // セクタ
 add $1, %cl
 cmp $MAX_SECTOR, %cl
 jbe readloop # セクタは 1 〜 MAX_SECTOR
 movb $1, %cl
 // ヘッド
 add $1, %dh
 cmp $MAX_HEAD, %dh
 jb readloop # ヘッドは 0 〜 MAX_HEAD - 1
 movb $0, %dh
 // シリンダ
 add $1, %ch
 cmp $MAX_CYLINDER, %ch
 jb readloop # シリンダは 0 〜 MAX_CYLINDER - 1
 jmp 0xC200  # 0x8000 + 0x4200 = 0xC200
 
error: 
 movw $msg, %si
putloop:
 movb (%si), %al
 add $1, %si
 cmp $0, %al
 je fin  # メッセージの後ろの0x00で終了する
 movb $0x0E, %ah # Write Character in TTY Mode
 movw $15, %bx # カラーコード
 int $0x10  # BIOS interrupt call
 jmp putloop
fin:
 hlt
 jmp fin

// メッセージ 
msg: 
 .string "\n\nload error\n"

 .org 0x1FE
 .byte 0x55, 0xAA # 55AAでブートセクタ

2014年9月21日日曜日

[OS作成]30日でできる!OS自作入門 3日目 (5)

まだまだ3日目だ。

今度は、OS本体起動時に、ビデオモードを設定してみる。

tinyos.s

.text
.code16 
 movb $0x00, %ah
 movb $0x13, %al
 int $0x10
fin: 
 hlt
 jmp fin
正しく実行できれば、真っ暗な画面が表示されるはず。
だが、試してみると動かない。

原因としては、次ののどちらか。
jmp 0xC200 で 0xC200にジャンプしていない。
FDからOS本体のイメージを正しく読んでいない。

前日に、jmp 0xC200 で実行したところ、正しく動作せず、
jmp *0xC200 にしたら、見掛け上は動いているようだったので
安心していたが、このコードでは、
0xC200に書いてあるアドレスにジャンプする命令だったようだ。

ソースとリンカスクリプトを再チェック、ビルド方法の確認をしてみたが、今のところお手上げ。
仕様がないので、qemu上のコードをGDBでデバッグすることにした。

Makefile に デバッグ用のターゲットを追加する。

Makefile

debug:
 qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file} \
  -gdb tcp::10000 \
  -S
GDBは、起動時に実行するスクリプトファイルを指定できる。
今後の開発で、多分、実行したいコマンドが増えると思うので、作成しておく。

gdb.scr

target remote localhost:10000
make debug で qemuを起動した後に、gdbを-xオプションでスクリプトファイルを指定して起動する。
$ gdb -x gdb.scr
ブレークポイントを0x7c00に設定する。
(gdb) b *0x7c00
Breakpoint 5 at 0x7c00
ブレークポイントまで実行する。
(gdb) c
Continuing.

Breakpoint 5, 0x00007c00 in ?? ()
止まった。 ジャンプする直前で止めたいので、lstファイルを確認すると、該当箇所は0x00acなので、 0x7cacにブレークポイントを設定し、continueする。
  80                            // シリンダ
  81 00a3 80C501                add     $1, %ch
  82 00a6 3A2E0A00              cmp     MAX_CYLINDER, %ch
  83 00aa 72B9                  jb      readloop        # シリンダは 0 〜 MAX_CYLINDER - 1
  84 00ac E90000                jmp     0xC200          # 0x8000 + 0x4200 = 0xC200  ← ◆ここ
(gdb) b *0x7cac
Breakpoint 2 at 0x7cac
(gdb) c
Continuing.

Breakpoint 2, 0x00007cac in ?? ()
レジスタ値を確認する。
(gdb) i all-registers 
eax            0x840 2112
ecx            0x101 257
edx            0x0 0
ebx            0x0 0
esp            0x7c00 0x7c00
ebp            0x0 0x0
esi            0x0 0
edi            0x0 0
eip            0x7cac 0x7cac
eflags         0x202 [ IF ]
cs             0x0 0
ss             0x0 0
ds             0x0 0
es             0x840 2112 ← ◆これ
fs             0x0 0
gs             0x0 0
あれ?ESレジスタが1回分しか0x20が加算されておらず、0x840になっている。 停止させたアドレスが間違っているのかと思って、continueしたけど
(gdb) c
Continuing.
このまま止まらなかった。 イメージ読み込みがうまく動いていないのか。
ソースをチェックすることにしよう...

参考

[OS作成]30日でできる!OS自作入門 3日目 (4)

いよいよOS本体を起動する処理の作成だ。

IPLとOS本体を含むイメージファイルを作成するには、筆者作成のedimg.exeを使うと簡単であるが、ここ  や ここ  を参考にすると、UbuntuやDebianのmtoolsパッケージに含まれるmformatコマンドを使ってFDイメージを作成しているので、同様の手順で作成することにした。

mformatコマンドを使うとイメージファイルを次のコマンドで作成できる。
$ mformat -f 1440 -B ブートセクタファイル -C -i イメージファイル名 ::
ipl.binはブートセクタのファイルをBオプションで指定できる。
なお、man mformatを見ても、iオプションの説明は無かった。
OS本体をイメージファイルにコピーするには、
$ mcopy OS本体 -i イメージファイル名 ::
とする。

それぞれのコマンドの後ろにtarget directoryとして :: としているが、イメージファイルの場合は、これを指定するようだ。

Makefileは以下のようになった。

Makefile

image_file=tinyos.img

ipl.bin: ipl.s lnk.ls
    gcc -nostdlib -o $@ -Tlnk.ls ipl.s
    gcc -Tlnk.ls -c -g -Wa,-a,-ad ipl.s > ipl.lst

tinyos.sys: tinyos.s lnk.ls
    gcc -nostdlib -o $@ -Tlnk.ls tinyos.s
    gcc -Tlnk.ls -c -g -Wa,-a,-ad tinyos.s > tinyos.lst

image_file: ipl.bin tinyos.sys
    mformat -f 1440 -B ipl.bin -C -i ${image_file} ::
    mcopy tinyos.sys -i ${image_file} ::

img:
    make image_file

run:
    qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file}

書籍通りに、まずは簡単なコードで確認する。

tinyos.s

fin:   
    hlt
    jmp fin

make img でイメージを作成してみる。

なお、tinyos.sのリスティングファイル(アセンブル結果)は以下の通り。

tinyos.lst

   1                  fin:   
   2 0000 F4               hlt
   3 0001 EBFD             jmp fin

書籍の通りに、0x2600にファイル情報がある。
00002600: 5449 4e59 4f53 2020 5359 5320 1800 e9b5  TINYOS  SYS ....
00002610: 3445 3445 0000 e9b5 3445 0200 0300 0000  4E4E....4E......
0x4200からOS本体のコードがある。
00004200: f4eb fd00 0000 0000 0000 0000 0000 0000  ................
イメージを0x8000から読み込ませているので、
イメージの0x4200からのコードを実行するには、
0x8000 + 0x4200 = 0xC200 になる。

ipl.s を修正して、FDの読み込み処理後に
    jmp    0xC200        # 0x8000 + 0x4200 = 0xC200
を追加した。

IPL用のリンカスクリプトとは別に、OS本体用のリンカスクリプトも必要だ。

tinyos_lnk.ls

OUTPUT_FORMAT("binary");

SECTIONS {
     . = 0xC200;
}
Makefileを、これに合わせて修正する。
image_file=tinyos.img

ipl.bin: ipl.s lnk.ls
 gcc -nostdlib -o $@ -Tipl_lnk.ls ipl.s
 gcc -Tipl_lnk.ls -c -g -Wa,-a,-ad ipl.s > ipl.lst

tinyos.sys: tinyos.s lnk.ls
 gcc -nostdlib -o $@ -Ttinyos_lnk.ls tinyos.s
 gcc -Ttinyos_lnk.ls -c -g -Wa,-a,-ad tinyos.s > tinyos.lst

image_file: ipl.bin tinyos.sys
 mformat -f 1440 -B ipl.bin -C -i ${image_file} ::
 mcopy tinyos.sys -i ${image_file} ::

img:
 make image_file

run:
 qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file}

だんだん汚なくなってきた。そのうち綺麗に書き直そう。

make imgしてイメージを作成後、qemuで試してみる。
が、正しく動かない。load errorと出てしまう。
JMPがすっとばされているのか?
リスティングファイルを確認する。

ipl.lst

  83 00ac E90000                jmp     0xC200          # 0x8000 + 0x4200 = 0xC200
どこにもC200が無い。これでは駄目だ。

いろいろ調べて、試行錯誤してみると、

ipl.s

    jmp    *0xC200        # 0x8000 + 0x4200 = 0xC200

ipl.lst

  83 00ac FF2600C2              jmp     *0xC200         # 0x8000 + 0x4200 = 0xC200
うーむ。*0xC200と書くのが正しいのか。
(実は、最初は指定アドレスは即値なので$0xC200と書いて怒られた。)
きちんとGASのリファレンスを読んだほうが良さそうだな。

これで試してみると、一応見かけ上は動いているみたい。
今日はここまで。

9/21 追記
この後、ビデオモードを変更する処理を書いてみたが、どうも上のコードでは、0xC200へjmpしていないようだ。
0xC200へJMPさせるには、どう書けば良いのか?うーむ。

参考


2014年9月20日土曜日

[OS作成]30日でできる!OS自作入門 3日目 (3)

課題は、18セクタ分を読み込む処理を書き,
その後に、それをベースに、10シリンダ分を読み込む処理を書くこと。
/*
 ipl.s
*/
 .code16
 jmp entry
 .byte 0x90
 .ascii "TINY_IPL" # ブートセクタの名前
 .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  # ?
 .int 2880  # ドライブのセクタ数
 .byte 0, 0, 0x29 # ?
 .int 0xFFFFFFFF # ボリュームシリアル番号
 .ascii "TINY-OS    " # ディスクの名前
 .ascii "FAT12   " # フォーマットの名前
 .space 18

// プログラム
 .set MAX_RETRY, 5 # 再読み込み最大回数
 .set MAX_SECTOR, 18 # 最大セクタ数
 .set MAX_HEAD, 2 # 最大ヘッド数
 .set MAX_CYLINDER, 10 # 最大シリンダ数

entry:
 movw $0, %ax
 movw %ax, %ss
 movw $0x7C00, %sp
 movw %ax, %ds

 // 2セクタから1セクタ分読み込む
 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 # Read sector(s) into memory
 movb $1, %al  # 1セクタ読み込む
 movw $0, %bx  # ES:BX Data buffer(0x8200に読み込む)
 movb $0x00, %dl # Aドライブ
 int $0x13  # BIOS interrupt call
 jnc next  # 読み込みOK でnextへ

 add $1, %si
 cmp MAX_RETRY, %si
 jae error  # SI >= MAX_RETRY でerrorへ

 movb $0x00, %ah # Reset disk system
 movb $0x00, %dl # Aドライブ
 int $0x13
 jmp retry

next:
 movw %es, %ax # ES = ES + 0x20(512バイト)
 add $0x20, %ax
 movw %ax, %es
 // セクタ
 add $1, %cl
 cmp MAX_SECTOR, %cl
 jbe readloop # セクタは 1 〜 MAX_SECTOR
 movb $1, %cl
 // ヘッド
 add $1, %dh
 cmp MAX_HEAD, %dh
 jb readloop # ヘッドは 0 〜 MAX_HEAD - 1
 movb $0, %dh
 // シリンダ
 add $1, %ch
 cmp MAX_CYLINDER, %ch
 jb readloop # シリンダは 0 〜 MAX_CYLINDER - 1

fin:
 hlt
 jmp fin

error: 
 movw $msg, %si
putloop:
 movb (%si), %al
 add $1, %si
 cmp $0, %al
 je fin  # メッセージの後ろの0x00で終了する
 movb $0x0E, %ah # Write Character in TTY Mode
 movw $15, %bx # カラーコード
 int $0x10  # BIOS interrupt call
 jmp putloop

// メッセージ 
msg: 
 .string "\n\nload error\n"

 .org 0x1FE
 .byte 0x55, 0xAA # 55AAでブートセクタ
プログラムにバグが無ければ、FDから読み込んだ (18 * 2 * 10 - 1) * 512 バイト分のデータが0x8200以降に格納されているはず。

FDを作って実機で動かせば、FDのデータを読み込んで様子が分かるのだろうけど、今のパソコンにFDDは付いていない。
qemuだけだと、正しく動いているのか、暴走しているのか良く分からない。
gdbでデバッグできるようだけど、まだ試していない。

今日はここまで。次はOS本体を書き始めるようだ。

2014年9月17日水曜日

[OS作成]30日でできる!OS自作入門 3日目 (2)

ファイル読み込みに失敗したら、最大5回リトライする処理。
読み込み失敗時は、ディスクシステムのリセットをするのがポイント。

最大リトライ回数の定義に、.setディレクティブを使ってみた。

変更部分は以下の通り。
// プログラム
 .set MAX_RETRY, 5 # 再読み込み最大回数

entry:
 movw $0, %ax
 movw %ax, %ss
 movw $0x7C00, %sp
 movw %ax, %ds

 // 2セクタから1セクタ分読み込む
 movw $0x0820, %ax
 movw %ax, %es
 movb $0, %ch  # シリンダ0
 movb $0, %dh  # ヘッド0
 movb $2, %cl  # セクタ2

 movw $0, %si  # 失敗回数

retry:
 movb $0x02, %ah # Read sector(s) into memory
 movb $1, %al  # 1セクタ読み込む
 movw $0, %bx  # ES:BX Data buffer(0x8200に読み込む)
 movb $0x00, %dl # Aドライブ
 int $0x13  # BIOS interrupt call
 jnc fin  # 読み込みOK でfinへ

 add $1, %si
 cmp MAX_RETRY, %si
 jae error  # SI >= MAX_RETRY でerrorへ

 movb $0x00, %ah # Reset disk system
 movb $0x00, %dl # Aドライブ
 int $0x13
 jmp retry

fin:
 hlt
 jmp fin

一応、うまく動作しているようだ。

2014年9月16日火曜日

[OS作成]30日でできる!OS自作入門 3日目 (1)

次の課題は、ブートセクタの次のセクタを読み込む処理。

シリンダ、ヘッド、セクタを指定して int 0x13 を呼び出す。
エラー発生時はCF=1となるので、その場合はエラーメッセージを表示する。

今日からイメージファイル名をtinyos.imgにした。
リンカスクリプトは変更なし。

Makefile

image_file=tinyos.img

ipl.bin: ipl.s lnk.ls
 gcc -nostdlib -o $@ -Tlnk.ls ipl.s
 gcc -Tlnk.ls -c -g -Wa,-a,-ad ipl.s > ipl.lst

zero.img:
# 1474560 - 512 = 1474048
 dd if=/dev/zero of=zero.img ibs=512 count=2879

image_file: ipl.bin zero.img
 cat ipl.bin zero.img > ${image_file}

img:
 make image_file

run:
 qemu-system-i386 -m 32 -localtime -vga std -fda ${image_file}

ipl.s

/*
 ipl.s
*/
 .code16
 jmp entry
 .byte 0x90
 .ascii "TINY_IPL" # ブートセクタの名前
 .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  # ?
 .int 2880  # ドライブのセクタ数
 .byte 0, 0, 0x29 # ?
 .int 0xFFFFFFFF # ボリュームシリアル番号
 .ascii "TINY-OS    " # ディスクの名前
 .ascii "FAT12   " # フォーマットの名前
 .space 18

// プログラム
entry:
 movw $0, %ax
 movw %ax, %ss
 movw $0x7C00, %sp
 movw %ax, %ds

 // 2セクタから1セクタ分読み込む
 movw $0x0820, %ax
 movw %ax, %es
 movb $0, %ch  # シリンダ0
 movb $0, %dh  # ヘッド0
 movb $2, %cl  # セクタ2

 movb $0x02, %ah # ディスク読み込み
 movb $1, %al  # 1セクタ読み込む
 movw $0, %bx  # ES:BX Data buffer(0x8200に読み込む)
 movb $0x00, %dl # Aドライブ
 int $0x13  # BIOS interrupt call
 jc error

fin:
 hlt
 jmp fin

error: 
 movw $msg, %si
putloop:
 movb (%si), %al
 add $1, %si
 cmp $0, %al
 je fin  # メッセージの後ろの0x00で終了する
 movb $0x0E, %ah # Write Character in TTY Mode
 movw $15, %bx # カラーコード
 int $0x10  # BIOS interrupt call
 jmp putloop

// メッセージ 
msg: 
 .string "\n\nload error\n"

 .org 0x1FE
 .byte 0x55, 0xAA # 55AAでブートセクタ

$ make img
$ make run

で実行したが、エラーメッセージは出ていない。
指定したセクタを正しく読めているようであるが、本当にそうなのかは、画面からは分からない。

今日はここまで。