Ozon разработал собственный инструмент для генерации и редактирования фона на изображении товаров, который работает с помощью алгоритмов машинного обучения.
По прогнозам экспертов, число предприятий, использующих отечественные решения, возрастет до 80% к концу 2024 г.
Сергей Темерханов | Auriga Inc, Игорь Починок | НИВЦ МГУ
В статье описывается применение процессоров ARMv8 в серверных (и опционально во встраиваемых) системах с точки зрения микропрограммных модулей и компонентов.
С момента своего релиза в 2011 году процессорная архитектура ARMv8 получила достаточно широкое распространение на рынке мобильных устройств. По прогнозам главы компании ARM Limited, к 2020 году процессоры этого поколения займут до 25% мирового рынка. Вполне закономерно, что и программная составляющая сформирована и развивается, наследуя черты и общие принципы исторически сложившейся инфраструктуры.
Принципиально иная ситуация сложилась в серверном сегменте. На этом рынке уже продолжительное время доминируют серверы на базе x86, и ARMv8 только начинает проникновение на него, причём речь пока идёт только об определённых специфических нишах. Новизна этого рынка для ARM и тот факт, что большая часть принятых стандартов и спецификаций (в первую очередь ACPI и UEFI) до недавнего времени не были адаптированы для ARM-систем, накладывает свой отпечаток на развитие программной инфраструктуры.
Данная статья сфокусирована на рассмотрении особенностей серверных ARM-систем и процессов и не претендует на исчерпывающее описание. Авторы хотели бы обратить внимание также на том, что изложенные здесь данные могут достаточно быстро устареть, поскольку уже в обозримом будущем новые процессоры будут сопровождаться новыми техническими решениями, которые потребуют иного подхода к реализации программной инфраструктуры.
Прежде всего следует указать, что нынешние реализации микропрограммных средств для серверных систем на ARMv8 состоят из нескольких относительно независимых компонентов. Это даёт ряд преимуществ, например возможность использования одних и тех же компонентов в микропрограммных средствах как серверных, так и встраиваемых систем, а также относительную независимость вносимых изменений.
Итак, какие же модули и компоненты используются в рассматриваемых системах, и каковы их функции? Общая схема загрузки и взаимодействия модулей показана на рис. 1. Процесс начинается с инициализации оборудования таких подсистем, как оперативная память и межпроцессорные интерфейсы. В текущих реализациях она исполняется отдельным модулем в режиме EL3S сразу после включения питания. Таким образом, данный компонент системы обладает максимально возможными привилегиями. Он обычно не взаимодействует с ОС непосредственно.
Далее управление передаётся следующему компоненту, чаще всего модулю ARM Trusted Firmware (ATF), исполняемому в том же режиме. Управление ATF может передаваться как непосредственно от загрузчика 0-го уровня, рассмотренного в предыдущем абзаце, так и опосредованно через специальный модуль UEFI, который реализует так называемую процедуру загрузки PEI (Pre- EFI Initialization). ATF состоит из нескольких модулей, получающих управление в разные моменты времени. Стартовый модуль BL1 выполняет инициализацию частей платформы, связанную с доверенным (secure) режимом работы процессора. Поскольку в системах на базе ARMv8 применяется аппаратное разделение доверенных и обычных ресурсов, включая оперативную память, то модуль BL1 создаёт такое окружение, в котором мог бы функционировать доверенный код. В частности, к этому типу инициализации относится настройка контроллеров памяти/кэша (чаще всего доверенные и обычные зоны размечаются путём программирования регистров в этих устройствах) и разметка накристалльных устройств (контроллеры энергонезависимой памяти). Такая разметка также вводит и фильтрацию DMA-транзакций на основе типа устройства (доверенный/недоверенный). При этом запись/чтение памяти возможны только в область/из области того же типа, что и устройство.
Реализация доверенного окружения может быть достаточно сложной, например включающей отдельную ОС. Однако описание подобных реализаций выходит за рамки данной статьи. Модуль BL1 настраивает таблицу преобразования адресов MMU, а также таблицу обработчиков исключений, самым важным элементом которой является обработчик исключения от инструкции вызова доверенного окружения (Secure Monitor Call, SMC). На данном этапе обработчик минимален и способен фактически только передавать управление загруженным в оперативную память образам. В процессе исполнения модуль BL1 загружает в ОЗУ следующую стадию, BL2, и передаёт управление на неё. Модуль BL2 работает с пониженными привилегиями, в режиме EL1S. Соответственно, передача управления этому модулю производится с помощью инструкции ERET.
Предназначение модуля BL2 — загрузка остальных микропрограммных модулей (частей BL3) и передача им управления. Пониженный уровень привилегий используется для того, чтобы не повредить код и данные EL3S, уже находящиеся в памяти. Управление этим частям передаётся путём вызова кода EL3S, размещённого на стадии BL1, с помощью инструкции SMC.
Тр е т ь я с т а д и я загрузки и инициализации ATF может состоять из трёх этапов, но довольно часто второй этап пропускается, так что на деле и их остаётся только два. Модуль BL3-1 представляет собой ту часть доверенного кода, которая доступна обычному ПО (ОС и т. д.) во время его исполнения. Ключевая часть этого модуля – функция обработки исключения, вызванного инструкцией SMC. В самом модуле располагаются функции реализации стандартных вызовов SMC: код, реализующий стандартизированный интерфейс PSCI (предназначен для управления всей платформой: включения/отключения процессорных ядер, питания всей платформы, перезагрузки и т. д.), а также отвечающий за вызовы, зависящие от производителя и конкретной реализации платформы (получение информации о платформе, управление некоторыми встроенными устройствами и т. п.).
Наличие модуля BL3-2, как мы уже отмечали выше, не является обязательным; его код (в случае наличия модуля) выполняется в режиме EL1S. Обычно он служит в качестве специализированного сервиса/монитора событий, происходящих во время работы платформы (прерывания от некоторых таймеров, устройств и т. п.).
BL3-3, по сути, модулем ATF не является. Это образ прошивки (firmware), исполняемой в незащищённом режиме. Запускается он обычно в режиме EL2 и представляет собой образ либо загрузчика наподобие широко известного U-Boot, либо окружения UEFI, стандартного для серверных систем.
Общая схема загрузки модулей ATF показана на рис. 2.
На ряде серверных систем на базе ARMv8 применяется другая схема загрузки: ATF запускается во время фазы UEFI PEI, после чего происходит переход на фазу UEFI DXE.
UEFI для ARMv8 значительно отличается от такового для x86. Фазы PEI (Pre-EFI initialization) и DXE (Driver) используются и для x86, и для ARMv8. Однако на многих системах ARMv8 фаза PEI значительно сокра-щена и во время неё не выполняется никакой инициализации оборудования. Этот этап сводится к настройке таблиц трансляции MMU, настройке контроллера прерываний и таймера (согласно спецификации UEFI, единственным обрабатываемым прерыванием для этого окружения является прерывание от таймера), построению блоков конфигурационной информации (EFI Hand-off block, HOB) и запуску модуля DXE Core. На данном этапе платформозависимые модули UEFI достаточно широко используют специфические для платформы вызовы SMC, описанные ранее.
На этапе DXE выполняется основной объём работы UEFI. Прежде всего это развёртывание и запуск драйверов аппаратного обеспечения — как встроенных в кристалл блоков, так и внешних устройств, подключаемых по интерфейсам PCIe, USB, SATA и пр.
Рис. 2. Схема загрузки в оперативную память модулей ATF
Следует отметить, что системы на базе ARMv8 демонстрируют довольно серьёзные отличия от подобных систем на основе архитектуры x86 в плане конфигурации, механизмов обнаружения устройств, и т. п. Так, для x86 основным механизмом обнаружения устройств является механизм обхода конфигурационного пространства PCI с назначением устройствам адресов памяти, которые они должны декодировать. В случае систем на базе ARMv8 встроенные блоки практически всегда имеют фиксированные адреса в пространстве памяти (пространство портов не используется, так как не является частью архитектуры) и в некоторых случаях не отображаются в конфигурационном пространстве PCI. Для таких систем используется описание аппаратуры, составленное в виде так называемой Flattened Device Tree — древовидной базы данных, описывающей иерархию подключения аппаратных блоков, а также такие ресурсы, как области памяти, номера прерывания и т. п., связанные с этими блоками.
В более продвинутых системах блоки, входящие в состав SoC, поддерживают доступ через конфигурационное пространство PCI, и соответственно обладают контроллерами, реализующими доступ к этому пространству посредством механизма ECAM (Enhanced Configuration Access Mechanism). Поскольку адреса памяти у таких блоков всё равно фиксированные, поддержка обычного механизма конфигурирования устройств PCI не представляется целесообразной. Специально для систем с фиксированными адресами PCI-устройств было разработано расширение Enhanced Allocation, разрешающее этот конфликт. Уникальные свойства этого расширения достойны отдельной публикации, но вкратце оно может быть описано как расширение, состоящее из набора альтернативных регистров, в которых содержится информация об адресах памяти, номерах шин (для встроенных мостов PCI—PCI) и т. д.
UEFI неотделимо от другого метода передачи информации о конфигурации платформы — ACPI. В настоящее время мы наблюдаем поступательное развитие и доработку спецификаций ACPI в интересах более полной поддержки именно архитектуры ARMv8. По имеющейся информации, ACPI должна стать основным методом передачи базовой информации о платформе (прежде всего о числе и конфигурации процессорных ядер, контроллеров PCI/PCIe) и управления ею для ARMv8. Некоторые из планируемых к выпуску на ARMv8 ОС поддерживают исключительно ACPI.
Итак, на этапе DXE выполняется обнаружение устройств (в тех системах, с которыми приходилось встречаться авторам, для этого используется механизм PCI ECAM), их инициализация и регистрация в UEFI, а также подготовка к запуску ОС. Последняя заключается в подготовке карты памяти системы и конфигурационной информации, то есть в загрузке, генерации и публикации таблиц ACPI, внесении в загруженные таблицы изменений, отражающих текущую конфигурацию платформы, внесении подобных же изменений в FDT, проверке и генерации контрольных сумм. Ряд модулей, загруженных на этом этапе, реализует так называемые UEFI Runtime Services — функции, доступные для вызова из ОС во время её исполнения.
По завершении этого этапа начинается этап выбора устройства для загрузки (Boot Device Selection, BDS). Обычно на этой стадии используется отдельный модуль, обрабатывающий значения переменных BootOrder, BootNext и т.д. Часто такой модуль реализует (псевдо)графический интерфейс. На этом этапе можно заметить большое сходство с системами на базе x86: используются те же самые методы загрузки – PXE, iSCSI, блочные устройства (такие как SATA/SAS/USB диски, SSD, NVME устройства) и т. п.
Отдельно хотелось бы обратить внимание на драйверы внешних устройств (обычно это устройства PCIe) для ARMv8 UEFI. Они могут быть реализованы как в виде модулей, расположенных на устройствах хранения (с файловой системой FAT32), так и располагаться непосредственно на самом устройстве (Option ROM). Добавление в список поддерживаемых архитектур ARMv8 в некоторых случаях вызывает проблемы для производителей. Простой перекомпиляции исходного кода для ARMv8 не всегда достаточно, так как ряд таких модулей не рассчитан на работу в полном 64-битном адресном пространстве. Также некоторые сложности порой вызывает тот факт, что на системах ARMv8 широко используется трансляция шинных адресов PCI в процессорные и наоборот. Это связано с тем, что при разработке решено было отказаться от унаследованных «окон», располагающихся в пределах младших 32 бит. В плане расширения поддержки могли бы помочь драйверы, скомпилированные в байт-код EBC, но на момент написания этой статьи интерпретатор EBC для ARMv8 находился на ранних стадиях разработки.
Передача управления загруженному в память модулю (загрузчик или непосредственно ядро ОС) выполняется в соответствии со спецификацией UEFI: уникальный указатель в регистре X0, указатель на системную таблицу в X1, и адрес возврата в X30 (LR).
Ядро ОС выполняет некоторые подготовительные процедуры, используя сервисы UEFI, затем устанавливает собственные таблицы трансляции и вызывает метод UEFI SetVirtualAddressMap(). Необходимость этих процедур обусловлена тем обстоятельством, что код UEFI выполняется в том же адресном пространстве, что и ОС, а также необходимостью отключения прерываний таймера.
Следует отметить, что в случае использования архитектуры ARMv8 для ОС Linux, есть один нюанс: основной код ядра работает в режиме EL1, тогда как в режиме EL2 вместе с сервисами UEFI выполняется только часть кода гипервизора KVM. После этого ядру из сервисов UEFI становятся доступны только так называемые Runtime Services – подмножество всех сервисов UEFI. Ядро Linux на ARMv8 широко использует интерфейс PSCI (при его наличии), реализуемый одним из модулей ATF, как было указано ранее. Это особенно характерно для многоядерных систем. Сам интерфейс и процесс инициализации процессорных ядер можно кратко описать как вызов SMC с номеров функции PSCI и точкой входа в функцию инициализации в качестве одного из параметров. Собственно, вызовы сервисов UEFI и SMC и являются в настоящее время основным способом взаимодействия ОС и микропрограммы (firmware). Существуют черновые спецификации способа уведомления ОС о различных событиях из микропрограммы, но на текущий момент о каких-либо готовых реализациях не сообщалось.
Подводя итог вышесказанному, следует ещё раз акцентировать внимание на том, что в статье представлено далеко не исчерпывающее описание функционирования и взаимодействия компонентов прошивки на ARMv8. Кроме того, архитектура находится в процессе постоянной доработки и переработки, что, вероятно, со временем даст материал для новых публикаций.