Самопатчинг ядра
- 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 - правило "запирать двери в последнюю очередь", требующее сначала выполнить патчинг