Linuxで書くOS自作入門 3日目(前半)
「30日でできる!OS自作入門」の3日目
環境はUbuntu, アセンブラはNASMで開発中
長くなりそうなのでharib00d, 10シリンダ読み込むプログラムまでをここに書きます.
書き換えた部分の概要
BPB(BIOS Parameter Block)の後にディスクをメモリに読み込むプログラムを書く.ディスク読み込みはINT
命令によって実行される.
- INT命令は割り込み(Interrupt)処理の呼び出しの命令であり,BIOSの機能を呼び出す.
- そもそもBIOSはBasic Input/Output Systemの略で,ハードウェアとの最も低レベルな入出力を行うプログラム. コンピュータのモデルごとに設計され,マザーボード上のメモリに埋め込まれている.
セクタごとにディスクを読み込んで行く.メモリのオフセットにはヘッド,シリンダ,セクタのサイズを考える必要があるためメモ.
(下記はフロッピーディスクの場合)
- 1ディスク = 2ヘッド = 160シリンダ = 2,880セクタ = 1,474,560バイト
- 1ヘッド = 80シリンダ = 1,440セクタ = 737,280バイト
- 1シリンダ = 18セクタ = 9,216バイト
- 1セクタ = 512バイト
読み込みループ部分
CYLS EQU 10 ; 読み込むシリンダ数
EQUは定数を定義できる.アセンブラが後で数字に書き換えてくれる.
BPB部分は前回と同じなので省略.
entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 ; ブートセクタは0x00007c00に読み込まれる MOV DS,AX ; ディスクを読む MOV AX,0x0820 MOV ES,AX ; セグメントレジスタ(ES:BX)なのでメモリ上では0x8200 MOV CH,0 ; シリンダ番号 MOV DH,0 ; ヘッド番号 MOV CL,2 ; セクタ番号 readloop: MOV SI,0 ; エラー回数リセット retry: MOV AH,0x02 ; AH=0x02 : ディスク読み込み MOV AL,1 ; 処理セクタ数 MOV BX,0 ; バッファアドレス MOV DL,0x00 ; ドライブ番号 0->Aドライブ INT 0x13 ; ディスクBIOS呼び出し JNC next ; エラーがおきなければnextへ ADD SI,1 ; SI(エラー回数)に1を足す CMP SI,5 ; SIと5を比較 JAE error ; エラー回数5回以上 だったらerrorへ MOV AH,0x00 MOV DL,0x00 ; ドライブ番号 0->Aドライブ INT 0x13 ; ドライブのリセット JMP retry next: MOV AX,ES ; アドレスを0x200進める 0x200は1セクタ分 ADD AX,0x0020 ; セグメントレジスタ(ES:BX)なので0x200->0x20 MOV ES,AX ; ADD ES,0x020 という命令がないのでこうしている ADD CL,1 ; CLに1を足す CMP CL,18 ; CLと18を比較 JBE readloop ; CL(セクタ番号) <= 18 だったらreadloopへ MOV CL,1 ; CLを1に戻す ADD DH,1 CMP DH,2 JB readloop ; DH(ヘッド番号) < 2 だったらreadloopへ MOV DH,0 ADD CH,1 CMP CH,CYLS JB readloop ; CH(シリンダ番号) < CYLS だったらreadloopへ JMP success
INT命令を呼び出す前に,各レジスタの値を設定する必要がある.(AT)BIOS - os-wiki
ヘッド,シリンダ,セクタの値を指定することでディスクの読み込み部分は確定する.
メモリ上への読み込み部分はセグメントレジスタ[ES:BX]を指定する.実際のメモリアドレスはES * 16 + BX
となるので,ESに代入するときは16進数だと下一桁の0が無くなるので注意する.
読み込みのエラーが起こる場合があるので,エラー回数をレジスタSIでカウントし,5回までならやり直すように設定した.(QEMUでエミュレートするだけなら特に意味はない)
後は,セクタ,シリンダ,ヘッドの順に,比較命令とジャンプ命令の組み合わせによって繰り返しをプログラムする.
終了処理
fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ
終了時のプログラムは前回と同じ.
本誌では読み込み成功時には何も表示されないプログラムとなっているが,本当に読み込めているか不安になってしまうため,オリジナルで成功時のメッセージを表示するように書き換えた.
success: MOV SI,msg2 JMP putloop error: MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; SIに1を足す CMP AL,0 JE fin MOV AH,0x0e ; 一文字表示ファンクション MOV BX,15 ; カラーコード INT 0x10 ; ビデオBIOS呼び出し JMP putloop msg: DB 0x0a, 0x0a ; 改行を2つ DB "load error" DB 0x0a ; 改行 DB 0 msg2: DB 0x0a, 0x0a ; 改行を2つ DB "load success" DB 0x0a ; 改行 DB 0 TIMES 0x7dfe-0x7c00-($-$$) DB 0 ; 0x7dfeまでを0x00で埋める命令 DB 0x55, 0xaa ;ブートセクタが有効であることを示す2バイト
実行
従来のMakefileで実行すると失敗した.
QEMUのオプションでフロッピーディスクのブートを明示してないことが原因だった.原因の特定にかなりの時間を消費した.
qemu-system-i386 -fda haribote.img # -fdaオプションをつける
今度はちゃんと成功表示が出た.
Linuxで書くOS自作入門 2日目
「30日でできる!OS自作入門」の2日目
環境はUbuntu, アセンブラはNASMで開発中
アセンブリの更新
- プログラム本体部分が意味を持つ命令で置き換えられた.
- ラベルが追加された.
- レジスタ名が記述されるようになった.
- 新しいアセンブリ命令
- 今まで空き部分を0で埋めるために使用していたRESB命令は警告が出るので,TIMES命令によって置き換えることにした.
TIMES
指定回数直後の命令を繰り返す
例RESB 18
→TIMES 18 DB 0
Makefileの作成
Win版のMakefileをもとに,Linuxで動作するように書き換えた.本では,独自ツールedimg.exeなどが登場するが,これらに頼らず自分でディスクイメージを作成する.今日の範囲ではブートセクタの部分とそれ以外の部分をcatで結合しているだけである.
ここでは,ファイル名を変更し,ブートセクタのアセンブリをipl.asm
,それ以外のアセンブリをtail.asm
としている.
default : make img ipl.bin : ipl.asm Makefile nasm ipl.asm -o ipl.bin -l ipl.lst tail.bin : tail.asm Makefile nasm tail.asm -o tail.bin -l tail.lst helloos.img : ipl.bin tail.bin Makefile cat ipl.bin tail.bin > helloos.img asm : make -r ipl.bin img : make -r helloos.img run : make img qemu-system-i386 helloos.img clean : -rm ipl.bin -rm tail.bin -rm ipl.lst -rm tail.lst src_only : make clean rm helloos.img
ブートセクタの簡単な解釈
ブートセクタ部分に関してはアセンブリが読めるようになってきたため,ここでアセンブリが何をしているのか読んで見る.その前に用語を整理する.
- ブートセクタ ディスクの最初のセクタ.そもそもセクタはディスクの読み書きの最小単位であり,512バイトである.最初にここを読み込むことによって,OSは起動する.
- FAT12フォーマット フロッピーディスクのフォーマット形式
ORG 0x7c00
機械語翻訳はされない,アセンブラのための命令.ブートセクタは0x00007c00 - 0x00007dffに読み込まれるので,これを明示化している1.絶対アドレスを正しく計算するために使っているのだと思う.
JMP entry DB 0x90 DB "HELLOIPL" ; ブートセクタの名前を自由に書いてよい(8バイト) DW 512 ; 1セクタの大きさ(512にしなければいけない) DB 1 ; クラスタの大きさ(1セクタにしなければいけない) DW 1 ; FATがどこから始まるか(普通は1セクタ目からにする) DB 2 ; FATの個数(2にしなければいけない) DW 224 ; ルートディレクトリ領域の大きさ(普通は224エントリにする) DW 2880 ; このドライブの大きさ(2880セクタにしなければいけない) DB 0xf0 ; メディアのタイプ(0xf0にしなければいけない) DW 9 ; FAT領域の長さ(9セクタにしなければいけない) DW 18 ; 1トラックにいくつのセクタがあるか(18にしなければいけない) DW 2 ; ヘッドの数(2にしなければいけない) DD 0 ; パーティションを使ってないのでここは必ず0 DD 2880 ; このドライブ大きさをもう一度書く DB 0,0,0x29 ; よくわからないけどこの値にしておくといいらしい DD 0xffffffff ; たぶんボリュームシリアル番号 DB "HELLO-OS " ; ディスクの名前(11バイト) DB "FAT12 " ; フォーマットの名前(8バイト) TIMES 18 DB 0 ; とりあえず18バイトあけておく
FAT12フォーマットフロッピーディスクのための記述であり,BPB(BIOS Parameter Block)と呼ばれるらしい.FATボリュームの認識のための記述.明確に定められた部分が多い.
ここに詳しく書いてあった.ELM FATファイル システムのしくみと操作法
entry: MOV AX,0 ; レジスタ初期化 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX MOV SI,msg
最初の命令,JMP entry
によってジャンプして最初に実行されるこのプログラムをブートストラッププログラムと呼ぶ.最初はレジスタの初期化が行われている.特筆すべきはスタックポインタSPはブートセクタの開始位置0x7c00に,ソースインデックスSIは表示文字列を指すラベルmsgが代入されている.
putloop: MOV AL,[SI] ADD SI,1 ; SIに1を足す CMP AL,0 JE fin MOV AH,0x0e ; 一文字表示ファンクション MOV BX,15 ; カラーコード INT 0x10 ; ビデオBIOS呼び出し JMP putloop
ソースインデックスSIの指すアドレス部分をALに読み込み,0x0eをAHに読み込んだ後,INT 0x10
命令を使うことによって文字が表示される.文字列が終わるってALがゼロになるまでループしている.
ここに詳しく書いてあった.(AT)BIOS - os-wiki
fin: HLT ; 何かあるまでCPUを停止させる JMP fin ; 無限ループ
HLT
は次の外部割り込みが発生するまでCPUを停止させる命令でこれを無限ループしている.これ無しで実行するとCPUが動き続けるらしく,ファンがうるさくなった.
msg: DB 0x0a, 0x0a ; 改行を2つ DB "Hello, world! " DB 0x0a ; 改行 DB 0
メッセージ部分.好きな文字を書ける.
TIMES 0x7dfe-0x7c00-($-$$) DB 0 ; 0x7dfeまでを0x00で埋める命令
ブートセクタの最後0x7dffから最後の2バイトを除いた0x7dfeまでを0で埋める.
DB 0x55, 0xaa
最後の2バイトをこれにすることによって,有効なブートセクタであることを示す.
Linuxで書くOS自作入門 1日目
「30日でできる!OS自作入門」を購入した.
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
このブログでは、Linux(Ubuntu16.04)を使ってOSを書いてみようと思います.
バイナリ写経
イメージファイルのバイナリ写経する.Linuxにはbviとかghexといったバイナリエディタがあるので,触っては見たが,飽きたので完成物をコピーしてきた.
sudo apt-get install qemu
qemu-system-i386 helloos.img
ひとまず動作確認.
アセンブラ
先ほどのバイナリを生成させるためにアセンブラを用いる.この本では独自のアセンブラNASMが用いられているが,Linuxで使用できる標準的なものを使いたい.
調べてみると,NASKの元となっているNASMの他に,GNUアセンブラGasなどが主流であることが分かった.NASMはIntel構文,GasはAT&T構文といった構文の違いがあるらしい.1
移植の作業量が少ないため,ひとまずはNASMを用いていくことにした.
しばらくは同じバイナリを生成するアセンブリなので,helloos1を飛ばし,helloos2をコンパイルする.
- 新しいアセンブリ命令
DB
指定の1バイト記入 文字列も可 Data ByteDW
指定の2バイト記入 Data WordDD
指定の4バイト記入 Data Double-WordRESB
指定バイト数0で初期化 REServe Bytes
nasmのインストール
sudo apt-get install nasm
nasm helloos.asm helloos.img
実行すると,helloos.asm:41: error: invalid operand type
というエラー.
調べてみると,RESB 0x1fe-($-$$)
とすべきようだ.2
nasmでは,$
はその行のアドレス,$$
はそのセクションの最初のアドレスを指すらしい.
せっかくなので表示文字列を変えてコンパイル,リンク
今度は成功.
余談
アセンブル時の命令を
nasm helloos.asm helloos.img -l helloos.lst
と付け加えることで,
アセンブリに対するバイナリの対応を見ることができる.