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

Сложная инструкция и внутренние микрооперации

Со стороны программиста x86 выглядит как набор команд переменной длины: от простого сложения регистров до операций вроде строкового копирования с префиксом повторения, которые по спецификации перебирают тысячи байт одной командой. Исполнять такой зверинец буквально, одной схемой на команду, было бы неподъёмно. Поэтому декодер ядра разбивает каждую инструкцию на микрооперации, короткие действия фиксированного формата: загрузить, сложить, сохранить, ветвиться. Простые команды распадаются на одну-две микрооперации прямо в аппаратном декодере. Сложные, редкие и многошаговые отправляются в секвенсер микрокода, который читает последовательность микроопераций из специального ПЗУ на кристалле.

В этом ПЗУ живут реализации команд вроде вызова через дальнюю границу сегмента, обработки исключений, переключения режимов, работы со строковыми примитивами. Там же сидят служебные потоки: реакция на машинные проверки, вход в системное управление, часть логики инициализации. Любопытно, что микрокод присутствует и в ядрах, которые маркетинг называет полностью аппаратными: даже если декодер справляется сам, начальная настройка блоков, таблицы соответствий и флаги поведения всё равно описаны данными, а не только транзисторами. Так что фраза "программа внутри процессора" не метафора, а рабочее описание.

Почему кремний вообще нужно чинить

Процессор проектируют люди, и люди ошибаются в спецификациях, в верификации и в краевых случаях. Классический пример 1994 года - дефект FDIV в первых Pentium: таблица констант для деления содержала пропущенные записи, и редкие комбинации операндов давали неточный результат. Тогда единственным лекарством была замена чипа, потому что деление было чисто аппаратным. Урок усвоили: всё больше логики переводили в обновляемый микрокод, чтобы следующий дефект можно было гасить программно.

С тех пор производители ведут формальные списки ошибок, известные как errata или спецификационные исправления. В документации Intel и AMD регулярно встречаются пункты про некорректную инвалидацию строк TLB, про неверное поведение мониторов на границах узлов в многосокетных системах, про редкие зависания при определённом чередовании спекулятивных ветвлений. Каждая такая запись обычно заканчивается пометкой о том, в какой ревизии микрокода появился обходной приём. История устройств подтверждает масштаб явления:

  1. В 2018 году семейства уязвимостей Spectre и Meltdown потребовали митигейшенов именно на уровне микрокода: появились новые биты управления непрямыми ветвлениями и команды сброса предсказателей.
  2. Транзакционная память TSX отключалась заплаткой микрокода целиком, когда выяснилось, что корректная реализация в существующем кремнии недостижима.
  3. Многочисленные ошибки когерентности и кэширования гасились флагами, меняющими поведение конвейера на более консервативное.
  4. Неточности в подсчёте событий мониторинга производительности исправлялись переписыванием соответствующих микропотоков.
  5. Отдельные модели получали заплатки под конкретные сценарии энергосбережения, где выход из глубокого сна нарушал состояние регистров.

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

Путь заплатки, BIOS, загрузчик, Windows

Микрокод летуч: после сброса питания процессор стартует с версией, зашитой в заводское ПЗУ. Заплатку нужно загружать заново при каждом включении, и делать это надо как можно раньше, пока дефектный путь ещё никто не тронул. Первым эшелоном выступает прошивка материнской платы. BIOS или UEFI содержит контейнер с обновлениями для поддерживаемых моделей, и код ранней инициализации, ещё до старта операционной системы, подбирает подходящий блок по идентификатору семейства, модели и степпинга, проверяет его и записывает в процессор специальной привилегированной инструкцией.

Второй эшелон - операционная система. Windows хранит свежий микрокод в каталоге System32 и применяет его через цепочку загрузки: диспетчер bootmgr передаёт управление загрузчику winload, а тот до запуска ядра вызывает модуль обновления микрокода, для процессоров Intel это библиотека mcupdate, известная как mc_GenuineIntel.dll, для AMD существует аналогичный модуль. Загрузчик считывает хранящиеся в системе заплатки и применяет подходящую на каждом логическом процессоре. Зачем этот второй слой, если BIOS уже отработал? Затем, что прошивки обновляют редко и неохотно, а Microsoft доставляет свежие микрокоды через обычный канал обновлений системы. Пользователь получает фикс без ковыряния в прошивке и без риска окирпичить плату. Если версия в ОС новее версии в BIOS, применится она; если старше, запись просто не произойдёт.

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

Живая заплатка без перезагрузки

Долгое время правило было простым: новый микрокод требует перезагрузки, потому что применять его нужно до старта ядра. В серверной практике это мучительно, и производители ввели механизм загрузки после включения, известный как late loading. Ядро Linux умеет применять обновление на работающей системе, останавливая остальные потоки на коротком интервале и записывая заплатку на каждом ядре последовательно. Родственная идея живёт и в гипервизорах, где обновление применяется на хосте без остановки виртуальных машин.

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

Что микрокод может и где его предел

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

Отсюда печальное следствие про производительность. Выключенный быстрый путь - это не исправленный быстрый путь: система платит за безопасность потерей тех самых процентов, ради которых путь проектировали. Случай TSX показателен ещё сильнее: команда процессора перестала существовать после заплатки, потому что обходных флагов для корректной работы транзакций в том кремнии не нашлось, и проще было отключить возможность целиком. Бывают и обратные случаи, когда дефект сидит в чисто аппаратной части без зацепок для микрокода, и тогда чинит только новый степпинг. Инженерный баланс таков: всё, что можно упрятать в обновляемую логику, прячут туда заранее, на этапе проектирования, чтобы у будущих заплаток было за что зацепиться.

Проверка версии и отличие от драйвера и прошивки

Узнать действующую ревизию микрокода несложно. В Windows текущее значение доступно в реестре в ветке описания центрального процессора, параметр Update Revision показывает применённую версию, а рядом лежит подпись предыдущего состояния. Командная строка тоже помогает: запрос к классу Win32_Processor через wmic или PowerShell возвращает характеристики процессора, а журналы Event Viewer фиксируют события обновления микрокода от источника ядра при загрузке. В Linux ревизия видна в выводе dmesg и в файле cpuinfo, поле microcode. Сторонние утилиты вроде CPU-Z читают ту же ревизию через модельно-специфические регистры. Сравнив увиденное с таблицей производителя, администратор понимает, применён ли нужный фикс, и чей слой его применил: если версия старше той, что лежит в System32, значит дошёл только уровень BIOS.

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

Как устроено обновление и кто его может отменить. Производитель процессора выпускает новую версию микрокода как подписанный блок, адресованный конкретной модели и степпингу кристалла; прошивка материнской платы читает его на этапе POST и записывает во внутреннюю память процессора, загрузчик операционной системы проверяет сравнение версий и применяет более свежий пакет уже в контексте загруженного ядра. Этапность критична: ложная версия не переходит, а подписанная заплатка сохраняется до следующего отключения питания. Именно сюда относится явление «обновление дошло до машины через Центр обновления, даже если на диске старая прошивка BIOS»: Windows доставляет микрокод в System32 и передаёт его соответствующему загрузчику, что закрывает окно уязвимости даже там, где сборщик плат не спешит выпускать свежие предложения BIOS.

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

Смена ревизий как жизненный документ. Список исправлений errata у поставщика - почти всегда длинный технический каталог именно потому, что технически сложное производство отличается серией на серию. Инженер, планирующий горячий пул серверов, отслеживает совпадение степпингов между узлами и сверяет версии микрокода, чтобы параллельные реплики работали на одном поведении; разнобой уровней в одном кластере, мягко говоря, приводит к странной несовместимости производительности и вынуждает администрирование по крайней мере синхронизировать процесс загрузки на программном уровне. Поэтому установка новой платформы нередко начинается с общего сведения: в пакете платы есть прошивка, в ос знакомый набор, а в плане обновления процессора - дополнительное приложение, согласованное с плантатором аппаратуры.