Загрузка операционной системы — процесс многоступенчатый и разнообразный. Несколько лет назад я писал о процессе загрузки сервера 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 не позволяли выйти за его пределы. Машины оказались вынуждены стартовать в несколько этапов.

  1. Компактный код в первых 512 байтах (из них реально доступно 446) загружает более крупный — первоначально объемом до 32 КБ, но позже расширенный до 1 МБ.

  2. Тот, в свою очередь, считывает минимальные драйвера файловых систем, находит настоящий загрузчик и передает ему управление.

  3. Загрузчик уже может совершать полезную работу, в том числе показывать красивые меню выбора операционных систем и запускать их.

Существует еще один вариант загрузки — по сети. В этом случае прошивка сетевой карты получает адрес загрузчика или ядра по протоколу 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.

Инициализация пространства пользователя

Создатель Linux, Линус Торвальдс, в 2012 году жестко заявил, что ни одно изменение в ядре не должно ломать программы в пространстве пользователя. Экспрессивность фразы и непоколебимость принципов сделала её мемом.  Источник.
Создатель Linux, Линус Торвальдс, в 2012 году жестко заявил, что ни одно изменение в ядре не должно ломать программы в пространстве пользователя. Экспрессивность фразы и непоколебимость принципов сделала её мемом.  Источник.

Когда ядро готово к работе, оно «перерождает» процесс с идентификатором 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)


  1. kovserg
    24.06.2026 10:14

    Лучше раскажите как загрузка linux идёт на одноплатных компьютерах (arm,mips,riscv), где загрузка идёт сflash, sdcard, emmc, usb… вот где разброд и шатание.


    1. event1
      24.06.2026 10:14

      Там всё то же самое, только вместо GRUB и UEFI — U-Boot и prom-загрузчик. Хотя для систем на arm64 вроде UEFI используют иногда


      1. checkpoint
        24.06.2026 10:14

        Там всё то же самое,

        За малым нюансом: вместо ACPI для конфигурации и управления настройками аппаратуры используется Flattened Device-Tree файл (DTB файл), а вся эта кухня представляет собой отдельный дивный мир со своими чудесами. :-)

        Ну как по мне, лучше уж U-Boot + Device-Tree, чем UEFI + ACPI.


  1. MrBotikkk
    24.06.2026 10:14

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


    1. Firemoon Автор
      24.06.2026 10:14

      Для иллюстрации «количества пингвинов при загрузке ядра» точный дистрибутив и используемый init как будто не принципиален. Главное что это Linux (и OpenRC упоминается в статье).

      У меня на компьютерах с Ubuntu ядро собрано без поддержки отображения пингвинов (параметр CONFIG_LOGO), поэтому авторское фото я не делал: пересобирать ядро ради скриншота — это перебор.


      1. MrBotikkk
        24.06.2026 10:14

        Логи зарузки на OpenRC: sudo dmesg -T > mylogfile.txt и /var/log/rc.log будут “немножко” отличаться от systemd: sudo journalctl -b > boot_log.txt