Friday, December 19, 2025

Процесс загрузки FreeBSD

 Оригинал:  https://klarasystems.com/articles/the-freebsd-boot-process/

 В этой статье изучается процесс загрузки FreeBSD. Он надёжен и хорошо продуман, но слегка различается в зависимости от архитектуры системы, файловой системы (UFS или ZFS), схемы разметки диска (GPT или MBR) и интерфейса прошивки, управляющего загрузкой (UEFI или BIOS/CSM).

    ● BIOS


BIOS также известен как режим CSM или legacy. Это старый механизм загрузки 16-битных систем, который до сих пор поддерживается на многих компьютерах. Он подходит для загрузки FreeBSD, хотя будет чуть медленней UEFI и не поддерживает загрузку с устройств NVMe. Режим BIOS поддерживает разметки MBR и более современную GPT, а также слайсы Freebsd.

    ● UEFI


Все современные системы архитектуры amd64 поддерживают режим загрузки UEFI; также многие из них поддерживают устаревший режим BIOS ─ некоторые даже предлагают установить один из них основным, а второй в качестве запасного, если первый метод не сработает.

Также режим UEFI доступен на многих системах с архитектурой arm64 ─ его поддержка требуется для сертификата  ServerReady ARM.

Некоторые системы arm64 и riscv/riscv64 поддерживают загрузку с помощью U-Boot вместо загрузки с помощью UEFI ─ FreeBSD поддерживает оба метода. Системы архитектуры RISC-V также можно загружать с помощью загрузчика OpenSBI, заменившего BBL(Berkley Boot Loader) из проекта FreeBSD. В данный момент команда FreeBSD работает над реализацией поддержки UEFI в системах RISC-V.

    ● Процесс загрузки Freebsd


Этот процесс бывает довольно сложным и включает в себя множество частей, которые сложно отличить друг от друга. Чтобы облегчить понимание, мы разбили статью на два раздела. В первом рассматривается часть загрузки, предшествующая запуску программы loader(8), которая отображает знаменитое "меню с Бисти"; второй посвящен тому, что происходит после того, как loader загрузит выбранное ядро системы.

        ▪ Стадии загрузки до активации загрузчика Freebsd ─ программы loader(8)


К этапу загрузки ядра Freebsd и передачи управления загрузкой процессу PID 1 ─ то есть системе init(8) ─ можно прийти разными путями в зависимости от архитектуры системы, метода загрузки, схемы разметки и файловой системы.

Прежде чем вникать в детали каждого режима загрузки, давайте схематично разобьем их на стадии:


BIOS/ MBR/UFS

  +-> MBR from 'Boot Device' BIOS disk          | MBR
    +-> boot0                                   | STAGE 0
      +-> boot1                                 | STAGE 1
        +-> boot2                               | STAGE 2
          +-> loader                            | STAGE 3
            +-> kernel                          | KERNEL
              +-> init                              | INIT

BIOS/ MBR/ZFS
  +-> MBR from 'Boot Device' BIOS disk          | MBR
    +-> boot0                                   | STAGE 0
      +-> boot1                                 | STAGE 1
        +-> zfsboot                            | STAGE 2
          +-> zfsloader                       | STAGE 3
            +-> kernel                           | KERNEL
              +-> init                               | INIT

BIOS/ GPT/UFS
  +-> GPT from 'Boot Device' BIOS disk          | GPT
    +-> pmbr                                     | STAGE 0
      +-> gptboot                              | STAGE 1 + STAGE 2
        +-> loader                               | STAGE 3
          +-> kernel                             | KERNEL
            +-> init                                 | INIT

BIOS/ GPT/ZFS
  +-> GPT from 'Boot Device' BIOS disk                 | GPT
    +-> pmbr                                                            | STAGE 0
      +-> gptzfsboot                            | STAGE 1 + STAGE 2
        +-> zfsloader (analogous to loader)     | STAGE 3
          +-> kernel                                 | KERNEL
            +-> init                                     | INIT
 
UEFI/GPT/MBR/UFS/ZFS
  +-> GPT/MBR from 'Boot Device' BIOS disk      | GPT/MBR
    +-> UEFI                                       | STAGE 0
      +-> boot1.efi (/efi/boot/boot${ARCH}.efi) | STAGE 1 + STAGE 2
        +-> loader.efi                          | STAGE 3
          +-> kernel                              | KERNEL
            +-> init                                  | INIT

UEFI/GPT/MBR/UFS/ZFS (13.0 and later)
  +-> GPT/MBR from 'Boot Device' BIOS disk      | GPT/MBR
    +-> UEFI                                                            | STAGE 0
      +-> loader.efi (/efi/FreeBSD/loader.efi)  | STAGE 1-3
        +-> kernel                                                    | KERNEL
          +-> init                                                        | INIT


Переменная ARCH, в зависимости от вашей системы, может принимать одно из значений:  x86 - x64 - arm - aa64.

Теперь, когда у вас есть общее представление о процессе загрузки, давайте рассмотрим каждый режим подробно.

    ▪ BIOS/Legacy, MBR и UFS


Использование таблицы разделов MBR и файловой системы UFS ─ самый старый метод загрузки FreeBSD. Допустим, ваше загрузочное устройство ─ диск с интерфейсом sata: /dev/ada0. На нём есть один основной раздел MBR (в терминах FreeBSD он называется слайс), который система видит как /dev/ada0s1. На этот слайс надо будет установить флаг активного (загрузочного) раздела. Внутри слайса вы увидите разделы (партиции) BSD, созданные утилитой bsdlabel(8). Корневой раздел, с которого будет производиться чтение в процессе загрузки, обозначен как /dev/ada0s1a.

    Stage 0: 

Из-за ограничений на размер файла загрузчика, накладываемых BIOS+MBR, загрузчик делится на стадии. BIOS загружает в память для исполнения первый сектор диска. Это называется нулевой стадией ─ stage 0. Нулевая стадия использует файл /boot/boot0, в котором записано 446 байт кода на ассемблере, а оставшиеся 66 байт (512-446) первого сектора содержат таблицу разделов (именно поэтому в ней может быть только 4 пункта) и сигнатуру. После загрузки в память программа, которая 446 байт, анализирует таблицу разделов и отображает меню загрузки на основании кода типа раздела, считанного из таблицы. Например:


    F1 FreeBSD
    F2 Win

    Default: F1

Вместо файла /boot/boot0 можно использовать /boot/boot0sio ─ в этом случае меню также будет выведено на последовательную консоль. Но если на вашей машине нет последовательной консоли, она может зависнуть в процессе загрузки, поэтому данный файл не используется по умолчанию. 

    Stage 1:

Следующий этап загрузки ─ считывание и выполнение файла  /boot/boot1 с первого сектора выбранного первичного раздела (слайса) ─ это называется Stage 1. Как правило, на этом этапе выполняются два файла: /boot/boot1 (512 байт) и  /boot/boot2  (7680 байт). Файл  /boot/boot2 занимает первые шестнадцать секторов по 512 байт, которые UFS не использует.

Главная задача boot1 ─ загрузка следующей стадии загрузчика, которая имеет более сложный код, чем первая стадия. Этот код загружает сервер BTX, после чего тот загружает свой клиент boot2, а затем ещё один клиент под названием loader. После этого мы переходим на вторую стадию.

    Stage 2:

boot 2 задаёт и инициализирует важную структуру данных bootinfo и передаёт её загрузчику, а затем ─ ядру FreeBSD, но об этом позже. boot 2 ─ это маленькая программа, которая имеет базовую информацию о файловой системе UFS, достаточную, чтобы найти файлы /boot/loader и /boot/kernel ─ учитывая объём кода (7680 байт), это весьма впечатляет.

    Stage 3:

Запускается loader, который ищет ядро и загружает его. Как уже было сказано, loader также является клиентом BTX. Считываются и загружаются конфигурационные файлы /boot/loader.rc и /boot/loader.conf. Основная задача программы loader - загрузка ядра FreeBSD; loader позволяет выбрать одно из нескольких ядер. После загрузки ядра в память начинается его инициализация.


    ▪ BIOS/Legacy, MBR и ZFS    


Схема загрузки ZFS с раздела MBR слегка отличается. На нулевой стадии (Stage 0) boot0 загружает другой файл boot1. Затем boot1 находит файл zfsboot ─ аналог boot2 ─ размером 64 КiB по смещению 1 Mib на разделе ZFS в предопределенном пустом месте файловой системы. 

Это означает, что на этапах Stage 1 и Stage 2 соответственно код zfsboot распознает файловую систему ZFS и загружает zfsloader. zfsloader выводит интерактивное меню, аналогичное тому, которое выдает loader для системы UFS. Точно так же, как и loader, zfsloader после этого загружает ядро и остальную часть системы FreeBSD.

Этап, на котором zfsloader уже загружен и работает ─ это Stage 3. На этой стадии можно выбрать, какой ZFS Boot Environment загружать.

    ▪ BIOS/Legacy, GPT и UFS


Теперь давайте рассмотрим чуть более современный процесс загрузки с использованием таблицы разделов GPT, но в режиме загрузки BIOS (Legacy, CSM). Хотя диск размечен с использованием таблицы GPT, он все равно содержит защитную загрузочную запись ─ Protective MBR или, сокращенно, pmbr. Она нужна для того, чтобы операционные системы, не поддерживающие схему GPT, не определяли диск как неразмеченный. 

pmbr загружает FreeBSD тем же способом, что и MBR ─ считывается один сектор размером 512 байт и выполняется код из него. Это можно назвать нулевой стадией ─ Stage 0.

Специальный загрузочный раздел GPT (его тип freebsd-boot) содержит код других стадий загрузчика: gptldr ─ аналог boot1 ─ и gptboot вместо boot2. Это будут соответственно Stage 1 и Stage 2.

pmbr находит этот загрузочный раздел и выполняет код, содержащийся в нём. Для файловой системы UFS используется файл gptboot. Как и раньше, первый этап (Stage 1) представляет собой программу на ассемблере размером 512 байт, которая только загружает и выполняет Stage 2. gptboot содержит код, который больше по размеру и позволяет обращаться к UFS, а также (опционально) понимает шифрование GELI, Этот код умеет находить загрузчик на разделе с файловой системой UFS и запускать его. 

На стадии Stage 3 loader загружен и работает, можно выбрать, какое ядро загружать.

    ▪ BIOS/Legacy, GPT и ZFS


Загрузка с GPT и файловой системой ZFS во многом похожа на схему с GPT и UFS2. Сначала pmbr загружает систему FreeBSD, выполняя код из первого сектора размером 512 байт  ─ как и в случае MBR. Этот этап можно назвать Stage 0.

Специальный загрузочный раздел GPT (тип freebsd-boot) содержит код других стадий загрузчика: gptldr ─ аналог boot1  ─ и zfsboot вместо boot2. Это Stage 1 и Stage 2.

pmbr считывает и выполняет код из этого раздела. Для ФС ZFS используется файл gptzfsboot, который содержит информацию о ZFS и ─ опционально ─ о GELI и может расшифровать, загрузить в память и запустить zfsloader из набора данных ZFS, созданного по умолчанию.

Когда zfsloader загружен и работает, начинается Stage 3 и можно выбрать, какую среду загрузки и какое ядро загружать.

    ▪ UEFI, GPT/MBR и UFS/ZFS


А теперь поговорим о "новейшем" способе загрузки FreeBSD ─ с помощью Unified Extensible Firmware Interface, (UEFI). UEFI быстрее, чем метод BIOS, поскольку переходит в 64-разрядный режим, как только это становится возможным. 

Немного предыстории: первой реализацией интерфейса был Intel EFI для архитектуры Itanium (IA64). Затем эту разработку "унифицировали" и перенесли на другие архитектуры ─ отсюда и "Unified" в начале названия. Разумеется, это очень сокращённая версия истории прошивки: кроличья нора значительно глубже, ─  но в данном случае этого достаточно.

FreeBSD прекрасно поддерживает режим загрузки UEFI. Разумеется, как и в случае других способов загрузки, процесс делится на стадии. Эти стадии практически не зависят от таблицы разделов на диске ─ GPT или MBR.

На этапе Stage 0 после включения питания запускается интерфейс прошивки UEFI и ищет загрузчик операционной системы на разделе  EFI File System Partition (ESP), отформатированном в FAT32. UUID системного раздела C12A7328-F81F-11D2-BA4B-00A0C93EC93B.

В зависимости от архитектуры файл загрузчика может называться следующим образом:

 ARCH  WIDE   PATH
   i386  32bit  /efi/boot/bootx86.efi
  amd64  64bit  /efi/boot/bootx64.efi
    arm  32bit  /efi/boot/bootarm.efi
  arm64  64bit  /efi/boot/bootaa64.efi


По умолчанию во FreeBSD файл boot1.efi установлен под именем bootx64.efi. Его исполнение означает, что мы находимся на этапе Stage 1 или Stage 2.

Для определения корневой файловой системы, с которой будет загружаться FreeBSD, файл boot1.efi использует такую последовательность:

    - Поиск загрузочных пулов для ZFS 
    - Поиск загрузочных разделов для UFS 

Раздел распознаётся как загрузочный, если boot1.efi может загрузить с него loader.efi. Если на одном устройстве есть как раздел UFS, так и ZFS, то предпочтение отдаётся ZFS. После того как UEFI исполнит код файла bootx64.efi (который на самом деле boot1.efi), начинается этап Stage 3 и загружается loader.efi, который позволяет выбрать среду загрузки и ядро. 

После того, как среда загрузки и ядро будут выбраны, loader.efi загрузит ядро FreeBSD.



    ● Ядро и многопользовательский режим


Прежде чем загрузить выбранное ядро, loader считывает и загружает параметры из файла /boot/device.hints. После выбора ядра и/или среды загрузки loader запускает ядро, которое начинает проверку наличия и инициализацию устройств.

Затем ядро передаёт управление процессу  /sbin/init (PID 1), который монтирует файловые системы. init(8) также занимается другими вещами ─ такими как виртуальные консоли, найденные в файле ttys(5), ─ и запускает процессы getty(8), которые, в свою очередь, инициализируют команды  login(1).

Закончив с этим, init(8) продолжает процесс загрузки в многопользовательский режим, после чего начинает конфигурацию системных служб FreeBSD ─ rc(8). Параметры системных служб по умолчанию считываются из файла /etc/defaults/rc.conf, а специфичные для системы данные ─ из файла /etc/rc.conf.

Затем монтируются файловые системы из /etc/fstab и выдаются адреса сетевым интерфейсам. И наконец запускаются сервисы и демоны с помощью стартовых скриптов из файлов /etc/rc.d и /usr/local/etc/rc.d. rcorder(8) гарантирует правильный порядок их запуска.


    ● nextboot(8)


Во FreeBSD есть утилита nextboot(8), которая позволяет загрузить определенное ядро, указав флаги к нему, только один раз ─ при следующей загрузке.

Это достигается тем, что loader(8) считывает информацию о ядре из файла /boot/nextboot.conf при его наличии. При перезагрузке машины nextboot.conf автоматически удаляется и система возвращается к предыдущей "постоянной" конфигурации.

Для файловых систем ZFS nextboot хранит метаданные в специально отведенном поле метки пула ZFS, поскольку загрузчик более ранней стадии не может записывать в сам пул. Метаданные nextboot стираются сразу после считывания и поэтому влияют только на один процесс загрузки после их записи.

Чтобы один раз загрузить ядро FreeBSD под названием CUSTOM, введите следующую команду:

    # nextboot -k CUSTOM


Если надо загрузить тестовое ядро TEST в однопользовательском режиме, введите команду:

    # nextboot -o "-s" -k TEST    


Следующая команда удаляет все существующие конфигурации nextboot(8):

    # nextboot -D

    ● Флаги загрузки bootme/bootonce/bootfailed 


Во FreeBSD существуют дополнительные флаги загрузки:  bootme, bootonce, и bootfailed. Флаг bootme указывает, с какого раздела должна производиться загрузка. Флаг bootonce используется вместе с bootme, чтобы загрузиться с указанного раздела один раз и затем вернуться к обычной конфигурации. Это достигается тем, что loader(8) удаляет флаги. Последний флаг ─ bootfailed ─ указывает, что с данного раздела система не смогла загрузиться. Чтобы управлять этими флагами, воспользуйтесь утилитой gpart(8). Например:

    # gpart set -a bootme -i 2 ada0

Эти флаги особенно полезны, если у вас более одного корневого раздела UFS с разными версиями FreeBSD: можно установить флаг bootme, чтобы загружаться с другого раздела.

    ● Примеры управления процессом загрузки с помощью команд


С помощью следующих команд можно изменить или обновить код загрузчика в MBR:
    
    # fdisk -B
    # fdisk -B -b /boot/boot0 ada0
    # boot0cfg -B
    # boot0cfg -s 1 -b /boot/boot0 ada0

Процесс установки zfsboot на слайс MBR:


    # gpart create -s mbr ada0
    # gpart add -t freebsd ada0
    # gpart bootcode -b /boot/boot0 ada0
    # gpart set -a active -i 1 ada0
    # dd if=/dev/zero of=/dev/ada0s1 count=2
    # dd if=/boot/zfsboot of=/dev/ada0s1 count=1
    # dd if=/boot/zfsboot of=/dev/ada0s1 iseek=1 oseek=1024

Процесс установки загрузчика для GPT:

    UFS 

# gpart bootcode -b /boot/pmbr -p /boot/gptboot -i 1 ada0   

     ZFS  

# gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 ada0

Создание раздела UEFI ESP вручную:

    # gpart add -a 4K -t efi -s 200M ada0
    # newfs_msdos /dev/ada0s1
    # mount_msdosfs /dev/ada0s1 /mnt
    # mkdir -p /mnt/efi/boot
    # cp /boot/boot1.efi /mnt/efi/boot/bootx64.efi

    ● Дополнительные источники:

Вот интересные ресурсы FreeBSD, посвященные процессу загрузки, которые также могут вам пригодиться:

   ▪ FreeBSD Architecture Handbook - Chapter 1 - Bootstrapping and Kernel Initialization
   ▪ FreeBSD Handbook - Chapter 13 - FreeBSD Booting Process
   ▪ boot(8)
   ▪ boot.config(5)
   ▪ boot0cfg(8)
   ▪ boot1.efi(8)
   ▪ efibootmgr(8)
   ▪ efivar(8)
   ▪ fdisk(8)
   ▪ gptboot(8)
   ▪ gptzfsboot(8)
   ▪ zfsboot(8)
   ▪ zfsbootcfg(8)
   ▪ loader(8)
   ▪ loader.efi(8)
   ▪ nextboot(8)
   ▪ fastboot(8)