『オペレーティングシステム - 設計と実装 (第3版)』 を読む。第6日目。プロセス 3(マスターブートブロック)
起動処理概要
コードは、bootディレクトリにある。
- BOISが最初のセクターに510バイトのオフセットでマジックナンバー (0xAA55) を持つディスクを検索。
- 該当のセクタをメモリのアドレスLOADOFFにロードし、実行。セクタの中身はインストール時に書き込まれたマスターブートブロックコード (masterboot.s)である。
- マスターブートブロックコードはコード自身をBUFFERにコピーし、コピー先にジャンプする。
fix
変数 (installbootユーティリティでパッチされている) によって指定されたパーティション、またたパーティションテーブル(パーティションテーブルはディスクの最初のセクターに書き込まれている)を検索してアクティブパーティションを見つけて、最初のセクタを読み込む。読み込まれるのは通常はブートストラップコード (bootblock.s)。 - ブートストラップもアドレスLOADOFFにロードされるが、 マスターブートブロックとは違って自身をコピーしない。ブートストラップは、ブートモニタコードをBOOTSEGにロードし、BOOTSEG:BOOTOFFに(つまり、ヘッダーをスキップし)ジャンプ。ブートモニタは4つのファイル(boothead.s、boot.c、bootimage.c、rawfs.c)から構成される。
- ブートストラップがブートモニターにジャンプした直後に、boothead.sのコードが実行される。メモリレイアウト、プロセッサ(286, 386, 486など)、現在のビデオモード、ブートされたデバイスを決定し、最後に
boot()
(boot.c)を呼び出す。Minix OSにジャンプする (必要に応じてリアルモードからプロテクトモードに切り替える) 機能もboothead.sにある。 -
boot()
はinitialize()
(同様にboot.cにある)をコールする。initialize()
はブートモニターを再配置し、カーネルに渡されるメモリマップからブートモニターを削除します (カーネルがブートモニターを上書きしないようにします) 。initialize()
は、次の2つの関数を呼び出す。-
get_parameters()
(boot.cにある)は、いくつかの環境変数や関数を設定し、bootparamsセクタ (アクティブなパーティションの2番目のセクタ) からいくつかのパラメータの値を取得する。 -
r_super()
(rawfs.cで見つかる)は、システムがminixファイルシステムであることを検証し、ファイルシステムのパラメータを決定する。
-
- これら2つの関数から戻ると、ブートモニターはユーザーが入力したコマンドを実行する。最も重要なコマンドはboot (システムをブートする)であり、
bootminix()
(bootimage.cにある)を呼び出す。 -
bootminix()
は以下の関数を呼び出す。-
select_image()
は、ディスク上の目的のOSイメージを検索する。 -
exec_image()
は、OSイメージをメモリにロードする。 -
minix()
(boothead.sで見つかる)はプロテクトモードに切り替え (カーネルがプロテクトモード向けにコンパイルされている場合) 、カーネルにジャンプする。
-
-
bootminix()
は、OSからブートモニターに戻ると(たとえば、ユーザーがminix shutdownコマンドを発行した場合に)復帰する。
rawfs.cは、ファイルシステム操作の小さなサブセットである。たとえば、ディスクからブロックを読み取ったり、ファイルに関する情報を見つけたり、ディレクトリの内容を読み取ったり、パス名をiノード番号に変換したりできる。
masterboot.s
のように、起動ディスクの最初のセクタをLOADOFFアドレスにロードし実行する。マスターブートブロックコードは自身をBUFFER領域にコピーする。アドレスの定義は以下
LOADOFF = 0x7C00 ! 0x0000:LOADOFF is where this code is loaded
BUFFER = 0x0600 ! First free memory
PART_TABLE = 446 ! Location of partition table within this code
PENTRYSIZE = 16 ! Size of one partition table entry
MAGIC = 510 ! Location of the AA55 magic number
これから、コードを見ていきます。
! Find active (sub)partition, load its first sector, run it.
master:
xor ax, ax
mov ds, ax
mov es, ax
第1オペランドのレジスタ(ax)と第2オペランド(ax)とで排他的論理和をとって、第1オペランド(ax)に結果を格納します。自分自身(ax)と排他的論理和は必ずゼロになるので、axレジスタをゼロにします (それ自身とxorされた数字はすべて0です) 。これはかなり一般的なテクニックです。
mov ax,#0
だと、xorの2バイトに比べて遅く、3バイトを消費する機械語になってしまう。その後、ds, esのセグメントレジスタを0にします。
cli
mov ss, ax ! ds = es = ss = Vector segment
mov sp, #LOADOFF
sti
スタックセグメントレジスタ (ss) またはスタックポインター (sp) に値をmovする場合は、最初に割り込みを無効にする必要がある。ssおよびspレジスタは、割り込みが完了した後に戻るアドレスを保持する。もしssレジスタとspレジスタが変動している間に割り込まれた場合、コードが戻る場所は不定である。
割り込みはcli (clear interrupts) 命令で無効になり、sti (set interrupts) 命令で再度有効になる。
! Copy this code to safety, then jump to it.
mov si, sp ! si = start of this code
push si ! Also where we'll return to eventually
mov di, #BUFFER ! Buffer area
mov cx, #512/2 ! One sector
cld
rep movs
この部分は、前述の図のように、LOADOFFから512バイトをBUFFER領域にコピーします。
rep movs命令は、cxワードをds:si(これは0:LOADOFF)からes:di(これは0:BUFFER)に移動する。起動直後のシステムは16ビットPCとして(リアルモードで)起動するので1ワードは2バイトになっている。このため512/2ワードを移動する必要がある。
cld (clear direction flag) は、rep movs命令が0:LOADOFFから0:LOADFF-512までのバイトではなく、0:LOADOFFから0:LOADOFF+512までのバイトをコピーすることを指定する。前者が必要な場合は、cldの代わりにstd (set direction flag) を使用する。
jmpf BUFFER+migrate, 0 ! To safety
migrate:
jmpf(far jump)は、セグメント0、オフセットBUFFER+migrateにジャンプする。よって、コピー先の領域にある、次の命令にジャンプすることになる。
findactive:
testb dl, dl
jns nextdisk ! No bootable partitions on floppies
BIOSは起動時にDLレジスタに起動ドライブの番号を設定する。0x00と0x01は1台目と2台目のフロッピードライブに対応し、0x80、0x81、0x82、0x83はハードディスクドライブ1~4に対応する。
testbはdl自身のビット単位の論理積をとり、signフラグを変更(負の場合に1)します。負でない場合(つまりフロッピードライブ)の場合、nextdiskへジャンプする。
mov si, #BUFFER+PART_TABLE
find: cmpb sysind(si), #0 ! Partition type, nonzero when in use
jz nextpart
testb bootind(si), #0x80 ! Active partition flag in bit 7
jz nextpart ! It's not active
パーティションテーブル(#BUFFER+PART_TABLE)を読み込んでチェックしていく。このsiの値は、ブートストラップコードでも使用する。
si+sysindの値が0の場合、もしくはsi+bootindの7ビット目が立っていなければ、次のパーティションのチェックにジャンプする。
パーティションテーブルのエントリの構造は以下のようになっています。
! <ibm/partition>.h:
bootind = 0
sysind = 4
lowsec = 8
loadpart:
call load ! Load partition bootstrap
jc error1 ! Not supposed to fail
bootstrap:
ret ! Jump to the master bootstrap
loadを呼び出して、アクティブパーティションの最初のセクタを読み込みます。
load:
mov di, #3 ! Three retries for floppy spinup
retry: push dx ! Save drive code
push es
push di ! Next call destroys es and di
movb ah, #0x08 ! Code for drive parameters
int 0x13
pop di
pop es
ah=0x08にセットしてint 0x13を呼び出すと、dlに指定したドライブのパラメータを返す。
バグのあるBIOSは、ES:DIを0x0000:0x0000にセットしてしまうので、es, diを予めスタックに退避して、呼び出し後に復元する。
andb cl, #0x3F ! cl = max sector number (1-origin)
incb dh ! dh = 1 + max head number (0-origin)
movb al, cl ! al = cl = sectors per track
mulb dh ! dh = heads, ax = heads * sectors
mov bx, ax ! bx = sectors per cylinder = heads * sectors
mov ax, lowsec+0(si)
mov dx, lowsec+2(si)! dx:ax = sector within drive
cmp dx, #[1024*255*63-255]>>16 ! Near 8G limit?
jae bigdisk
int 0x13呼び出し後に、clの5:0ビットにトラックあたりのシリンダ数、hdにヘッダの最大インデックス(0開始)がセットされる。bxにシリンダあたりのセクタ数を計算しておきます。
dxの値で、開始位置が8GBを超えていないかをチェックして、超えていた場合はbigdiskの処理を実行する。
div bx ! ax = cylinder, dx = sector within cylinder
xchg ax, dx ! ax = sector within cylinder, dx = cylinder
movb ch, dl ! ch = low 8 bits of cylinder
divb cl ! al = head, ah = sector (0-origin)
xorb dl, dl ! About to shift bits 8-9 of cylinder into dl
shr dx, #1
shr dx, #1 ! dl[6..7] = high cylinder
orb dl, ah ! dl[0..5] = sector (0-origin)
movb cl, dl ! cl[0..5] = sector, cl[6..7] = high cyl
incb cl ! cl[0..5] = sector (1-origin)
pop dx ! Restore drive code in dl
movb dh, al ! dh = al = head
mov bx, #LOADOFF ! es:bx = where sector is loaded
mov ax, #0x0201 ! Code for read, just one sector
int 0x13 ! Call the BIOS for a read
jmp rdeval ! Evaluate read result
以下のパラメータを計算して、ah=0x02にセットしてint 0x13を呼び出して、アクティブパーティションの最初のセクタをロードする。ロード後はrdevalにジャンプして、処理を続ける。
レジスタ | 設定値 |
---|---|
AL | 読み込むセクタ数 |
CH | シリンダ番号 |
CL | セクタ番号 |
DH | ヘッダインデックス |
DL | ドライブ |
ES:BX | 読み出し先アドレス |
(bigdisk:は省略)
rdeval:
jnc rdok ! Read succeeded
エラーがなければCFフラグはクリアされてるので、エラーがなければrdokへジャンプ
rdok: cmp LOADOFF+MAGIC, #0xAA55
jne nosig ! Error if signature wrong
ret ! Return with carry still clear
ロードしたセクタにマジックナンバーがセットされていれば、retで戻っていくことでロードしたブートストラップコード(bootblock.s)に処理を移す。