пятница, 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.

среда, 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'а. Если кто-то может проверить на реальном железе, буду рад комментарию.

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

Что нового?

... давно ничего не писал сюда (по-моему, сообщение, начинающееся с таких слов, уже было :)
На самом деле я переодически вспоминаю про блог, захожу, смотрю статистику, но почему-то не возникало желания черкнуть что-нибудь. А сегодня вот возникло.

Много всего произошло со времени написания последнего сообщения. На мой взгляд, наиболее интересные и обсуждаемые события (которые мне запомнились):
  1. Официальное объявление об окончании IPv4 адресов;
  2. Выход двух релизов FreeBSD - 7.4 и 8.2;
  3. Выход релиза Debian/kFreeBSD;
  4. Возобновление работ над новой программой установки FreeBSD;
  5. ZFS v28 наконец-то интегрирована в систему.
Забавно было наблюдать за всеобщим ажиотажем вокруг исчерпания адресного пространства IPv4. На различных сайтах то и дело появлялись новости "до исчерпания осталось N дней", и под конец люди уже часы считали. И вот, свершилось. В рассылках, форумах, чатах на несколько дней сразу активизировалось тестирование IPv6. Много вопросов о настройке, об обнаруженных проблемах... Но, прошёл месяц и что-то пыл активистов слегка угас :)
Сужу по организации где работаю я, ну и по ряду контор в нашей "деревне". У всех IPv4 адресов хватает, запасались заранее. Я даже /48 сетку IPv6 себе зарегистрировал почти 2 года назад... Хочется конечно потестировать IPv6, но в нашем городе никто из провайдеров не может обеспечить условий, на данный момент единственный способ - туннели. Изредка почитываю книжку "IPv6 Администрирование сетей", но времени пока на это нет.

Следуя уже устоявшейся традиции релизы FreeBSD были выпущены позднее предполагаемой даты. Если взглянуть на release notes, то видно, что разработка не стоит на месте и было сделано много нового. Хотя, я хронически сижу на CURRENT, и мне эти изменения как-то не особо заметны. Спасибо release notes'ам за весь список :)
Работа по подготовке и выпуску релизов проделана немалая, но не обошлось и без ложки дёгтя в бочке мёда. Как только стало известно о релизах, в списках рассылки и в gnats появились отчёты об обнаруженных проблемах. О чём это говорит? Народ не особо-то жаждет принимать участие в тестировании BETA версий, все надеются на то, что за них это сделают разработчики. А разработчики невсегда могут проверить всё и во всех возможных ситуациях.
От сюда вытекает целая тема для размышления - что запускать в промышленную эксплуатацию RELEASE, STABLE или может CURRENT? И я склоняюсь больше к последним двум, но это только моя точка зрения и она основана на моём опыте, моих задачах и количестве машин :)

Debian GNU/kFreeBSD - ещё одна штука, о которой много говорили. Даже в IRC канале разработчиков FreeBSD её вспоминали не раз и не два. Мнения разные, но стоит признать и принять то, что разработчики Debian достаточно настырные ребята. Я скачал один ISO образ, установил вчера в VirtualBox'е, но пока не смотрел.

Возобновление работ над новой программой установки было быстрым и неожиданным. Если не помните, то Warner Losh некоторое время назад начал работу над интеграцией PC-BSD'шной программы установки. Он даже в head/ уже интегрировал её. И вот, тут появился Nathan Whitehorn с ещё одним инсталлятором - bsdinstall. Причём появился он так внезапно и активно внедряя свой инсталлятор, что даже Warner растерялся. А ещё этот процесс сопровождался обновлением библиотеки libdialog.
Надо заметить, что новая библиотека libdialog коренным образом отличается от нашей старой. Она, конечно, в плане возможностей стала значительно интереснее, но всё так же не позволяет делать то, что хотелось мне реализовать в sade, в связи с чем я и сделал customdlg.
В итоге, Nathan и Warner нашли общий язык и согласились, что стоит объединить усилия и создать нечто на основе того, что уже сделано ими. Это нечто планируется сделать инсталлятором по-умолчанию для FreeBSD 9.0+, релиз которой, кстати, уже не за горами.
Что же касается sade, то Nathan признаёт, что он удобнее его partedit'а и было бы неплохо, интегрировать его в систему. Вот только нужно опять убить кучу времени на изучение этой libdialog и адаптацию того, что уже написано под неё :(

ZFS v28 уже в head/. Я вчера обновил систему на домашнем компе, но ZFS пока не обновлял. На первый взгляд вроде всё работает после обновления, хотя некоторые жалуются на аномально высокую нагрузку. Через пару дней попробую обновиться...

О своей деятельности сказать особо нечего, закрыл несколько PR связанных с паниками в GEOM, в ноду ng_one2many добавил новый алгоритм NG_ONE2MANY_XMIT_FAILOVER (патч от Максима Игнатенко). Вчера добавил новый ключик для команды `gpart show -p`. Предназначен он для вывода имён провайдеров вместо индексов разделов:

> gpart show -p
=>       34  156301421    ada0  GPT  (75G)
         34        512  ada0p1  freebsd-boot  (256K)
        546    8388608  ada0p2  freebsd-swap  (4.0G)
    8389154  147912301  ada0p3  freebsd-zfs  (71G)