Вам наверное интересно, почему за 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 переключает процессор в защищённый режим и подготавливает простое окружение перед вызовом клиента. Это включает в себя:
виртуальный режим v86. Означает, что BTX является v86 монитором. Инструкции реального режима, такие как pushf, popf, cli, sti доступны для клиента.
Устанавливается Таблица Дескрипторов Прерываний (Interrupt Descriptor Table, IDT). Таким образом, все аппаратные прерывания перенаправляются к стандартным процедурам BIOS, а также устанавливается прерывание 0x30 для того, чтобы быть шлюзом к системным вызовам.
Определены два системных вызова: exec
и exit
:
sys/boot/i386/btx/lib/btxsys.s: .set INT_SYS,0x30 # Interrupt number # # System call: exit # __exit: xorl %eax,%eax # BTX system int $INT_SYS # call 0x0 # # System call: exec # __exec: movl $0x1,%eax # BTX system int $INT_SYS # call 0x1
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));
Пред. | Начало | След. |
Этап boot0 | Уровень выше | Стадия загрузчика |
Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.