『オペレーティングシステム - 設計と実装 (第3版)』 を読む。第7日目。プロセス 4(ブートストラップとブートモニタ)
bootblock.s
このファイルには、最終的にフロッピーディスクまたはハードディスクパーティションから読み取られるコードが含まれている。それは、ブートデバイスから/boot/bootをアドレス0x10000のメモリにロードし、それを実行するのに十分な賢さがあるだけである。/boot/bootのディスクアドレスは、installbootによって24ビットのセクター番号と、enddataより大きな8ビットのセクタ数としてこのコードにパッチされる。/boot/bootは、Minixカーネルのさまざまな部分をメモリにロードして実行し、最終的にMinixを起動するために十分に賢い。
このコードがハードドライブから始まり、マスターブートコードがこのコードを読み込んだ場合は、ブートパーティションに対応するパーティションテーブルエントリがes:siに渡される。
boot:
xor ax, ax ! ax = 0x0000, the vector segment
mov ds, ax
cli ! Ignore interrupts while setting stack
mov ss, ax ! ss = ds = vector segment
mov sp, #LOADOFF ! Usual place for a bootstrap stack
sti
(masterboot.sと同様に)axレジスタを0にセットし、ds, ssも0に設定する。スタックは#LOADOFFより下位に配置される。(コードよりも下位アドレスってのはちょっと馴染みがない)
push ax
push ax ! Push a zero lowsec(bp)
push dx ! Boot device in dl will be device(bp)
mov bp, sp ! Using var(bp) is one byte cheaper then var.
6バイト分だけスタック領域を確保して、変数(bp)によるアドレスでアクセスできるようにしている。用途は以下の通り。
! Variables addressed using bp register
device = 0 ! The boot device
lowsec = 2 ! Offset of boot partition within drive
secpcyl = 6 ! Sectors per cylinder = heads * sectors
全部で8バイト必要なのだが、6バイトしか確保しなかったので、最初の命令xor ax, ax
のコードが上書きされてしまう。だが、この命令は二度と実行されないので問題にはならない。まだコードセクションとかを指定していないので、コード領域に書き込んでもエラーにならない。
push es
push si ! es:si = partition table entry if hard disk
masterboot.sで設定されたパーティションテーブルのエントリのアドレスを退避。
mov di, #LOADOFF+sectors ! char *di = sectors;
diレジスタに、sectorデータ用のポインタをセット。(ハードディスク起動の場合、参照先のデータは後で書き換えられる)
testb dl, dl ! Winchester disks if dl >= 0x80
jge floppy
masterboot.sと同様にフロッピーからの起動かをチェックして分岐。
winchester:
! Get the offset of the first sector of the boot partition from the partition
! table. The table is found at es:si, the lowsec parameter at offset LOWSEC.
eseg
les ax, LOWSEC(si) ! es:ax = LOWSEC+2(si):LOWSEC(si)
mov lowsec+0(bp), ax ! Low 16 bits of partition's first sector
mov lowsec+2(bp), es ! High 16 bits of partition's first sector
(si)にパーティションテーブルのエントリがある。そこからLOWSECオフセットの位置にパーティションの開始アドレスがあるのでax、esレジスタに読み込む。読み込んだ値をスタックに保存する。
結局のところ、以下のようにパーティションテーブルのエントリの一部をスタックにコピーしている。
movb ah, #0x08 ! Code for drive parameters
int 0x13 ! dl still contains drive
andb cl, #0x3F ! cl = max sector number (1-origin)
movb (di), cl ! Number of sectors per track
incb dh ! dh = 1 + max head number (0-origin)
jmp loadboot
masterboot.sと同様にディスク情報を取得。
loadboot:
! Load /boot from the boot device
movb al, (di) ! al = (di) = sectors per track
mulb dh ! dh = heads, ax = heads * sectors
mov secpcyl(bp), ax ! Sectors per cylinder = heads * sectors
セクタ数/シリンダを計算し、secpcyl(bp)に保存
mov ax, #BOOTSEG ! Segment to load /boot into
mov es, ax
xor bx, bx ! Load first sector at es:bx = BOOTSEG:0x0000
bootプログラムの読み出し先を設定。
mov si, #LOADOFF+addresses ! Start of the boot code addresses
installbootユーティリティによって設定される値のアドレスをセット。
load:
mov ax, 1(si) ! Get next sector number: low 16 bits
movb dl, 3(si) ! Bits 16-23 for your up to 8GB partition
xorb dh, dh ! dx:ax = sector within partition
bootプログラムはディスク上に分割して格納されている。addressesは、分割の単位毎にセクタ数(1バイト)、パーティション内での開始セクタ(3バイト)の4バイトのエントリがが作成される。ここでは、開始セクタをdx:axとして取得している。
add ax, lowsec+0(bp)
adc dx, lowsec+2(bp)! dx:ax = sector within drive
cmp dx, #[1024*255*63-255]>>16 ! Near 8G limit?
jae bigdisk
lowsecの情報を使って、開始セクタをパーティション内からディスクの先頭する。(8GB以上の大容量ディスクのチェックもしている)
div secpcyl(bp) ! 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 (di) ! 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)
movb dh, al ! dh = al = head
movb dl, device(bp) ! dl = device to read
movb al, (di) ! Sectors per track - Sector number (0-origin)
subb al, ah ! = Sectors left on this track
cmpb al, (si) ! Compare with # sectors to read
jbe read ! Can't read past the end of a cylinder?
movb al, (si) ! (si) < sectors left on this track
int 13呼び出しのためのパラメータを計算。トラックの最後まで読み出す必要がない場合は、(si)のセクタ数文だけ読み出す。
read: push ax ! Save al = sectors to read
movb ah, #0x02 ! Code for disk read (all registers in use now!)
int 0x13 ! Call the BIOS for a read
pop cx ! Restore al in cl
jmp rdeval
! 略
rdeval:
jc error ! Jump on disk read error
movb al, cl ! Restore al = sectors read
addb bh, al ! bx += 2 * al * 256 (add bytes read)
addb bh, al ! es:bx = where next sector must be read
add 1(si), ax ! Update address by sectors read
adcb 3(si), ah ! Don't forget bits 16-23 (add ah = 0)
subb (si), al ! Decrement sector count by sectors read
jnz load ! Not all sectors have been read
add si, #4 ! Next (address, count) pair
cmpb ah, (si) ! Done when no sectors to read
jnz load ! Read next chunk of /boot
int 13呼び出しでディスクを読む。読み出した結果でaddressesを更新し、続きがあるならloadをループする。
done:
! Call /boot, assuming a long a.out header (48 bytes). The a.out header is
! usually short (32 bytes), but to be sure /boot has two entry points:
! One at offset 0 for the long, and one at offset 16 for the short header.
! Parameters passed in registers are:
!
! dl = Boot-device.
! es:si = Partition table entry if hard disk.
!
pop si ! Restore es:si = partition table entry
pop es ! dl is still loaded
jmpf BOOTOFF, BOOTSEG ! jmp to sec. boot (skipping header).
読み込みが完了したら、es:siにパーティションテーブルのエントリをセットし直して、bootプログラムの(ヘッダ部分をスキップして)コード部分へジャンプ。
boothead.s
jmpf boot, LOADSEG+3 ! Set cs right (skipping long a.out header)
.space 11 ! jmpf + 11 = 16 bytes
jmpf boot, LOADSEG+2 ! Set cs right (skipping short a.out header)
bootblock.sの最後で、jmpf BOOTOFF, BOOTSEG
によって、bootプログラムの0x30バイト目にジャンプしている。bootプログラムのヘッダ部分は、48バイト(0x30)、または、32バイト(0x20)の場合がある。48バイトのヘッダの場合は最初のjmpf boot, LOADSEG+3
へ、32バイトの場合は2つ目のjmpf boot, LOADSEG+2
に到達する。最終的には、boot
ラベルへジャンプするようにしている。
boot:
mov ax, #LOADSEG
mov ds, ax ! ds = header
dsをヘッダの位置に設定する。
movb al, a_flags
testb al, #A_SEP ! Separate I&D?
jnz sepID
ヘッダ部のa_flagsを調べて条件分岐、MINIX 3のデフォルトはI&Dは分離されている。
sepID:
mov ax, a_total ! Total nontext memory usage
and ax, #0xFFFE ! Round down to even
mov a_total, ax ! total - text = data + bss + heap + stack
a_totalを2バイトアライメントをとる。
cli ! Ignore interrupts while stack in limbo
mov sp, ax ! Set sp at the top of all that
スタックポインタ(sp)をメモリの最後尾に設定
mov ax, a_text ! Determine offset of ds above cs
movb cl, #4
shr ax, cl
a_textサイズをセグメントの単位に変換。
mov cx, cs
add ax, cx
mov ds, ax ! ds = cs + text / 16
データセグメントの開始位置をテキストセグメントの後に設定
mov ss, ax
sti ! Stack ok now
スタックセグメントの開始位置をdsと同じにする。
push es ! Save es, we need it for the partition table
mov es, ax
cld ! C compiler wants UP
! Clear bss
xor ax, ax ! Zero
mov di, #_edata ! Start of bss is at end of data
mov cx, #_end ! End of bss (begin of heap)
sub cx, di ! Number of bss bytes
shr cx, #1 ! Number of words
rep
stos ! Clear bss
esを退避してから、bssの初期化を行う。es:diからcxカウントだけax(=0)を代入している。
! Copy primary boot parameters to variables. (Can do this now that bss is
! cleared and may be written into).
xorb dh, dh
mov _device, dx ! Boot device (probably 0x00 or 0x80)
mov _rem_part+0, si ! Remote partition table offset
pop _rem_part+2 ! and segment (saved es)
起動パラメータをbssにコピー。
! Remember the current video mode for restoration on exit.
movb ah, #0x0F ! Get current video mode
int 0x10
andb al, #0x7F ! Mask off bit 7 (no blanking)
movb old_vid_mode, al
movb cur_vid_mode, al
現在のビデオモードを調べて保存。
! Give C code access to the code segment, data segment and the size of this
! process.
xor ax, ax
mov dx, cs
call seg2abs
mov _caddr+0, ax
mov _caddr+2, dx
xor ax, ax
mov dx, ds
call seg2abs
mov _daddr+0, ax
mov _daddr+2, dx
push ds
mov ax, #LOADSEG
mov ds, ax ! Back to the header once more
mov ax, a_total+0
mov dx, a_total+2 ! dx:ax = data + bss + heap + stack
add ax, a_text+0
adc dx, a_text+2 ! dx:ax = text + data + bss + heap + stack
pop ds
mov _runsize+0, ax
mov _runsize+2, dx ! 32 bit size of this process
C言語のコードから、コードセグメント開始アドレス(_caddr)、データセグメント開始アドレス(_daddr)、プロセスのサイズ(_runsize)をアクセスできるようにする。
boot.hにて、caddr、daddr、runsizeは定義されている。
! Determine available memory as a list of (base,size) pairs as follows:
! mem[0] = low memory, mem[1] = memory between 1M and 16M, mem[2] = memory
! above 16M. Last two coalesced into mem[1] if adjacent.
mov di, #_mem ! di = memory list
int 0x12 ! Returns low memory size (in K) in ax
mul c1024
mov 4(di), ax ! mem[0].size = low memory size in bytes
mov 6(di), dx
call _getprocessor
cmp ax, #286 ! Only 286s and above have extended memory
jb no_ext
cmp ax, #486 ! Assume 486s were the first to have >64M
jb small_ext ! (It helps to be paranoid when using the BIOS)
big_ext:
mov ax, #0xE801 ! Code for get memory size for >64M
int 0x15 ! ax = mem at 1M per 1K, bx = mem at 16M per 64K
jnc got_ext
! 略
got_ext:
mov cx, ax ! cx = copy of ext mem at 1M
mov 10(di), #0x0010 ! mem[1].base = 0x00100000 (1M)
mul c1024
mov 12(di), ax ! mem[1].size = "ext mem at 1M" * 1024
mov 14(di), dx
test bx, bx
jz no_ext ! No more ext mem above 16M?
cmp cx, #15*1024 ! Chunks adjacent? (precisely 15M at 1M?)
je adj_ext
mov 18(di), #0x0100 ! mem[2].base = 0x01000000 (16M)
mov 22(di), bx ! mem[2].size = "ext mem at 16M" * 64K
jmp no_ext
adj_ext:
add 14(di), bx ! Add ext mem above 16M to mem below 16M
no_ext:
boot.hのmemにメモリの情報をセットする。
int 0x12は、axレジスタにメモリサイズをKiB単位でセットする。
int 0x15は、axレジスタに1MiBのアドレスからのメモリサイズをKiB単位で、bxレジスタに16MiBのアドレスからのメモリサイズを64KiB単位でセットする。
c1024
は1024の即値。
- mem[0]は、0x0000から開始されるメモリの開始アドレスとサイズ
- mem[1]は、0x0010_0000(1MiB)から開始されるメモリのアドレスとサイズ
- mem[2]は、0x0100_0000(16MiB)から開始されるメモリのアドレスと、64KiBのチャンク数
! Time to switch to a higher level language (not much higher)
call _boot
C言語で書かれたboot(boot.cにある)を呼び出す。