среда, 16 марта 2011 г.

Варианты загрузки FreeBSD: ZFS пул без таблиц разделов

В списках рассылки freebsd-fs@ и freebsd-stable@ было несколько жалоб на то, что FreeBSD 8.2 и предыдущие сборки 8-STABLE не грузятся при использовании загрузочного кода zfsboot. Сам я никогда его не использовал, у меня везде "стандартная" схема - GPT + 3 раздела (freebsd-boot, freebsd-swap и freebsd-zfs). Решил я проверить эту проблему.

Почти два дня я провел, совмещая работу и эксперименты в virtualbox'е. И это я ещё не все возможные варианты попробовал. У меня была виртуальная машина с 8.1-STABLE, я обновил её и оставил /usr/obj для установки собранной системы на другие образы дисков.

Первым вариантом для тестирования я выбрал ZFS пул, создаваемый на целом диске без использования таблиц разделов. Выбор пал на такую конфигурацию благодаря одному моему коллеге, которого я уже упоминал как-то в своём блоге. Он написал статью, обобщив свой опыт по установке FreeBSD на ZFS. Он же периодически подкидывает мне ссылки на обсуждение своей статьи. Там-то как раз и обсуждался такой метод установки. Установку можно описать буквально несколькими командами (набираю по памяти, мог что-нибудь забыть):

# zpool create z ad6
# zfs create z/root
# cd /usr/src
# make DESTDIR=/z/root installworld distribution installkernel
# zpool set bootfs=z/root z
# zpool set cachefile=/z/root/boot/zfs/zpool.cache z
# cat > /z/root/boot/loader.conf
zfs_load="YES"
vfs.root.mountfrom="zfs:z/root"
^D
# echo 'zfs_enable="YES"' > /z/root/etc/rc.conf
# touch /z/root/etc/fstab
# zfs set mountpoint=none z
# zpool export z
# dd if=/boot/zfsboot of=/dev/ad6 count=1
# dd if=/boot/zfsboot of=/dev/ad6 skip=1 seek=1024

Проблема подтвердилась сразу. Самое неприятное, что никакого удобного способа отладить загрузочный код я не видел. Единственное, что приходило на ум - читать код и вставлять printf'ы в определённые места, для того чтобы хоть как-то отслеживать процесс выполнения загрузочного кода.

Чтение исходников - это часто бывает полезным. Вот, например, объяснение последних двух команд - почему именно такие параметры? ;) Это можно понять из исходного кода.

Образ загрузочного кода zfsboot состоит из двух частей - zfsboot1 и zfsboot2. Первая часть предназначена для записи в первый сектор диска, куда BIOS обычно передаёт управление для загрузки системы. Грубо говоря, zfsboot1 - это образ MBR с небольшой частью загрузочного кода, который выполняет некоторые стандартные манипуляции для загрузчика, загружает zfsboot2, а так же предоставляет ему некоторые сервисные функции. Написан он на ассемблере. Вторая часть - zfsboot2, написана на Си. Она уже обладает значительно большим функционалом, и размер у неё, соответственно, побольше. В частности, она выполняет поиск ZFS пула и загружает из него zfsloader.

Так вот, первая команда dd выполняет запись zfsboot1 в первый сектор диска. Вторая команда выполняет запись zfsboot2 по смещению в 512 кбайт. Это место внутри ZFS во FreeBSD специально зарезервировано под загрузочный код:

/*
 * Size and offset of embedded boot loader region on each label.
 * The total size of the first two labels plus the boot area is 4MB.
 */
#define VDEV_BOOT_OFFSET        (2 * sizeof (vdev_label_t))
#define VDEV_BOOT_SIZE          (7ULL << 19)                    /* 3.5M */
 

Как видно из комментария, его размер может достигать трёх с половиной мегабайт. На данный момент в 9.0-CURRENT с ZFS v28 его размер чуть больше 32кбайт, но для "ровного" числа  он создаётся размером 64 кбайт, в которых чуть меньше половины забито нулями, + 512 байт от zfsboot1.

Вернёмся к решению проблемы. Как мне стало известно из переписки с людьми, сообщившими о проблеме, не работает zfsboot в 9.0-CURRENT примерно с сентября 2010 года. А именно тогда туда были внесены крупные изменения. Методом printf'а я нашёл, что зависание происходит в функции drvread, которая вызывает код чтения секторов диска из zfsboot1. Сравнив содержимое этой функции с тем, что было до тех изменений было замечено всего одно маленькое отличие.
Я вернул убранную строчку и всё заработало. Попутно, была обнаружена ошибка в ассемблерном коде, появившаяся после внедрения ZFS v28. Сейчас я выполняю дополнительные тесты для проверки, не повлияет ли это изменение на другие варианты загрузки, о которых, возможно, напишу позднее. Для тех, кому нужен загрузочный код zfsboot сейчас, то пропатченную версию из 8.2-STABLE можно взять здесь.

Так же, тестируя уже рабочий код zfsboot'а в virtualbox'е обнаружилась другая проблема - если в системе присутствует несколько дисков, а диск с пулом не является первым в списке BIOS'а, то не удаётся загрузиться выбрав устройство загрузки из меню BIOS'а. Возможно это особенность BIOS'а virtualbox'а. Если кто-то может проверить на реальном железе, буду рад комментарию.

17 комментариев:

  1. Спасибо, весьма познавательно. Сегодня по тупости второй раз наступил на эти грабли (первый раз вылезло где-то в январе'11).
    Правда сегодня заливал zfsboot у себя на буке на живой системе, включив предварительно kern.geom.debugflags=16. После ребута - описанная вами ситуация. Попытка реанимировать с помощью пропатченного zfsboot по вашей ссылке окончилась неудачей - при загрузке zfsboot: No ZFS pools located, can't boot

    ОтветитьУдалить
  2. Такая же ситуация
    при загрузке версии 32 кб zfsboot: No ZFS pools located.

    версия zfsboot от 2011-Apr-11 16:29:17 64 kb пишет Read error
    Диск в системе один

    ОтветитьУдалить
  3. Нужно больше информации. Какая версия системы, пула, zfs, обнаруживается ли пул при загрузке с livecd, разметка диска в точности такая как описано тут или есть отличия?

    ОтветитьУдалить
  4. Freebsd 9 current
    ZFS filesystem version 5
    ZFS pool version 28

    Да пул при загрузке с mfsbsd импортируется.
    zfs list
    dboot2 291G 1,05T 256G none
    dboot2/mysql 84,4M 1,05T 4,22M /var/db/mysql
    dboot2/mysql/ibdata 68,0M 1,05T 58,3M /var/db/mysql/ibdata
    dboot2/mysql/iblogs 12,1M 1,05T 10,0M /var/db/mysql/iblogs
    dboot2/swap 4,13G 1,06T 1,23G -
    dboot2/tmp 73K 1,05T 39K /tmp
    dboot2/usr 24,5G 1,05T 14,7G /usr
    dboot2/usr/home 314M 1,05T 313M /usr/home
    dboot2/usr/ports 7,14G 1,05T 320M /usr/ports
    dboot2/usr/ports/distfiles 6,48G 1,05T 6,48G /usr/ports/distfiles
    dboot2/usr/ports/packages 3,49M 1,05T 3,49M /usr/ports/packages
    dboot2/usr/src 369M 1,05T 345M /usr/src
    dboot2/var 5,60G 1,05T 5,27G /var
    dboot2/var/crash 50,5K 1,05T 31,5K /var/crash
    dboot2/var/db 292M 1,05T 191M /var/db
    dboot2/var/db/pkg 25,1M 1,05T 19,1M /var/db/pkg
    dboot2/var/empty 22K 1,05T 22K /var/empty
    dboot2/var/log 27,0M 1,05T 26,0M /var/log
    dboot2/var/mail 51K 1,05T 32K /var/mail
    dboot2/var/run 188K 1,05T 104K /var/run
    dboot2/var/tmp 350K 1,05T 258K /var/tmp

    Грузилось и с вашего загрузичка (который был 32 кб)пока не сделал zfs upgrade до 5 версии файловой системы.

    ОтветитьУдалить
  5. Тот zfsboot, что был 32кб - он из 8.2-STBALE, а ZFS v28 туда ещё не смерджили. Файл, который выложен сейчас - он из CURRENT, и он работает у меня. Попробуйте перезаписать ещё раз с правильными смещениями.. Больше посоветовать нечего, несколько человек мне отписывались о том, что он работает.

    ОтветитьУдалить
  6. Проблема решилась банальным zpool import c live-cd.

    ОтветитьУдалить
  7. > anon комментирует...
    >
    > Такая же ситуация
    > при загрузке версии 32 кб zfsboot: No ZFS pools located.
    >
    > версия zfsboot от 2011-Apr-11 16:29:17 64 kb пишет Read error
    > Диск в системе один

    Та же самая ситуация приключилась на боевом сервере в датацентре, только загрузочный пул из двух дисков в зеркале. Перед этим дважды протестировал сценарий обновления в VirtualBox-е - все нормально...

    ОтветитьУдалить
  8. Правильное смещение это
    dd if=/boot/zfsboot of=/dev/ad1 count=1
    dd if=/boot/zfsboot of=/dev/ad1 skip=1 seek=1024
    ?
    Поясняю ситуацию
    Грузилось с Вашего загрузчика 32кб после zpool upgrade, после zfs upgrade перестало грузиться и со старого (32кб) и текущим.
    Перешел на gpt.

    ОтветитьУдалить
  9. Андрей, подскажите,а как сейчас обстоят дела с этой проблемой?
    Вчера ради экспериментов обновил тестовую машину до текущего 8.2-STABLE и перевел пул на 28-ю версию + фс на 5-ю. После этого система перестала грузиться ни с одним из загрузчиков. Сейчас глянул - ваш патч в STABLE уже присутствует, однако при старте получается все тот же "Read error"

    ОтветитьУдалить
  10. Сейчас в списке рассылки freebsd-stable@ John Baldwin пытается отладить и решить эту проблему, можете проследить или поучавствовать. Тема: ZFS boot inside on the second partition inside a slice

    ОтветитьУдалить
  11. У меня после обновления прошивки на двух из четырёх дисков, на каждом из которых первый раздел отдан под ZFS mirror с системой, перестало загружаться с ошибкой "ZFS: i/o error -all block copies unavailable". 9.0-BETA2.

    ОтветитьУдалить
  12. Хотелось бы спросить, а что если пользователь создал пул не на том месте, где следовало и решил его удалить! Принудительное удаление через #zpool destroy -f ему никакого результата не принесло и по команде #zpool import все равно показывается пул, который вроде как убил! Подобное возникло у меня: http://sysadmins.ru/topic334772.html . Возникло когда учился ставить FreeBSD на MBR+ZFS и сейчас весит "артефакт", который вроде как работе не мешает, но тем не менее хочется убрать с глаз долой )

    ОтветитьУдалить
  13. Думаю для этого нужно перезаписать zfs vdev labels. Их всего 4, две вначале диска, две в конце. Каждая метка занимает 256к. Включая загрузочный код, это 4М в начале и 512к в конце.
    PS. Я не проверял, просто предположение.

    ОтветитьУдалить
  14. Спрашивал в IRC, но пусть и тут будет, может кто проходя подскажет :)
    Имеется FreeBSD-CURRENT, установленна на root ZFS с таким вот разбиением
    > gpart show
    => 34 625142381 ada0 GPT (298G)
    34 128 1 freebsd-boot (64k)
    162 26621952 2 freebsd-ufs (12G)
    26622114 8388608 3 freebsd-swap (4.0G)
    35010722 590131693 4 freebsd-zfs (281G)
    Всё в общем то работает, но хотелось бы ещё каким то шаманством запускать иногда систему с ada0p2, а новый загрузчик напрочь утратил функцию загрузки по F1, F2..., да и вообще похоже хочет только zfs :(

    Вариант с GRUB конечно есть, но как то уж очень не хорошо.

    ОтветитьУдалить
    Ответы
    1. Проблема в том, что загрузочный код сейчас может грузить либо с ZFS - gptzfsboot, либо с UFS - gptboot. Поэтому со штатным загрузчиком не выйдет. Для нескольких UFS в на GPT можно установить аттрибуты bootme/bootonce для выбора с какой ФС грузиться, это не совсем удобно (в сравнении с F1..F2), но хоть что-то.

      Удалить
  15. При выполнении zpool upgrade появляется подсказка, что нужно выполнить
    gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0

    что делать если использовал zfsboot ?

    ОтветитьУдалить
    Ответы
    1. На сколько я помню, единственным 100% работающим вариантом тут я вляется загрузка с livefs и проделывание манипуляций с dd. У меня есть незаконченный код, который будет это делать через "zpool bootcode", но пока он незавершён и времени на него пока нет...

      Удалить