
Загрузка операционной системы — процесс многоступенчатый и разнообразный. Несколько лет назад я писал о процессе загрузки сервера x86 в режимах Legacy и UEFI, но акцент тогда был именно на «железной» части.
Пришло время сместить внимание на программную составляющую. Посмотрим, какие стадии преодолевает ядро Linux, что происходит, и какие «фишки» можно выполнить на старте системы.
Содержание
→ До операционной системы
→ Распаковка ядра
→ Инициализация пространства ядра
→ Инициализация пространства пользователя
→ Трюки при загрузке
→ Заключение
До операционной системы
Для начала определимся с «базой». Операционная система (ОС) — это целый комплекс программ, который управляет всеми ресурсами компьютера. Да, все они кажутся необычными и используют интерфейсы, с которыми рядовые пользователи никогда не встречаются. Однако до загрузки операционной системы должно пройти еще несколько важных процессов.
Когда нажимается кнопка включения компьютера, в его отдельных частях запускаются механизмы инициализации аппаратной части: блок питания дожидается стабилизации напряжения, затем энергия подается на все устройства, которые также приводят себя в минимально рабочее состояние.
Первой выполняется прошивка материнской платы, именуемая BIOS (Basic Input-Output System) — базовая система ввода‑вывода. Ее цель — проверить работоспособность всех компонентов и передать управление следующей программе — или загрузчику операционной системы, или непосредственно ей самой.
BIOS — в значительной степени исторический термин, относящийся к компьютерам на архитектуре x86. Сейчас такой режим называется Legacy. Тогда был настоящий инженерный хаос: все как‑то работало, но документации было мало, а интерфейсы и взаимодействие компонентов не стандартизированы.
Сейчас все разработчики компонентов придерживаются нового унифицированного стандарта — UEFI (Unified Extensible Firmware Interface), который строго регламентирует этапы запуска аппаратного обеспечения и интерфейсы программного взаимодействия.
Центральная часть операционной системы — ядро, или kernel в английской терминологии. Именно с него начинается загрузка ОС, причем есть несколько сценариев.
В русскоязычной терминологии наблюдается небольшая путаница. Основа ОС — это программное ядро (kernel), вычислительный блок внутри процессора — это тоже ядро, но аппаратное (core).
Ядро может быть совместимым с UEFI — тогда прошивка компьютера запустит его самостоятельно. Это современный подход, он повсеместно встречается на современных компьютерах, ноутбуках, смартфонах и серверах.
До появления UEFI программа запуска размещалась в MBR (Master Boot Record) — первых 512 байтах накопителя, которых когда‑то хватало для полноценного управляющего кода. В какой‑то момент такого объема оказалось недостаточно, а ограничения BIOS не позволяли выйти за его пределы. Машины оказались вынуждены стартовать в несколько этапов.
Компактный код в первых 512 байтах (из них реально доступно 446) загружает более крупный — первоначально объемом до 32 КБ, но позже расширенный до 1 МБ.
Тот, в свою очередь, считывает минимальные драйвера файловых систем, находит настоящий загрузчик и передает ему управление.
Загрузчик уже может совершать полезную работу, в том числе показывать красивые меню выбора операционных систем и запускать их.
Существует еще один вариант загрузки — по сети. В этом случае прошивка сетевой карты получает адрес загрузчика или ядра по протоколу DHCP, а затем скачивает файлы по TFTP.
Чтобы узнать, что делает сервер до загрузчика — прочитайте статьи о загрузке сервера x86 в режимах Legacy и UEFI. А если хотите увидеть заметки по готовящимся статьям, то подписывайтесь на мой Telegram‑канал.
Из всех возможных вариантов рассмотрим загрузку Linux с локального диска при помощи универсального загрузчика GRUB (GRand Unified Bootloader) на архитектуре x86 на примере Ubuntu. В файлах конфигурации GRUB процесс описывается несколькими командами:
insmod part_gpt insmod ext2 search --no-floppy --fs-uuid --set=root a6a79603-1a85-44b1-a672-e1788f16f469 echo 'Loading Linux 6.8.0-124-generic ...' linux /vmlinuz-6.8.0-124-generic root=/dev/mapper/vgubuntu-root ro quiet splash $vt_handoff echo 'Loading initial ramdisk ...' initrd /initrd.img-6.8.0-124-generic
Идея проста:
загрузить модули для работы с файловой системой командой —
insmod;задать устройство, на котором находится корень файловой системы —
search;поместить в оперативную память ядро Linux — например,
vmlinuz-6.8.0-124-generic— и передать аргументы запуска — скажем,root=/dev/mapper/vgubuntu-root ro quiet splash $vt_handoff;считать в оперативную память начальный RAM-диск —
initrd.
Естественно, загрузчик не может положить данные «куда-то», он должен следовать соглашениям — например, для архитектуры x86 существует x86 Boot Protocol. Должна быть создана и заполнена структура boot_params с данными о компьютере и соответствующими полями, такими как адреса в оперативной памяти, где расположены аргументы запуска и начальный RAM-диск.
Последнее действие загрузчика — сохранить адрес структуры в регистр rsi и передать управление ядру — просто «прыгнуть» в точку входа.
Распаковка ядра

Помните мем: «Забудьте, чему вас учили в школе»? Это не шутка. Загрузчик может иметь десятки модулей для различных файловых систем и внешних устройств, но при передаче управления в ядро они… исчезают. Ядро не особо хочет знать о загрузчике и его структурах, и оно все начинает «с нуля».
Первый шаг — распаковка. Если вы внимательно читали конфиг, возможно, у вас появился вопрос, почему ядро Linux называется vmlinuz. Дело в том, что ядро сжато для экономии места и ускорения загрузки с медленных носителей. Буква «z» обозначает наличие компрессии. Несжатое ядро имеет очевидное название vmlinux, но оно используется значительно реже. Поэтому первое, что делает ядро — проводит минимальную инициализацию процессора, распаковывает свою сжатую часть и раскладывает ее в правильные места в оперативной памяти.
На архитектуре x86 в режиме Legacy загрузчик передает управление ядру в 16‑битном режиме (Real Mode), в котором доступен всего 1 МБ памяти, что значительно меньше сжатого ядра.
Чтобы преодолеть это ограничение, загрузчик-распаковщик ядра переводит процессор сперва в 32‑битный режим Protected Mode, а затем, если процессор поддерживает, — в 64‑битный Long Mode.
В режиме UEFI процессор сразу работает в 32‑битном пространстве. Затем также производится переключение в 64‑битный режим, если процессор его поддерживает.
После распаковки происходит «прыжок» в распакованное ядро и начинается инициализация подсистем.

Бесплатный курс «Системный администратор Linux с нуля»
Освойте администрирование Linux на SelectOS и станьте востребованным специалистом.
Инициализация пространства ядра

Распакованное ядро подготавливает структуры данных и некоторые внешние устройства, необходимые для продолжения загрузки:
настраивает таблицы страниц, стека, областей памяти для каждого аппаратного ядра процессора;
инициализирует планировщики ресурсов, аллокатор памяти;
задает параметры прерываний и таймеров;
активирует шины взаимодействия с внешними устройствами — такими, как PCIe и ACPI;
загружает встроенные модули ядра ОС;
запускает логические ядра процессора — в некоторых реализациях показывается соответствующее число пингвинов.
При инициализации ядро создает ряд служебных процессов для выполнения задач операционной системы. Процесс с идентификатором 1 (PID, Process ID) — это основной процесс, который проводит всю основную работу. Остальные — создаются динамически в соответствии с конфигурацией оборудования и настройками. После запуска их можно увидеть в диспетчере задач — например, базовый kthreadd и фоновый kworker потоки ядра.
Когда основное вычислительное окружение готово, ядро, наконец, обращает внимание на начальный RAM-диск и монтирует его — это первичная файловая система с минимально необходимым набором подпрограмм и модулями.
В современном GRUB RAM-диск копируется в память командой initrd, образ файловой системы также называется initrd (Initial RAM Disk), а «внутри» него — initramfs (Initial RAM File System). С точки зрения пользователя они стали синонимами, хотя в историческом контексте разница есть.
initrd— старый способ, при котором с помощью ramdisk эмулируется блочное устройство с файловой системой ext2. Этот способ требует присутствия соответствующих драйверов в ядре, добавляет слой абстракции и лишний раз копирует содержимое диска в оперативной памяти.initramfs— современный способ, в котором используется примитивный CPIO-архив и временная сверхбыстрая файловая система tmpfs, драйвер которой уже встроен в ядро.
Вне зависимости от используемой технологии, начальный RAM-диск содержит интерпретатор командной строки и набор драйверов и утилит для накопителей и файловых систем. Но главное — в образе лежит программа, с которой начинается пространство пользователя (userspace) — init.
Инициализация пространства пользователя

Когда ядро готово к работе, оно «перерождает» процесс с идентификатором 1 в программу /init. Формально он не завершается, просто заменяется на другую программу. С этого перерождения начинается активность в пространстве пользователя.
Хотя ядро уже готово, нужно решить еще несколько важных задач:
загрузить модули ядра, отвечающие за взаимодействие с накопителями и файловыми системами;
организовать будущий новый корень файловой системы — собрать программные RAID-массивы, LVM, примонтировать все разделы;
«переключиться» на новый корень;
размонтировать начальный RAM-диск и освободить выделенные на него ресурсы;.
«переродиться» в лучшую версию себя —
/sbin/init.
Последние три задачи выполняются трансформацией «глупого» /init в switch_root(8). Эта программа переносит новый корень в указанный каталог, перемонтирует все виртуальные файловые системы — такие как sysfs, procfs, devfs, — а затем удаляет все файлы и каталоги старого корня. В конце очистки switch_root(8) становится /sbin/init, главная цель которого — следить за порядком в пространстве пользователя.
В нем процесс с идентификатором 1 ядро считает главным и наделяет его особыми полномочиями. Он может игнорировать любые сигналы, которые не обрабатывает сам — даже SIGKILL и SIGSTOP, которые обычным процессам проигнорировать не получится.
Однако с большой властью приходит большая ответственность.
Если главный процесс неожиданно или аварийно завершится, то произойдет паника ядра — сообщение о критичной ошибке с немедленной остановкой системы, работоспособность которой напрямую зависит от стабильности init.
Init обязан усыновлять все осиротевшие процессы — те, у которых родитель завершился раньше.
На этом обязательные с точки зрения ядра задачи заканчиваются. Все остальное — это потребности и удобства администратора и пользователей. Зависят они от настроек текущей системы, реализации init и, конечно же, эпохи.
Здесь необходимо сделать небольшое историческое отступление для понимания некоторых принципов.
В старых версиях Research Unix задач у init было немного: выполнить скрипт /etc/rc, да перезапускать менеджер терминала getty, когда пользователь завершил свою сессию. Запуском служб (демонов) занимался непосредственно rc.
В SystemV систему решили усложнить и распределить фоновые службы по уровням загрузки (runlevel). Каждая из них описывалась shell-скриптом с предопределенными реакциями на каждое действие: включение, выключение, перезагрузка. Реализация этой идеи получила свое название — SysVinit.
Уровни загрузки — это готовые шаблоны, которые определяли, что должно быть выполнено, а что нет. Вместо одного бинарного состояния системы — уже загрузилась или еще нет — их стало несколько. Официально стандартизировано только три уровня:
0— выключение;1— однопользовательский режим, без инициализации сетевого стека и без запуска демонов (для обслуживания системы);6— перезагрузка.
Спецификация Linux Standard Base (LSB) расширяет этот список:
2— многопользовательский режим без инициализации сетевого стека;3— многопользовательский режим с сетевым стеком;4— не определен, может использоваться для нужд администратора;5— полная загрузка (уровень3+ экранный менеджер).
Однако спецификация — это идеальный мир, а в реальности разные дистрибутивы трактуют эти уровни по‑своему и сходятся только в том, что уровень 5 — это полная загрузка.
Когда-то давно четыре народа жили в мире. Но всё изменилось, когда Народ Огня… Первые версии Linux начали с классического init. Потом, в 1992‑ом перешли на SysVinit. Реализация работала, но имела свои недоработки. Службы запускались последовательно, зависимости описывались слабо, работа с shell-скриптами была не самой удобной, уровни загрузки не были унифицированы.
Несколько дистрибутивов пытались справиться с этими недостатками изобретением своих альтернатив SysVinit: Upstart, OpenRC, runit, s6. Но в 2010‑ом появился он — systemd. Огромный комбайн, который выполняет роль init-процесса: следит за службами, умеет запускать процессы параллельно, унифицирует уровни загрузки и позволяет единообразно описывать разные объекты системы.
В отличие от SysVinit в systemd каждая служба описывается набором параметров и команд, а сам systemd занимается разрешением зависимостей и приведением ее к заданному состоянию.
systemd вызвал множество споров. Его обвиняли в несоответствии философии Unix: «Делай одну вещь, но делай ее хорошо». Не нравилось и то, что он безальтернативно «прорастает» в дистрибутивы.
Изначально systemd планировался просто как замена старому SysVinit. Сейчас же он занимается и логированием, и пользовательскими сессиями, и событиями устройств, и временем, и сетью, и временными файлами, и домашними каталогами, и OOM (Out-of-Memory-менеджер).
Большинство современных дистрибутивов Linux перешли на systemd полностью. Debian по умолчанию использует systemd, но допускает установку альтернативных решений. Противники systemd ведут список всех грехов systemd и перечисляют дистрибутивы без него.
Несмотря на распространенность systemd, в современных дистрибутивах есть слой совместимости с историческими сложившимися вещами. Например, команда runlevel показывает текущий уровень загрузки, равно как и файл /sbin/init присутствует на диске, пусть и в форме символьной ссылки на systemd.
systemd заменил shell-скрипты на юниты, а уровни загрузки на цели (target) — группу юнитов. Внутри каждого юнита можно указать мягкие и жесткие зависимости, конфликты, ожидаемый порядок загрузки.
При запуске /sbin/init стартует systemd и строит дерево зависимостей для цели default.target, которая является символической ссылкой на multi-user.target (уровень 3, обычно для серверных систем) или graphical.target (уровень 5, для персональных компьютеров). Запуск до уровня 5 проходит в несколько этапов-таргетов, но я отмечу четыре:
sysinit.target— фундаментальные юниты: логирование, устройства, файловые системы, модули ядра;basic.target— точка синхронизации, момент времени, к которому должны быть загружены все необходимые сервисы для старта пользовательских служб, кроме тяжеловесных сервисов, вроде графического и сетевых интерфейсов;multi-user.target— полная загрузка в текстовом режиме;graphical.target— полная загрузка в графическом режиме.
После достижения цели default.target операционная система готова к работе.
Мы проследили весь процесс: он нажатия кнопки питания до полной готовности всех служб systemd. Остался вопрос: можем ли в него вмешаться? Если да, то что полезного можно сделать?
Трюки при загрузке
Аргументы ядра
В самом начале упоминалось, что загрузчик передает ядру аргументы — они используются на всех этапах загрузки. Их перечень и так довольно обширный, но допускается передавать и совершенно произвольные — их можно найти в файле /proc/cmdline пространства пользователя. Например, мы можем ввести в терминале:
cat /proc/cmdline
В ответ увидим все параметры ядра:
BOOT_IMAGE=/vmlinuz-6.8.0-124-generic root=/dev/mapper/vgubuntu-root ro quiet splash vt.handoff=7
Один из самых интересных предопределенных аргументов — init. Он позволяет подменить исполняемый файл init. Если указать init=/bin/bash, то после загрузки ядра первым процессом станет командный интерпретатор.
Это давно известный трюк, который при наличии физического доступа к компьютеру позволяет загрузиться в Linux с правами суперпользователя без знания пароля от системы.
Свой init
Как отмечалось ранее, /sbin/init — это всего лишь программа, которая должна стабильно работать и следить за процессами-сиротами. В чисто академических целях можно реализовать и свой init.
В современных Linux-системах с сильной привязкой к systemd опыт получится, конечно, малоприятный. Однако если обратиться к FreeBSD или старым версиям Linux, то самостоятельная разработка окажется вполне по силам.
Более того, из-за особенностей работы системного вызова execve(2), текстовые файлы с шебангом в начале (последовательностью символов #!) трактуются как скрипты, исполняемые указанным интерпретатором. А это значит, что /sbin/init может быть шелл-скриптом, а не классической программой.
Заключение
Загрузка Linux — это цепочка последовательных передач управления: от прошивки компьютера к загрузчику, от загрузчика к ядру, от ядра к initramfs, а затем к настоящей системе на диске. На каждом этапе предыдущий компонент подготавливает минимальную среду для следующего. Лишь достигнув default.target, система переходит из состояния загрузки в рабочее.
В итоге вся сложная процедура сводится к одной идее: Linux загружается не одномоментно, а как аккуратно выстроенный конвейер, где каждый этап делает ровно то, что необходимо, чтобы следующий смог продолжить запуск системы.
Комментарии (6)

MrBotikkk
24.06.2026 10:14Шедевр. Использовать скрин загрузки Gentoo Minimal Installation CD c OpenRC init


Firemoon Автор
24.06.2026 10:14Для иллюстрации «количества пингвинов при загрузке ядра» точный дистрибутив и используемый init как будто не принципиален. Главное что это Linux (и OpenRC упоминается в статье).
У меня на компьютерах с Ubuntu ядро собрано без поддержки отображения пингвинов (параметр
CONFIG_LOGO), поэтому авторское фото я не делал: пересобирать ядро ради скриншота — это перебор.
MrBotikkk
24.06.2026 10:14Логи зарузки на OpenRC:
sudo dmesg -T > mylogfile.txtи/var/log/rc.logбудут “немножко” отличаться от systemd:sudo journalctl -b > boot_log.txt
kovserg
Лучше раскажите как загрузка linux идёт на одноплатных компьютерах (arm,mips,riscv), где загрузка идёт сflash, sdcard, emmc, usb… вот где разброд и шатание.
event1
Там всё то же самое, только вместо GRUB и UEFI — U-Boot и prom-загрузчик. Хотя для систем на arm64 вроде UEFI используют иногда
checkpoint
За малым нюансом: вместо ACPI для конфигурации и управления настройками аппаратуры используется Flattened Device-Tree файл (DTB файл), а вся эта кухня представляет собой отдельный дивный мир со своими чудесами. :-)
Ну как по мне, лучше уж U-Boot + Device-Tree, чем UEFI + ACPI.