FreeBSD 系统结构手册
上一页 | 第1章 　引导过程与内核初始化 | 下一页

# 1.5 `boot2`阶段

也许你想知道，为什么`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:
      		.set INT_SYS,0x30		# 中断号
      #
      # 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));

上一页 | 起始页 | 下一页
`boot0`阶段 | 上一级 | loader阶段

本文档和其它文档可从这里下载：ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题，请先阅读文档，如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.