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

This series is intended to demonstrate and teach operating system development from the ground up.

はじめに

ようこそ!

前回のチュートリアルで、多くのことを学びました。電源ボタンを押したときに何が起こるのか、そしてBIOSがどのように起動するのかを正確に見てきました。また、BIOS 割り込み (INT) 0x19 を見て、ブートサイン (0xAA55) を検索し、見つかったら 0x7C00 にあるブートローダをロードして実行することを説明しました。

また、簡単なブートローダを開発し、ビルドプロセス全体を経験することができました。

このチュートリアルでは、ブートローダを拡張していきます。このチュートリアルでは、ブートローダを拡張します。

  • BIOS パラメータブロックと MBR
  • プロセッサのモード
  • 割り込み - テキストの印刷とその他
  • セグメント:オフセットアドレスモード

注意:ここから先は、ブートローダがシステム全体を完全に制御します。これはつまり、私たちがコードを書くことにすべてがかかっているということです。全ては私たち次第なのです。要するに、これからもっとたくさんのコードを書いていくことになります。

これから先、物事はより複雑になっていくでしょう。このシリーズの構成をしっかりさせるために、これから始まるチュートリアルには、ダウンロード可能なデモを用意するつもりです。これは、コンセプトの理解を助けるものです。それでも、このチュートリアルでは、すべてを詳しく説明していきますので、ご安心ください。

準備はいいですか?

プロセッサーモード

さてさて...この言葉、どこかで聞いたことがあるような?そうですね...すべてのチュートリアルで!

プロセッサーモードについては、これまであまり触れてきませんでした。プロセッサのモードを理解することは、私たちにとって非常に重要なことです。なぜでしょうか?

前の2つのチュートリアルでは、x86ファミリーが16ビット環境で起動する方法と理由について説明しました。私たちは32ビットオペレーティングシステム(OS)を開発したいので、プロセッサを16ビットモードから32ビットモードに切り替える必要があります。

モードは2つ以上あります。それぞれについて説明しましょう。

リアルモード

ご存知のように、x86プロセッサは16ビット環境で起動します。このモードは何なのでしょうか?そうですね :)

リアルモードって何?リアルモードは...

  • ネイティブのセグメント:オフセットメモリーモデルを使用します。
  • メモリは1MB(+64k)に制限される。
  • 仮想メモリや メモリ保護はありません。
これらのうちいくつかは非常に簡単です。ひとつ注意すべきは、上記のすべてが間接的あるいは直接的にメモリに関連していることです。

もう少し詳しく見てみましょう。

セグメント:オフセット・メモリ・モード - 歴史

もう一度、チュートリアル2を見てみましょう。メモリの概念とオペレーティングシステムの使用は1950年代までさかのぼります。この頃のコンピュータはパーソナルコンピュータではなく、大型のメインフレームコンピュータでした。

当時、すべてのコンピュータは非常に大きくかさばるハードウェアデバイスを持っていたことを思い出してください。 時を経て(チュートリアル2を振り返って)、オペレーティングシステムだけでなく、コンピュータも進歩していることがわかります。

コンピュータが普及するにつれて、その需要も増えてきました。コンピュータが8ビットだった頃、多くの人が16ビットを欲しがりました。16ビットの時代が来たとき、マイクロソフトはすでに32ビットを考えていた。32ビットの時代が来ると同時に、64ビットが主流になった。しかし、128ビットが登場した。

一番の問題は、コンピュータ業界の進歩が速すぎることです。

Intelが8086プロセッサを設計していたとき、プロセッサは16ビットのレジスタを使い、64KBまでのメモリにしかアクセスできなかった。しかし、問題は、多くのソフトウェアがこれ以上のメモリを必要とすることだった。

8086は、8088と同じ時期に設計されていた。しかし、8088はインテルの「次世代」プロセッサになる予定だったが、予想以上に時間がかかってしまった。そこで、インテルは8086を開発・発売し、8088が発売されるまでの間、他社に対抗しようと考えた。

しかし、ソフトウエアは64KB以上のメモリを要求しており、インテルは8088が出るまでの間、すでに16ビットプロセッサを作っている競合他社に対抗するために、8086というプロセッサを開発したのである。そこで、インテル社は、ある工夫をした。

そこで、8086の設計者は、ある解決策を提案した。8086は16ビットのまま、最大1MBのメモリにアクセスできるようにするのだ。彼らは同意し、インテルも承認した。

セグメント:オフセット・メモリ方式が誕生したのである。

セグメント:オフセット方式を理解するために、まずセグメントとオフセットを分解して見てみよう。

セグメント

セグメント(Segment)とは、簡単に言えば、全体の一部分である。この場合、セグメントとはメモリの一部分のことです。 そう、基本的にはそれだけです。

メモリをセクションに分割することを想像してください。このセクションがセグメントです。

x86プロセッサーのファミリーは、セグメントの開始位置を格納する4つのプライマリーレジスタを使用します。これはベースアドレスのようなもので、セグメントの開始位置を提供します。

通常、セグメントは64KBの大きさで、自由に移動することができます。

セグメントとは、単にメモリ上のセクションを表すものであることを忘れないでください。この場合、セグメントのベースアドレスが0であれば、バイト0と64KBの間のセグメントを表します。

レジスタは、CS、DS、ES、 SSです。このレジスタは、セグメントのベースアドレスを格納します。 このモードでのアドレッシングを見た後で、詳しく見ていきましょう。

オフセット

オフセットとは、基本番号に追加される数値のことです。たとえば、ベースナンバーが3の場合。

オフセット=基準番号(3)+オフセット番号
オフセット2は、3+2=5
オフセット4は、3+4=7

さて、これがどう関係するのでしょうか。セグメント:オフセットアドレッシングでは、ベースアドレス(セグメントはベースアドレ スを表すことを覚えておいてください)をオフセットアドレスに加えます。

かなり簡単でしょう?さて、すべてをまとめてみましょう。

セグメント:オフセットアドレッシング

しかし、前項でReal Modeのセグメントアドレスは16ビットであることを述べた。つまり、セグメントを16(10進数)倍してから、オフセットを加算する必要があります。 これが現在の計算式です。
Absolute (Exact) Memory Address = (Segment Address * 16(decimal)) + Offset

セグメント:オフセットの規則

セグメントとオフセットのアドレスは、通常、コロン(:)で区切られます。 通常は、セグメント :オフセット例えば、次のようになります。
07C0:0000    < 07C0 is the segment, and 0 is the offset
上記を絶対アドレス0x7C00に変換するには、次の式を使用します。
                 base address = base address * segment size (16) + offset
			07C0:0000 = 07C0 * 16 (decimal) + 0
                                  = 07C00 + 0 = 0x7C00

セグメント:オフセットの問題

セグメント:オフセットはかなりユニークです。セグメントとオフセットの値を変更することで、異なるセグメントとオフセットのペアが同じ絶対アドレスを生成することを見つけることができます。なぜでしょうか?なぜなら、両方とも同じメモリロケーションを参照しているからです。

例えば、以下のアドレスはすべて、0x7C00 にあるブートローダを参照しています。

  0007:7B90   0008:7B80   0009:7B70   000A:7B60   000B:7B50   000C:7B40   

  0047:7790   0048:7780   0049:7770   004A:7760   004B:7750   004C:7740   

  0077:7490   0078:7480   0079:7470   007A:7460   007B:7450   007C:7440   

  01FF:5C10   0200:5C00   0201:5BF0   0202:5BE0   0203:5BD0   0204:5BC0   

  07BB:0050   07BC:0040   07BD:0030   07BE:0020   07BF:0010   07C0:0000
これらはほんの一部に過ぎません。技術的には、メモリ上の同じバイトを参照できるセグメントとオフセットの組み合わせは、正確には4096通りあります -- これは、メモリ上の各バイトに対してです!

もし、64KB以内のセグメントアドレスが2つあったらどうでしょうか?セグメントのサイズ(とオフセット)は16ビットであることを思い出してください。そして、セグメントアドレスは、セグメントのベースのみを参照する。 これが、オーバーラップされたセグメントである。

レイヤーの上にレイヤーがあり、それが他のセグメントの上に重なっていると想像してください。これは問題を引き起こす可能性がある。

つまり、Real Modeでは、メモリ上のすべてのバイトに4,000通り以上のアクセスが可能で、知らないうちにセグメントをオーバーラップさせて、その領域のメモリを破壊する可能性がある。 これが、Real Modeにメモリ保護がないことの意味である。

x86がセグメント参照に使用するレジスタは以下の通りである。

  • CS (コードセグメント) -コードのベースセグメントアドレスを格納します。
  • DS (データセグメント) - データ用のベースセグメントアドレスを格納します。
  • ES (エクストラセグメント) - 何でも格納するセグメントアドレスを格納します。
  • SS (スタックセグメント) - スタックのベースセグメントアドレスを格納します。

うわー、リアルモードには問題がたくさんある。私たちは、この問題から何を守ればいいのでしょうか?

プロテクトモード

プロテクトモード(PMode)は、よく耳にする言葉ですが、これからもっと耳にすることになると思います。PModeでは、メモリレイアウトを記述したディスクリプタテーブルを使用することで、メモリ保護を行うことができます。

PModeは32bitのプロセッサモードなので、32bitのレジスタも使えるし、最大4GBのRAMにもアクセスできる。Real Modeに比べれば大きな進歩です。

私たちはPModeを使用することになります。そうです、聞かれる前に言っておきますが、WindowsはPMode OSです :)

PModeはセットアップとその動作を完全に理解するのが少し難しいです。 PModeについての詳細は後ほど説明します。

アンリアルモード

PMモードは、いつでも好きなときにプロセッサモードを切り替えることができます。アンリアルモードとは、PModeのアドレス空間(4GB制限)を持つReal Modeを表現したダジャレです。

Unreal Modeを有効にするには、プロセッサをReal ModeからPModeに切り替え、新しいDescriptorをロードしたらまた元に戻すだけでよいのです。

Descriptor Tablesは非常にわかりにくいものです。詳しくはProtected Mode (PMode)の説明の時に説明します。

仮想8086モード

Virtual 8086 Mode (v86 Mode) は、Protected Mode を 16bit Real Mode でエミュレートした環境として表現したモードです。

これはちょっと奇妙に思えるかもしれませんが、v86は便利なものなのです。すべてのBIOS割り込みはリアルモードでのみ利用可能です!v86モードはPModeの中からBIOS割り込みを実行する方法を提供します。これについては後で詳しく説明します。

プロセッサモードの切り替え

ここでは、プロセッサのモードを切り替えるためのコードについては、まだ説明しません。その代わりに、一歩下がって、いくつかの重要なコンセプトを説明したいと思います。

実際のモードとして組み込まれているのは、Real ModeとPotected Modeの2つだけです。つまり、他のモードであるUnreal Modeとv86 Modeは、この2つのモードを元に作られています。

Unreal ModeはReal Modeだが、Protected Mode (PMode) Addressing Systemを使っていること、Virtual 8086 ModeはPModeだが、16ビットコードを実行するためにReal Modeを使っていることを思い出してほしい。

このように、v86モードもUnrealモードもReal ModeとProtected Modeをベースにしているだけなので、PModeを理解しないとこれらのモードがどのように動作するのか理解するのは難しいかもしれません。

PMode、Unreal Mode、v86 Modeについては近日中に詳しく説明しますので、ご心配なく :)

しかし、PModeについて覚えておくべきいくつかの重要なことがあります。

  • 割り込みは絶対に使えません。自分で書く必要があります。ハードウェア、ソフトウェアにかかわらず、割り込みを使用するとトリプルフォルトが発生します。
  • PModeに切り替えた後は、わずかなミスでもTriple Faultが発生します。注意してください。
  • PModeでは、GDT、LDT、IDTなどの Descriptor Tablesを使用する必要があります。
  • PModeでは、4GBのメモリが使用できます。
  • セグメント:オフセットアドレスとリニアアドレスが使用されます。
  • 32ビットレジスタへのアクセスおよび使用
PModeについては後でもっと詳しく説明します。

ブートローダの拡張

さて、ここまでで多くのことを学びましたね。Protected Mode、Unreal Mode、v86 Modeの基本的な理論について説明しました。しかし、Real Modeについては深く掘り下げて説明しました。なぜか?なぜなら、コンピュータはDOSとの後方互換性のために16ビット環境で起動することを思い出してください。この16ビット環境はReal Modeです。

ブートローダが実行されると、リアルモードになります。待って!これってBIOS割り込みが使えるってことだよね? うん :)VGAビデオ割り込みや、ハードウェアから直接マッピングされたその他の割り込みも使えます :)

便利なルーチンとBIOS割り込み

OEMパラメータブロック

OEMパラメータブロックは、WindowsのMBRとブートレコードの情報を格納します。 その主な目的は、ディスク上のファイルシステムを記述することです。 このテーブルについては、ファイルシステムを見るまで説明しません。しかし、このテーブルがないと先に進めません。

これは、Windowsからの "Not formatted "メッセージも修正します。

今のところ、この表は必要なものだけと考えてください。詳しくは、ファイルシステムやディスクからのファイルのロードについて説明するときに、説明します。

以下は、このテーブルを使ったブートローダです。

;********************************************* ; 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 ;*************************************************; 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 " ;*************************************************; ; Bootloader Entry Point ;*************************************************; loader: cli ; Clear all Interrupts hlt ; halt the system times 510 - ($-$$) db 0 ; We have to be 512 bytes. Clear the rest of the bytes with 0 dw 0xAA55 ; Boot Signiture

テキストの印刷 - 割り込み0x10関数0x0E

INT 0x10はビデオ割り込みに使用できます。ただし、基本的な割り込みしか動作しないことを忘れないでください。

INT 0x10 - ビデオテレタイプ出力

AH = 0x0E
AL = 書き込む文字
BH - ページ番号 (0であるべき)
BL = 前景色 (グラフィックモードのみ)
例えば

xor bx, bx ; A faster method of clearing BX to 0 mov ah, 0x0e mov al, 'A' int 0x10
これは、画面上に文字'A'を表示します。

文字列の印刷 - 割り込み 0x10 関数 0x0E

同じ割り込みを使って、0終端の文字列を簡単に出力することができます。
msg db "Welcome to My Operating System!", 0 ;*************************************** ; Prints a string ; DS=>SI: 0 terminated string ;*************************************** Print: lodsb or al, al ; al=current character jz PrintDone ; null terminator found mov ah, 0eh ; get next character int 10h jmp Print PrintDone: ret ;*************************************************; ; Bootloader Entry Point ;*************************************************; loader: ; Error Fix 1 ------------------------------------------ xor ax, ax ; Setup segments to insure they are 0. Remember that mov ds, ax ; we have ORG 0x7c00. This means all addresses are based mov es, ax ; from 0x7c00:0. Because the data segments are within the same ; code segment, null em. mov si, msg call Print cli ; Clear all Interrupts hlt ; halt the system times 510 - ($-$$) db 0 ; We have to be 512 bytes. Clear the rest of the bytes with 0 dw 0xAA55 ; Boot Signiture

RAMの量を取得する

これは簡単すぎです。

INT 0x12 - BIOS GET MEMORY SIZE(メモリサイズ取得
戻り値AX = 絶対アドレス0x0から始まる連続したメモリのキロバイト。

例です。

xor ax, ax int 0x12 ; Now AX = Amount of KB in system recorded by BIOS
うわぁ...大変だったでしょう?)実は、プロテクトモード(PMode)では、割り込みが使えないので、とても大変なのです。

注意:BIOSから実際に返されるメモリ量は正確ではないかもしれません!後で他の方法について見てみましょう。

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 ;*************************************************; ; Error Fix 2 - Removing the ugly TIMES directive ------------------------------------- ;; TIMES 0Bh-$+start DB 0 ; The OEM Parameter Block is exactally 3 bytes ; from where we are loaded at. This fills in those ; 3 bytes, along with 8 more. Why? bpbOEM db "My OS " ; This member must be exactally 8 bytes. It is just ; the name of your OS :) Everything else remains the same. 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 " msg db "Welcome to My Operating System!", 0 ; the string to print ;*************************************** ; 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: xor ax, ax ; Setup segments to insure they are 0. Remember that mov ds, ax ; we have ORG 0x7c00. This means all addresses are based mov es, ax ; from 0x7c00:0. Because the data segments are within the same ; code segment, null em. mov si, msg ; our message to print call Print ; call our print function xor ax, ax ; clear ax int 0x12 ; get the amount of KB from the BIOS cli ; Clear all Interrupts hlt ; halt the system times 510 - ($-$$) db 0 ; We have to be 512 bytes. Clear the rest of the bytes with 0 dw 0xAA55 ; Boot Signiture

まとめ

ここまでできたら自分を褒めてあげてください :)

このチュートリアルは厄介なものでした。セグメント:オフセットアドレッシングとプロセッサモードを深く掘り下げることなく説明する非常に良い方法を見つけなければなりませんでした。うまくできたと思います :)

リアルモード、プロテクトモード、アンリアルモード、v86 などのプロセッサモードについて説明しました。 リアルモードについては、ブートローダを開発するときに使うモードなので、詳しく調べました。 また、セグメントオフセットアドレッシングについても説明しました。これは DOS プログラマにとっては再教育になるかもしれません。さらに、いくつかの BIOS 割り込みについても説明し、最後に完全な例を示しました。

次回のチュートリアルでは、追加した醜いOEMパラメータブロックの解読を行います。 また、基本的なファイルシステムの理論とディスクからのセクタのロードも見ていきます。

では、また次回。