1.5. Этап boot2

Вам наверное интересно, почему за boot0 следует boot2, а не boot1. На самом деле, в каталоге /boot также есть и файл boot1 размером 512 байт. Он используется для загрузки с флоппи-диска и выполняет ту же работу, что и boot0 при загрузке с жёсткого диска: он обнаруживает boot2 и передаёт ему управление.

Вы могли заметить, что также существует и файл /boot/mbr. Это упрощённая версия boot0. Код mbr не предоставляет меню для пользователя: он просто слепо загружает раздел, помеченный, как активный.

Исходные тексты boot2 находятся в каталоге sys/boot/i386/boot2/, а сам исполняемый файл в /boot. Файлы boot0 и boot2 из /boot не используются в процессе загрузки, однако используются такими утилитами, как boot0cfg. Действительное местоположение boot0 - в MBR, а boot2 - в начале загрузочного слайса FreeBSD. Эти места не находятся под контролем файловой системы, поэтому являются невидимыми для таких команд, как ls.

Основная задача boot2 состоит в загрузке файла /boot/loader, который является третьей стадией процесса загрузки. Код из boot2 не может использовать какие-либо сервисы вроде open() и read(), потому как ядро ещё не загружено. Он должен просмотреть жёсткий диск, зная о структуре файловой системы, найти файл /boot/loader, считать его в память с помощью средств BIOS и затем передать управление в точку входа загрузчика.

Кроме того, boot2 с помощью командной строки позволяет пользователю загрузить загрузчик с другого диска, юнита, слайса и раздела.

Двоичный файл 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 (расширитель загрузки) является фрагментом когда, предоставляющим окружение защищённого режима для программы, называемой клиентом, c которой он скомпонован. Итак, boot2 - это BTX клиент, т.е. он использует сервисы предоставляемые BTX.

Утилита btxld является компоновщиком. Она связывает два двоичных файла. Разница между btxld(8) и ld(1) заключается в том, что ld обычно компонует объектные файлы в разделяемый объект или в исполняемый файл, в то время как btxld связывает объектный файл с BTX, создавая двоичный файл, пригодный для размещения в начале раздела для загрузки системы.

boot0 передаёт управление в точку входа BTX. После этого BTX переключает процессор в защищённый режим и подготавливает простое окружение перед вызовом клиента. Это включает в себя:

BTX создаёт Глобальную Таблицу Дескрипторов (Global Descriptors Table, GDT):

sys/boot/i386/btx/btx/btx.s:
gdt:        .word 0x0,0x0,0x0,0x0       # Null entry
        .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), а селектор (SEL_UCODE) указывает на сегмент кода клиента. Дескриптор SEL_UCODE имеет Уровень Привилегий Дескриптора (Descriptor Privilege Level, DPL) равный 3, который является наименьшим уровнем привилегий. Однако обработчик инструкции INT 0x30 располагается в сегменте, на который указывает селектор SEL_SCODE (код супервизора), как показано в коде создания IDT:

       mov $SEL_SCODE,%dh      # Segment selector
init.2:     shr %bx             # Handle this int?
        jnc init.3          # No
        mov %ax,(%di)           # Set handler offset
        mov %dh,0x2(%di)        #  and selector
        mov %dl,0x5(%di)        # Set P:DPL:type
        add $0x4,%ax            # Next handler

Таким образом, когда клиент вызывает __exec() код будет исполнен с наивысшими привилегиями. Это позволяет ядру позже, при необходимости, изменять структуры данных защищённого режима, таких как таблицы страниц, GDT, IDT и т.д.

boot2 определяет важную структуру struct bootinfo. Эта структура инициализируется кодом boot2 и передаётся загрузчику, а затем дальше ядру. Некоторые поля данной структуры устанавливаются boot2, остальные загрузчиком. Кроме всей прочей информации, эта структура содержит имя файла ядра, BIOS геометрию жёсткого диска, BIOS номер привода загрузочного устройства, доступную физическую память, указатель envp и т.д. Вот её определение:

/usr/include/machine/bootinfo.h
struct bootinfo {
    u_int32_t   bi_version;
    u_int32_t   bi_kernelname;      /* represents a char * */
    u_int32_t   bi_nfs_diskless;    /* struct nfs_diskless * */
                /* End of fields that are always present. */
#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;        /* bootdev BIOS unit number */
    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 * */
                /* Items below only from advanced bootloader */
    u_int32_t   bi_kernend;     /* end of kernel space */
    u_int32_t   bi_envp;        /* environment */
    u_int32_t   bi_modulep;     /* preloaded modules */
};

boot2 в бесконечном цикле ожидает пользовательского ввода, после чего вызывает load(). Если же пользователь ничего не нажал, цикл прерывается по тайм-ауту и load() загрузит файл по умолчанию (/boot/loader). Функции ino_t lookup(char *filename) и int xfsread(ino_t inode, void *buf, size_t nbyte) используются для считывания содержимого файла в память. /boot/loader является двоичным файлом формата ELF, однако его ELF заголовок дополнительно содержит структуру struct exec формата a.out. load() просматривает 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, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.