Анатомия загрузки Linux: от питания до системного окружения

Курс раскрывает последовательную цепочку передачи управления между компонентами системы при старте. Вы изучите логику инициализации оборудования и программной среды, что позволит точно локализовать ошибки при сбое загрузки.

Первичная инициализация: роль прошивок BIOS и UEFI в подготовке оборудования

Первичная инициализация: роль прошивок BIOS и UEFI в подготовке оборудования

Вы нажимаете кнопку питания на сервере. В этот момент процессор получает ток, но он абсолютно «слеп». У него нет операционной системы, он не знает, где находится жесткий диск, и не умеет работать с файлами. До того как ядро Linux выведет на экран первую строчку логов, должна отработать невидимая, но критически важная цепочка инструкций, зашитая прямо в материнскую плату.

Если сервер не загружается, проблема часто кроется не в Linux, а в том, что система даже не смогла до него добраться. Давайте разберем самый первый этап жизни сервера — от подачи питания до поиска загрузчика.

Пробуждение железа: POST и Reset Vector

Как только напряжение стабилизируется, процессор обращается к жестко заданному адресу памяти — Reset Vector. По этому адресу находится точка входа в микропрограмму материнской платы (BIOS или UEFI).

Первое, что делает эта прошивка — запускает процедуру POST (Power-On Self-Test). Это базовая проверка жизнеспособности оборудования:

  1. Инициализация контроллера оперативной памяти (без RAM дальнейшая работа невозможна).
  2. Проверка наличия процессора и базовых шин ввода-вывода.
  3. Инициализация видеокарты (именно в этот момент на мониторе появляется логотип производителя).
  4. Опрос подключенных накопителей (HDD, SSD, NVMe) и USB-устройств.

Если на этапе POST обнаруживается критическая аппаратная ошибка (например, сгорела планка памяти), загрузка останавливается навсегда. Вы услышите звуковые сигналы (Beep codes) или увидите код ошибки на LED-индикаторе материнской платы. До Linux дело даже не дойдет.

Если POST пройден успешно, перед материнской платой встает главная задача: найти операционную систему на подключенных дисках и передать ей управление. Исторически сложилось два совершенно разных подхода к решению этой задачи.

Наследие BIOS: Загрузка вслепую

Классический BIOS (Basic Input/Output System) был создан в эпоху, когда диски измерялись мегабайтами. Его логика поиска операционной системы примитивна: BIOS ничего не знает о файловых системах, папках или файлах.

Он работает по жесткому алгоритму:

  • BIOS опрашивает диски в порядке очереди (Boot Order).
  • Находит первый диск и считывает его самый первый физический сектор — MBR (Master Boot Record).
  • Размер этого сектора — всего 512 байт. В них нужно уместить таблицу разделов и крошечный кусочек машинного кода.
  • BIOS загружает эти 512 байт в оперативную память и слепо передает им управление.

Этот крошечный код из MBR должен был сам разобраться, как прочитать остальную часть диска, чтобы вытянуть полноценный загрузчик ОС. Из-за физических ограничений MBR не поддерживал диски объемом более 2 ТБ и мог содержать только 4 основных раздела. Сегодня этот подход считается устаревшим и на современных серверах используется только в режиме совместимости (Legacy/CSM).

Эпоха UEFI: Прошивка как мини-ОС

На смену BIOS пришел UEFI (Unified Extensible Firmware Interface). Это уже не просто набор базовых инструкций, а полноценная мини-операционная система, живущая на материнской плате.

Главное отличие UEFI от BIOS — UEFI умеет читать файловые системы (в частности, FAT32). Ему больше не нужно вслепую запускать код из первого сектора диска. Вместо этого UEFI ищет на диске специально подготовленный раздел.

Этот специальный раздел называется ESP (EFI System Partition).

  • Это небольшой раздел (обычно от 100 до 500 МБ).
  • Он отформатирован в файловой системе FAT32.
  • На нем хранятся исполняемые файлы загрузчиков с расширением .efi.

Логика работы UEFI выглядит так:

  1. UEFI считывает таблицу разделов диска (используется современный формат GPT, который поддерживает диски любого объема).
  2. Находит раздел с флагом ESP.
  3. Монтирует его и ищет файл загрузчика по заранее сохраненному пути (например, \EFI\ubuntu\grubx64.efi или дефолтный \EFI\BOOT\BOOTX64.EFI).
  4. Загружает этот файл в оперативную память и передает ему управление.

Финал инициализации: Передача эстафеты

Как только UEFI нашел файл .efi и запустил его, роль материнской платы заканчивается. Прошивка сделала свое дело: проверила железо и нашла программу, которая знает, что делать дальше.

Этим .efi файлом в мире Linux в 99% случаев является GRUB (GRand Unified Bootloader). Именно GRUB возьмет на себя следующую задачу: показать вам меню выбора операционной системы, найти на диске сжатый образ ядра Linux и распаковать его. Но о том, как GRUB ищет ядро и какие параметры ему передает, мы поговорим в следующей главе.

Загрузчик GRUB: поиск ядра, передача параметров и выбор режима загрузки

Загрузчик GRUB: поиск ядра, передача параметров и выбор режима загрузки

Прошивка UEFI успешно нашла на разделе ESP файл grubx64.efi и передала ему управление. На этом этапе аппаратная часть свою работу выполнила. Но загрузчик GRUB (GRand Unified Bootloader) — это еще не операционная система. Это узкоспециализированная программа-мост, чья единственная цель — найти ядро Linux, объяснить ему, как нужно работать, и навсегда уйти со сцены.

Главная проблема этого этапа: UEFI умеет читать только простую файловую систему FAT32. Однако само ядро Linux и его компоненты лежат на системном диске, отформатированном в сложных форматах вроде ext4, xfs или btrfs. Как загрузчик до них доберется?

Суперсила GRUB: встроенные драйверы

В отличие от старых примитивных загрузчиков, GRUB — это мини-операционная система. В него встроены собственные драйверы файловых систем.

Получив управление, GRUB подгружает нужные модули (например, ext2.mod или btrfs.mod), что позволяет ему «увидеть» содержимое корневого раздела Linux или специального раздела /boot.

Как только GRUB получает доступ к файловой системе, он ищет свою карту местности — конфигурационный файл /boot/grub/grub.cfg. Именно этот файл формирует то самое меню выбора операционных систем, которое вы видите на экране при включении сервера.

Анатомия пункта меню (grub.cfg)

Если заглянуть под капот grub.cfg, типичный пункт меню загрузки Ubuntu или CentOS выглядит примерно так:

menuentry 'Ubuntu' {
    set root='hd0,gpt2'
    linux   /boot/vmlinuz-6.8.0-generic root=UUID=a1b2c3d4 ro quiet splash
    initrd  /boot/initrd.img-6.8.0-generic
}

Разберем три ключевые строки, которые определяют судьбу сервера на ближайшие секунды:

  1. set root — указывает GRUB, на каком диске и разделе лежат файлы ядра. В данном случае: первый жесткий диск (hd0), второй раздел GPT (gpt2).
  2. linux — самая важная строка. Она указывает путь к сжатому файлу ядра (vmlinuz) и передает ему параметры загрузки.
  3. initrd — путь к временной файловой системе (initramfs), которая понадобится ядру для загрузки драйверов. Эту концепцию мы подробно разберем через одну главу.

Параметры ядра: панель управления системой

Строка linux не просто указывает путь к файлу. Все, что написано после пути к ядру — это Kernel Command Line (командная строка ядра). Это инструкции, которые ядро прочитает в первую секунду своей жизни.

Параметры ядра — это ваш главный инструмент диагностики на этапе загрузки. Изменив их прямо в меню GRUB, можно спасти сервер, который отказывается загружаться.

Рассмотрим стандартные параметры:

  • root=UUID=... — указывает ядру, где находится настоящий корневой раздел с операционной системой, который нужно смонтировать.
  • ro (read-only) — приказывает ядру смонтировать корневой раздел в режиме «только чтение». Это защитная мера: пока ядро не проверит диск на ошибки, записывать на него небезопасно. Позже система сама перемонтирует его в режим чтения-записи (rw).
  • quiet — скрывает поток отладочных текстовых сообщений ядра (тот самый «бегущий текст» хакеров из кино).
  • splash — показывает красивую графическую заставку с логотипом дистрибутива вместо черного экрана.

Траблшутинг на лету

Если сервер зависает при загрузке, системный администратор может нажать клавишу e в меню GRUB и временно отредактировать строку linux.

Проблема Что изменить в GRUB Результат
Сервер зависает на логотипе, причина неизвестна Удалить quiet splash На экран выведется подробный лог (dmesg). Вы увидите, на каком именно драйвере или службе споткнулось ядро.
Черный экран после обновления драйверов видеокарты Добавить nomodeset Запрещает ядру загружать графические драйверы видеокарты. Система загрузится в базовом текстовом режиме, позволяя удалить сбойный драйвер.
Забыли пароль root / система сломана Добавить init=/bin/bash Ядро не будет запускать систему инициализации (systemd), а сразу выдаст вам командную строку с правами суперпользователя.

Передача управления

После того как вы выбрали пункт меню (или истек таймаут по умолчанию), GRUB выполняет свою финальную задачу:

  1. Читает файл vmlinuz с диска и копирует его в оперативную память.
  2. Копирует в память файл initrd (временную файловую систему).
  3. Сохраняет строку параметров ядра в специальный регистр процессора.
  4. Выполняет инструкцию процессора JMP (jump) на стартовый адрес ядра в оперативной памяти.

В этот момент GRUB выгружается из памяти. Он больше не существует. Управление сервером полностью и безвозвратно переходит к ядру Linux. О том, как ядро распаковывает себя и делает первые шаги, мы поговорим на следующем этапе.

Развертывание ядра: распаковка, инициализация драйверов и переход в защищенный режим

Развертывание ядра: распаковка, инициализация драйверов и переход в защищенный режим

Загрузчик передал управление первому байту файла vmlinuz, лежащего в оперативной памяти. В этот момент система максимально уязвима и «глупа»: процессор находится в архаичном режиме совместимости, оперативной памяти «не существует» (нет виртуальной адресации), а сам файл ядра представляет собой нечитаемый сжатый архив. Чтобы превратиться в полноценную операционную систему, ядру предстоит вытащить самого себя за волосы из этого состояния.

Матрешка vmlinuz и эволюция процессора

Файл vmlinuz, который загрузчик поместил в память — это не само ядро. Это самораспаковывающийся архив, состоящий из нескольких слоев. Исторически архитектура x86 требует, чтобы процессор при старте работал в Real Mode (реальном режиме) — 16-битном состоянии, имитирующем процессоры 1970-х годов. Даже если UEFI уже перевел процессор в более современный режим, ядро Linux спроектировано так, чтобы самостоятельно и надежно инициализировать нужные состояния CPU.

Внешняя оболочка vmlinuz содержит крошечный загрузочный код (bootstrap). Его единственная задача — подготовить минимальное окружение для распаковки.

Код начальной загрузки выполняет архитектурный прыжок:

  1. Отключает прерывания, чтобы случайный аппаратный сигнал не разрушил хрупкий процесс инициализации.
  2. Переводит процессор в Protected Mode (защищенный режим, 32 бита), а затем в Long Mode (64 бита).
  3. Настраивает базовую сегментацию памяти, чтобы процессор мог адресовать больше 1 МБ ОЗУ.

Только после того, как процессор обретает способность работать со всей доступной оперативной памятью в 64-битном режиме, запускается следующий слой матрешки — декомпрессор.

Великая распаковка

Сжатие ядра используется не столько для экономии места на диске, сколько для скорости. Исторически чтение данных с диска происходило на порядки медленнее, чем работа процессора. Быстрее прочитать с диска сжатый файл в 10 МБ и распаковать его силами CPU, чем читать несжатые 30 МБ.

Декомпрессор (обычно использующий алгоритмы zstd, lzma или gzip) берет сжатую полезную нагрузку и разворачивает ее в новую область оперативной памяти.

Распакованный бинарный файл называется vmlinux (с буквой «x» на конце, от eXecutable). Это и есть настоящее, исполняемое ядро операционной системы. Как только последний байт извлечен, декомпрессор передает управление на точку входа vmlinux — функцию start_kernel().

Пробуждение: start_kernel()

Вызов start_kernel() — это момент рождения операционной системы. С этого момента начинает работать код, написанный на C, а не на низкоуровневом ассемблере.

Ядро начинает формировать системное окружение:

  • Инициализация структур памяти: создаются таблицы страниц для виртуальной памяти. Теперь ядро может безопасно изолировать процессы друг от друга.
  • Парсинг Kernel Command Line: ядро читает текстовую строку параметров, которую мы рассматривали ранее (ro, quiet, nomodeset). Эти флаги определяют, как именно будут инициализироваться последующие подсистемы.
  • Настройка планировщика: создается самый первый процесс в системе с PID 0 (swapper/idle), который будет отдавать процессорное время другим задачам.

Опрос оборудования и встроенные драйверы

Ядро живо, но оно пока ничего не знает о физическом сервере, на котором запущено. Начинается фаза зондирования (probing).

Ядро обращается к системным шинам (PCI, USB) и запрашивает у подключенных устройств их идентификаторы (Vendor ID и Device ID). Получив ответ, ядро ищет подходящий драйвер для управления этим устройством.

Здесь кроется фундаментальное ограничение этого этапа загрузки. Драйверы в Linux могут существовать в двух видах:

  1. Встроенные (built-in) — скомпилированы прямо внутрь файла vmlinux.
  2. Модули (modules) — лежат в виде отдельных файлов .ko в файловой системе на диске.

На этапе start_kernel() ядро не умеет читать файловые системы с диска. У него еще нет драйвера для вашей файловой системы (например, ext4 или xfs), и, возможно, нет драйвера для самого RAID-контроллера или NVMe-накопителя, на котором эта система лежит.

Ядро может инициализировать только то оборудование, драйверы для которого были жестко вшиты в него при компиляции (built-in). Если драйвер диска оказался модулем, процесс загрузки зайдет в тупик: ядро не сможет смонтировать корневой раздел /, чтобы прочитать этот самый модуль.

Чтобы разорвать этот замкнутый круг — когда для чтения диска нужен драйвер, который лежит на самом диске — используется временная файловая система в оперативной памяти.

Временная файловая система initramfs: монтирование корня и подготовка к переходу в user-space

Временная файловая система initramfs: монтирование корня и подготовка к переходу в user-space

Функция ядра start_kernel() отработала, базовая инициализация памяти и процессора завершена. Ядро «ожило» в оперативной памяти и готово запустить первый пользовательский процесс операционной системы. Но здесь возникает фундаментальный парадокс, который останавливает загрузку.

Парадокс «курицы и яйца»

Чтобы запустить системное окружение, ядру нужно прочитать файлы с жесткого диска (например, из раздела /). Для чтения файлов с диска ядру нужны две вещи: драйвер контроллера диска (например, NVMe или RAID) и драйвер файловой системы (например, ext4 или xfs).

Где лежат эти драйверы? В виде скомпилированных модулей (.ko) они хранятся на том самом жестком диске, в директории /lib/modules/.

Ядру нужны драйверы с диска, чтобы получить возможность прочитать этот диск.

Почему бы просто не встроить все существующие драйверы дисков и файловых систем прямо в бинарный файл ядра (vmlinux), избежав этой проблемы?

Подход Плюсы Минусы
Встроенные (Built-in) Доступны мгновенно, не нужен доступ к диску. Ядро раздувается до сотен мегабайт. Потребляет лишнюю оперативную память, дольше грузится загрузчиком.
Модули (Modules) Ядро остается компактным и быстрым. Загружается только то, что реально нужно железу. Невозможно загрузить модуль с диска, пока диск не примонтирован.

Решение этого парадокса — создание промежуточного этапа загрузки.

Спасательный круг в оперативной памяти

Загрузчик GRUB, прежде чем передать управление ядру, поместил в оперативную память не только само ядро, но и еще один файл — initrd.img (Initial RAM Disk).

Ядро распаковывает этот файл прямо в оперативной памяти, создавая временную файловую систему — initramfs (Initial RAM File System). Это крошечная, урезанная версия Linux, которая живет исключительно в RAM.

Что находится внутри этой временной файловой системы?

  1. Базовый набор директорий: /bin, /lib, /sys, /proc.
  2. BusyBox: один компактный исполняемый файл, который заменяет собой десятки стандартных утилит (sh, ls, mount, grep).
  3. Минимальный набор модулей: только те драйверы .ko, которые критически необходимы для монтирования корневого диска конкретно этого сервера.
  4. Скрипт init: главный сценарий, который ядро запускает сразу после создания initramfs.

С этого момента ядро передает эстафету скрипту init. Начинается подготовка к переходу в настоящее системное окружение.

Миссия временного скрипта init

Сценарий, работающий внутри initramfs, выполняет строго определенную последовательность действий:

  1. Монтирование виртуальных систем. Скрипт монтирует /sys и /proc, чтобы получить информацию от ядра о найденном оборудовании.
  2. Загрузка модулей. Опираясь на данные об оборудовании, утилита udev (или ее легковесный аналог) находит в /lib/modules/ (внутри RAM) нужные драйверы и загружает их в ядро командой modprobe. Теперь ядро «научилось» понимать NVMe и ext4.
  3. Поиск реального корня. Скрипт читает параметры командной строки ядра (те самые, что передал GRUB, например root=UUID=1234-5678). Он ищет среди появившихся блочных устройств то, чей UUID совпадает с заданным.
  4. Монтирование реального диска. Найденный раздел монтируется во временную директорию внутри initramfs, обычно это /sysroot.

На этом этапе у нас есть работающее ядро, корень файловой системы в оперативной памяти (/) и настоящий корень на жестком диске, примонтированный как подпапка (/sysroot).

Осталось сделать последний, самый сложный шаг — поменять их местами.

Кульминация: операция switch_root

Нельзя просто так отмонтировать корень, из которого в данный момент работает система (ведь скрипт init запущен именно оттуда). Для решения этой задачи используется специальная системная утилита switch_root.

Она выполняет хирургическую операцию в три этапа:

  1. Перемещает точку монтирования /sysroot на место основного корня /.
  2. Рекурсивно удаляет всё содержимое старого initramfs из оперативной памяти, освобождая ресурсы.
  3. Запускает целевой процесс инициализации (обычно /sbin/init, который является ссылкой на systemd) уже из новой, реальной корневой файловой системы.

Как только switch_root передает управление systemd, временная файловая система прекращает свое существование. Сервер переходит к этапу запуска системных служб.

Траблшутинг: когда диск не найден

Если вы администрируете Linux, рано или поздно вы столкнетесь с ситуацией, когда сервер не загружается, а на экране висит мрачное приглашение:

(initramfs)

Это означает, что скрипт init не смог выполнить свою миссию (найти или примонтировать реальный корень) и, чтобы не паниковать окончательно, выбросил вас во встроенную оболочку BusyBox.

Главные причины и как их дебажить прямо в этой консоли:

  • Изменился UUID диска. (Например, после клонирования или восстановления из бэкапа).
    • Действие: Введите blkid. Сравните выведенные UUID с тем, что GRUB передал ядру (можно посмотреть через cat /proc/cmdline).
  • Отсутствует нужный драйвер. (Вы перенесли диск в другой сервер с другим RAID-контроллером, а драйвера для него в старом initramfs нет).
    • Действие: Проверьте наличие дисков командой ls /dev/sd* или ls /dev/nvme*. Если их нет, значит ядро не видит контроллер. Потребуется загрузиться с LiveCD, сделать chroot и пересобрать initramfs утилитой update-initramfs (или dracut).
  • Повреждена файловая система. (Сбой питания).
    • Действие: Диск виден, но не монтируется. Запустите проверку прямо отсюда: fsck /dev/mapper/vg-root (указав ваш путь к разделу).

Понимание того, что (initramfs) — это не «умершая» система, а полноценный, хоть и крошечный Linux в оперативной памяти, дает вам мощный инструмент для диагностики сервера до того, как загрузятся основные логи.

Передача управления: запуск процесса init и первая стадия работы systemd

Передача управления: запуск процесса init и первая стадия работы systemd

Сразу после выполнения switch_root ядро Linux оказывается в странном положении. Оно полностью загружено в оперативную память, драйверы инициализированы, а настоящий корневой раздел диска успешно примонтирован. Но система при этом абсолютно мертва: нет ни сети, ни графического интерфейса, ни даже командной оболочки, чтобы ввести команду. Ядро — это лишь двигатель и трансмиссия. Чтобы машина поехала, нужен водитель. Этим водителем становится первый процесс в пространстве пользователя (user-space).

Поиск первого процесса

Ядро не умеет самостоятельно запускать системные службы или читать конфигурационные файлы сети. Его последняя задача на этапе загрузки — найти и запустить одну-единственную программу, передав ей полный контроль над пользовательским пространством.

В исходном коде ядра (функция kernel_init) жестко зашит порядок поиска этого заветного бинарного файла. Ядро последовательно проверяет пути в новом корневом разделе:

  1. /sbin/init
  2. /etc/init
  3. /bin/init
  4. /bin/sh

Как только ядро находит первый существующий исполняемый файл, оно запускает его. На этом этапе загрузка самого ядра официально завершается.

В современных дистрибутивах /sbin/init — это не самостоятельная программа, а символическая ссылка на /lib/systemd/systemd. Таким образом, историческое имя сохраняется для совместимости, но фактическое управление берет на себя systemd.

Если вы хотите переопределить этот процесс (например, для диагностики), вы можете использовать параметр ядра init=, передав его через GRUB. Указав init=/bin/bash, вы заставите ядро проигнорировать systemd и сразу выдать вам корневую оболочку с правами root. Это мощный инструмент, если штатный процесс инициализации сломан.

Корона PID 1: почему первый процесс особенный

Программа, которую ядро запустило первой, получает идентификатор процесса PID 1. Это не просто порядковый номер. Ядро наделяет PID 1 исключительными правами и обязанностями, делая его фундаментом всей операционной системы.

У PID 1 есть два уникальных свойства:

1. Иммунитет к сигналам смерти. Обычный процесс можно убить командой kill -9 <PID> (сигнал SIGKILL), и ядро немедленно уничтожит его, даже не спрашивая разрешения. Но PID 1 обладает абсолютным иммунитетом к SIGKILL и SIGSTOP. Ядро просто игнорирует эти сигналы, если они направлены первому процессу. Если PID 1 завершится (из-за ошибки или сбоя), ядро решит, что система осталась без управления, и вызовет критическую ошибку — Kernel Panic, намертво остановив работу сервера.

2. Усыновление сирот (Reaping orphans). В Linux процессы порождают друг друга, образуя генеалогическое дерево. Если родительский процесс завершается раньше своего дочернего процесса, дочерний процесс становится «сиротой». Ядро не терпит беспризорных процессов и немедленно переназначает их родителем PID 1.

Systemd, выступая в роли PID 1, обязан периодически опрашивать состояние таких усыновленных процессов (вызывать системную функцию wait()), чтобы после их завершения ядро могло корректно очистить память. Если бы PID 1 этого не делал, система быстро заполнилась бы процессами-зомби.

Первая фаза systemd: подготовка сцены

Когда systemd стартует в роли PID 1, он еще не читает конфигурации ваших веб-серверов или баз данных. Прежде чем запускать высокоуровневые службы, systemd должен создать базовую инфраструктуру, через которую программы будут общаться с ядром.

Первым делом systemd монтирует так называемые API-файловые системы. Это виртуальные файловые системы, которые не существуют на диске, а генерируются ядром в оперативной памяти на лету:

  • /proc — интерфейс для получения информации о процессах и аппаратной конфигурации. Без него не сработает ни одна команда вроде top или ps.
  • /sys — интерфейс для взаимодействия с оборудованием и драйверами (sysfs). Через него пространство пользователя узнает о подключенных устройствах.
  • /dev — файловая система устройств (devtmpfs). Здесь появляются файлы вроде /dev/sda или /dev/tty, через которые программы читают и пишут данные на железо.
  • /run — временное хранилище (tmpfs) для PID-файлов и сокетов, необходимых для общения программ друг с другом до того, как будет доступен основной диск для записи.

Смонтировав эти критически важные директории, systemd получает возможность полноценно «видеть» состояние ядра и оборудования. Только после этого он переходит к парсингу файла /etc/fstab, чтобы примонтировать остальные физические диски (например, /home или /var).

На этом этапе фундамент ОС полностью заложен. Ядро работает, драйверы загружены, виртуальные интерфейсы смонтированы, а systemd готов к главной задаче — формированию рабочего окружения, которое требуется пользователю. Для этого ему предстоит разобраться в сложной паутине зависимостей и целей (targets), что станет следующим шагом загрузки.

Цели и юниты: формирование рабочего окружения и запуск системных служб

Цели и юниты: формирование рабочего окружения и запуск системных служб

Процесс с PID 1 запущен, а базовые интерфейсы ядра смонтированы в /proc и /sys. Система жива, но абсолютно бесполезна: нет ни сети, ни графического интерфейса, ни даже приглашения для ввода логина. В классических UNIX-системах на этом этапе запускался длинный bash-скрипт, который строго по очереди, строка за строкой, поднимал службы. Загрузка сервера могла занимать минуты. systemd изменил правила игры: он запускает всё одновременно.

Чтобы параллельный старт сотен компонентов не превратился в хаос, где веб-сервер пытается запуститься до включения сетевой карты, systemd использует строгую декларативную модель зависимостей.

Юниты: атомы системы

systemd не работает со скриптами напрямую. Он оперирует юнитами (units) — стандартизированными конфигурационными файлами, которые описывают каждый ресурс системы.

Юниты бывают разных типов, их легко отличить по расширению файла:

  • .service — системная служба (демон), например, веб-сервер или база данных.
  • .mount — точка монтирования файловой системы.
  • .timer — планировщик задач (современная альтернатива cron).
  • .device — физическое или виртуальное устройство, обнаруженное ядром.
  • .target — логическая группа других юнитов.

Вместо того чтобы писать код запуска, администратор пишет декларативный манифест. Вот типичная структура юнита nginx.service:

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=network.target

[Service]
ExecStart=/usr/sbin/nginx -g 'daemon off;'
Restart=on-failure

[Install]
WantedBy=multi-user.target

systemd читает эти файлы и перед тем, как запустить хотя бы один процесс, строит в оперативной памяти гигантский направленный граф — карту того, что и с чем связано.

Паутина зависимостей: наличие против порядка

Главная инновация systemd кроется в блоке [Unit]. Зависимости здесь разделены на две независимые оси: наличие (нужно ли вообще это запускать) и порядок (кто за кем стартует). Это самая частая точка путаницы при диагностике проблем загрузки.

Ось наличия:

  • Wants= (Мягкая зависимость). Если мы запускаем службу А, systemd попытается запустить и службу Б. Если Б упадет при старте, служба А всё равно продолжит работу.
  • Requires= (Жесткая зависимость). Если служба Б не смогла запуститься, служба А даже не попытается стартовать.

Ось порядка:

  • Before= и After=. Определяют хронологию.

Если в юните А написано Requires=B.service, systemd начнет запускать их одновременно. Жесткая зависимость не означает ожидания. Если службе А критически важно, чтобы служба Б уже работала на момент ее старта, необходимо указать обе директивы: Requires=B.service и After=B.service.

Цели (Targets): вехи загрузки

Если юниты .service — это кирпичи, то юниты .target — это этажи здания. Target-юнит не делает ничего сам по себе. Это просто пустая сущность, которая через зависимости Wants и Requires стягивает к себе десятки других юнитов.

Процесс загрузки Linux — это последовательное достижение цепочки целей. Каждая следующая цель требует выполнения предыдущей.

  1. sysinit.target — ранняя инициализация. Монтируются локальные файловые системы (описанные в /etc/fstab), инициализируется пространство подкачки (swap), настраивается генератор случайных чисел.
  2. basic.target — базовая система. Запускаются службы межпроцессного взаимодействия (D-Bus), настраиваются сокеты. Система готова к запуску обычных демонов.
  3. multi-user.target — многопользовательский режим без графики. Поднимается сеть, запускается SSH-сервер, база данных, веб-серверы и появляется текстовое приглашение login:.
  4. graphical.target — запуск графической оболочки (X11 или Wayland) и дисплейного менеджера.

Исторически в Linux использовались «уровни выполнения» (runlevels) от 0 до 6. systemd сохранил обратную совместимость: если вы попросите систему перейти в runlevel3, systemd автоматически перенаправит этот запрос к multi-user.target.

Классический SysV init Цель systemd (Target) Состояние системы
Runlevel 1 rescue.target Однопользовательский аварийный режим
Runlevel 3 multi-user.target Полноценная работа в консоли, сеть активна
Runlevel 5 graphical.target Графический интерфейс пользователя
Runlevel 6 reboot.target Перезагрузка системы

default.target: конечная станция

Как systemd, будучи PID 1, понимает, в какой момент загрузка считается завершенной?

При старте systemd всегда ищет одну конкретную цель — default.target. Это не самостоятельный файл, а символическая ссылка (symlink), которая указывает на реальную конечную цель.

На серверных ОС без монитора default.target обычно указывает на multi-user.target. На десктопных дистрибутивах (Ubuntu, Fedora) — на graphical.target.

Как только systemd доходит до default.target и убеждается, что все зависящие от неё службы успешно стартовали, процесс формирования рабочего окружения завершается. Система полностью готова к приему пользователей и выполнению своих бизнес-задач. Однако, если на этом длинном пути какой-то критический юнит завершился с ошибкой, администратору придется погружаться в логи — именно там остаются следы всех решений, принятых планировщиком systemd.

Диагностика старта: анализ логов dmesg и journalctl для выявления критических ошибок

Диагностика старта: анализ логов dmesg и journalctl для выявления критических ошибок

Система достигла multi-user.target, вы подключаетесь по SSH, но нужный сервис не отвечает. Или сервер, который обычно стартует за десять секунд, в этот раз загружался пять минут. Процесс загрузки Linux асинхронен и стремителен: сотни служб запускаются параллельно, драйверы инициализируют оборудование за миллисекунды. Вы не можете прочитать эти сообщения в реальном времени. Чтобы понять, почему произошел сбой, нужно уметь читать прошлое.

Первые секунды жизни: кольцевой буфер и dmesg

Как мы выяснили на этапе распаковки ядра, функция start_kernel() начинает работу задолго до того, как будет примонтирован диск с корневой файловой системой. Ядру нужно куда-то записывать информацию о найденных процессорах, инициализации памяти и загрузке драйверов. Писать в файл оно еще не может.

Для этого ядро выделяет небольшой участок в оперативной памяти — кольцевой буфер (Ring Buffer).

Кольцевой буфер имеет фиксированный размер (обычно от 128 КБ до нескольких мегабайт, задается при компиляции). Когда буфер заполняется, новые сообщения начинают затирать самые старые. Именно поэтому он называется кольцевым.

Чтобы прочитать содержимое этого буфера из пространства пользователя, применяется утилита dmesg (diagnostic messages). Она обращается к ядру и выводит все, что ядро успело записать с момента старта.

Сырой вывод dmesg читать сложно: временные метки там указаны в секундах с момента включения питания. Для диагностики загрузки критически важны два флага:

  • dmesg -T — переводит машинное время в человекочитаемый формат (Human Readable).
  • dmesg -l err,crit — фильтрует вывод, оставляя только сообщения об ошибках (Error) и критических сбоях (Critical).

Если на этапе загрузки отвалился жесткий диск, сетевая карта не получила прошивку или произошел сбой в оперативной памяти (Machine Check Exception), следы этого нужно искать именно здесь.

Пространство пользователя: journalctl и системный журнал

Как только ядро передает управление PID 1 (systemd), правила игры меняются. Службам пространства пользователя не нужно писать в память — файловые системы уже доступны.

systemd запускает специальный компонент — systemd-journald. Эта служба перехватывает стандартный вывод (stdout) и вывод ошибок (stderr) абсолютно всех юнитов, а также собирает сообщения от самого systemd. В отличие от простых текстовых логов из прошлого (вроде /var/log/syslog), журнал systemd бинарный и строго индексированный.

Для чтения этого бинарного журнала используется journalctl. Поскольку журнал хранит историю за месяцы работы, при диагностике загрузки нужно жестко ограничивать область поиска:

  1. Изоляция текущей загрузки: journalctl -b (boot). Этот флаг отсекает все прошлые сессии и показывает логи только с момента последнего включения сервера. Если сервер ушел в перезагрузку из-за сбоя, и вы хотите посмотреть логи предыдущей загрузки, используйте journalctl -b -1.
  2. Фокус на конкретном юните: journalctl -u nginx.service -b. Показывает, почему конкретно этот юнит не смог достичь статуса active.
  3. Фильтрация по важности: journalctl -p 3 -b. Аналог уровня err в dmesg. Выведет все ошибки текущей загрузки со всех служб.

Практика: распутываем цепочку падения

Представим реальную ситуацию: после перезагрузки сервера база данных PostgreSQL не поднялась. Диагностика всегда идет сверху вниз: от симптома в пространстве пользователя к аппаратной причине.

Шаг 1: Проверяем юнит. Выполняем journalctl -u postgresql.service -b. Журнал показывает ошибку от самой СУБД: «FATAL: data directory "/var/lib/postgresql/data" does not exist».

Шаг 2: Проверяем зависимости systemd. Мы знаем, что директория данных лежит на отдельном NVMe-накопителе, который монтируется через systemd.mount. Проверяем общие ошибки загрузки: journalctl -p 3 -b. Видим сообщение от systemd: «Failed to mount /var/lib/postgresql/data. Dependency failed for PostgreSQL Database Server». Теперь ясно: база упала не из-за своих настроек, а потому что диск не примонтировался. systemd честно пытался выполнить Requires, но не смог.

Шаг 3: Спускаемся на уровень ядра. Почему диск не примонтировался? systemd этого не знает, он лишь управляет процессами. Идем в логи оборудования: dmesg -T | grep nvme. Видим строчку ядра: «nvme0: controller is down; will reset» и следом «I/O error, dev nvme0n1, sector 2048».

Цепочка замкнулась. Аппаратный сбой контроллера диска (зафиксирован ядром в dmesg) привел к невозможности смонтировать файловую систему (зафиксировано systemd в journalctl), что по цепочке зависимостей обрушило юнит базы данных.

На этом мы завершаем разбор анатомии загрузки Linux. Мы прошли путь от подачи питания и инициализации оборудования прошивкой UEFI, через работу загрузчика GRUB и распаковку ядра в памяти, до развертывания временной файловой системы initramfs и передачи управления PID 1. Понимание этой цепи — от микросхемы до графа зависимостей systemd — дает полный контроль над системой и превращает любую «магическую» ошибку старта в решаемую инженерную задачу.