вторник, 12 ноября 2013 г.

Автоматическое изменение размера в GEOM_PART

Некоторое время назад Edward Tomasz Napierala выполнял проект, который позволяет изменять размеры файловых систем без необходимости их размонтирования (при определённых условиях конечно). В результате выполнения этого проекта у GEOM появились новый метод класса g_resize и вспомогательная функция g_resize_provider(). Так же были сделаны соответствующие изменения для некоторых GEOM классов, чтобы задействовать новый функционал.

Теперь несколько слов о том, как это работает.  В качестве целевой аудитории пользователей этого функционала предполагались владельцы умных СХД (систем хранения данных), которые могут динамически изменять размеры своих виртуальных дисков. Администратор такой системы может подвигать ползунком в веб морде СХД (например) и увеличить размер виртуального диска. Эта информация может быть донесена до SCSI диска, а драйвер диска передаёт её geom объекту класса GEOM_DISK. Geom объект вызывает функцию g_resize_provider() и это запускает цепочку действий по информированию всех consumer'ов этого диска об изменившемся размере. Если GEOM класс consumer'а имеет реализацию метода g_resize, то этот метод будет вызван. Таким образом GEOM класс может обработать это событие и выполнить какие-то свои действия.

Как же этот новый функционал можно применить в GEOM_PART? Самое малое было реализовано Эдвардом в рамках выполняемого им проекта - он сделал так, чтобы при выполнении команды gpart resize вызывалась функция g_resize_provider().  Это позволило по аналогии с цепочкой передачи события об изменении размера диска "СХД - драйвер диска - consumer'ы диска" реализовать цепочку "пользователь - раздел в таблице - consumer'ы раздела".  Т.е. теперь, все consumer'ы подключённые к разделу, размер которого изменился, могут что-то предпринять в ответ на это событие.
Если же диск меняет размер, то очевидно, что информация в метаданных таблицы разделов может перестать соответствовать действительности.  И здесь были нужны несколько большие изменения.

В итоге в FreeBSD 11.0-CURRENT сейчас сделано следующее. Для класса GEOM_PART реализован метод g_resize и теперь, при получении события об изменении размера родительского провайдера, будет произведена корректировка метаданных. Но, чтобы уменьшить вероятность совершения тяжело поправимых ошибок со стороны пользователя, эти изменения в метаданных не записываются сразу же на диск. Вместо этого в консоль выводится сообщение "GEOM_PART: %s was automatically resized" и диск помечается как "modified". Увидев такое сообщение в консоли или в системном журнале сообщений, пользователь должен сам решить, хочет он сохранить эти изменения или нет. Для сохранения ему необходимо выполнить команду gpart commit, или сделать любые изменения в таблице разделов, которые вызывают gpart commit неявно.

Это реализовано практически для всех поддерживаемых таблиц разделов, за исключением LDM и GPT. GPT имеет возможность быть помеченной CORRUPT, и для неё может быть использована команды recover, так что не особенно актуально. А LDM поддерживается в режиме только для чтения.

четверг, 11 апреля 2013 г.

PCPU счётчики в ядре

Несколько дней назад Глеб Смирнов (glebius@) добавил в ядро FreeBSD 10-CURRENT новое API для работы со счётчиками - counter(9). Счётчики в системе используются для ведения статистики. Обычно это структура данных, значения полей которой изменяются при возникновении тех или иных событий. Например, получение сетевого пакета, обнаружение некорректных данных, подсчёт количества определённых действий совершённых системой и т.п. Некоторые счётчики изменяются относительно редко, а некоторые могут изменяться миллионы раз в секунду. Ядром используется обычно два способа их изменения - это простой инкремент (операция добавления) или атомарное добавление (atomic(9)). 
У обоих методов есть недостатки. Обычный инкремент на многопроцессорных/многоядерных системах приводит к потере данных, т.е. когда несколько ядер одновременно изменяют одну и ту же переменную, выполнить это удаётся, грубо говоря, только одному ядру. Поэтому статистика временами заметно не соответствует действительности.
Атомарные операции не теряют данные благодаря блокированию шины памяти, что значительно сказывается на скорости выполнения. 
Общим недостатком является то, что изменение счётчика приводит к инвалидированию cache line, в которой находится счётчик из кешей процессоров, что опять же отрицательно сказывается на производительности.
Теперь о том, как эти проблемы решает новое API. Для каждого ядра процессора выделяется своя область памяти, а счётчики преобразуются таким образом, что каждому ядру соответствует своя копия счётчика. Поэтому, даже при одновременном изменении счётчика несколькими ядрами, каждое ядро будет изменять свой счётчик. При этом не будет происходить блокирование шины памяти, не будет потери данных и не будет вымывания кеша, т.к. каждое ядро обращается к своим данным. 
Чтобы обновить счётчик, нужно вычислить адрес счётчика в памяти приватной для данного ядра, а затем произвести его изменение. Для того чтобы в это время контекст не мигрировал на другое ядро, используются критические секции. Однако, не на всех архитектурах это необходимо и с помощью некоторых know-how удаётся избежать использования критических секций. В частности, на нашей основной архитектуре amd64 обновление счётчика осуществляется в одну процессорную инструкцию!
Для того чтобы прочитать значение счётчика, нужно сложить значения копий счётчика со всех ядер. Ну и объём памяти, занимаемый счётчиком, конечно же стал в несколько раз больше (в зависимости от числа ядер процессоров).
Это, на мой взгляд, приемлемая плата вот за какие результаты:
  • в ряде тестов новые счётчики показывают повышение производительности примерно на 63% по сравнению с обычной операцией инкремента, но при этом нет потерь данных, а обычный инкремент теряет 98% данных;
  • в сравнении с атомарными инкрементами новые счётчики в 22 раза быстрее;
  • на реальных тестах по приёму сетевого трафика замена счётчиков IP статистики приводит к снижению нагрузки на CPU примерно на 50%.

воскресенье, 2 декабря 2012 г.

Что нового?

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

Первой ласточкой стало то, чего давно хотелось. Ещё когда я не был коммитером, хотелось чтобы ipfw fwd работало без перекомпиляции ядра. Но тогда на моё предложение возразили, что этот функционал значительно снижает быстродействие сетевой подсистемы. Но как показали испытания, никакого снижения замечено не было. В системе хватает других мест, которые вносят значительные "тормоза".

Итак, вероятно некоторые уже заметили, что в src/UPDATING появилась запись, информирующая об удалении опции IPFIREWALL_FORWARD. Теперь этот функционал работает "из коробки", достаточно просто загрузить модуль ядра ipfw.ko. В stable/9 это изменение уже тоже есть, но в релиз оно не попадёт.

Другим крупным шагом был MFC изменений в загрузчике в stable/9. Теперь код загрузчиков в 9-ке практически идентичен тому, что в 10-ке. Я ещё думаю о переносе изменений в 8-ку, но, честно говоря не хочется этого делать. Уж очень сильно в 8-ке он отличается и придётся сделать MFC ещё для кучи ревизий. Так же, можно поблагодарить Андрея avg@ за исправление проблемы в загрузочном коде, которая обнаруживалась на некоторых "кривых" BIOS.

Кроме того, был осуществлён первых подход к изучению и тестированию IPv6 стека на производительность. Который показал плачевное состояние в этой области. Правда, благодаря профилированию и нескольким небольшим изменениям, удалось улучшить результаты почти в три раза. Не буду озвучивать цифры, они сильно зависят от железа. Но факт в том, что оптимизация IPv6 во FreeBSD - это ещё "напахано поле". Впрочем, в IPv4 базовая система тоже не блещет результатами и всего несколько строк в /boot/loader.conf и /etc/sysctl.conf может улучшить эти результаты в разы.

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

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

суббота, 1 сентября 2012 г.

FreeBSD mini-summit в Москве

25-го августа в Москве состоялся уже второй саммит разработчиков FreeBSD. Как и в прошлый раз он проходил в офисе компании Рамблер, правда сейчас это уже был новый офис :)
Формат встречи был прежним - доклады, вопросы, затем обсуждения. Так как народу в этот раз было поменьше, а места было побольше, то обсуждения получились интересными и продуктивными.

Первый доклад сделал Максим Евменкин из компании Netflix. Честно говоря, я об этой компании раньше слышал немного, а оказывается заграницей она довольно известна. Компания предлагает решения по доставке медиаконтента, причём организованы эти решения на основе FreeBSD. Сборка FreeBSD 9-STABLE, из которой убрано всё лишнее, установлена на зеркало из двух SSD внутри "чёрного ящика", в который дополнительно установлено ещё несколько десятков 3-4 ТБ жестких дисков. Неожиданным для меня стало то, что на этих дисках используется UFSJ. Как рассказал Максим, почти единственное, чем система отличается от обычной FreeBSD - несколько патчей к драйверам сетевых карт Intel, все остальные изменения и исправления они стараются возвращать в базовую систему. А патчи для карт Intel являются довольно специфичными и наталкиваются на непонимание/нежелание майнтейнера драйвера. 
Что ещё хотелось бы упомянуть - это используемое ими решение для автоматического обновления системы. Принцип такой: на SSD носителях используется GPT и два зеркалируемых раздела. Одно зеркало - это текущая рабочая система, второе - место, куда записывается новая "firmware". Перед перезагрузкой на разделы, куда записалась прошивка устанавливается FreeBSD-specific GPT атрибут bootonce, который говорит загрузчику gptboot пробовать загружаться сначала с указанного раздела. Если загрузка не удалась, либо произошла паника и т.п., то при следующей загрузке система будет загружаться снова с первого зеркала, которое является рабочим. Подробнее об этом атрибуте можно прочитать в странице gpart(8).

Глеб Смирнов подготовил доклад о работе, проделанной им над пакетным фильтром pf в отдельном бранче projects/pf. Теперь pf стал значительно эффективнее работать на SMP системах, что, например, можно увидеть на графике загрузки тестовой машины.

Константин Белоусов рассказал о причинах возникновения дэдлока между подсистемами VM и VFS, какие методы были опробованы им для решения этой проблемы, и как в итоге  проблема была решена.

вторник, 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.