FreeBSD完整的内核代码在FreeBSD的“/sys”目录下。其中,FreeBSD 的 Boot Manager代码是 sys\boot\i386\boot0\boot0.s,它是FreeBSD自带的Boot Manager,其功能虽然不如Linux的lilo和Grub功能强大,但它只需按一个键就可以引导,界面直观。小小的512字节,可以给你一个简单明了的启动选择界面,还能记住你上次的选择。以下,就对此代码进行详细分析:
当我们打开计算机电源时,计算机叽叽嘎嘎进行设备和内存检测过后就读取硬盘或者软盘的引导扇区,这个扇区只有512字节,显然这512字节不能够有多大作用,这512字节的代码被BIOS放在地址从0x0000:0x7c00开始处。然后直接跳转到0x0000:0x7c00处去执行。以上工作是BIOS 干的,你什么也不用作。操作系统需要通过这个引导扇区代码再装载操作系统的其他部分。 在还没有跳转到这段代码之前,也就是BIOS把磁盘的引导扇区读入到内存之后,其DL和ES、SI寄存器的内容如下: DL:表示启动设备,例如,如果计算机是从软盘启动的则DL=0,若是从IDE的C、D盘(严格来说是物理磁盘一和物理磁盘二,而不是逻辑磁盘分区)启动的则DL分别为0x80和0x81。如果是从硬盘启动的话,ES:SI是指向BIOS中的硬盘分区表存放的地址。
好了,我们现在已经知道,计算机的BIOS已经把引导扇区的512字节的内容读入到了0:0x7c00处,然后就跳转到0:0x7C00处去执行,也就是执行引导扇区代码,引导扇区代码boot0执行代码dump如下(它很有用,以后我们还不时回头来看):

上图中的4x16个字节是保留的4个分区信息
下面,我们对FreeBSD启动扇区代码boot0.s进行逐步分析。boot0.s代码如下:
## Copyright (c) 1998 Robert Nordier# All rights reserved.## Redistribution and use in source and binary forms are freely# permitted provided that the above copyright notice and this# paragraph and the following disclaimer are duplicated in all# such forms.## This software is provided "AS IS" and without any express or# implied warranties, including, without limitation, the implied# warranties of merchantability and fitness for a particular# purpose.#以上的Coyright就不用翻译了。# $FreeBSD: src/sys/boot/i386/boot0/boot0.s,v 1.27 2003/11/20 20:28:18 jhb Exp $以上供版本管理软件使用 # A 512-byte boot manager. .set NHRDRV,0x475 # Number of hard drives .set ORIGIN,0x600 # Execution address .set FAKE,0x800 # Partition entry .set LOAD,0x7c00 # Load address .set PRT_OFF,0x1be # Partition table .set TBL0SZ,0x3 # Table 0 size .set TBL1SZ,0xb # Table 1 size .set MAGIC,0xaa55 # Magic: bootable .set B0MAGIC,0xbb66 # Identification .set KEY_ENTER,0x1c # Enter key scan code .set KEY_F1,0x3b # F1 key scan code .set KEY_1,0x02 # #1 key scan code## Addresses in the sector of embedded data values.# Accessed with negative offsets from the end of the relocated sector (%ebp).# .set _NXTDRV,-0x48 # Next drive .set _OPT,-0x47 # Default option .set _SETDRV,-0x46 # Drive to force .set _FLAGS,-0x45 # Flags .set _TICKS,-0x44 # Timeout ticks .set _FAKE,0x0 # Fake partition entry .set _MNUOPT,0xc # Menu options 以上是定义相关的参数值,例如“.set NHRDRV,0x475”类似于C语言中的“#define NHRDRV 0x475” .globl start # Entry point .code16 # This runs in real mode## Initialise segments and registers to known values.# segments start at 0.# The stack is immediately below the address we were loaded to.#start: cld # String ops inc xorw %ax,%ax # Zero movw %ax,%es # Address movw %ax,%ds # data movw %ax,%ss # Set up movw $LOAD,%sp # stack 以上代码:1)首先使用“cld”指令清除方向标志,使得以下的进行“rep”操作时SI和DI的值递增。2)使ax清零,并使除代码段cs外的另外两个数据段寄存器es、ds和堆栈段ss清零。当然,此时cs 由于reset或初始上电已经为零了。3)BIOS已经把引导扇区的512字节的内容读入到了0:0x7c00处,movw $LOAD,%sp 使得堆栈指针指向扇区 代码(或曰本段代码 0:0x7c00)的顶部。虽然堆栈向下生长可能会影响代码的内容,但下面我 们马上就把位于0:7c00处代码移到其他地方去执行。 ## Copy this code to the address it was linked for# movw %sp,%si # Source movw $start,%di # Destination movw $0x100,%cx # Word count rep # Relocate movsw # code把位于0:7c00处的代码搬移到0:0x600处。注意,此时由于代码连接的重定向,$start=0x600。## Set address for variable space beyond code, and clear it.# Notice that this is also used to point to the values embedded in the block,# by using negative offsets. movw %di,%bp # Address variables movb $0x8,%cl # Words to clear rep # Zero stosw # them通过以上一段代码的执行,本代码已被搬移到0:0x600处,此时si=di=0x600+0x100,以上代码把di的值保存到bp,bp此时指向本程序搬移后的未用的空间的首部,且把此bp所指的16字节空间清零。以上过程如下图所示: ┏>0:0x600 ┏━━━━━┓ ┃ ┃ ┃ ┃ ┃ 搬 ┃ ┃ ┃ 移 ┃ ┃ ┃ 之 ┃ ┃ ┃ 后 ┃ ┃ ┃ 的 ┃ ┃ ┃ 代 ┃ ┃ ┃ 码 ┃ ┃ ┃ ┃ ┃ 0:0x7ff ┣━━━━━┫ ┃ ┃ 0 ┃<-bp指向这里(0:0x800),以此开始的16字节被清零。 ┃ ┣━━━━━┫以下所称的fake partition entry就是指这里。 ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┗━━━━━┛0:0x7c00 ┏━━━━━┓ ┛ ┃ ┃ ┃ 搬 ┃ ┃ 移 ┃ ┃ 之 ┃ ┃ 前 ┃ ┃ 的 ┃ ┃ 代 ┃ ┃ 码 ┃ ┃ ┃0:0x7dff ┗━━━━━┛ 图(二)## Relocate to the new copy of the code.# incb -0xe(%di) # Sector number jmp main-LOAD+ORIGIN # To relocated code把以上清零的16字节的第二个字节置为1,表示我们已经读取了一个分区。然后跳转到搬移之后的新代码的main处执行。## Check what flags were loaded with us, specifically, Use a predefined Drive.# If what the bios gives us is bad, use the '0' in the block instead, as well.#main: testb $0x20,_FLAGS(%bp) # Set number drive? jnz main.1 # Yes testb %dl,%dl # Drive number valid? js main.2 # Possibly (0x80 set)main.1: movb _SETDRV(%bp),%dl # Drive number to use上面说过,BIOS把磁盘的引导扇区读入到内存之后,其dl的内容表示启动设备,但我们安装好FreeBSD之后,我们可以改变此引导扇区的内容,其中的一个改变就是可以使我们可以“手动指定”我们实际安装FreeBSD的分区,如果我们希望指定FreeBSD所在的boot分区,那么我们在bp-0x45处的位置(即_FLAGS(%bp)处)的bit 0x20置1,那么上面的“movb _SETDRV(%bp),%dl”一句中movb_SETDRV(%bp),%dl(即bp-0x46)即指向我们“手动指定”FreeBSD所在分区代码,例如,IDE的C、D盘(严格来说是第一个物理磁盘的第一个和第二个分区)的代码分别为 0x80和0x81。如果没有“手动指定”启动分区,那么,我们缺省使用机器当前启动的分区,上面说过,机器当前启动的分区代码放在dl中。因为FreeBSD Boot Manager 不可能安装到软盘(如果从软盘启动则dl为0),所以,使用testb %dl,%dl来判断驱动器代码是否合法(volid)。有关_FLAGS(%bp)中其他bit位表示的意义,在随后的代码分析中慢慢给你道来。## Whatever we decided to use, now store it into the fake# partition entry that lives in the data space above us.#main.2: movb %dl,_FAKE(%bp) # Save drive number callw putn # To new line pushw %dx # Save drive number以上第一句把FreeBSD启动分区的代码保存到_FAKE(%bp)(bp-0)处,也就是说,上图(二)的bp处保存的是FreeBSD启动分区的代码(_FAKE=0)。“callw putn”一句在屏幕上打印“回车”和“换行”,“pushw %dx”一句把启动分区的值压入堆栈。## Start out with a pointer to the 4th byte of the first table entry# so that after 4 iterations it's beyond the end of the sector.# and beyond a 256 byte boundary and has overflowed 8 bits (see next comment).# (remember that the table starts 2 bytes earlier than you would expect# as the bootable flag is after it in the block)# movw $(partbl+0x4),%bx # Partition table (+4) xorw %dx,%dx # Item number以上代码首先把%bx指向分区表partbl的的第四个字节,这里存放的是分区类型,如82表示Linux Native分区83表示Linux Swap 分区,有关分区表的细节请详见本文的尾部。然后dx清零,此后,dx将作为遍历磁盘分区的列举代号使用。启动分区代码dl的原来的值在上面已经压入了堆栈保存。## Loop around on the partition table, printing values until we# pass a 256 byte boundary. The end of loop test is at main.5.#main.3: movb %ch,-0x4(%bx) # Zero active flag (ch == 0) btw %dx,_FLAGS(%bp) # Entry enabled? jnc main.5 # No上面首先使得第一个分区的活动标志为0,标志它不为活动标志,因为ch的值为0。至于第二句“btw %dx,_FLAGS(%bp)”中的_FLAGS(%bp)是上面我们说到的“手动指定我们实际安装FreeBSD的分区代码”。其中的bit 0x20我们在上面已经提到过。_FLAGS(%bp)中的其他位表示是否我们需要检查相应的磁盘分区。缺省情况下,我们需要检查所有的磁盘分区。检查磁盘分区看是否有可以启动的磁盘分区,例如,可能磁盘上的某个分区为WindowsXP或者是Linux等。如果我们没有改变在磁盘上该处的值,则相应的bit位的值为0,表示所有的分区都要检查(因为此时_FLAGS(%bp)中的值为0),否则,只针对FLAGS(%bp)中相应的bit位未被设置为1的分区进行检查。大家知道,FreeBSD Manager启动时可能出现以下的提示:F1 FreeBSDF2 ??F3 BSDF4 ??Default F1其中,上面的提示中出现了令人讨厌的“??”,为了避免出现“??”的提示,我们可以设置相应的第一分区和第四分区不检查,就需要正确设置_FLAGS(%bp)中的bit位。设置好后,屏幕可能出现以下的提示:F1 FreeBSDF2 BSDDefault F1## If any of the entries in the table are# the same as the 'type' in the slice table entry,# then this is an empty or non bootable partition. Skip it.# movb (%bx),%al # Load type movw $tables,%di # Lookup tables movb $TBL0SZ,%cl # Number of entries repne # Exclude scasb # partition? je main.5 # Yes我们从上面已经知道起始(%bx)指向的是MBR中分区信息1(16字节)的位置(见图(三)),以上代码在“忽略的分区类型$tables”中查找看是否本分区是不可启动的或者不合法的分区。不可启动的或者不合法的分区类型有3($TBL0SZ=3)个,它们是“0x0, 0x5, 0xf”,见下面的$tables处。如果是不可启动的或者不合法的分区类型则跳转到main.5,进行下一轮循环。## Now scan the table of known types# movb $TBL1SZ,%cl # Number of entries repne # Known scasb # type? jne main.4 # No## If it matches get the matching element in the# next array. if it doesn't, we are already# pointing at its first element which points to a "?".# addw $TBL1SZ,%di # Adjustmain.4: movb (%di),%cl # Partition addw %cx,%di # description callw putx # Display it上面检查看所检查的分区类型是否为我们知道的分区类型,知道的分区类型有11($TBL1SZ=0xb)个,它们是:“0x1, 0x4, 0x6, 0xb, 0xc, 0xe, 0x83,0x9f, 0xa5, 0xa6, 0xa9”,见下面的$tables处。如果不是以上的类型,则跳转到main.4。那么,(%di)所指的字串是“??”,如果分区类型是“0x1, 0x4, 0x6, 0xb, 0xc, 0xe, 0x83,0x9f, 0xa5, 0xa6, 0xa9”之一,则(%di)所指的字串是“DOS”、“Linux”、“FreeBSD”或“BSD”等。见下面的“os_misc”、“os_dos”、“os_linux”、“os_freebsd”、“os_bsd”等标记。callw putx调用putx函数,在屏幕上打印:“Fx XXX”。其中XXX为DOS”、“Linux”、“FreeBSD”或“BSD”等。main.5: incw %dx # Next item addb $0x10,%bl # Next entry jnc main.3 # Till done遍历磁盘分区的举代号dx加1,重复下一轮循环查找。bl加上0x10(0x10=16)表示寻址到下一个分区信息(加16字节)入口。循环直到255字节边界。## Passed a 256 byte boundary..# table is finished.# Add one to the drive number and check it is valid,# popw %ax # Drive number subb $0x80-0x1,%al # Does next cmpb NHRDRV,%al # drive exist? (from BIOS?) jb main.6 # Yes“popw %ax”把上面压入堆栈的bx(当前的启动扇区)值弹出到ax中。例如,如果计算机是从软盘启动的则dl=0,若是从IDE的C、D盘启动的则dl分别为 0x80和0x81。然而,FreeBSD的Boot Manerger不能够安装到软盘上,所以,dl只能为0x80、0x81,0x82...等。在计算机的BIOS地址0:0x475处存放的是计算机的硬盘的数目,“subb $0x80-0x1,%al”一句等于“sub$0x79,%al”,例如,即当前驱动器如果是C盘,则al的值是ox80-0x79=1,然后再与计算机的硬盘的数目比较,如果当前所在硬盘不是最后一个硬盘,则直接跳转到main.6。如果当前所在硬盘是最后一个硬盘,则继续执行。# If not then if there is only one drive,# Don't display drive as an option.# decw %ax # Already drive 0? jz main.7 # Yes如果只有一个硬盘,则直接跳转到main.7,这样,本计算机只有一个硬盘,所以不用显示其他磁盘相关的提示。# If it was illegal or we cycled through them,# then go back to drive 0.# xorb %al,%al # Drive 0下面的内容表示多于一个磁盘的情况。此时“al”清0,与磁盘列举相关。## Whatever drive we selected, make it an ascii digit and save it back# to the "next drive" location in the loaded block in case we# want to save it for next time.# This also is part of the printed drive string so add 0x80 to indicate# end of string.#main.6: addb