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

воскресенье, 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 сек

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

понедельник, 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). Но пока работа в этом направлении идёт неспешно.