четверг, 30 июня 2011 г.

ipfw call / return

28 июня официально начался "Code Slush for 9.0-RELEASE", который так же известен как "Feature Freeze". Т.е. теперь в head/ бранч не разрешается вносить новый функционал. Цель этого мероприятия - стабилизировать состояние системы и сосредоточиться на поиске и исправлении ошибок. На 17-ое июля запланирован "Code Freeze" после которого последует выход первой бета версии FreeBSD 9.0.

Теперь о теме. Вадим Гончаров, узнав о предстоящем "Code Slush", выдал очередной патч для ipfw, который во что бы то ни стало должен оказаться в 9.0-RELEASE. :)
Патч добавляет два новых действия в ipfw: "call" и "return". Первая команда выполняет "вызов" подпрограммы, вторая - возврат из подпрограммы к следующему правилу. На практике, конечно, никаких подпрограмм не вызывается. Команда "call" принимает в качестве аргумента номер правила, к которому нужно выполнить переход. Переход осуществляется аналогично тому, как это делается для команды "skipto", только он может осуществляться в любом направлении по отношению к текущему правилу. Кроме того, перед выполнением перехода команда "call" сохраняет во внутреннем стеке номер своего правила в качестве точки возвращения. Размер стека ограничен 16 элементами. Это значит, что "подпрограмма" может содержать в себе другие вызовы "call" в другие "подпрограммы".
Возврат из "подпрограммы" осуществляется размещением правила с командой "return". У неё нет аргументов. Она находит в стеке первый элемент (по дисциплине LIFO), извлекает сохранённый номер правила и осуществляет возврат к следующему правилу с номером большим на 1.

Теперь о применении этого. Несомненно, есть несколько задач, в которых данный функционал может оказаться полезным. Но в то же время, скорость появления этого патча и практически полное отсутствие его тестирования ставит под сомнение плюсы, которые можно получить от него. Так что, если найдутся желающие на широких каналах протестировать этот функционал, было бы неплохо. Для этого, в общем-то, достаточно скачать образ 9.0-CURRENT и протестировать ;)

Кстати, Hiroki Sato сообщил о возобновлении сборки снепшотов FreeBSD CURRENT на ресурсе allbsd.org. Что является отличной новостью, для меня по крайней мере. Обычно для тестирования чего-либо бывает быстрее скачать образ и установить систему, чем обновляться из исходников.

пятница, 24 июня 2011 г.

Что нового?

Последние несколько недель в свободное время занимался обработкой открытых PR. В основном они были связаны с ipfw, dummynet и ipfw_nat, но не все. В общем, сократил количество открытых проблем где-то на полтора десятка, может даже на два. Считать, если честно - лень :)

Что хотелось бы отметить - это несколько исправлений и патчей, добавляющих новую функциональность. Желательно бы их получше оттестировать перед MFC. И, кстати говоря, вот уже почти неделю назад должен был начаться "code slush" в связи с подготовкой к выпуску 9.0-RELEASE.

Итак, первое исправление затрагивает kern/136695, kern/147720 и kern/150798. Связано оно с функцией форвардинга пакетов в ipfw и использованием динамических правил. Теперь реализация аналога reply-to из PF не должна составить труда и в ipfw (описание этого случая можно найти у Вадима Гончарова в блоге).

Следующее исправление связано с kern/122109, kern/129093 и kern/157379. Оно делает поведение ipfw_nat более похожим на natd. Возможно, те кто говорил, что ipfw_nat работает не так с правилами, с которыми работает natd, теперь вздохнут с облегчением. Теперь ipfw_nat будет более "гуманно" относится к пакетам, которые libalias не захотела обрабатывать. Раньше такие пакеты просто отбрасывались, что приводило к симптомам, хорошо показанным в перечисленных PR.

Теперь о новом функционале в ipfw_nat - kern/157867. Это аналог опции globalport для natd. Если у вас сконфигурировано несколько NAT'ов, то теперь можно одним правилом заставить ipfw проверять пакет сразу для всех таблиц трансляций. Для этого добавлено новое "кодовое слово"- nat global. Пакет будет обрабатываться в соответствии с конфигурацией той таблицы, в которой найдётся подходящее состояние.


В модуль libalias, обрабатывающий FTP соединения внесены изменения kern/157957, благодаря которым он корректно работает при использование опции redirect_port для ipfw_nat. Кстати, Глеб Смирнов изменил обработку опций NAT для ipfw(8). Теперь командная строка не ограничена длиной NAT_BUF_LEN и при настройках NAT можно указывать сколько угодно опций.

Кроме того, для ipfw setfib добавлена поддержка tablearg - kern/156410.

Еще было несколько мелких исправлений, которые не так интересны. Но в общем, за большинство из этих изменений и исправлений можно поблагодарить Александра Черникова. Именно у него хватило терпения и желания, копаться в коде, который местами далеко не очевиден, писать патчи и продвигать их, чтобы они оказались в системе.

Да, ещё. По проблеме с загрузкой с ZFS. Вчера John Baldwin добавил исправление в загрузочный код, которое, вероятно исправит большинство проблем.

четверг, 9 июня 2011 г.

Выравнивание разделов в gpart. Итоги.

Некоторое время назад я публиковал заметку с патчами на тему выравнивания разделов. С того времени этот функционал несколько раз подвергался изменениям. Больше никаких изменений в этой области я не планирую, поэтому опишу то что сделано.

Многие из вас слышали, что некоторые современные жесткие диски уже выпускаются с размером сектора в 4 Кбайт. Хотя на данный момент я не слышал ни об одном из них, который бы честно в этом признавался. Все они продолжают рапортовать о том, что у них размер сектора равен 512 Байтам. В связи с чем часто возникают проблемы с производительностью при работе с такими дисками. Для обхода этой проблемы есть несколько решений. Одно из последних появилось во FreeBSD 9.0 совсем недавно - с ревизией r222520.

Александр Мотин добавил в драйвер ada(4) новый quirk - ADA_Q_4K. Который говорит драйверу установить geom провайдеру размер stripesize равным 4 Кбайта. На основе этой информации все консьюмеры этого провайдера могут проявлять свой "интеллект" и пытаться выполнить некоторые оптимизации при работе.

В драйвер уже "вшита" информация о некоторых диска, размер сектора которых 4 Кбайт. Для них stripesize будет установлен автоматически. Для других же дисков, которые не прописаны в драйвере, можно установить данный quirk через loader.conf. Так, для диска ada3 это будет примерно так:

# echo 'kern.cam.ada.3.quirks="1"' >> /boot/loader.conf

проверить работоспособность можно, например, так:

# geom disk list ada3
Geom name: ada3
Providers:
1. Name: ada3
   Mediasize: 80026361856 (74G)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 0
   Mode: r2w2e4
   descr: SAMSUNG HD080HJ/P
   ident: S0DEJ1NL817767
   fwsectors: 63
   fwheads: 16


Теперь вернёмся к gpart(8). Я добавил туда поддержку stripesize, а так же возможность вручную задавать нужное выравнивание:

# gpart
usage: gpart add -t type [-a alignment] [-b start] [-s size] [-i index] [-l label] [-f flags] geom
 ...
       gpart resize -i index [-a alignment] [-s size] [-f flags] geom 

У двух подкоманд "add" и "resize" появилась новая опция -a alignment. Которую можно задавать как в секторах, так и в байтах. Наличие этой опции влияет на то, как gpart рассчитывает границы разделов. Например, если вы хотите, чтобы раздел был выравнен по границе в 4 Кбайт, то достаточно указать "-a 4k" дополнительно к команде "gpart add". При использовании stripesize выравнивание можно не задавать, оно будет "подхвачено" автоматически.

Для проверки, выравнен раздел или нет, можно использовать команду diskinfo(8):
# diskinfo -v ada0p3
ada0p3
        512             # sectorsize
        75731098112     # mediasize in bytes (70G)
        147912301       # mediasize in sectors
        4096            # stripesize
        1024            # stripeoffset
        146738          # Cylinders according to firmware.
        16              # Heads according to firmware.
        63              # Sectors according to firmware.
        S0DEJ1NL817767  # Disk ident.

#  echo 1024 % 4096 | bc
1024
Следующая после diskinfo(8) команда подсчитывает остаток от деления между stipeoffset и желаемым выравниванием. Если получаемое значение не ноль, значит раздел не выравнен.

четверг, 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

четверг, 28 апреля 2011 г.

Метки разделов и выравнивание в gpart


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

Первый патч предназначен для решения проблемы с метками разделов, которые, как известно, реализуются сейчас классом GEOM_LABEL. Основная проблема в том, что для приемлемой работы с метками GEOM_LABEL предъявляет некоторые требования к провайдерам. Например, обнаружение меток GEOM_LABEL выполняет только во время tasting. Изменение меток может быть обнаружено только при условии использования механизма spoiling с последующим retaste. Что при существующей реализации класса GEOM_PART не представляется возможным.
Класс GEOM_PART управляет таблицей разделов и метки этих разделов хранятся в таблице, а не на провайдере раздела. Поэтому, при изменении метки механизм spoiling'а не запускается для провайдера раздела.

Предлагаемый мной патч отключает реализацию меток gpt/gptid в GEOM_LABEL и добавляет новый класс PART::LABEL. Он тесно связан с классом PART и, кроме того, имеет несколько функциональных отличий от GEOM_LABEL. Во-первых, он не использует механизм tasting, объекты класса и его провайдеры создаются по инициативе класса PART. Во-вторых, он не использует механизм spoiling, поэтому его провайдеры существуют всё время, а не исчезают, когда какой либо связанный провайдер начинает использоваться. Благодаря этому устраняется проблема с изменением меток, которые становятся видны сразу после изменения. И, на мой взгляд, удобнее, когда метки в /dev существуют всегда. К тому же, кроме реализации gpt/gptid я добавил поддержку меток в схемы PC98 и APM.

Я планирую включить этот патч в head/ примерно через неделю. Второй патч не вносит изменений в ядро. Он добавляет к утилите gpart(8) новую опцию "-а alignment", при использовании которой gpart выполняет выравнивание создаваемых разделов на заданную величину. Например так:

# gpart create -s gpt md0
md0 created
# gpart show md0
=>    34  409533  md0  GPT  (200M)
      34  409533       - free -  (200M)

# gpart add -t freebsd-boot -s 128k -a 4k md0
md0p1 added
# gpart add -t freebsd-ufs -s 60m -a 4k md0
md0p2 added
# gpart add -t freebsd-ufs -a 4k md0
md0p3 added
# gpart show md0
=>    34  409533  md0  GPT  (200M)
      34       6       - free -  (3.0K)
      40     256    1  freebsd-boot  (128K)
     296  122880    2  freebsd-ufs  (60M)
  123176  286384    3  freebsd-ufs  (139M)
  409560       7       - free -  (3.5K)

четверг, 14 апреля 2011 г.

web-камера в skype во FreeBSD - теперь работает!

Использование Web-камеры во FreeBSD некоторое время назад было довольно больной темой. Проблема частично решилась с появлением webcamd и cuse4bsd. Нет, конечно в портах было несколько модулей ядра, реализующих поддержку небольшого списка web-камер, но, к сожалению, такие камеры редко ставят на ноутбуки, да и в продаже их найти сложно.

Два проекта cuse4bsd и webcamd добавили поддержку огромного количества различных камер во FreeBSD. Cuse4bsd - это модуль ядра и библиотека, предоставляющая программный интерфейс для работы с этим модулем. Если вкратце, то используя эту библиотеку в своём приложении вы можете создать псевдоустройство (в нашем случае это /dev/video0), к которому могут обращаться другие программы. Все обращения к этому устройству, такие как ioctl, read, write, и другие через libcuse4bsd перенаправляются к вашему приложению. Вы можете их обработать и отправить результат обратно.

Точно так же webcamd использует эту библиотеку и модуль ядра. Сам же webcamd довольно интересное приложение - он включает в себя часть ядра linux. Если взглянуть в его исходный код, то можно найти кучу обёрток для различных функций, типов, макросов, структур ядра linux, которые позволяют скомпилировать драйверы различных устройств и использовать их в приложении.
Webcamd через libusb может обращаться к различным камерам и тюнерам, которые подключены в системе. В то же время, он создаёт устройства /dev/videoX и используя драйверы, что взяты из ядра linux, выполняет их на уровне пользователя.  Другими словами webcamd  организует взаимодействие между linux-драйвером, реальным устройством и приложениями, которые обращаются из FreeBSD к /dev/videoX.

Всё бы хорошо, большинство web-камер работает, к ним можно обращаться из pwcview, mplayer, vlc и других програм, кроме skype. Это бинарник для linux и он ни в какую не хочет работать с современными камерами. Причин несколько. Skype использует только video4linux1 API и от камеры хочет данные только в определённом формате. Кстати, эти проблемы не специфичные для FreeBSD, в linux они тоже присутствуют.
Но в linux есть решение - пакет libv4l, который включает в себя несколько библиотек - v4l2convert и v4l1compat. Первая осуществляет конвертацию форматов данных, вторая предназначена для приложений, использующих устаревший API video4linux1.

Так вот, используя эти библиотеки в linux можно заставить skype работать с современными камерами. Но во FreeBSD режим эмуляции linux не поддерживает API Video4Linux2, поэтому использовать libv4l не получится.
Системные вызовы (в данном случае это ioctl с кодами V4L2), которые будут идти от libv4l к ядру FreeBSD не будут обработаны, так как они не реализованы.

Но! :)
Мы можем поблагодарить Juergen Lock (nox@). Он выложил патчи, реализующие V4L2 в режиме linux эмуляции. Теперь, используя libv4l камеры работают и в skype, а так же и во Flash в браузере.

Патчи есть для 8-ки и 9-ки. libv4l для linux в портах вроде пока ещё нет, но думаю это вопрос времени. Я нашёл в поисковике такой пакет libv4l-0.6.2-1.fc10.i386.rpm и установил его как учат вот тут.
После этого слегка модифицировал стартовый скрипт skype:

!/bin/sh
export LD_PRELOAD=/usr/lib/libv4l/v4l2convert.so
/usr/local/share/skype/skype --resources=/usr/local/share/skype $@



PS. Вы конечно поняли, что про webcamd я не просто так рассказывал, он у меня тоже установлен и настроен.

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