Przejdź do treści
Lekcja 27 z 117

Kurs › Moduł 2 › Lekcja 27

Jak Linux startuje

⏱ ok. 11 min czytania· moduł 2: Procesy i jak działa system

Po tej lekcji: kursant opowie, co się dzieje od przycisku zasilania do ekranu logowania, zmieni domyślny tryb startu systemu i doda parametr jądra na RHEL i na Ubuntu.

Wstęp

Naciskasz przycisk. Mija dwadzieścia sekund i jest ekran logowania. Co się stało pomiędzy?

Większość ludzi nie wie i nie musi wiedzieć. Do pierwszego razu, kiedy serwer po aktualizacji nie wstanie, a na ekranie zobaczysz grub> z migającym kursorem. Albo „Kernel panic”. Albo „You are in emergency mode”. Wtedy wiedza, który etap się wyłożył, to różnica między pięcioma minutami a całą nocą.

Dziś idziemy tę drogę krok po kroku. To jak sztafeta: pięciu biegaczy, każdy przekazuje pałeczkę następnemu.

Biegacz 1: BIOS albo UEFI

Pierwszy budzi się program zapisany w chipie na płycie głównej. Jak ochroniarz, który rano otwiera biurowiec: zapala światła, sprawdza, czy są drzwi, okna i windy, czyli procesor, pamięć i dyski. To jest POST, test po włączeniu zasilania.

Potem szuka, kto ma przejąć dalej. Starszy BIOS czyta pierwszy sektor dysku, 512 bajtów, czyli MBR. Nowszy UEFI jest mądrzejszy: czyta specjalną małą partycję z systemem plików FAT, nazywaną ESP, i uruchamia z niej plik programu startowego. UEFI ma też Secure Boot, czyli sprawdzanie podpisu: uruchomi tylko program podpisany zaufanym kluczem.

Sprawdzisz, jak wystartowała Twoja maszyna:

ls /sys/firmware/efi

Jest folder? UEFI. „No such file or directory”? Stary dobry BIOS albo jego emulacja.

Biegacz 2: GRUB

GRUB to bootloader, program startowy. Jego jedyne zadanie: znaleźć jądro systemu, załadować je do pamięci i przekazać mu pałeczkę. To on pokazuje menu z listą jąder, kiedy masz ich kilka po aktualizacjach.

W menu GRUB-a możesz wcisnąć e i edytować wpis na jeden raz. Tak naprawia się większość serwerów, które nie chcą wstać, ale to temat na osobna lekcja w module o awariach.

GRUB przekazuje jądru parametry startowe, tak zwaną linię poleceń jądra. Zobaczysz ją w działającym systemie tu:

cat /proc/cmdline

Na typowym serwerze RHEL będzie tam ścieżka do jądra, root=, czyli gdzie leży główny system plików, ro, żeby zacząć tylko do odczytu, i kilka innych. Jak zmienić te parametry na stałe, pokażę za chwilę, bo tu RHEL i Ubuntu idą różnymi drogami.

Biegacz 3 i 4: jądro i initramfs

Jądro rusza, rozpoznaje sprzęt i… ma problem. Żeby załadować system z dysku, potrzebuje sterownika do tego dysku. A sterownik leży na dysku. To jak kluczyk do sejfu zamknięty w tym samym sejfie.

Rozwiązaniem jest initramfs: mały, spakowany system plików, który GRUB ładuje do pamięci razem z jądrem. W środku jest dokładnie to, co potrzebne, żeby dostać się do prawdziwego dysku: sterowniki, narzędzia do LVM, do szyfrowania dysku, do RAID. Jak walizka z narzędziami przywiązana do sejfu z zewnątrz.

Jądro rozpakowuje walizkę, montuje prawdziwy system plików, zamienia go z walizką miejscami i uruchamia pierwszy prawdziwy program.

ls /boot

Tu leżą obaj biegacze. Pliki vmlinuz-… to jądra, a obok ich walizki: [RHEL] initramfs-….img, [Ubuntu] initrd.img-…. Zawartość walizki podejrzysz przez [RHEL] lsinitrd, [Ubuntu] lsinitramfs. A odbudujesz ją, kiedy na przykład zmienisz sterownik dysku: [RHEL] sudo dracut -f, [Ubuntu 24.04] sudo update-initramfs -u.

Biegacz 5: PID 1, czyli kierownik zmiany

Pierwszy program, który uruchamia jądro, dostaje numer procesu 1. Pamiętasz z lekcji o procesach: to kierownik zmiany. Uruchamia wszystkie usługi, pilnuje ich, adoptuje sieroty i ostatni wyłącza światło. Jeśli PID 1 umrze, umiera cały system: kernel panic.

ps -p 1 -o pid,comm
readlink -f /sbin/init

Na każdej współczesnej dystrybucji odpowiedź będzie ta sama: systemd. Ale nie zawsze tak było.

Trochę historii: SysV init i Upstart

Przez dekady kierownikiem był SysV init. Działał jak kierownik z segregatorem: system miał poziomy uruchomienia, runlevele, ponumerowane od 0 do 6. Zero to wyłączenie, sześć to restart, jedynka to tryb jednego użytkownika do napraw, a pomiędzy tryby pracy, na przykład trzy to serwer bez grafiki, a pięć z grafiką. Dla każdego poziomu był folder ze skryptami Basha w kolejności alfabetycznej, uruchamianymi jeden po drugim. Prosto, czytelnie i… powoli. Jak kolejka w urzędzie z jednym okienkiem.

Ubuntu próbowało to przyspieszyć własnym kierownikiem, Upstartem, który reagował na zdarzenia: „dysk się pojawił, to odpalam to, co go potrzebuje”. Upstart był w Ubuntu od wersji 6.10 do 14.10, a w 15.04 Ubuntu przeszło na to, czego używa dziś cały świat.

systemd napisali Lennart Poettering i Kay Sievers, pierwsza wersja wyszła w 2010 roku. Domyślnym kierownikiem został w Fedorze 15 w 2011, w RHEL 7 w 2014, a w Debianie 8 i Ubuntu 15.04 w 2015 roku. systemd uruchamia usługi równolegle, pilnuje zależności, zbiera logi i robi jeszcze kilkanaście innych rzeczy. Dlatego jedni go kochają, a inni piszą o nim długie posty na forach. Ty po prostu musisz go znać, bo jest wszędzie.

Targety: tryby pracy budynku

W systemd zamiast poziomów są targety, cele. Target to po prostu lista: „żeby budynek był w trybie biurowym, muszą działać windy, recepcja, klimatyzacja i drzwi obrotowe”. Najważniejsze:

  • multi-user.target — pełny serwer: sieć, usługi, logowanie, ale bez grafiki. Tak działa prawie każdy serwer.
  • graphical.target — to samo plus ekran logowania z grafiką.
  • rescue.target — tryb ratunkowy: zamontowane dyski, minimum usług, powłoka roota. Odpowiednik dawnego trybu jednego użytkownika.
  • emergency.target — jeszcze mniej: tylko kierownik i powłoka. Używany też automatycznie, kiedy na przykład wymagany system plików nie chce się zamontować.

A co ze starymi numerkami? systemd ma targety zgodności o nazwach runlevel0.target do runlevel6.target. Zobacz, na co wskazują:

ls -l /usr/lib/systemd/system/runlevel*.target
/usr/lib/systemd/system/runlevel0.target -> poweroff.target
/usr/lib/systemd/system/runlevel1.target -> rescue.target
/usr/lib/systemd/system/runlevel2.target -> multi-user.target
/usr/lib/systemd/system/runlevel3.target -> multi-user.target
/usr/lib/systemd/system/runlevel4.target -> multi-user.target
/usr/lib/systemd/system/runlevel5.target -> graphical.target
/usr/lib/systemd/system/runlevel6.target -> reboot.target

To są dowiązania, czyli skróty. Zero, jeden i sześć to oficjalne aliasy. Dla dwójki do piątki dokumentacja systemd mówi ostrożniej: „dobrym pomysłem” jest wskazanie graphical.target dla piątki i multi-user.target dla pozostałych. Tak właśnie robią dystrybucje, co widać wyżej. Na egzaminie i w rozmowie wystarczy zapamiętać: „trójka to multi-user, piątka to graphical”.

get-default, set-default, isolate

W jakim trybie system startuje?

systemctl get-default
graphical.target

Na serwerze bez grafiki nie ma sensu startować w trybie graficznym. Zmieniamy na stałe:

sudo systemctl set-default multi-user.target
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.

I znowu: to tylko dowiązanie. Domyślny tryb to plik default.target, a set-default przestawia, na co on wskazuje. Działa od następnego startu.

A jeśli chcesz przejść do innego trybu teraz, bez restartu, jest isolate:

sudo systemctl isolate multi-user.target

Uruchamia wszystko, czego wymaga wskazany target, i zatrzymuje wszystko inne. Jeśli siedzisz w okienku graficznym i zrobisz isolate multi-user.target, Twoje okienko właśnie znikło. Dokumentacja systemd sama nazywa tę komendę niebezpieczną. Działa tylko na targetach, które na to pozwalają, więc nie odizolujesz się do przypadkowej usługi.

Uwaga: isolate rescue.target na serwerze, do którego jesteś podłączony przez SSH, zatrzyma też SSH. Odetniesz się od własnego serwera. Rób to tylko na maszynie wirtualnej albo z dostępem do konsoli.

Parametry jądra na stałe: RHEL

Na RHEL konfiguracja GRUB-a jest podzielona na małe pliki, po jednym na każde jądro, w folderze /boot/loader/entries/. To tak zwany standard BLS. Nie edytujesz ich ręcznie, od tego jest grubby:

sudo grubby --default-kernel
sudo grubby --info=ALL

Pierwsze pokaże plik jądra, które startuje domyślnie. Drugie wszystkie wpisy z ich parametrami. Dodanie parametru do wszystkich jąder:

sudo grubby --update-kernel=ALL --args="kurs=27"

Usunięcie:

sudo grubby --update-kernel=ALL --remove-args="kurs=27"

Zamiast ALL możesz podać jedno jądro, na przykład bieżące: --update-kernel=/boot/vmlinuz-$(uname -r). A zmiana domyślnego jądra, kiedy nowe po aktualizacji sprawia problemy:

sudo grubby --set-default /boot/vmlinuz-5.14.0-427.13.1.el9_4.x86_64

Numer wersji jest tu przykładowy, swój odczytasz z grubby --info=ALL. Wszystko działa od następnego startu, po którym sprawdzasz cat /proc/cmdline.

Jest też druga droga, którą znajdziesz w starszych poradnikach: parametry w pliku /etc/default/grub, w linijce GRUB_CMDLINE_LINUX, a potem przebudowa konfiguracji:

sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Na RHEL 9 ta sama ścieżka obowiązuje i dla BIOS-u, i dla UEFI. I uwaga: parametry dodane przez grubby siedzą we wpisach BLS, a nie w /etc/default/grub. Jeśli chcesz, żeby grub2-mkconfig nadpisał wpisy wartościami z tego pliku, dodajesz --update-bls-cmdline. Na co dzień na RHEL: grubby.

Parametry jądra na stałe: Ubuntu

[Ubuntu] Tu nie ma grubby. Edytujesz /etc/default/grub:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

Parametry dopisujesz do GRUB_CMDLINE_LINUX, który trafia do wszystkich wpisów. GRUB_CMDLINE_LINUX_DEFAULT trafia tylko do zwykłego startu, nie do trybu ratunkowego. GRUB_TIMEOUT=0 i hidden znaczą, że menu się nie pokazuje. Na serwerze warto dać kilka sekund, żeby w razie awarii zdążyć coś wybrać. Potem:

sudo update-grub

A czym jest update-grub? Zajrzyjmy do środka:

cat /usr/sbin/update-grub
#!/bin/sh
set -e
exec grub-mkconfig -o /boot/grub/grub.cfg "$@"

Trzy linijki. To ta sama komenda co na RHEL, tylko bez dwójki w nazwie i z inną ścieżką. Cała różnica między dystrybucjami: [RHEL] grub2-mkconfig -o /boot/grub2/grub.cfg, [Ubuntu] grub-mkconfig -o /boot/grub/grub.cfg, czyli update-grub.

Bezpieczeństwo: zepsuty grub.cfg to serwer, który nie wstanie. Nigdy nie edytuj grub.cfg ręcznie, bo następna aktualizacja jądra i tak go nadpisze. Zmieniaj źródła i generuj. I pierwsze próby rób na maszynie wirtualnej ze zrobioną migawką.

Jak to działa pod spodem

Kiedy jądro skończy własną inicjalizację, uruchamia program /init z initramfs. Na RHEL ten initramfs buduje narzędzie dracut i w środku jest… mały systemd. On montuje prawdziwy system plików pod /sysroot, a potem wykonuje switch_root: zamienia korzeń systemu plików na prawdziwy dysk, zwalnia pamięć walizki i uruchamia /sbin/init z dysku, czyli właściwy systemd. Ten proces dostaje PID 1, czyta default.target i idzie po zależnościach jak po drzewie: graphical.target wymaga multi-user.target, ten wymaga basic.target, ten sysinit.target i tak dalej. Wszystko, co nie zależy od siebie nawzajem, startuje równolegle. Cały ten łańcuch opisuje man bootup, z ładnym rysunkiem z ASCII.

Ściąga flag

KomendaFlagaCo robi
systemctlget-defaultpokazuje domyślny target
systemctlset-default TARGETustawia domyślny target na stałe
systemctlisolate TARGETprzechodzi do targetu teraz, zatrzymując resztę
systemctllist-units --type=targetaktywne targety
systemd-analyzeblamektóre usługi startowały najdłużej
systemd-analyzecritical-chainnajwolniejsza ścieżka startu
grubby--default-kerneldomyślne jądro
grubby--info=ALLwszystkie wpisy z parametrami
grubby--update-kernel=ALL --args="X"dodaje parametr do wszystkich jąder
grubby--update-kernel=ALL --remove-args="X"usuwa parametr
grubby--set-default ŚCIEŻKAustawia domyślne jądro
grub2-mkconfig-o /boot/grub2/grub.cfg[RHEL] generuje konfigurację GRUB-a
update-grub(brak)[Ubuntu] to samo: grub-mkconfig -o /boot/grub/grub.cfg
dracut-f[RHEL] przebudowuje initramfs
update-initramfs-u[Ubuntu] przebudowuje initramfs

⚠ Typowy błąd

Admin na Ubuntu znalazł w internecie poradnik dla RHEL i wpisuje:

sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo: grub2-mkconfig: command not found

To akurat dobrze, bo nic się nie zepsuło. Gorzej w drugą stronę: na RHEL ktoś edytuje /etc/default/grub, generuje grub.cfg, restartuje… i parametru nie ma w /proc/cmdline. Dlaczego? Bo na RHEL parametry każdego jądra mieszkają we wpisach BLS, a samo grub2-mkconfig ich nie przepisuje. Zmieniłeś plik, którego nikt nie czyta. Jak wywieszenie ogłoszenia na tablicy w budynku, z którego wszyscy się wyprowadzili.

Naprawa: na RHEL użyj grubby --update-kernel=ALL --args=... albo dodaj do grub2-mkconfig opcję --update-bls-cmdline. Po restarcie zawsze sprawdzaj cat /proc/cmdline. Zaufanie jest dobre, ale /proc/cmdline jest lepsze.

✎ Zadanie

Na maszynie wirtualnej, najlepiej z migawką:

  1. Sprawdź, czy maszyna wystartowała przez UEFI, czy BIOS, i jaki program ma PID 1.
  2. Sprawdź domyślny target, ustaw multi-user.target, zrestartuj i sprawdź ponownie.
  3. Dodaj do parametrów jądra testowe słowo kurs=27: na RHEL przez grubby, na Ubuntu przez /etc/default/grub i update-grub. Zrestartuj i znajdź je w /proc/cmdline. Potem je usuń.
Pokaż rozwiązanie
ls /sys/firmware/efi
ps -p 1 -o comm=
systemctl get-default
sudo systemctl set-default multi-user.target
systemctl get-default
ls: cannot access '/sys/firmware/efi': No such file or directory
systemd
graphical.target
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.
multi-user.target

U mnie brak folderu efi, więc start przez BIOS. PID 1 to systemd. Po restarcie maszyna wstaje bez grafiki. Parametr jądra:

# [RHEL]
sudo grubby --update-kernel=ALL --args="kurs=27"
# [Ubuntu]: w /etc/default/grub GRUB_CMDLINE_LINUX="kurs=27", potem:
sudo update-grub
# po restarcie, w obu:
grep -o 'kurs=27' /proc/cmdline

Jeśli grep nic nie wypisał, sprawdź, czy na Ubuntu nie zapomniałeś o update-grub. Plik bez wygenerowania konfiguracji to list bez znaczka: napisany, ale nigdzie nie dojdzie. Usuwasz przez --remove-args albo kasując wpis i ponownie update-grub.

Co dalej

Kierownik zmiany już pracuje. W następnej lekcji uczymy się z nim rozmawiać: systemctl, włączanie, wyłączanie i maskowanie usług, a na koniec piszemy własną usługę z naszego skryptu.