1.5 boot2阶段

  也许你想知道,为什么boot2是在 boot0之后,而不是在boot1之后。事实上, 也有一个512字节的文件boot1存放在目录 /boot里,那是用来从一张软盘引导系统的。 从软盘引导时,boot1起着 boot0对硬盘引导相同的作用:它找到 boot2并运行之。

  你可能已经看到有一文件/boot/mbr。 这是boot0的简化版本。 mbr中的代码不会显示菜单让用户选择, 而只是简单的引导被标志的分区。

  实现boot2的代码存放在目录 sys/boot/i386/boot2/里,对应的可执行文件在 /boot里。在/boot里的文件 boot0boot2不会在引导过程中使用, 只有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将处理器切换至保护模式,并准备一个简单的环境, 然后调用客户。这个环境包括:

  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));

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

如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<[email protected]>.
关于本文档的问题请发信联系 <[email protected]>.