也許你想知道,為什麼boot2是在boot0之後,而不是在boot1之後。事實上,也有一個512字節的文件boot1存放在目錄/boot裡,那是用來從一張軟盤引導系統的。從軟盤引導時,boot1起著boot0對硬盤引導相同的作用:它找到boot2並運行之。
你可能已經看到有一文件/boot/mbr。這是boot0的簡化版本。mbr中的代碼不會顯示菜單讓用戶選擇,而只是簡單的引導被標志的分區。
實現boot2的代碼存放在目錄sys/boot/i386/boot2/裡,對應的可執行文件在/boot裡。在/boot裡的文件boot0和boot2不會在引導過程中使用,只有boot0cfg這樣的工具才會使用它們。boot0的內容應在MBR中才能生效。boot2位於可引導的FreeBSD分區的開始。這些位置不受文件系統控制,所以它們不可用ls之類的命令查看。
boot2的主要任務是裝載文件/boot/loader,那是引導過程的第三階段。在boot2中的代碼不能使用諸如open()和read()之類的例程函數,因為內核還沒有被加載。而應當掃描硬盤,讀取文件系統結構,找到文件/boot/loader,用BIOS的功能將它讀入內存,然後從其入口點開始執行之。
除此之外,boot2還可提示用戶進行選擇,loader可以從其它磁盤、系統單元、分區裝載。
boot2 的二進制代碼用特殊的方式產生:
sys/boot/i386/boot2/Makefile
boot2: boot2.ldr boot2.bin ${BTX}/btx/btx
btxld -v -E ${ORG2} -f bin -b ${BTX}/btx/btx -l boot2.ldr \
-o boot2.ld -P 1 boot2.bin
這個Makefile片斷表明btxld(8)被用來鏈接二進制代碼。BTX表示引導擴展器(BooT eXtender)是給程序(稱為客戶(client))提供保護模式環境、並與客戶程序相鏈接的一段代碼。所以boot2是一個BTX客戶,使用BTX提供的服務。
工具btxld是鏈接器,它將兩個二進制代碼鏈接在一起。btxld(8)和ld(1)的區別是ld通常將兩個目標文件鏈接成一個動態鏈接庫或可執行文件,而btxld則將一個目標文件與BTX鏈接起來,產生適合於放在分區首部的二進制代碼,以實現系統引導。
boot0執行跳轉至BTX的入口點。然後,BTX將處理器切換至保護模式,並准備一個簡單的環境,然後調用客戶。這個環境包括:
虛擬8086模式。這意味著BTX是虛擬8086的監視程序。實模式指令,如pushf, popf, cli, sti, if,均可被客戶調用。
建立中斷描述符表(Interrupt Descriptor Table, IDT),使得所有的硬件中斷可被缺省的BIOS程序處理。建立中斷0x30,這是系統調用關口。
兩個系統調用exec和 exit的定義如下:
sys/boot/i386/btx/lib/btxsys.s:
#
# System call: exit
#
__exit: xorl %eax,%eax # BTX系統調用0x0
int $INT_SYS #
#
# System call: exec
#
__exec: movl $0x1,%eax # BTX系統調用0x1
int $INT_SYS #
BTX建立全局描述符表(Global Descriptor Table, GDT):
sys/boot/i386/btx/btx/btx.s:
gdt: .word 0x0,0x0,0x0,0x0 # 以空為入口
.word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE
.word 0xffff,0x0,0x9200,0xcf # SEL_SDATA
.word 0xffff,0x0,0x9a00,0x0 # SEL_RCODE
.word 0xffff,0x0,0x9200,0x0 # SEL_RDATA
.word 0xffff,MEM_USR,0xfa00,0xcf# SEL_UCODE
.word 0xffff,MEM_USR,0xf200,0xcf# SEL_UDATA
.word _TSSLM,MEM_TSS,0x8900,0x0 # SEL_TSS
客戶的代碼和數據始於地址MEM_USR(0xa000),選擇符(selector) SEL_UCODE指向客戶的數據段。選擇符 SEL_UCODE 擁有第3級描述符權限(Descriptor Privilege Level, DPL),這是最低級權限。但是INT 0x30 指令的處理程序存儲於另一個段裡,這個段的選擇符SEL_SCODE (supervisor code)由有著管理級權限。正如代碼建立IDT(中斷描述符表)時進行的操作那樣:
mov $SEL_SCODE,%dh # 段選擇符
init.2: shr %bx # 是否處理這個中斷?
jnc init.3 # 否
mov %ax,(%di) # 設置處理程序偏移量
mov %dh,0x2(%di) # 設置處理程序選擇符
mov %dl,0x5(%di) # 設置 P:DPL:type
add $0x4,%ax # 下一個中斷處理程序
所以,當客戶調用 __exec()時,代碼將被以最高權限執行。這使得內核可以修改保護模式數據結構,如分頁表(page tables)、全局描述符表(GDT)、中斷描述符表(IDT)等。
boot2 定義了一個重要的數據結構:struct bootinfo。這個結構由 boot2 初始化,然後被轉送到loader,之後又被轉入內核。這個結構的部分項目由boot2設定,其余的由loader設定。這個結構中的信息包括內核文件名、BIOS提供的硬盤柱面/磁頭/扇區數目信息、BIOS提供的引導設備的驅動器編號,可用的物理內存大小,envp指針(環境指針)等。定義如下:
/usr/include/machine/bootinfo.h
struct bootinfo {
u_int32_t bi_version;
u_int32_t bi_kernelname; /* 用一個字節表示 * */
u_int32_t bi_nfs_diskless; /* struct nfs_diskless * */
/* 以上為常備項 */
#define bi_endcommon bi_n_bios_used
u_int32_t bi_n_bios_used;
u_int32_t bi_bios_geom[N_BIOS_GEOM];
u_int32_t bi_size;
u_int8_t bi_memsizes_valid;
u_int8_t bi_bios_dev; /* 引導設備的BIOS單元編號 */
u_int8_t bi_pad[2];
u_int32_t bi_basemem;
u_int32_t bi_extmem;
u_int32_t bi_symtab; /* struct symtab * */
u_int32_t bi_esymtab; /* struct symtab * */
/* 以下項目僅高級bootloader提供 */
u_int32_t bi_kernend; /* 內核空間末端 */
u_int32_t bi_envp; /* 環境 */
u_int32_t bi_modulep; /* 預裝載的模塊 */
};
boot2 進入一個循環等待用戶輸入,然後調用load()。如果用戶不做任何輸入,循環將在一段時間後結束,load() 將會裝載缺省文件(/boot/loader)。函數 ino_t lookup(char *filename)和int xfsread(ino_t inode, void *buf, size_t nbyte) 用來將文件內容讀入內存。/boot/loader是一個ELF格式二進制文件,不過它的頭部被換成了a.out格式中的struct exec結構。load()掃描loader的ELF頭部,裝載/boot/loader至內存,然後跳轉至入口執行之:
sys/boot/i386/boot2/boot2.c:
__exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part),
0, 0, 0, VTOP(&bootinfo));