EnglishРусский Map

Самопатчинг ядра

title
Самопатчинг ядра
type
concept
summary
Три механизма перезаписи кода в Linux - jump_label, static_call и alternative_instructions: оптимизация одного vmlinux под десятки поколений CPU
tags
linux, kernel, cpu-features, performance
created
2026-05-14
updated
2026-05-14
lang
ru
translation_of
kernel-self-patching
source_updated
2026-05-14
translated
2026-09-01
translator
lllm/antigravity/gemini-3.7-flash-medium

При загрузке ядро Linux перезаписывает собственный сегмент кода (.text), чтобы адаптировать универсальный бинарник под конкретную машину. Для этого используются три механизма, каждый из которых снижает свои накладные расходы:

jump_label - бесплатные флаги возможностей

В исходном коде static_branch_unlikely(&some_key) выглядит как if (some_flag), но компилятор вставляет на это место NOP (или JMP) и регистрирует запись для патчинга. jump_label_init() при загрузке обходит зарегистрированную таблицу и перезаписывает каждое место в зависимости от того, включён ли ключ сейчас.

Выключенная возможность стоит ноль тактов CPU на горячих путях: никаких чтений регистров, записей в предсказателе переходов и промахов кэша. Переключение флага заново патчит все места. Ядро использует этот механизм для хуков трассировки, событий perf, отладочного инструментария и всего остального, что в выключенном состоянии не должно создавать никакой нагрузки.

static_call - прямые вызовы вместо косвенных

static_call(func)(args) выглядит как косвенный вызов, но при загрузке патчится в прямой вызов текущей привязанной функции. Это быстрее указателя на функцию и избавляет от проблем со спекулятивным выполнением вроде Spectre/Meltdown. Механизм используется в планировщике, KVM и точках трассировки (tracepoints).

alternative_instructions - выбор инструкций под конкретный CPU

Самый радикальный из трёх механизмов. Ядро поставляется с макросами ALTERNATIVE, которые описывают два (или более) варианта одного и того же кода - например, реализацию memcpy с AVX-512 и запасной вариант (fallback). Функция alternative_instructions() выполняется в Phase 4 после того, как CPUID определит реальный набор возможностей процессора, и патчит каждое место с ALTERNATIVE, оставляя только подходящий для этого CPU вариант.

Один и тот же vmlinux из состава Debian оптимально запускается на любом чипе x86_64, поддерживаемом дистрибутивом: патчинг при загрузке формирует бинарник, адаптированный под AVX-512 на Zen 5, под обычный SSE на 15-летнем Atom или под нужный конкретному чипу вариант защиты от Spectre - и всё это из единого исходного кода.

Зачем нужны три разных механизма

Они патчат одно и то же адресное пространство, но решают разные задачи:

Механизм Что патчит Когда принимается решение Стоимость в выключенном состоянии
jump_label NOP / JMP runtime, изменяемо ноль
static_call цель вызова runtime, изменяемо стоимость прямого вызова
alternative_instructions последовательность инструкций при загрузке, неизменяемо работает fallback-вариант

jump_label и static_call можно переключать во время работы (runtime). alternative_instructions выполняется один раз при загрузке: если на процессоре без поддержки AVX-512 путь с AVX-512 был отброшен, вернуть его без перезагрузки уже нельзя.

Предварительные требования

Для самопатчинга требуется poking_init() (механизм, позволяющий ядру безопасно перезаписывать собственный код вопреки rodata и W^X), поэтому mm_core_init() инициализирует его на раннем этапе. Кроме того, патчинг должен произойти до вызова mark_readonly() в Phase 6: как только rodata блокируется на уровне таблиц страниц, окно для модификации закрывается.

Ссылки

  • linux-kernel-startup - где именно эти три механизма запускаются в процессе загрузки
  • linux-boot-phases - правило "запирать двери в последнюю очередь", требующее сначала выполнить патчинг