Jak Linux startuje
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/efiJest 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/cmdlineNa 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 /bootTu 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/initNa 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.targetTo 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-defaultgraphical.targetNa serwerze bez grafiki nie ma sensu startować w trybie graficznym. Zmieniamy na stałe:
sudo systemctl set-default multi-user.targetCreated 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.targetUruchamia 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=ALLPierwsze 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_64Numer 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.cfgNa 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-grubA 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
| Komenda | Flaga | Co robi |
|---|---|---|
| systemctl | get-default | pokazuje domyślny target |
| systemctl | set-default TARGET | ustawia domyślny target na stałe |
| systemctl | isolate TARGET | przechodzi do targetu teraz, zatrzymując resztę |
| systemctl | list-units --type=target | aktywne targety |
| systemd-analyze | blame | które usługi startowały najdłużej |
| systemd-analyze | critical-chain | najwolniejsza ścieżka startu |
| grubby | --default-kernel | domyślne jądro |
| grubby | --info=ALL | wszystkie 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ŻKA | ustawia 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.cfgsudo: grub2-mkconfig: command not foundTo 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ą:
- Sprawdź, czy maszyna wystartowała przez UEFI, czy BIOS, i jaki program ma PID 1.
- Sprawdź domyślny target, ustaw
multi-user.target, zrestartuj i sprawdź ponownie. - Dodaj do parametrów jądra testowe słowo
kurs=27: na RHEL przezgrubby, na Ubuntu przez/etc/default/grubiupdate-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-defaultls: 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.targetU 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/cmdlineJeś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.