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

понедельник, 20 декабря 2010 г.

GEOM: практические знания. Часть 2.

Продолжить начатую ранее тему устройства GEOM я хотел бы с таких понятий как tasting, orphanization и spoiling. Для пользователя системы наиболее интересным наверно покажется первый термин. Tasting - это процесс автоматической настройки geom объектов. Этот процесс инициируется подсистемой GEOM каждый раз, когда создаётся новый GEOM класс или провайдер. С точки зрения алгоритма работы эти два случая немного различаются, так tasting при создании нового провайдера выглядит примерно так:
  1. Ядро проходит в цикле по списку известных ему GEOM классов.
  2. Для каждого из них вызывается метод класса taste, который уже в зависимости от реализации класса может создать новый geom объект, либо посчитать этот новый провайдер неподходящим для использования классом.
В случае, когда создаётся новый класс, алгоритм такой:
  1. Ядро проходит в цикле по списку известных ему GEOM классов.
  2. Для каждого класса просматривается каждый существующий geom объект и все его провайдеры, которые передаются на проверку в метод taste для нового класса. А тот уже решает, подходит ему этот провайдер или нет.
Таким образом сделана возможность своего рода "plug-and-play". При загрузке модуля ядра, реализующего какой-то новый GEOM класс, у него имеется возможность автоматически найти нужный провайдер и начать выполнять свои действия.

Рассмотрим пример возможного сценария работы tasting при обнаружении жесткого диска:
  1. Драйвер жесткого диска создаёт geom объект ada0 класса DISK, тот в свою очередь создаёт провайдер ada0.
  2. Создание провайдера инициирует tasting. Последовательность может отличаться, но мы предположим такое развитие событий - вызывается метод taste класса DEV. Этот класс служит для создания файлов устройств в файловой системе devfs. Т.е. это его мы можем поблагодарить за создание /dev/ada0 (стоит заметить, что этот класс не создаёт своих провайдеров).
  3. На этом процесс tasting не заканчивается. Предположим, что в списке классов, которые знает ядро, следующим стоит класс PART. Ядро вызывает метод taste этого класса, он считывает секторы этого провайдера, в которых могут находиться таблицы разделов, и проверяет есть ли они там. Если есть, то создаются новые провайдеры, в соответствии с таблицей разделов. Это, в свою очередь, опять же инициирует tasting.
Многие GEOM классы (не все) хранят свои метаданные на диске, обычно для этого используется один последний сектор. Метод taste этих классов считывает нужный классу сектор провайдера и проверяет считанные данные, если он находит там метаданные класса, он предпринимает какие либо действия. Чтобы пользователь случайно не повредил метаданные, размер создаваемого классом провайдера обычно меньше на один сектор. Например, когда вы создаёте зеркало на основе класса MIRROR, то размер итогового mirror/gm0 будет на один сектор меньше, чем размер провайдеров, из которых он состоит. Повреждение, перезапись последнего сектора, изменение размера провайдера или размера сектора - всё это приводит к тому, что метод taste не обнаружит свои метаданные на провайдере, а значит и не будет создан geom объект этого класса. Поэтому, если вы задумали нечто такое - не удивляйтесь последствиям :)


Про tasting сказать вроде больше нечего, перейдём к orphanization. Это обратный tasting процесс, во время которого провайдеры уничтожаются. В переводе orphanization означает "осиротение" и это не просто так. Например, когда вы отключаете жесткий диск, драйвер диска посылает классу DISK команду на уничтожение. Тот, в свою очередь, инициирует процесс "осиротения" и все объекты geom, связанные с провайдером диска через консьюмеров "информируются" о том, что они стали "сиротами". Им передаётся код ошибки, который будет возвращён пользователю при любых попытка взаимодействия с осиротевшими провайдерами. Т.е. если вы попробуете записать данные на такой провайдер, то получите ошибку. Информирование выполняется через вызов базового для всех классов метода orphan, в реализации которого разработчик может предусмотреть действия, необходимые для завершения работы объекта. Ну и заканчивается этот процесс самоуничтожением всех осиротевших провайдеров и объектов. Если не углубляться в особенности реализации, то можно сказать, что "оно" всё делает "само". Само находит что ему нужно во время tasting, само уничтожается во время orphanization :)

Теперь рассмотрим spoiling. Этот механизм предназначен для защиты от устаревших метаданных. Стоит понимать, что защащются здесь не метаданные, а находящийся в памяти geom объект. Так как система оперирует с данными в памяти, важно чтобы они были синхронизированы с метаданными на диске.

Ранее я рассказывал о счётчиках доступа, которые по-совместительству служат для ограничения доступа. Этими же счётчиками подсистема GEOM пользуется для активации механизма spoiling. Когда кто-либо обращается к провайдеру, он явно или неявно вызывает функцию g_access, в которой указывается желаемый режим доступа. Например, вы записываете что-то на диск, для этого вам нужно получить "разрешение" на запись, при этом счётчик w увеличивается. Как только вы прекращаете запись - счётчик уменьшается. Переход состояния счётчика w из нуля в значение большее нуля вызывает механизм spoiling. При этом все консьюмеры этого провайдера предупреждаются о том, что кто-то открыл провайдера для записи. Никто не знает, что вы туда будете записывать, но если geom объекту это важно (там могут быть его метаданные), то он примет информацию к сведению. Обычно "принять к сведению" означает, что geom объект самоуничтожится (так же, как он это делает во время orphanization). Но стоит вам завершить запись, чтобы значение счётчика w перешло в ноль, будет иницирован процесс re-tasting по алгоритму, используемому при создании нового провайдера.

Таким образом срабатывает защита от устаревших метаданных. Если вы измените метаданные, или же просто произведёте запись не связанную с ними, то GEOM класс, использующий провайдера, заново обнаружит свои метаданные на нём и автоматически сконфигурируется. Если же вы удалите или повредите метаданные, то spoiling уничтожит geom объект, использующий провайдер. Жизненный пример на эту тему - gpart vs. bsdlabel:

# gpart show md0 md0s1
=>     9  204786  md0  MBR  (100M)
       9  204786    1  freebsd  (100M)

=>     0  204786  md0s1  BSD  (100M)
       0   51200      1  freebsd-ufs  (25M)
   51200   51200      2  freebsd-ufs  (25M)
  102400   51200      4  freebsd-ufs  (25M)
  153600   51186         - free -  (25M)

# bsdlabel -w md0s1
# gpart show md0 md0s1
=>     9  204786  md0  MBR  (100M)
       9  204786    1  freebsd  (100M)

=>     0  204786  md0s1  BSD  (100M)
       0      16         - free -  (8.0K)
      16  204770      1  !0  (100M)

В первом выводе gpart show есть два geom объекта класса PART. md0 - представляет таблицу разделов MBR, а md0s1 - является партицией MBR, провайдером объекта md0, и, в то же время, содержит разметку BSD. Выполнив bsdlabel -w мы выполнили запись на раздел md0s1, изменив метаданные объекта md0s1. При этом, в момент вызова команды bsdlabel она открыла провайдера md0s1 для записи, что инициировало spoiling - самоуничтожение объекта md0s1 и всех его партиций. После завершения записи на провайдере md0s1 снова были найдены метаданные разметки BSD, но на этот раз там один раздел.

Ещё один пример, эксплуатирующий эти особенности GEOM - это запуск механизма re-tasting в ручную. Хотя, честно говоря, мне он ниразу не понадобился, но в списках рассылки вчтречаются люди, которым это нужно. Делается это просто, нужно всего лишь "открыть" провайдер для записи и "закрыть" его, например так:

# true > /dev/ada2 

Это инициирует re-tasting и GEOM классы заново "обследуют" этот провайдер.


На этом пока всё. На счёт продолжения не уверен, но, возможно, будет ещё одна заметка.

    пятница, 10 декабря 2010 г.

    GEOM: практические знания. Часть 1.

    Многие пользователи FreeBSD слабо представляют себе что такое GEOM, для чего он нужен и как он работает. По разговорам в IRC и обсуждениям в списках рассылки видно, что даже те из пользователей, которые часто и много где используют различные GEOM классы, имеют смутное представлени о происходящих внутри GEOM процессах. В этой заметке я попробую прояснить некоторые моменты, которыми часто интересуются.

    Начнём с определений. Что такое GEOM класс? Классом он называется в связи с похожестью на классы в Си++. Т.е. это некоторая структура, содержащая в себе методы-функции которые реализуют возможности этого класса. Набор методов стандартный для всех классов. Грубо говоря, разработка какого-то нового класса - это новая реализация этих методов.

    GEOM класс может иметь объекты (а может и не иметь). Если проводить аналогии с классами Си++, то это переменные этого класса. Например, есть GEOM класс MIRROR, он предоставляет возможность создавать зеркала. Когда вы загружаете модуль ядра, либо он у вас вкомпилирован туда, то ядро "знает" о существовании такого класса. Но когда вы создаёте зеркало, вы создаёте объект этого класса.

    Каждый объект geom может иметь "провайдера" (provider), даже несколько. Провайдер, это в общем-то то, ради чего GEOM и придуман. Они предоставляют пользователю услугу реализуемую GEOM классом. Тот же GEOM класс MIRROR предоставляет нам провайдера, например mirror/gm0, работая с которым мы получаем "услугу", реализующую программное зеркало.

    Другим важным понятием является консьюмер (consumer). Консьюмер - это то, через что geom объект получает доступ к провайдерам других объектов geom. Пример: у вас есть два жестких диска. Кстати говоря, драйверы дисков создают geom объекты класса DISK. Вы можете выполнить команду "geom DISK list", чтобы просмотреть информацию об объектах класса DISK:

     > geom DISK list
    Geom name: ada0
    Providers:
    1. Name: ada0
       Mediasize: 41110142976 (38G)
       Sectorsize: 512
       Mode: r0w0e0
       fwsectors: 63
       fwheads: 16
    
    Geom name: ada1
    Providers:
    1. Name: ada1
       Mediasize: 163928604672 (153G)
       Sectorsize: 512
       Mode: r2w2e5
       fwsectors: 63
       fwheads: 16
    
    Как видно, у меня тут два диска ada0 и ada1. Они у меня разные да и заняты данными, так что портить для демонстрации я их не буду. Создам два идентичных объекта класса MD:

    # mdconfig -s 100m
    md0
    # mdconfig -s 100m
    md1
    # geom MD list
    Geom name: md0
    Providers:
    1. Name: md0
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r0w0e0
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 0
    
    Geom name: md1
    Providers:
    1. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r0w0e0
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 1
    Как видно из вывода команд, у нас есть два объекта md0 и md1, у каждого из них по одному провайдеру, соответственно md0 и md1. Теперь, при создании geom объекта класса MIRROR его консьюмеры подключатся к провайдерам объектов md0 и md1, и будет создан провайдер mirror/gm0:

    # gmirror label gm0 md0 md1
    # geom MIRROR list
    Geom name: gm0
    State: COMPLETE
    Components: 2
    Balance: load
    Slice: 4096
    Flags: NONE
    GenID: 0
    SyncID: 1
    ID: 7231867
    Providers:
    1. Name: mirror/gm0
       Mediasize: 104857088 (100M)
       Sectorsize: 512
       Mode: r0w0e0
    Consumers:
    1. Name: md0   Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       State: ACTIVE
       Priority: 0
       Flags: NONE
       GenID: 0
       SyncID: 1
       ID: 2647920667
    2. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       State: ACTIVE
       Priority: 1
       Flags: NONE
       GenID: 0
       SyncID: 1
       ID: 4248479672
    
    Думаю этого должно быть достаточно для понимания, если недостаточно, попробуйте прочитать ещё раз :)

    Глядя на вывод команды geom <КЛАСС> list можно увидеть много полезной информации, для каждого класса "полезность" определил разработчик, поэтому у каких-то классов больше, у других - меньше, у третьих вообще ничего нет...

    Я бы хотел обратить внимание на строку содержащую слово "Mode:" и набор букв с цифрами. Она есть у всех провайдеров и консьюмеров и показывает режим доступа, который осуществляется по отношению к ним на данный момент. В первом выводе "geom MD list" режим доступа к провайдерам отображается как "r0w0e0". Это означает, что никто не пытается с них читать (r = 0), никто не пытается на них записывать (w = 0) и никто не пытается получить эксклюзивный доступ к ним (e = 0).

    Эти счётчики служат не только для отображения информации о текущем состоянии провайдеров и консьюмеров. Они так же являются ограничителями доступа. Например, если кто-то уже получил эксклюзивный доступ к провайдеру, то любой другой желающий получить доступ к нему будет "обрадован" сообщением об ошибке EPERM - "Operation not permitted". Наверное, многие из вас сталкивались с этим сообщением при попытке записать загрузочный код на провайдер, который содержит смонтированные разделы. Именно эта "защита от прострела ноги" отключается переменной "kern.geom.debugflags=16".

    Если выполнить команду "geom MD list" сейчас, после создания зеркала на них, то мы увидим, что у провайдеров изменился режим:

    # geom MD list
    Geom name: md0
    Providers:
    1. Name: md0
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 0
    
    Geom name: md1
    Providers:
    1. Name: md1
       Mediasize: 104857600 (100M)
       Sectorsize: 512
       Mode: r1w1e1
       type: swap
       length: 104857600
       fwsectors: 0
       fwheads: 0
       unit: 1
    
    Это постарались консьюмеры объекта mirror/gm0. Т.е. теперь, эти провайдеры защищены от случайной записи с вашей стороны, а значит и объект mirror/gm0 не будет неприятно удивлён испорченными данными. Поэтому использование "kern.geom.debugflags=16" является плохой практикой.

    ... Продолжение следует ...

    понедельник, 22 ноября 2010 г.

    Резервное копирование таблицы разделов при помощи gpart

    Добавил в gpart(8) возможность выполнять резервное копирование и восстановление таблицы разделов. Уже давно что-то такое вертелось на уме и знакомые просили. Но вот, собрались трое, кому это нужно, в IRC и окончательно сформулировалась задача :)

    Резервное копирование выполняется при помощи команды "gpart backup", которая в специальном текстовом формате выводит информацию о таблице разделов. Эту информацию можно перенаправить в файл, либо отдать на вход команде "gpart restore". Небольшой пример:

    # gpart backup ada0
    MBR 4
    1 freebsd       63 20964762   [active]
    2    ntfs 20964825 40965750
    3 freebsd 61930575 18362295
    # gpart backup ada1
    GPT 128
    1   freebsd-boot        34       512 boot
    2   freebsd-swap       546   8388608 swap
    3    freebsd-zfs   8389154 311783869 zfs
    
    Как можно догадаться, первоя строка в выводе - это опции команды "gpart create", следующие - для "gpart add". У команды restore есть несколько ключей:

    gpart restore [-lF] [-f flags] provider [...]
    Ключ "-l" указывает gpart восстанавливать символические метки разделов, которые по-умолчанию не восстанавливаются. С ключем "-F" gpart перед восстановлением уничтожит таблицу разделов на носителе, куда будет восстанвливаться резервная копия. Иначе, если там будет обнаружена какая-либо таблица разделов, он просто завершится с ошибкой. Ну и ключ -f выполняет те же функции, что и у дргих подкоманд - откладывает запись всех изменений на носитель до вызова команды commit. Т.е. если вы не уверены в своих действиях - этот ключ ваш лучший товарищ.
    Теперь немного примеров по использованию. Типичные действия для резервного копирования:

    # gpart backup ada0 > ada0.backup
    # cat ada0.backup
    MBR 4
    1 freebsd       63 20964762   [active]
    2    ntfs 20964825 40965750
    3 freebsd 61930575 18362295
    Теперь этот ada0.backup на ответственное хранение в сейф. И вдруг, после очередной пьянкисмены паролей, утром вы обнаружили что таблица разделов уничтожена =)
    Бежите к сейфу, загружаетесь с livefs и восстанавливаете таблицу разделов:

    # gpart restore ada0 < /mnt/ada0.backup
    Теперь более реальный пример. Вы захотели к уже установленной системе подключить ещё один диск и сделать зеракало на ZFS:

    # gpart backup ada0 | gpart restore ada1
    
    

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

    # gpart backup ada0 | gpart restore ada1 ada2 
    
    Теперь пример для неопытных пользователей:

    # gpart backup ada0 | gpart restore -F -f x ada1
    # gpart show 
    
    О, боже! Не тот диск, вы уничтожили диск с фотографиями вашей жены! =) Спокойно, -f вас спас:

    # gpart undo ada1

    Вот так вот, немного шутя можно работать с gpart. А ещё у меня новость, мои менторы освободили меня от "надзора" :) Теперь я стал самостоятельным коммитером.

     PS. Ещё раз хочу заметить, что gpart backup/restore выполняют копирование и восстановление ТОЛЬКО таблицы разделов. Данные, находящиеся на этих разделах, бэкапить и восстанавливать нужно отдельно. В том числе, и содержимое в разделы freebsd-boot нужно записывать повторно.

    вторник, 9 ноября 2010 г.

    sade - редактор диска, часть 6.

    Вчера сделал MFC в stable/8 последних изменений в gpart. Теперь в грядущем релизе 8.2-RELEASE будут и возможность уничтожения таблицы без необходимости удаления всех её разделов, и восстановление повреждённой GPT. На ближайшее будущее новых идей относительно gpart вроде больше нет. Поэтому потихоньку вернулся к sade.

    Первым делом привёл всё к работоспособному виду. На данный момент, работать оно может только в 9.0-CURRENT из-за некоторых изменений, о которых я уже раньше писал. Затем, перелопатил весь код работы с файловыми системами, который приводил меня в уныние. Я отказался от идеи сделать какой-то универсальный интерфейс frontend-backend, который изначально пытался делать. Пока из-за отсутствия опыта работы с различными FS у меня не вырисовывается стройной картины того, как можно эту работу "универсально" организовать.

    Решил начать с малого. Переписал редактор файловых систем, теперь он предназначен для работы с UFS и весь код этой самой работы сосредоточен в файле ufsed.c. Что уже сдеалано: основное окно редактора предоставляет возможность посмотреть список найденных разделов с типом freebsd-ufs (в терминалогии gpart). При навигации по этому списку отображается информация о файловой системе, если она имеется на этом разделе, примерно вот так это выглядит:
    Логика такая. Что делал пользователь, когда у него не было sade? :) Он действовал по такому алгоритму:
    1. Создание таблицы разделов;
    2. Создание слайса FreeBSD;
    3. Создание разметки bsdlabel;
    4. Создание файловой системы;
    5. Сохранение информации о ней в /etc/fstab.
    Я решил придерживаться этого алгоритма внутри sade. Запустив sade, заходим в редактор разделов и выбираем из списка устройство, на котором создаём таблицу разделов, внутри неё создаём слайсы. Внутри слайса, если у него тип "freebsd" создаём таблицу BSD и размечаем разделы, выбирая "freebsd-ufs" или "freebsd-swap". Этим редакотором покрываются  первые 3 пункта + возможность записи загрузочного кода.
     Далее мы сохраняем всё что насоздавали в редакторе разделов, выходим в главное меню и заходим в редактор файловых систем. Там видим выше указанное окно. Сейчас у нас есть возможность создать новые файловые системы либо изменить уже существующие. Диалог создания файловой системы:
    Простейшую историю команд я реализовал, т.е. теперь любые действия не выполняются немедленно, а сохраняются в историю, которая применяется во время сохранения изменений, либо откатывается полностью. В общем-то, так же, как и в редакторе разделов.
    Для существующих файловых систем можно выбрать команду "Modify", которая открывает следующий диалог:
    По сути этот диалог является UI для утилиты tunefs. А предыдущий - для newfs. На данный момент редактор не делает никаких изменений, т.е. команды записываются в историю, но не выполняются, даже при сохранении. Нужно обдумать как это более правильно сделать. 
    Дальше планирую сделать возможность редактировать fstab, ну и, собственно, само сохранение изменений.