Operating Systems Development Series
Bootloaders 3
by Mike, 2008, 2009

このシリーズは、オペレーティングシステムの開発を一から実演し、教えることを目的としています。

注意:この章は近々更新される予定で、誤りを修正し、トピックに関するより詳細な情報を提供します。

はじめに

ようこそ

前回のチュートリアルでは、様々なプロセッサモードと簡単な BIOS インタラプトを取り上げました。 また、リアルモードでのセグメント:オフセットアドレッシングを取り上げ、リアルモードの詳細について説明しました。また、ブートローダに謎の「OEM パラメータブロック」を追加し、画面上に文字列を印刷する機能 を追加しました

このチュートリアルでは、アプリケーションプログラミングと システムプログラミングの違いを表す、さまざまな「リング」を見ていきます。

また、シングルステージとマルチステージのブートローダについて、それぞれの長所と短所を説明します。

最後に、BIOS 割り込み 0x13OEM パラメータブロックプログラムの読み込み、ロード、実行につ いて説明します。このプログラムは、セカンドステージブートローダとなります。セカンドステージブートローダは、32 ビット環境を設定し、C カーネルをロードする準備をします。

準備はいいですか?

The Rings of Assembly Language

アセンブリ言語では、「リング0プログラム」、「このプログラムはリング3で動作しています」という言葉を使うことがあります。 リングの違い(とその内容)を理解することは、OS開発において有益なことです。

リング - 理論

さて、リングとは何でしょうか?リング」とは、アセンブリ言語では、プログラムがシステムに対して持つ保護と制御のレベルを表します。リングは4つあります。リング0、リング1、リング2、リング3です。

リング0はシステム内の全てを制御することができ、リング3は制御しにくい。 リング番号が小さいほど、ソフトウェアが制御できる範囲が広くなる(保護レベルが低くなる)。

リングは概念ではなく、プロセッサー・アーキテクチャに組み込まれている。

コンピュータが起動するとき、ブートローダが実行されるときでさえ、プロセッサはリング0です。DOSアプリケーションなど、ほとんどのアプリケーションはリング3で実行されます。つまり、Ring 0で動作するオペレーティングシステムは、通常のRing 3のアプリケーションよりもはるかに多くの制御を行うことができるのです。

リングの切り替え

リングはプロセッサ・アーキテクチャの一部であるため、プロセッサは必要なときに状態を変更します。 変更するのは...
  • ディレクティブ命令は、farジャンプ、farコール、farリターンなど、異なるリングレベルでプログラムを実行する命令です。
  • トラップ命令(INT、SYSCALLSYSENTERなど
  • 例外
例外処理については、SYSCALL命令やSYSENTER命令と同様、後で説明します。

マルチステージブートローダ

シングルステージブートローダ

ブートローダ、およびブートセクタのサイズはわずか512バイトであることを忘れないでください。ブートローダが、その同じ512バイトの中で、カーネルを直接実行する場合、それはシングルステージブートローダと呼ばれます。

しかし、問題はその大きさです。512バイトの中にいろいろなことができる余地はほとんどないのです。16ビットのブートローダで32ビットのカーネルをセットアップし、ロードし、実行するのは非常に困難です。これには、エラー処理コードは含まれません。これには、以下のコードが含まれます。GDT、IDT、A20、PMode、32ビットカーネルのロードと検索、カーネルの実行、エラー処理。これらのコードをすべて512バイトに収めることは不可能です。このため、シングルステージのブートローダは、16ビットのカーネルをロードして実行しなければなりません。

この問題から、ほとんどのブートローダはマルチステージローダになっています。

マルチステージブートローダ

マルチステージブートローダは、1つの512バイトのブートローダ(シングルステージローダ)で構成されていますが、別のローダ(セカンドステージブートローダ)をロードして実行します。セカンドステージブートローダは通常16ビットですが、(前のセクションで挙げた)すべてのコードとそれ以上のコードが含まれます。32ビットカーネルをロードして実行することができます。

これがうまくいくのは、512バイトの制限がブートローダだけだからです。ブートローダがセカンドステージローダ用のセクタをすべてきちんとロードしていれば、セカンドステージローダはサイズに制限がありません。このため、カーネルのセットアップが非常に簡単になります。

ここでは、2ステージブートローダを使用します。

ディスクからセクタをロードする

ブートローダは512バイトに制限されていることを忘れないでください。前のセクションで述べたように、2ステージのブートローダを使用するつもりです。つまり、Stage 2 のプログラム(カーネルローダー)をロードして実行するために、ブートロー ダーが必要なのです。

もし望むなら、The Stage 2 ローダーは、あなた自身の "Choose your Operating System" と "Advanced Options" メニューを含めることができる場所です :) 。さあ、欲しいでしょ :)

BIOS割り込み(INT) 0x13 Function 0 - フロッピーディスクをリセットする

BIOS割り込み0x13は、ディスクアクセスに使用されます。INT 0x13, Function 0を使用すると、フロッピーディスクドライブをリセットできます。 これは、フロッピーコントローラがどこから読み込もうと、すぐにディスク上の最初のセクタに移動することを意味します。

INT 0x13/AH=0x0 - DISK : DISK SYSTEMのRESET
AH = 0x0
DL = リセットへのドライブ

を返します。
AH = 状態コード
CF (キャリー・フラグ) は成功するとクリアされ、失敗するとセットされる

これは完全な例です。これはフロッピーディスクドライブをリセットし、エラーが発生した場合に再試行します。

.Reset: mov ah, 0 ; reset floppy disk function mov dl, 0 ; drive 0 is floppy drive int 0x13 ; call BIOS jc .Reset ; If Carry Flag (CF) is set, there was an error. Try resetting again
なぜ、この割り込みが重要なのか?セクタを読み込む前に、セクタ0から始まることを確認する必要があります。これは良くないことで、再起動するといつでも変わってしまう可能性があるからです。ディスクをセクタ0にリセットすることで、毎回同じセクタを読み込んでいることを確認できます。

INT 0x13/AH=0x02 - DISK : セクタをメモリに読み込む
AH = 0x02
AL = 読み込むセクタ数
CH = シリンダ番号の下位8ビット
CL = セクタ番号(ビット0-5)。6-7ビットはハードディスク用のみ
DH = ヘッド番号
DL = ドライブ番号 (ハードディスクの場合はビット7がセットされます)
ES:BX = セクタを読み込むためのバッファ

を返します。
AH = ステータス・コード
AL = 読み込まれたセクタの数
CF = 失敗したらセット、成功したらクリア

さて、これは考えるべきことがたくさんあります。簡単なものもあれば、もっと説明すべきものもあります。 もう少し詳しく見てみましょう。

CH = Low eight bits of cylinder number
シリンダーとは何ですか?シリンダーとは、ディスク上の同じ半径のトラックの集まりです。 これをよりよく理解するために、絵を見てみましょう。

上の図を見て、思い出してください。

  • 各Trackは通常512バイトのセクタに分割されています。フロッピーでは、1トラックあたり18のセクタがあります。
  • シリンダーとは、同じ半径のトラックの集まりです(上の写真の赤いトラックは1つのシリンダーです)。
  • フロッピーディスクには2つのヘッドがあります。
  • 合計で2880個のセクタがあります。

これは何を意味するのでしょうか。シリンダーナンバーは、基本的に1枚のディスクのトラックナンバーを表します。 フロッピーディスクの場合は、読み出すトラックを表します。

まとめると、1セクタあたり512バイト、1トラックあたり18セクタ、1面あたり80トラックということになります。

CL = Sector Number (Bits 0-5). Bits 6-7 are for hard disks only
これは読み取りを開始する最初のセクタです。覚えておいてください。つまり、この値は 0 から 17 の間にしかないのです。現在のトラック番号を増やし、セクタ番号が正しく設定されていることを確認し、正しいセクタを読み取る必要があります。

この値が18より大きい場合、フロッピー・コントローラは例外を発生させます。この例外に対応するハンドラがないため、CPUは2つ目の例外を生成し、最終的にトリプルフォルトを引き起こします。

DH = Head Number
フロッピーディスクには2つのヘッド、または側面があることを忘れないでください。ヘッド0は表側で、そこにはセクタ0があります。このため、私たちはヘッド0から読み取ることになります。

この値が2より大きい場合、ヘッドが存在しないため、フロッピーディスクコントローラは例外を生成します。ハンドラが存在しないため、CPUは2つ目の故障例外を生成し、最終的にTriple Faultに至ります。

DL = Drive Number (Bit 7 set for hard disks) ES:BX = Buffer to read sectors to
ドライブ番号とは何ですか?ドライブ番号0は通常、フロッピーディスク・ドライブを表します。ドライブ番号1は通常5-1/4 "フロッピーディスクドライブを表します。

今回はフロッピーなので、フロッピーディスクから読み出したい。そのため、読み出し先のドライブ番号は0となります。

ES:BXには、セクタを読み込むセグメント:オフセットのベース・アドレスが格納されます。ベースアドレスは読み出しの開始アドレスを表していることに注意してください。

それでは、セクタを読み込んでみましょう。

セクタの読み込みとロード

ディスクからセクタを読み取るには、まずフロッピーディスクドライブをリセットし、そのまま読み取ります。
.Reset: mov ah, 0 ; reset floppy disk function mov dl, 0 ; drive 0 is floppy drive int 0x13 ; call BIOS jc .Reset ; If Carry Flag (CF) is set, there was an error. Try resetting again mov ax, 0x1000 ; we are going to read sector to into address 0x1000:0 mov es, ax xor bx, bx .Read: mov ah, 0x02 ; function 2 mov al, 1 ; read 1 sector mov ch, 1 ; we are reading the second sector past us, so its still on track 1 mov cl, 2 ; sector to read (The second sector) mov dh, 0 ; head number mov dl, 0 ; drive number. Remember Drive 0 is floppy drive. int 0x13 ; call BIOS - Read the sector jc .Read ; Error, so try again jmp 0x1000:0x0 ; jump to execute the sector!

注意:セクタの読み込みに問題があり、そこにジャンプして実行しようとすると、CPUはセクタが読み込まれているかどうかにかかわらず、そのアドレスの命令を実行します。 これは通常、CPUが無効/アンカット命令またはメモリの終端に遭遇することを意味し、両方ともトリプルフォールトになります。

上記のコードでは、生のセクタを読み取って実行するだけなので、我々のニーズには無意味です。 一つには、現在、PartCopyは512バイトだけコピーするように設定されています。つまり、「どこで、どのように、未加工セクタを作成するのか?

また、このRawセクターは存在しないので、「ファイル名」を与えることはできません。単なるロー・セクタです。

最後に、私たちは現在、FAT12ファイルシステム用にブートローダを設定しています。Windowsは、セクタ2と3から特定のテーブル(ファイルアロケーションテーブル)を読み取ろうとします。しかし、生セクタでは、これらのテーブルは存在しないため、Windowsはゴミのような値(あたかもそれがテーブルであるかのように)を取ります。その結果、Windowsでフロッピーディスクを読み込むと、ファイルやディレクトリの名前が壊れていたり、サイズが大きかったりします(3.14MBのフロッピーに2.5Gバイトのファイルを見たことがありますか? 私はあります)。

もちろん、私たちはこの方法でセクタをロードする必要があります。しかし、その前に、ファイルを正しく読み込むために、ファイルの開始セクタ、セクタ数、ベースアドレスなどを調べなければなりません。これが、ディスクからファイルをロードするための基本です。

これについては、次に説明します。

Navigating The FAT12 FileSystem

OEM パラメータブロック - 詳細

前回の記事で、私たちはコード中に醜いテーブルを捨てました。それは何だったのでしょうか?そうそう...
bpbBytesPerSector: DW 512 bpbSectorsPerCluster: DB 1 bpbReservedSectors: DW 1 bpbNumberOfFATs: DB 2 bpbRootEntries: DW 224 bpbTotalSectors: DW 2880 bpbMedia: DB 0xF0 bpbSectorsPerFAT: DW 9 bpbSectorsPerTrack: DW 18 bpbHeadsPerCylinder: DW 2 bpbHiddenSectors: DD 0 bpbTotalSectorsBig: DD 0 bsDriveNumber: DB 0 bsUnused: DB 0 bsExtBootSignature: DB 0x29 bsSerialNumber: DD 0xa0a1a2a3 bsVolumeLabel: DB "MOS FLOPPY " bsFileSystem: DB "FAT12 "
この多くはとてもシンプルです。では、これを詳しく分析してみましょう。
bpbBytesPerSector: DW 512 bpbSectorsPerCluster: DB 1
bpbBytesPerSectorは、1つのセクタを表すバイト数を表します。通常フロッピーディスクでは512バイトです。

bpbSectorsPerClusterは1クラスタあたりのセクタ数を示します。ここでは、1クラスタあたり1セクタとします。

bpbReservedSectors: DW 1 bpbNumberOfFATs: DB 2
Reserved Sectorは、FAT12に含まれないセクタ数、つまりRoot Directoryに含まれないセクタです。 この場合、ブートローダを含むブートセクタは、このディレクトリに含まれません。このため、bpbReservedSectorsは1でなければなりません。

これは、予約セクタ(Our bootloader)にファイルアロケーションテーブルが含まれないことも意味します。

bpbNumberOfFATsは、ディスク上のファイルアロケーションテーブル(FAT)の数を表します。FAT12ファイルシステムは常に2つのFATを持っています。

通常、これらのFATテーブルを作成する必要があります。しかし、ここではVFDを使用しているため、Windows/VFDがディスクをフォーマットする際に、これらのテーブルを作成してもらうことができます。

注:これらのテーブルは、エントリを追加または削除したときにも、Windows/VFDによって書き込まれます。

bpbRootEntries: DW 224 bpbTotalSectors: DW 2880
フロッピーディスクは、Root Directory内に最大224個のディレクトリを持ちます。 また、フロッピーディスクには2880個のセクタがあることを忘れないでください。
bpbMedia: DB 0xF0 bpbSectorsPerFAT: DW 9
Media Descriptor Byte(bpbMedia)は、ディスクに関する情報を含むバイトコードです。 このバイトは、Bit Patternです。
  • ビット0:Sides/Heads = 片面なら0 、両面なら1
  • ビット1:サイズ = FATあたり9セクタの場合は0、8セクタの場合は1。
  • ビット2:密度 = 80トラックなら0、40トラックなら1。
  • ビット3:タイプ =固定ディスク(ハードディスクなど)なら0、リムーバブル(フロッピーディスクなど)なら1。
  • ビット4から7は 未使用で、常に1です。
0xF0 = 11110000のバイナリです。これは、片面、FATあたり9セクタ、80トラックで、ムーバブルディスクであることを意味します。 bpbSectorsPerFATを見ると、これも9であることがわかります。
bpbSectorsPerTrack: DW 18 bpbHeadsPerCylinder: DW 2
以前のチュートリアルを思い出してください/1トラックあたり18セクタあります。 bpbHeadsPerCylinderは、単にシリンダーを表す2つのヘッドがあることを表しています。 (シリンダーが何かわからない場合は、「BIOS Interrupt (INT) 0x13」セクションのセクタの読み方を読んでください)。
bpbHiddenSectors: DD 0
これは、物理ディスクの先頭とボリュームの先頭からのセクタ数を表しています。
bpbTotalSectorsBig: DD 0 bsDriveNumber: DB 0
フロッピーディスクドライブはドライブ0であることを思い出してください。
bsUnused: DB 0 bsExtBootSignature: DB 0x29
Boot Signitureは、このBIOS Parameter Block(このOEMテーブル)の種類とバージョンを表しています。 値は、以下の通りです。
  • 0x28 と 0x29 は、MS/PC-DOS バージョン 4.0 の Bios Parameter Block (BPB) であることを示す。
ここでは0x29なので、このバージョンです。
bsSerialNumber: DD 0xa0a1a2a3 bsVolumeLabel: DB "MOS FLOPPY " bsFileSystem: DB "FAT12 "
シリアルナンバーは、フォーマットするユーティリティによって割り当てられます。シリアル・ナンバーは、そのフロッピー・ディスクに固有のものであり、同じものは2つとないはずです。

Microsoft、PC、DR-DOSでは、シリアルナンバーは現在の時刻と日付から次のように算出される。

Low 16 bits = ((seconds + month) << 8) + (hundredths + day_of_month) High 16 bits = (hours << 8) + minutes + year
シリアルナンバーは上書きされるから、好きなものを入れればいいんだ。

ボリュームラベルは、ディスクに何が入っているかを示す文字列である。OSによっては、これを名前として表示するものもあります。注:この文字列は11バイトでなければなりませんそれ以上でも以下でもない。

ファイルシステム(Filesystem)文字列も同じ目的で使用されるが、それ以上ではな い。注:この文字列は、8バイトでなければなりません。

Demo

わあ、たくさんあるね。以下は、このチュートリアルで開発したブートローダで、すべてをまとめています。

注意: このデモは *そのままでは* 動きません。元々デモ用として作られたもので、このままではビルドできません。このチュートリアルを更新し、次回のシリーズ改訂時にデモをビルド可能にする予定です。

;********************************************* ; Boot1.asm ; - A Simple Bootloader ; ; Operating Systems Development Tutorial ;********************************************* bits 16 ; We are still in 16 bit Real Mode org 0x7c00 ; We are loaded by BIOS at 0x7C00 start: jmp loader ; jump over OEM block ;*************************************************; ; OEM Parameter block / BIOS Parameter Block ;*************************************************; TIMES 0Bh-$+start DB 0 bpbBytesPerSector: DW 512 bpbSectorsPerCluster: DB 1 bpbReservedSectors: DW 1 bpbNumberOfFATs: DB 2 bpbRootEntries: DW 224 bpbTotalSectors: DW 2880 bpbMedia: DB 0xF0 bpbSectorsPerFAT: DW 9 bpbSectorsPerTrack: DW 18 bpbHeadsPerCylinder: DW 2 bpbHiddenSectors: DD 0 bpbTotalSectorsBig: DD 0 bsDriveNumber: DB 0 bsUnused: DB 0 bsExtBootSignature: DB 0x29 bsSerialNumber: DD 0xa0a1a2a3 bsVolumeLabel: DB "MOS FLOPPY " bsFileSystem: DB "FAT12 " ;*************************************** ; Prints a string ; DS=>SI: 0 terminated string ;*************************************** Print: lodsb ; load next byte from string from SI to AL or al, al ; Does AL=0? jz PrintDone ; Yep, null terminator found-bail out mov ah, 0eh ; Nope-Print the character int 10h jmp Print ; Repeat until null terminator found PrintDone: ret ; we are done, so return ;*************************************************; ; Bootloader Entry Point ;*************************************************; loader: .Reset: mov ah, 0 ; reset floppy disk function mov dl, 0 ; drive 0 is floppy drive int 0x13 ; call BIOS jc .Reset ; If Carry Flag (CF) is set, there was an error. Try resetting again mov ax, 0x1000 ; we are going to read sector to into address 0x1000:0 mov es, ax xor bx, bx mov ah, 0x02 ; read floppy sector function mov al, 1 ; read 1 sector mov ch, 1 ; we are reading the second sector past us, so its still on track 1 mov cl, 2 ; sector to read (The second sector) mov dh, 0 ; head number mov dl, 0 ; drive number. Remember Drive 0 is floppy drive. int 0x13 ; call BIOS - Read the sector jmp 0x1000:0x0 ; jump to execute the sector! times 510 - ($-$$) db 0 ; We have to be 512 bytes. Clear the rest of the bytes with 0 dw 0xAA55 ; Boot Signiture ; End of sector 1, beginning of sector 2 --------------------------------- org 0x1000 ; This sector is loaded at 0x1000:0 by the bootsector cli ; just halt the system hlt

まとめ

ディスクの読み込みとBIOSパラメータブロック(BPB)について、かなり詳しく説明しました。さらに、すべてを組み合わせた簡単なデモも作成しました。

また、アセンブリ言語のリングの違いを見て、私たちのOSはリング0であり、他の多くのプログラムとは異なることを学びました。これにより、アプリケーションプログラムにはない、より特別な命令を使うことができるようになりました。

これで、セカンドステージローダを見つけてロードするのに必要なものはすべて揃いました。次のチュートリアルでは、FAT12に関するすべてのことを学び、セカンドステージをロードします。待ち遠しいですね。:) それではまた次回。