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

воскресенье, 30 сентября 2012 г.

loader(8): оптимизация и результаты.

Наконец-то я нашёл время и ресурсы на проведение оптимизации в загрузчике системы. Организовал стенд на базе VMWare ESXi, в которой я создал несколько виртуалок с FreeBSD 10, установленных с использованием различных комбинаций таблиц разделов и файловых систем. Расшарил по NFS рабочий каталог с исходниками системы со своей машины, подключил его к виртуалкам и начал отладку... Ну как "отладку", добавил несколько printf'ов в libstand и в драйвер диска biosdisk; включил отладку в недавно добавленном модуле с DISK API; в виртуалках создал файл /boot.config с опциями "-D -S115200", в VMWare настроил запись вывода с последовательного порта в файл и начал тесты.

Получив логи, начал смотреть код и искать способы уменьшения количества дисковых операций, которых оказалось великое множество. В итоге, в модуль с DISK API был добавлен код кеширования обращений к дискам. А нужно сказать, что почти каждое открытие файла с использованием libstand приводит к чтению метаданных таблиц разделов. Чтобы уменьшить это число обращений раньше использовался bcache - блочный кэш. Но, printf'ы внутри дискового драйвера показали, что его эффективность довольно низка, а при использовании GPT - совсем удручает.

Итак, новый кэш работает по другому. При открытии файла, запрос open() приводит к вызову функции devopen(), которая и "открывает" текущее устройство (обычно диск, с которого происходит загрузка), например, disk1p3. Открытие диска - это чтение его метаданных, поиск таблиц разделов и определение смещения, с которого начинается открываемый раздел. Каждое успешное открытие добавляет в кеш запись. Таким образом, при открытии следующего файла на этом диске, уже не нужно будет читать метаданные, а смещение будет прочитано из кэша. В результате, если раньше при загрузке с UFS чтение метаданных выполнялось примерно 50 раз (с учётом попаданий в блочный кеш - около 30), то сейчас это происходит 1 раз.

Далее меня заинтересовало почему при открытии, например, ядра или его модулей, в логе загрузки эта операция отображается несколько раз (от трёх до четырёх)? Т.е. конечно чтение всего ядра не происходит четыре раза, но каждое открытие сопровождается чтением некоторого объёма данных, и только последнее открытие читает заметно больший объём данных. Как оказалось, для архитектуры x86/x64 loader одновременно поддерживает несколько форматов ELF - 32-битные и 64-битные. Причём в массиве, где перечислены указатели на обработчики этих форматов первыми стоят 32-битные, а затем уже 64-битные. Поэтому loader при загрузке модулей ядра в цикле "пробует" модуль каждым обработчиком, пока не найдёт подходящий. И так как у меня тестовые машины 64-битные, то обработчик ядра стоит  3-им в этом массиве, а обработчик модулей ядра - 4-ым.

В общем, эта досадная несправедливость в отношении 64-битных систем была мной ликвидирована тоже. В итоге, я сравнил результаты по затратам времени загрузки с момента старта loader'а и до момента запуска ядра в разных конфигурациях системы:

UFS+GPTZFS+GPTZFS+GPT+несколько дисков
Старый loader7,203 сек20,584 сек26,079 сек
Обновлённый loader4,334 сек9,422 сек11,245 сек

Во всех случаях, кроме самого ядра загружалось ещё несколько модулей.

вторник, 7 августа 2012 г.

Обновление загрузчика

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

Немного об изменениях. Было добавлено два программных интерфейса, назовём их PART и DISK. Первый предоставляет функции для работы с различными таблицами разделов: поиск метаданных таблиц разделов, парсинг и формирование списков разделов, получение информации по каждому разделу и т.п. На данный момент поддерживаются GPT, MBR, EBR, BSD label и VTOC8. Набор поддерживаемых таблиц разделов определяется переменными make на этапе компиляции. Обычно это GPT, MBR+EBR и BSD label.

PART API можно использовать напрямую, как это теперь делает ZFS, либо через DISK API. DISK API предназначен для использования в дисковых драйверах загрузчика. Интерфейс вобрал в себя всё общее, что есть между драйверами дисков загрузчиков разных аппаратных архитектур. Это позволило, во-первых, значительно упростить код драйверов, а, во-вторых, унифицировать алгоритмы их работы относительно определения таблиц разделов. Причём, эти алгоритмы почти на 100% совпадают с используемыми ядром. Поэтому, теперь не должно возникать ситуаций, когда загрузчик видит одни разделы, а ядро другие. Раньше такое было вполне возможно.

Основные особенности, которые попали в CURRENT:
  • теперь loader умеет считать контрольную сумму для GPT заголовка и таблицы, а так же использовать резервную копию GPT;
  • PMBR так же научился работать с резервной GPT;
  • в качестве дополнительного бонуса, улучшенная поддержка EBR позволяет загрузить систему с логического тома. Правда boot0 делать этого не умеет, на сколько я помню.
  • Так как ZFS научилась читать таблицы разделов, теперь она не перебирает все возможные комбинации разделов, а тестирует только те, на которых ZFS может быть установлена. Т.е. это разделы с типом "freebsd" и "freebsd-zfs", так что, если у вас есть загрузочный пул на разделах с другим типом, это нужно исправлять.
От идеи с определением метаданных GEOM в загрузчике пока отказались. Так же остались ещё патчи для uboot и архитектур arm/powerpc, которые так никто и не протестировал.

Мой студент в GSoC прошёл первый "отборочный тур", пока с небольшим отставанием от графика, но надежда на то, что скоро у нас на x86 заработает загрузка через EFI есть.

Дальше я планирую провести серию тестов по загрузке системы в различных конфигурациях с разным размером дискового кеша. Сейчас по-умолчанию его размер всего 16 Кбайт, и как показали первые тесты, его увеличение даёт прирост в скорости загрузки на несколько секунд, по крайней мере на моей машине.

В свободное время почитал код различных библиотек, утилит, загрузчиков и UEFI шелла, а так же документацию, какую удалось найти. Теперь уже гораздо лучше представлю, как будет работать наш UEFI загрузчик. Так что, если студент не справится, его работа не пропадёт даром :)

среда, 27 июня 2012 г.

Тестирование патчей для загрузчика

Прошло уже почти три месяца со дня публикации сообщения о патчах к загрузочному коду. И вот, на прошлой неделе я решил заняться этим вплотную. В итоге, создал бранч в SVN, куда стал коммитить свои наработки. Теперь все жалющие принять участие в тестировании могут это сделать :)

Историю изменений можно посмотреть здесь: http://svn.freebsd.org/base/user/ae/bootcode/
Патч относительно 10-CURRENT тут: http://people.freebsd.org/~ae/boot.diff

Какие изменения были сделаны:
  1. На основе кода, используемого в GEOM_PART были написаны функции, предоставляющий интерфейс для поиска таблицы разделов на носителе и выдачи информации о разделах.
  2. Было удалено много старого, дублирующегося кода.
  3. Похожий по назнаению код в "драйверах" дисков  загрузчика был унифицирован и объединён в нескольких функциях, а так же переписан с использованием нового API работы с таблицами разделов.
  4. zfsloader был научен использовать информацию о таблицах разелов, что значительно ускорило его работу на системах, в которых установлено много жёстких дисков.
  5. loader теперь умеет полноценно работать с GPT, т.е. проверяеят контрольные суммы, в случае необходимости умеет находить резервную копию GPT.
  6. В код PMBR так же внесены изменения, которые научили его находить резервную копию GPT, только CRC он не проверяет из-за ограничений на объём кода.
  7. gptboot и PMBR при обнаружении повреждений в заголовке основной таблицы GPT ищут резервную копию в последнем секторе. Если же там её нет, но есть метаданные GEOM класса, то они смотрят в предыдущем секторе. Т.е. теперь, иметь GPT внутри GEOM_MIRROR стало ещё безопаснее, даже в случае поверждения основной таблицы, загрузочный код сможет найти резервную.
На 9-ку патчи пока не накладываются, возможно после выходных avg@ сделает MFC нескольких своих изменений в коде, обеспечивающем загрузку с ZFS, тогда будет возможность тестировать и на STABLE.

понедельник, 2 апреля 2012 г.

Проблемы в работе zfsloader во FreeBSD

Сначала небольшой оффтопик - поддержка LDM уже включена во FreeBSD 10-CURRENT и в ближайшие пару недель будет перенесена в STABLE. В отличие от первоначальных патчей, текущая реализация умеет работать с дисками, размеченными GPT. А в остальном всё осталось примерно так же.

Теперь о теме сообщения. Возможно некоторые из вас замечали, что при загрузке FreeBSD c ZFS в качестве корневой файловой системы, наблюдается некоторая задержка между идентификацией дисков и началом загрузки ядра. Эта задержка в ряде случаев (например, большое количество дисков; много разделов; система в гостевой виртуальной машине) может составлять довольно ощутимое время.

Потратив некоторое время на изучение кода в каталоге sys/boot, могу сделать вывод, что работа нашего загрузчика zfsloader далеко не оптимальна. И корни этой проблемы растут с давних времён.

Во-первых, на данный момент из-за "ограниченности vs. универсальности" интерфейса libstand(3) нет никакой возможности определить количество найденных дисков из кода инициализации ZFS. Поэтому ZFS просто последовательно пробует открыть каждый диск поддерживаемый загрузчиком, а это 32 штуки (от "disk0:" до "disk31:").

Во-вторых, опять же, нет никакой возможности из кода инициализации ZFS узнать тип таблицы разделов и количество разделов в ней, чтобы проверить наличие ZFS на этих разделах. Поэтому выполняется простой перебор всех возможных разделов из диапазона от 1 до 128, например, выполняется попытка откыть "disk0p1:", если не удалось, то делается попытка открыть "disk0s1:" и т.д. до 128.

В-третьих, из-за привязанности к интерфейсу libstand(3), код работы с дисковыми устройствами выполняет чтение таблицы разделов, её проверку и т.д. на каждый вызов open(2). Т.е. когда код инициализации ZFS открывает "disk0:", затем открывает каждый его раздел. Все эти действия приводят к повторному чтению таблицы разделов.

Это частично компенсируется наличием уровня блочного кэша (да, даже у загрузчика он есть). Но размер его всего 16 кБайт, поэтому промахи имеют место быть, особенно для случая с GPT, когда таблица разделов занимает от 34 секторов.

В-четвёртых, для случая с GPT не проверяется ни контрольная сумма, ни резервная таблица с заголовком. Т.е. возможен вариант, когда у второй ступени загрузочного кода gptzfsboot и zfsloader будут разные представления о таблицах разделов.

В итоге, я написал вот такой вот патч, который решает часть проблем, но всё же далеко не идеален. Хотя, судя по отзывам увеличивает скорость инициализации загрузчика на порядок.

В идеале же хочется выделить логику работы с таблицами разделов (а он встречается в sys/boot не один и не два раза) в отдельные файлы с необходимым интерфейсом, не зависящим от libstand(3). Но пока работа в этом направлении идёт неспешно.