Показаны сообщения с ярлыком gptboot. Показать все сообщения
Показаны сообщения с ярлыком gptboot. Показать все сообщения

четверг, 26 мая 2011 г.

Назначение различных образов загрузочного кода во FreeBSD

Со времени появления поддержки таблиц GPT и файловой системы ZFS во FreeBSD содержимое каталога /boot довольно сильно изменилось. Я имею ввиду, сколько различных образов загрузочного кода теперь там находится.

> ls /boot/*boot* /boot/*mbr*
/boot/boot              /boot/boot1             /boot/gptboot           /boot/pmbr
/boot/boot0             /boot/boot2             /boot/gptzfsboot        /boot/pxeboot
/boot/boot0sio          /boot/cdboot            /boot/mbr               /boot/zfsboot
В этой заметке я попробую восполнить недостаток документации и описания этих файлов, рассказав для чего они необходимы.

Итак, по-порядку. BIOS передаёт управление загрузочному коду, хранящемуся в MBR. Его задача - найти на диске загрузочный код следующей ступени, который для таблицы разделов MBR обычно находится в первом секторе активного раздела. Т.е. он должен прочитать таблицу разделов MBR, найти в ней раздел, помеченный активным и передать управление коду, находящемуся в первом секторе этого раздела. Эти задачи выполняет код, копия которого находится в файле /boot/mbr. Более продвинутая его версия - это менеджер загрузки /boot/boot0. Он имеет простейший функционал взаимодействия с пользователем - меню для выбора раздела или диска, с которого выполнять загрузку.

Далее вступает в работу загрузочный код первой ступени /boot/boot1. Он схож по функционалу с тем, что делает /boot/mbr. Разница в том, что он должен считать таблицу разделов BSD и код второй ступени загрузки /boot/boot2. Обычно они используются вместе в виде образа /boot/boot. Т.е. /boot/boot это склееные вместе /boot/boot1 и /boot/boot2.

Вторая ступень загрузки уже более функциональна и может считывать файлы с UFS. Задача этого кода загрузить и передать управление загрузчику /boot/loader (третьей ступени), либо самому ядру /boot/kernel/kernel.

Загрузочный код /boot/zfsboot выполняет те же функции, что и /boot/boot, но для файловой системы ZFS. Он тоже состоит из двух частей. Первая часть аналогично boot1 необходима для поиска и запуска второй ступени, которая уже может прочитать файлы с ZFS и запустить ядро, либо третью ступень /boot/zfsloader.

Теперь что касается GPT. Для таблицы GPT во FreeBSD пока нет подобного /boot/boot0 менеджера загрузки. Но для систем, которые не используют EFI, загрузка с GPT происходит аналогично тому, как это делается для MBR. Т.е. BIOS передаёт управление загрузочному коду, находящемуся в первом секторе диска. А там должна находиться копия /boot/pmbr. Этот код выполняет поиск раздела с типом "freebsd-boot" по таблице GPT. Если он находит его, то загружает его содержимое в память и передаёт туда управление.

Здесь уже могут быть варианты. Первый - /boot/gptboot. Его задача проверить корректность таблиц и заголовков GPT (посчитать и сверить контрольные суммы), а затем найти в таблице раздел с типом "freebsd-ufs", попытаться загрузить с этого раздела /boot/loader или ядро. Если это не получается, он ищет следующий раздел. Кроме того, не так давно pjd@ добавил поддержу специальных GPT-атрибутов bootme и bootonce, с помощью которых можно устанавливать приоритетность загрузки разделов, на случай, если несколько копий/версий FreeBSD находятся на одном диске.

Второй вариант - /boot/gptzfsboot. Этот код представляет собой комбинацию zfsboot  и gptboot. Он выполняет поиск GPT разделов с типом "freebsd-zfs", на которых он ищет пулы. На самом деле логика его работы немного сложнее, так как пулы могут состоять из нескольких устройств. Но цель его - загрузка /boot/zfsloader или ядра с ZFS пула.

В заключение несколько слов о том, где должен располагаться загрузочный код, для того чтобы он выполнил свои задачи. Думаю, не надо упоминать о том, что находясь в каталоге /boot он выполниться никак не сможет, поэтому эти образы должны быть записаны в определённый области на диске. Для записи загрузочного кода на диск обычно используется команда "gpart bootcode", но некоторые образы требуют специальных "манипуляций".

Например, образы mbr, boot0, pmbr должны быть записаны в первый сектор диска. Образ /boot/boot - в начало MBR раздела с типом "freebsd". Образы gptboot и gptzfsboot должны быть записаны на GPT раздел с типом "freebsd-boot". Специальные манипуляции нужны для загрузочного кода zfsboot. Первые 512 байт его должны быть записаны в начало раздела или диска, на котором создан ZFS пул. Оставшаяся часть должна быть записана со смещением в 512 кбайт от начала диска или раздела. Т.е. для случая, когда пул создан на целом диске ada0, загрузочный код записывается вот так:
# dd if=/boot/zfsboot of=/dev/ada0 count=1
# dd if=/boot/zfsboot of=/dev/ada0 skip=1 seek=1024

пятница, 18 марта 2011 г.

Варианты загрузки FreeBSD: gmirror + GPT + UFS

Продолжу тему вариантов загрузки FreeBSD. Кстати, исправления в zfsboot я закоммитил, как оказалось, ошибка была в другом месте, но всё та же, а в drvread её умело "обошли" ещё до изменений pjd@.
Некоторое время назад я участвовал в обсуждении темы использования разметки GPT и программного зеркала на основе GEOM_MIRROR. Это было в какой-то из рассылок, там я пообещал, что обязательно попробую данную конфигурацию. Проблема была в том, что у людей не получалось организовать GPT поверх gmirror. То ли загрузка не шла, то ли паника была.. Не помню уже.

В общем, настраивал я опять всё в virtualbox'е, всё на той же 8.2-STABLE. Для установки я использовал примерно такие команды (замечу, что я делаю это в тестовых целях, поэтому делается всё довольно просто, не для реального использования):

# gmirror label gm0 ad6 ad8
# gmirror load
# gpart create -s gpt mirror/gm0
# gpart add -t freebsd-boot -s 128k mirror/gm0
# gpart add -t freebsd-swap -s 1G -l swap mirror/gm0
# gpart add -t freebsd-ufs -s 2G mirror/gm0 
# gpart add -t freebsd-ufs mirror/gm0
# newfs -L rootfs /dev/mirror/gm0p3
# newfs -L usrfs /dev/mirror/gm0p4
# mount /dev/ufs/rootfs /mnt
# mkdir /mnt/usr
# mount /dev/ufs/usrfs /mnt/usr
# cd /usr/src
# make DESTDIR=/mnt installworld distribution installkernel
# cat > /mnt/boot/loader.conf
geom_mirror_load="YES"
vfs.root.mountfrom="ufs:/dev/ufs/rootfs"
^D
# cat > /mnt/etc/fstab
# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/gpt/swap           none            swap    sw              0       0
/dev/ufs/rootfs         /               ufs     rw              1       1
/dev/ufs/usrfs          /usr            ufs     rw              2       2
^D
# gpart bootcode -b /boot/pmbr -p /boot/gptboot -i 1 mirror/gm0
Вроде бы ничего сложного нет:
  • сначала создаётся зеркало mirror/gm0 на целых дисках ad6 и ad8; 
  • затем на зеркале создаётся таблица разделов GPT и 4 раздела - freebsd-boot для загрузочного кода, который умеет загружать с GPT, swap раздел и два (для примера) раздела с UFS - для корневой файловой системы и для /usr; 
  • затем создаются файловые системы с использованием символьных меток rootfs и usrfs;
  • И в конце записывается загрузочный код.
Перезагрузившись, убедился, что всё работает как и ожидалось. Было несколько нестрашных сообщений от загрузчика и ядра, которые могут напугать неподготовленного пользователя ;)
Первое - во время загрузки gptboot сообщил:
gptboot: invalid backup GPT header
Причина понятна - GPT была создана поверх зеркала. Размер провайдера mirror/gm0 на 1 сектор меньше, чем размер диска, так как gmirror забирает последний сектор для хранения своих метаданных. Загрузчик gptboot ничего не знает о программном зеркале и ищет резервный заголовок GPT в конце диска, а там находятся метаданные gmirror.

Тут есть небольшое разногласие между gptboot и GEOM_PART_GPT. Некоторое время назад я изменил алгоритм проверки корректности GPT в GEOM_PART_GPT. А именно, после прочтения основного заголовка GPT и проверки его контрольной суммы, резервный заголовок считывается по хранящемуся в основном заголовке адресу. В случае, когда основной заголовок повреждён, резервный заголовок ищется в конце диска. Так, например, во время загрузки созданной выше системы, можно увидеть сообщения:
GEOM: ad4: the secondary GPT header is not in the last LBA.
GEOM: ad6: the secondary GPT header is not in the last LBA.
GEOM_MIRROR: Device mirror/gm0 launched (2/2).
Эти сообщения уже идут от GEOM, который выполняя поиск метаданных на провайдерах ad4 и ad6 обнаружил GPT, ту же, что и gptboot. Но затем GEOM_MIRROR обнаружил свои метаданные и создал провайдер mirror/gm0, на котором, в свою очередь, тоже была обнаружена GPT, но с ней всё хорошо - все размеры, смещения и контрольные суммые корректны и совпадают.

В итоге, после окончания загрузки, в системе имеется зеркало и GPT на нём; две файловые системы, смонтированные по UFS меткам; swap раздел, подключенный по gpt метке.
# gmirror status
      Name    Status  Components
mirror/gm0  COMPLETE  ad4
                      ad6
# gpart show
=>      34  20971452  mirror/gm0  GPT  (10G)
        34       256           1  freebsd-boot  (128K)
       290   2097152           2  freebsd-swap  (1.0G)
   2097442   4194304           3  freebsd-ufs  (2.0G)
   6291746  14679740           4  freebsd-ufs  (7.0G)

# swapinfo
Device          1K-blocks     Used    Avail Capacity
/dev/gpt/swap     1048576        0  1048576     0%
# mount
/dev/ufs/rootfs on / (ufs, local)
devfs on /dev (devfs, local, multilabel)
/dev/ufs/usrfs on /usr (ufs, local)

Теперь нужно проверить "живучесть" такой системы. Первым испытанием будет исчезновение диска ad4. После отключения диска и запуска виртуальной машины загрузка прошла успешно. Отмечу только эти сообщения от GEOM:
GEOM: ad6: the secondary GPT header is not in the last LBA.
GEOM_MIRROR: Force device gm0 start due to timeout.
GEOM_MIRROR: Device mirror/gm0 launched (1/2).
И вывод пары команд для подтверждения своих слов:
# gmirror status
      Name    Status  Components
mirror/gm0  DEGRADED  ad6
# gpart show
=>      34  20971452  mirror/gm0  GPT  (10G)
        34       256           1  freebsd-boot  (128K)
       290   2097152           2  freebsd-swap  (1.0G)
   2097442   4194304           3  freebsd-ufs  (2.0G)
   6291746  14679740           4  freebsd-ufs  (7.0G)
Теперь отключу автоматическую загрузку модуля geom_mirror и проверю, загрузится ли система. Загрузка прошла опять же без проблем, но слегка изменился вывод:
# dmesg | grep GEOM
GEOM: ad6: the secondary GPT header is not in the last LBA.
# kldstat
Id Refs Address    Size     Name
 1    1 0xc0400000 be46fc   kernel
# gpart show
=>      34  20971452  ad6  GPT  (10G) [CORRUPT]
        34       256    1  freebsd-boot  (128K)
       290   2097152    2  freebsd-swap  (1.0G)
   2097442   4194304    3  freebsd-ufs  (2.0G)
   6291746  14679740    4  freebsd-ufs  (7.0G)

# swapinfo
Device          1K-blocks     Used    Avail Capacity
/dev/gpt/swap     1048576        0  1048576     0%
# mount
/dev/ufs/rootfs on / (ufs, local)
devfs on /dev (devfs, local, multilabel)
/dev/ufs/usrfs on /usr (ufs, local)
Таблица разделов помечена как повреждённая. В таком состоянии её нельзя изменять, а значит меньше шансов что-то испортить. Теперь, верну всё на свои места и снова загружусь:
# dmesg | grep GEOM
GEOM: ad4: the secondary GPT header is not in the last LBA.
GEOM: ad6: the secondary GPT header is not in the last LBA.
GEOM_MIRROR: Device mirror/gm0 launched (1/2).
GEOM_MIRROR: Device gm0: rebuilding provider ad4.
# gmirror status
      Name    Status  Components
mirror/gm0  DEGRADED  ad4 (6%)
                      ad6
Как видите, всё работает.

Какие выводы можно сделать в заключение? Если грамотно использовать возможности GEOM классов, то обычно получаешь то, что запланировал. Важно понимать, что и как ты настраиваешь, а не просто копировать набор команд из какого-то howto. Умелое использование меток GEOM_LABEL поможет настроить "живучую" систему, в том плане, что она может без проблем загрузиться после таких воздействий, которые я проделал выше, и не только.

Не забывайте о подводных камнях при использовании GEOM_MIRROR -  загрузочный код не знает о его существовании, а значит во время загрузки gptboot будет выполняться с того диска, с которого выбрана загрузка в BIOS. И он загрузит ядро с этого диска, и настройки loader.conf он прочитает оттуда же. Но после того как ядро загрузится и смонтирует файловые системы, данные могут оказаться другими. Например, в приведённых примерах, если не включить загрузку geom_mirror в loader.conf на ad6, то после синхронизации зеркала она будет выключена. Что может оказаться сюрпризом при следующей перезагрузке.

По поводу несогласованности действий gptboot и GEOM_PART_GPT, возможно я решу эту проблему, обсудив детали с pjd@, который переписал реализацию gptboot.