При использовании нашей операционной системы пользователи регулярно обращают внимание на то, что процесс её загрузки от передачи управления ядру, до начала выполнения прикладного кода, не всегда прозрачный. В результате анализа обращений мы пришли к выводу, что необходимо внедрить штатный механизм информирования и профилирования процесса загрузки системы (boot sequence). Основной особенностью внедренного решения является его децентрализованность, поскольку ОС является микроядерной и все привычные системные сервисы реализованы вне ядра. Технология прикладного профилирования (тот же gprof) в данном случае не самый подходящий инструмент.

Особенности архитектуры и процедуры загрузки ЗОСРВ «Нейтрино»

Процесс загрузки микроядерных ОС отличается от загрузки монолитных. Одной из главных особенностей ЗОСРВ «Нейтрино» является развитая инфраструктура менеджеров ресурсов, которая функционирует вокруг микроядра. Менеджер ресурсов (он же resource manager, resmgr) — это серверный процесс, который обслуживает клиентские сообщения в контексте стандарта POSIX и может взаимодействовать с оборудованием. Все драйверы устройств, файловые системы и другие типичные низкоуровневые сервисы в ЗОСРВ «Нейтрино» реализуются именно так.

После отработки первичного и вторичного загрузчиков, управление передается так называемому загрузочному образу системы, который представляет собой бинарный слепок простейшей файловой системы, включающий ядро и минимально-необходимый набор менеджеров. Кроме того в него входит shell-подобный загрузочный скрипт, который и запускает те или иные сервисы, утилиты и драйвера. Для полнофункциональной загрузки в этот набор входит как минимум драйвер блочного устройства или сетевые компоненты, а также менеджер файловой системы. Это позволяет подмонтировать носитель и начать запускать всё остальное. Минимальный образ может не включать и это, ограничиваясь, например, одним shell-интерпретатором и драйвером последовательного порта.

В ЗОСРВ “Нейтрино”  существуют два типа загрузочных образов, так называемые кастомизированные образы и автоматизированные образы, основанные на сервисе diskboot. Первые создаются для конкретных аппаратных конфигураций с запуском лишь тех служб, менеджеров и драйверов, которые нужны пользователю и необходимы для работоспособности конкретной системы. Образы, основанные на diskboot, обычно осуществляют загрузку системы в автоматическом режиме с наиболее полным запуском всего необходимого для работы. Именно на этом этапе и возникает проблема у наших пользователей. В автоматическом режиме судить о происходящем можно лишь по перечню запущенных в итоге процессов, когда уже утрачены сведения о принимаемых на этапе загрузки решениях. Кроме того, в обоих случаях теряется информация о том сколько времени затрачено на детектирование, запуск и инициализацию конкретных служб.

В основе своей работы diskboot использует принцип поиска устройств (например, на шине PCI). Используя встроенные перечни поддерживаемого оборудования и файлы конфигурации по уникальным идентификаторам VID / DID (Vendor ID / Device ID) он запускает нужные процессы с нужными параметрами. При каждом запуске системы и загрузки из образа diskboot будет производить все эти манипуляции, что не может не влиять на скорость запуска ОС. В тех случаях, когда это является ключевым фактором, рекомендуется заниматься кастомизацией образов под конкретную задачу.

В связи с этим появилась идея создать набор инструментов, которые могли бы применяться для профилирования на этапе загрузки системы.

Модель работы diskboot (boot sequence при автоматической загрузке)

Для начала разберемся как и что запускает diskboot при старте системы. Он запускает службу системного журнала (slogger), менеджер распределения системных ресурсов для x86-совместимых платформ, сервер pci-bios, файловые системы для всех разделов на блочных устройствах и монтирует их.

После этого выполняется сценарий /etc/system/sysinit, обеспечивающий:

• выполнение файла /etc/rc.d/rc.sysinit, обеспечивающего исполнение других /etc/rc.d/rc.* и последующий запуск shell-сессии через менеджер tinit для обеспечения возможности входа в систему.

•  выполнение утилиты enum-devices для определения имеющихся в наличии устройств;

•  запуск системных служб, свойственных монолитным UNIX-подобным ОС;

Библиотека профилирования boot sequence

Для решения этой задачи была разработана библиотека профилирования запуска процессов при загрузке ОС (bprofile). Функции этой библиотеки направлены на добавление временных меток в системный журнал. Метки вычитываются через макрос системной библиотеки SYSPAGE_ENTRY(). Он возвращает указатель на запись в системной страницы, которая содержит поле nsec в структуре qtime - количество наносекунд, прошедших с момента старта системы.

Пример кода:

/* Количество секунд/милисекунд с момента старта системы */

uptime_sec = SYSPAGE_ENTRY( qtime ) -> nsec / 1000000000;
uptime_nsec = SYSPAGE_ENTRY( qtime ) -> nsec / 1000000 % 1000;

Функция инициализации библиотеки bprofile_init() принимает на вход уровень профилирования и флаг для вывода временных меток на экран (в stdout), если это нужно. Для возможности передавать параметры профилирования дочерним процессам используются переменные окружения PROFILE_BOOT и PROFILE_BOOT_PRINT. Они определяют наследуемое поведение библиотеки профилирования в дочернем процессе. Значение этих переменных:

Для PROFILE_BOOT:

  • 0 -  Отключение профилирования.

  • 1  -  Включение профилирования. 

  • 2 - Включение более подробного профилирования с указанием аргументов запускаемых команд.

Для PROFILE_BOOT_PRINT:

  • 0 - Выводить сведения только в системный журнал

  • 1 - Дополнительный вывод в stdout

Профилирование образов с diskboot

Для автоматизированных загрузочных образов единственной точкой входа в boot sequence является diskboot, который пришлось модифицировать. Ему были добавлены ключи:

  • -t режим — Включить профилирование с использованием системного журнала. Значения аргумента режим могут принимать следующие значения:

    • 0 - Отключение профилирования.

    • 1 - Включение профилирования (данный режим включен по умолчанию).

    • 2 - Включение более подробного профилирования с указанием аргументов запускаемых команд.

  • -T — Включить профилирование с выводом результатов на консоль. Работает при включенном профилировании.

Таким образом, в этом виде образов достаточно загрузиться и проанализировать результаты (см. далее).

Профилирование образов, не использующих diskboot

Для профилирования образов, не использующих diskboot, используется утилита btime (основанная на библиотеке bprofile). Для выставления временных меток она также использует макрос SYSPAGE_ENTRY().

Фрагмент загрузочного скрипта (файла построения загрузочного образа) с использованием утилиты btime для профилирования:

[+script] startup-script = {

    procmgr_symlink ../../proc/boot/libc-ksz.so.3 /usr/lib/ldqnx.so.2

    display_msg Запуск менеджера системного журнала

    btime -p -s "Starting slogger"

    slogger &

    waitfor /dev/slog

    display_msg Запуск системных сервисов, менеджера каналов и очередей

    btime -p -s "Starting devc-pty"

    devc-pty

    ...
}

В данном примере перед запуском утилиты slogger запускается утилита btime, которая будет выводить сообщение с временной меткой в stdout и в системный журнал. Перед запуском devc-pty тоже запускается утилита btime, так можно будет увидеть временную разницу между запуском этих двух процессов. Более полные примеры файлов построения загрузочных образов доступны в самом дистрибутиве ОС.

Если планируется использовать системные скрипты в образах, не использующих diskboot, для включения профилирования в них следует определить переменные PROFILE_BOOT и PROFILE_BOOT_PRINT в загрузочном скрипте и там же вызвать их.

Пример:

PROFILE_BOOT=2
PROFILE_BOOT_PRINT=1

Утилита btime также может использоваться для профилирования собственных shell-скриптов.

Анализ результатов профилирования

Для более удобного просмотра результатов профилирования имеется утилита bootlog. Данная утилита выводит только результаты профилирования из системного журнала, игнорируя любые другие сообщения из системного журнала. 

Пример вывода: 

[0.004 с.] Starting slogger -s256
[0.004 с.] Starting seedres
[0.005 с.] Starting pci-bios
[0.107 с.] Starting /proc/boot/io-usb -duhci -dohci -dehci -dxhci
[2.318 с.] Starting /proc/boot/devc-con-hid
[8.554 с.] Starting devb-ahci blk auto=partition dos exe=all qnx6 sync=optional 
...

Если проанализировать представленный вывод, то видно, что разница между запуском devc-con-hid и devb-ahci отличается на ~6 сек. Это вполне логично, поскольку в этот временной промежуток у пользователя запрашивается нажатие клавиши пробел для выбора дополнительных параметров загрузки (этим грешат только diskboot-based образы).

Как альтернативу утилиты bootlog, можно использовать системный журнал, где также содержатся временные метки и лог загрузки, но они перемежаются с другими сообщениями в системном журнале.

Фрагмент дампа системного журнала (см. sloginfo):

May 29 13:46:18  5  1  1 Starting slogger -s256
May 29 13:46:18  5  1  1 Starting seedres
May 29 13:46:18  5  1  1 Starting pci-bios
May 29 13:46:18  5  1  1 Starting /proc/boot/io-usb -duhci -dohci -dehci -dxhci
May 29 13:46:20  5  1  1 Starting /proc/boot/devc-con-hid
May 29 13:46:26  5  1  1 Starting devb-ahci blk auto=partition dos exe=all qnx6 ...
...

Заключение

В следующем номерном релизе ЗОСРВ "Нейтрино" описанная функциональность станет доступна пользователям “из коробки”, что сделает процесс загрузки более прозрачным, и позволит анализировать какие процессы запускаются и с какими характеристиками.

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

Комментарии (2)


  1. Apoheliy
    08.07.2023 08:33

    uptime_sec = SYSPAGE_ENTRY( qtime ) -> nsec / 1000000000;
    uptime_nsec = SYSPAGE_ENTRY( qtime ) -> nsec / 1000000 % 1000;

    Тут вопрос: зачем вызывать SYSPAGE ... nsec дважды? - время же может уйти вперёд, скакнуть через границу секунды и можно получить "назад в будущее":

    8.554 ....

    8.000 - следующая запись в логе

    Также можно uptime_nsec переименовать в uptime_msec. А ещё лучше оставить наносекунды ... зачем загрублять часы?


    1. a-n-d
      08.07.2023 08:33

      Это лишь пример кода, поясняющий о чем идет речь. Он не используется в таком виде во внедренных механизмах. Но Ваше замечание по своей сути верно.