Ранее мы уже разбирали последовательность запуска сервера на примере устаревшего Legacy. Настало время познакомиться с UEFI поближе.
Первая версия того, что сейчас известно как Unified Extensive Firmware Interface (UEFI), разрабатывалась в 90-е годы прошлого тысячелетия специально под системы на Intel® Itanium® и называлась Intel Boot Initiative, а позже — EFI.
Желание «обновить» процесс загрузки было ожидаемо. PC-BIOS, именуемый ныне Legacy, предлагает работать в 16-битном real mode, адресует всего 1 МБ оперативной памяти, а загрузчик вместе с таблицей разделов должен размещаться в первых 512 байтах накопителя. Более того, PC-BIOS передает управление первому найденному загрузчику без возможности возврата назад. При этом обработку случаев с несколькими операционными системами возлагают на плечи загрузчика.
Ограничение на размер загрузчика диктует использование разметки Master Boot Record (MBR), появившийся в 1983 году. MBR не стандартизирован, однако множество производителей придерживаются «сложившихся традиций». У MBR есть серьезные ограничения: по умолчанию поддерживается только 4 раздела и объем накопителя не более 2.2 ТБ.
В декабре 2000 года была выпущена первая широко распространенная спецификация EFI под версией 1.02. Спустя пять лет Intel передали EFI в UEFI Forum, добавив Unified в название, чтобы подчеркнуть изменения. Спецификация UEFI лежит в открытом доступе и состоит из нескольких документов:
- ACPI Specification;
- UEFI Specification;
- UEFI Shell Specification;
- UEFI Platform Initialization Specification;
- UEFI Platform Initialization Distribution Packaging Specification.
Самое интересное начинается в UEFI Platform Initialization Specification, где описываются все фазы загрузки платформы.
UEFI универсален, но в данной статье мы будем опираться на стандарт, поглядывая в сторону процессоров на архитектуре x86_64.
Wake up, Neo!
Последовательность фаз загрузки UEFI (источник UEFI Platform Initialization Specification)
После инициации включения платформы блок питания ждет, пока не завершатся переходные процессы, и после устанавливает сигнал на линию Power_Good. И первым начинает работу не центральный процессор, а автономная подсистема Intel® Management Engine (ME) или аналогичная ей AMD Secure Technology (ST). Эта подсистема проводит собственные операции, а затем подготавливает и запускает первое ядро одного процессора, именуемое Bootstrap Processor (BSP).
В соответствии с принятой терминологией ядро/поток процессора здесь и далее будет называться процессором: начальным (bootstrap processor) или прикладным (application processor).
Как и в Legacy, процессор начинает выполнять первую инструкцию в конце адресного пространства по адресу 0xFFFFFFF0. Эта инструкция — прыжок на первую фазу инициализации платформы — SEC.
Фаза SEC (Security)
В данной фазе должны быть решены следующие задачи:
- обработка события включения;
- инициализация достаточного количества памяти для следующей фазы;
- становление корня доверия системы;
- передача необходимой информации и управления на следующую фазу.
Процессоры x86_64 запускаются в 16-битном реальном режиме, и в процессе первичной инициализации BSP переводится в 32-битный защищенный режим. Затем происходит обновление микрокода всех доступных процессоров.
Следом происходит обработка события включения. Под этим подразумевается агрегация информации о состояниях оборудования, чтобы на следующей фазе некоторые модули могли сделать выводы о «здоровье» и общем состоянии платформы.
Во время фазы SEC не происходит инициализация оперативной памяти. Вместо этого свободный кэш процессора помечается как несбрасываемый, и он превращается во временную оперативную память. Такой режим называется no-eviction mode (NEM). В выделенной памяти создается стек, что позволит модулям из следующих фаз использовать стековые языки программирования до инициализации основной оперативной памяти.
Далее происходит инициализация всех прикладных процессоров (Application Processor, AP) с отправкой им специальной последовательности межпроцессорных прерываний (Inter-Processor Interrupt, IPI). Последовательность Init IPI — Start-up IPI — пробуждает прикладной процессор и запускает на нем самотестирование — Built-In Self-Test (BIST). Результаты тестирования записываются и передаются далее для анализа.
В конце фазы Security необходимо найти раздел Boot Firmware Volume (BFV), на котором располагается исполняемый код следующей фазы, а также по возможности найти другие, неосновные, разделы с кодом (Firmware Volume, FV).
Чтобы оправдать название фазы Security и стать корнем доверия, во время выполнения этой фазы код, которому мы планируем передать управление, может быть проверен на отсутствие несанкционированных изменений и вредоносных частей программы.
В конце выполнения SEC собрана следующая информация:
- размер и адрес Boot Firmware Volume (BFV);
- размер и адреса других Firmware Volume (FV);
- размер и адрес временной оперативной памяти;
- размер и адрес стека.
После чего начинается следующий этап — Pre EFI Initialization.
Фаза PEI (Pre EFI Initialization)
Фаза PEI на материнской плате SuperMicro
Задача фазы Pre EFI Initialization заключается в сборе информации о подключенных устройствах и подготовке минимально необходимого количества оборудования для запуска процесса полной инициализации.
По своей задумке фаза PEI должна легковесной, так как память процессорного кэша ограничена. Помимо этого, в фазе PEI может происходить восстановление после сбоя, поэтому есть потребность размещать код фазы PEI в более отказоустойчивом хранилище.
Данная фаза состоит из ядра, называемого PEI Foundation, и подключаемых модулей PEI Module (PEIM). Центральной частью ядра является диспетчер модулей, PEI Dispatcher, который управляет порядком исполнения модулей, а также организует межмодульное взаимодействие (PEIM-to-PEIM Interface, PPI).
Отметим, что фаза SEC исполнялась с флэш-памяти на материнской плате, и только в начале PEI необходимый для этой фазы исполняемый код копируется во временную оперативную память.
Далее к работе приступает PEI Dispatcher. Он запускает PEI модули в конкретном порядке: сначала модули без зависимостей, затем зависящие от первых и так до тех пор, пока модули не закончатся.
Архитектура фазы PEI позволяет разрабатывать собственные модули, которые могут передавать результаты своей деятельности в следующую фазу. Передача информации происходит через специальную структуру данных Hand-off Block (HOB).
В процессе запуска PEI модулей отметим следующие:
- CPU PEIM — инициализация процессоров;
- Platform PEIM — инициализация северного (в т.ч. Memory Controller Hub) и южного (I/O Controller Hub) мостов;
- Memory Initialization PEIM — инициализация основной оперативной памяти и перенос данных из временной памяти в RAM.
Ранее из фазы SEC была получена информация о включении. Если событием включения является S3 Resume, то следом выполняется S3 BootScript, который восстанавливает сохраненное состояние процессоров и всех подключенных устройств, а после передает управление напрямую в ОС.
Состояние S3 (Suspend to RAM) — это состояние сна, при котором процессор и часть чипсета отключаются с потерей контекста. При пробуждении из такого состояния процессор начинает выполнение как при обычном включении. Но вместо полной инициализации и прохождения всех тестов система ограничивается восстановлением состояния всех устройств.
При запуске из любого другого состояния управление передается в фазу Driver Execution Environment.
Фаза DXE (Driver eXecution Environment)
Инициализация механизма AHCI в фазе DXE
Задача фазы Driver Execution Environment (DXE) сводится к инициализации оставшихся устройств. К моменту старта фазы DXE процессор и основная память уже готовы к работе, а на драйверы DXE не накладываются строгие ограничения по потребляемым ресурсам.
Аналогично PEI Foundation в данной фазе есть собственное ядро — DXE Foundation. Ядро создает необходимые интерфейсы и загружает три вида DXE сервисов:
- UEFI Boot Services — сервисы времени загрузки;
- UEFI Runtime Services — сервисы времени исполнения;
- DXE Services — специальные сервисы, необходимые ядру DXE.
После инициализации сервисов начинает работу DXE Dispatcher. Он находит и загружает DXE драйверы, которые, в свою очередь, завершают инициализацию оборудования.
В UEFI нет специализированной фазы, где оборудование проходит POST (Power-On Self-Test). Вместо этого каждый модуль PEI и DXE фазы проводит свой набор тестов и сообщает об этом с помощью POST-кодов пользователю и с помощью HOB в следующие фазы.
Среди множества загружаемых драйверов на процессорах x86_64 стоит уделить внимание драйверу System Management Mode Init (SMM Init). Этот драйвер подготавливает все для работы режима системного управления (System Management Mode, SMM). SMM — это особый привилегированный режим, который позволяет приостановить выполнение текущего кода (в т.ч. операционную систему) и выполнить программу из защищенной области памяти SMRAM в собственном контексте.
Режим SMM неофициально считается кольцом защиты с номером -2. Ядро ОС работает на 0 кольце, а более ограниченные в правах кольца защиты имеют номер от 1 до 3. Официально нулевое кольцо считается наиболее привилегированным. Тем не менее, гипервизор с аппаратной виртуализацией условно называют кольцом -1, а Intel ME и AMD ST — кольцом -3.
Дополнительно отметим модуль Compatibility Support Module (CSM), который обеспечивает совместимость с Legacy и позволяет загружать ОС без поддержки UEFI. Позднее мы рассмотрим этот модуль подробнее.
После инициализации всего оборудования наступает время выбора загрузочного устройства.
Фаза BDS (Boot Device Select)
В фазе Boot Device Select реализуется политика загрузки приложений UEFI. Несмотря на то, что это отдельная фаза, все сервисы, включая диспетчера, созданные на фазе DXE, остаются доступны.
Цель фазы BDS сводится к выполнению следующих задач:
- инициализация консольных устройств;
- поиск устройств, с которых можно загрузиться;
- попытка загрузиться с найденных устройств в порядке приоритета.
PCIe BIOS карты расширения LSI
Поиском загружаемых областей на устройствах занимается Boot Manager. На некоторых картах расширения, например, на сетевых картах и RAID-контроллерах, может находиться собственный «BIOS», называемый Option ROM, или OpROM. Содержимое OpROM устройств запускаются сразу после обнаружения, а после выполнения управление возвращается в Boot Manager.
Все разделы, на которых находятся загружаемые области, сохраняются в памяти менеджера загрузки и упорядочиваются в соответствии с порядком загрузки. Если ни одного приложения не нашлось, Boot Manager может вызвать диспетчера DXE, на случай если за время поисков диспетчер загрузил дополнительные драйвера и менеджеру загрузки могут «открыться» новые устройства.
Как отмечалось ранее, использование разметки Master Boot Record накладывает ограничения на размер разделов и их количество на накопителе, а также вызывает определенные неудобства в содержании нескольких операционных систем. Решение всех этих проблем является частью спецификации UEFI — GUID Partition Table.
GPT (GUID Partition Table)
GUID Partition Table — это стандартизированный формат размещения таблиц разделов, пришедший на смену устаревшей MBR.
Во-первых, GPT использует адресацию логических блоков (Logical Block Addressing, LBA) вместо адресации «Цилиндр — Головка — Сектор» («Cylinder, Head, Sector», CHS). Смена способа адресации позволяет GPT работать с накопителями объемом до 9.4 ЗБ (9.4 * 1021 байт) против 2.2 ТБ у MBR.
Во-вторых, таблица разделов претерпела изменения, и теперь в пределах одного накопителя можно создать до 264 разделов, хотя операционные системы поддерживают не более 128 в случае Microsoft Windows и 256 в случае Linux.
В-третьих, каждый раздел имеет свой идентификатор типа, который описывает назначение раздела. Так, например, идентификатор C12A7328-F81F-11D2-BA4B-00A0C93EC93B однозначно указывает на системный раздел EFI (EFI System Partition, ESP), с которого Boot Manager может попробовать загрузить приложение.
При разработке GPT не обошли стороной и совместимость с MBR. Дисковые утилиты могли не распознать GPT диск и затереть его. Чтобы избежать этого, при разметке GPT первые 512 байт заполняются защитной MBR (Protective MBR) — разметкой из одного раздела на весь накопитель с системным идентификатором 0xEE. Такой подход позволяет UEFI понимать, что перед ним не настоящий MBR, а старому программному обеспечению без поддержки GPT — видеть раздел с данными неизвестного типа.
В GPT отказались от загрузочной области в пользу ESP-разделов, которые распознаются как загрузочные. Boot Manager собирает информацию обо всех ESP на диске, что позволяет без конфликтов иметь несколько загрузчиков на накопителе, по одному на каждый ESP.
Загрузка операционной системы
После опроса всех устройств и поиска загрузочных областей Boot Manager начинает загружать в порядке приоритета загрузки. В общем случае управление передается в UEFI-приложение, которое начинает выполнять свою логику. Однако для систем с совместимостью с Legacy режимом в списке загрузочных областей может найтись накопитель с разметкой MBR и придется обращаться к CSM, модулю поддержки совместимости.
Модуль CSM позволяет запускать операционные системы, которые не поддерживают UEFI. Для загрузки таких ОС модуль CSM эмулирует окружение, в которое попадает «классическая» ОС:
- загружает Legacy-драйвер;
- загружает Legacy BIOS;
- переводит видеовывод в совместимый с Legacy режим;
- создает в памяти необходимые для Legacy структуры данных, отсутствующие в UEFI;
- загружает драйвер CompatibilitySmm для работы SMM в Legacy.
Напомним, что в Legacy-режиме загрузка ОС начинается в 16-битном режиме, в то время как в UEFI все работает в 32-битном режиме. CSM запускает Legacy-загрузчик в 16-битном режиме и при необходимости обеспечивает связь с 32-битными UEFI-драйверами.
Фаза RT (Run Time)
Начало загрузки ОС или Legacy-загрузчика приводит к началу фазы Run Time. В данной фазе все сервисы DXE (кроме UEFI Runtime Services) перестают быть доступны.
Содержимое фазы RT может быть разным. Здесь может быть привычный по Legacy загрузчик ОС — например, GRUB2 или Windows Boot Manager, который переводит процессор в 64-битный режим и запускает ОС. Но могут быть и самостоятельные приложения или сразу ядро операционной системы.
Ядро Linux начиная с версии 3.3 при наличии флага CONFIG_EFI_STUB превращается в обычное UEFI-приложение и может быть запущено из UEFI без использования сторонних загрузчиков.
Как и в случае с Legacy, загрузчику или самому ядру необходимо перевести процессор в 64-битный режим, загрузить все драйвера, настроить планировщик и запустить init. Init, в свою очередь, запускает процессы в пространстве пользователя, после чего появляется окно логина в ОС.
Заключение
Загрузка в UEFI — это более сложный, но стандартизированный и во многом универсальный процесс. Схожесть с Legacy наблюдается лишь в общих чертах, а дьявол, как известно, кроется в деталях.
Как Вы думаете, как скоро получится полностью уйти от Legacy?
Пишите свое мнение в комментариях.
Автор: Владимир