5 lutego 2009

Dłubiemy w systemie - cz. 2: Konfiguracja i kompilacja jądra

Poniższy opis odnosi się do Linuksa w wersji 2.6.x. W przypadku kompilacji jądra serii 2.4 i 2.6 występują subtelne różnice.

Przebieg kompilacji i instalacji jądra możemy bardzo ogólnie opisać w paru punktach:
  • Zdobycie źródeł, oraz informacji o sprzęcie

  • Konfiguracja – od zera, lub z wykorzystaniem istniejącej konfiguracji

  • Kompilacja
    • Obrazu jądra
    • Modułów

  • Instalacja (kolejność nie ma znaczenia)
    • Modułów
    • Skopiowanie lub przeniesienie obrazu jądra do /boot
    • Wygenerowanie ramdysku initrd i umieszczenie go w /boot (opcjonalnie – w przypadku gdy główny system plików, którego obsługa jest niezbędna do załadowania systemu nie został wkompilowany na stałe w jądro, lub w przypadku korzystania z duetu bufora ramki (frame buffer, pozwalający na osiągnięcie wysokiej rozdzielczości w konsoli, a także 32 bitowej palety kolorów, co pozwala m.in. oglądać filmy w czystej konsoli (po uprzednim ustawieniu wyjscia video w naszym ulubionym odtwarzaczu na bufor ramki)) i tzw. bootsplash'a, czyli grafiki/animacji wyświetlanej podczas uruchamiania systemu)
  • Konfiguracja bootloadera (dodanie nowych wpisów dla nowego kernela)
  • Reboot i pacierz ;)


Pierwszym krokiem jest zaopatrzenie się w źródła jądra. Archiwa 'waniliowego' (czyli gołego, bez dodatkowych patchsetów dostępnych w dystrybucyjnych Linuksach) kernela możemy pobrać ze strony http://www.kernel.org. Naukę kompilacji najlepiej przeprowadzać pod Slackware, ponieważ nie korzysta on z żadnych patchsetów, co pozwoli nam na uniknięcie przykrych niespodzianek typu „Po kompilacji jądra nie działa mi skaner” (bo jego sterownik dostępny był w postaci patcha lub wchodził w skład patchsetu). Jeśli już musimy kompilować jądra np. pod Mandrivą, Fedorą czy Ubuntu zalecam aby zaopatrzyć się w pakiet ze źródłami jądra pochodzący z repozytorium. W ten sposób otrzymamy kompletne, połatane źródła Linuksa.

Źródła „jajka” najczęściej umieszcza się w /usr/src. Po wypakowaniu archiwum naszym oczom ukaże się katalog linux-wersja. Warto utworzyć sobie dowiązanie symboliczne /usr/src/linux prowadzące do źródeł naszego jądra.

Niektóre programy podczas kompilacji wymagają dostępu do źródeł jądra. Przykładem mogą być sterowniki dostarczane w postaci kodu źródłowego, z którego budowany jest moduł jądra. Jeśli w systemie mamy kilka kerneli, może pojawić się pytanie – skąd wiadomo w którym katalogu szukać źródeł jądra? Modyfikacja dowiązania /usr/src/linux przy każdym uruchomieniu systemu na danym jądrze jest rozwiązaniem bardzo nieeleganckim, poszukiwanie źródeł w /usr/src/ z wykorzystaniem uname -r także nie jest zbyt szczęśliwym wyjściem z sytuacji bo te niekoniecznie muszą się znajdować akurat tam. Dlatego istnieją inne dowiązania - /lib/modules/`uname -r`/source

Sam katalog /lib/modules
jak sama nazwa wskazuje jest miejscem, w którym przechowywane są skompilowane moduły jądra, w każdej chwili gotowe do załadowania.



Jeśli chodzi o informacje na temat sprzętu – podstawowe informacje możemy otrzymać poprzez wykorzystanie polecenialspci, lsusb oraz dmesg.

Czym są moduły?
Większość ficzerów możemy zbudować w postaci modułu, zamiast integrować z samym jądrem (co ma wpływ na wielkość jego obrazu). Główną zaletą modułów jest możliwość ich ładowania i usuwania z pamięci, czyli poszerzanie/zwężanie możliwości naszego Linuksa i to bez potrzeby ponownego uruchamiania systemu (Co ma niebagatelne znaczenie np. w przypadku serwerów)! Ficzery z których korzystamy rzadko, a są pamięciożerne (np. obsługa systemów plików – jeśli nie używamy danego systemu plików na danej maszynie, lub rzadko zachodzi taka potrzeba) warto kompilować do postaci modułu (załóżmy, że zdarza się, iż raz na miesiąc musimy coś przekopiować z cudzego dysku na którym wykorzystuje się inny system plików. Trzymanie w pamięci funkcji odpowiedzialnych za jego obsługę jest marnotrawieniem czasu, podobnie jak kompilacja osobnego jądra wyposażonego w daną funkcjonalność. W takim przypadku mamy możliwość załadowania modułu na czas wykonania odpowiednich zadań i późniejsze ich wyłączenie, bez ponownego startu systemu).



Ogromne pole do popisu dają tzw. SoftLevele, dostępne m.in. w Gentoo, które pozwalają tworzyć osobne profile skryptów startowych, w zależności od parametru podanego w bootloaderze. Co to oznacza w praktyce? Jeśli np. w domu korzystamy z połączenia kablowego, natomiast na uczelni przesiadamy się na sieć bezprzewodową, to możemy z poziomu bootloadera zadecydować m.in. które skrypty startowe zostaną załadowane (możemy stworzyć 2 skrypty startowe, z czego 1 odpowiada za załadowanie modułów do obsługi karty wlan oraz podniesienie odpowiednich interfejsów, a drugi dotyczy tradycyjnej karty sieciowej). I nie marnujemy czasu na kompilację 2 linuksów, tym samym oszczędzając wolną pamięć.



Kwestia konfiguracji – tysiące przeróżnych HOWTO's i Artykułów powstało na ten temat. Najlepszą metodą na opanowanie tej sztuki jest eksperymentowanie i samodzielne próby. Dobrą techniką jest użycie konfiguracji wykorzystywanej w jądrze dystrybucyjnym i jego modyfikacja pod kątem własnych potrzeb. Jeśli jądro zostało skompilowane z obsługą IKCONFIG_PROC („General Setup” → „Enable access to .config through /proc/config.gz”, wymaga uwcześniejszego zaznaczenia opcji „Kernel .config support” znajdujuącego się w tym samym podmenu) możemy wyświetlić config kernela przy pomocy polecenia zcat /proc/config.gz. W przeciwnym wypadku kopia pliku konfiguracyjnego powinna znajdować się w katalogu /boot.
Moja rada: Początkujący powienien trzymać się zasady, że jeśli nie jest pewien jakiejś opcji to powinien jej nie ruszać.



Konfiguracja jądra zawiera się w 1 pliku tekstowym .config (kropka na początku nazwy), znajdującym się w katalogu źródeł. Jego modyfikacji, czyli „skrajania kernela na miarę”, możemy dokonywac na kilka sposobów:

  • Edycję pliku przy pomocy ulubionego edytora tekstowego, jednak jest to rozwiązanie dla masochistów ;)

  • Użycie jednego z kilku dostępnych narzędzi (wywołujemy poprzez polecenie make nazwa_narzędzia):
    • menuconfig – tekstowy edytor wykorzystujący bibliotekę ncurses. W tym przypadku symbol gwiazdki ('*') oznacza 'wkompilowane w jądro', duża litera 'M' oznacza moduł, natomiast puste pole to wyłączenie danej funkcji z jądra. Jeśli '*'/'M'/' ' jest otoczone ostrymi nawiasami – osnacza to, iż dana opcja może zostać wkompilowana w jądro na stałe, jako moduł, lub wcale. Nawias indeksowy oznacza, że dana opcja nie może pojawić się w postaci modułu. Przykładem może być widoczna na obrazku obsługa modułów (2 pozycja w menu). Myśliki oznaczają brak możliwości zmiany statusu. Strzałka za nazwą oznacza, iż opcja posiada podmenu.

      Podsumowując - jest to najlepsze narzedzie, ze względu na przejrzystość interfejsu oraz niewielkie zależności software'owe.

      Ilustracja 2: xconfig (biblioteka QT)

      Ilustracja 3: gconfig (GTK)

    • xconfig, gconfig – graficzne narzędzie do konfiguracji jądra, których filozofia i obsługa jest analogiczna do menuconfig. Ich wadą jsą zależności – poza systemem Xwindow (X.Org/XFree86[starsza wersja]) wymagane są także odpowiednie biblioteki, np. Qt, czy GTK. Niewątpliwą zaletą tych 2 rozwiązań jest przejrzystość.

    • oldconfig – po wcześniejszym skopiowaniu pliku konfiguracyjnego pochodzącego ze starszego lub nowszego jądra w katalogu ze źródłami, narzędzie to uaktualnia jego zawartość (jeśli w skopiowanym .config'u któraś z opcji się nie pojawiła, dopisuje ją i ustawia jej domyślny atrybut)

    • defconfig – tworzy nowy .config używając domyślnych ustawień.


  • Więcej aktualnych informacji można uzyskać poprzez komendę make help

    Praktyczne i uniwersalna wskazówka z mojej strony: Takie rzeczy jak obsługa kontrolera dysków, czy systemu plików znajdującego się na głównej partycji zawsze warto integrować z jądrem. Są to rzeczy z których korzystać będziemy zawsze, więc zbudowanie ich w postaci modułów nie przyniesie nam żadnych korzyści (chyba że jest się deweloperem jakiejś dystrybucji odpowiedzialnym za sam kernel i chce się zapewnić uniwersalność, połączoną z możliwościa wyłączenia niepotrzebnych sterowników [z tym, że jeśli ktoś się chwyta za takie rzeczy to posiada odpowiednią wiedzę i nie trzeba go pouczać]). W przeciwnym wypadku musimy pamiętać nie tylko o wygenerowaniu initrd, ale także o włączeniu odpowiedniej opcji w jądrze, która umożliwia zastosowanie tego rozwiązania.

    Dlaczego?
    Jak już wcześniej wspominałem – moduły znajdują się na dysku twardym, w katalogu /lib/modules/wersja_jądra/. Aby się do nich dostać wymagana jest obsługa kontrolera i systemu plików. Nie jesteśmy w stanie wczytac sterownika, które zapewnia obsługę danego urządzenia, jeśli znajduje się na tym urządzeniu (tutaj goraco pozdrawiam producentów napedów blu-ray, którzy sterowniki umieszczają na krążkach blue-ray ;) ). Aby ominąć ten problem, należy umieścić sterownik w ram dysku (initrd).

    Przejdźmy do kolejnego kroku – czyli właściwej kompilacji. Możemy kompilowac na dwa sposoby:

      • Używając jednego polecenia: make all lub make (bo domyślnym targetem dla make jest 'all'), które: skompiluje jądro (make vmlinux), utworzy jego skompresowany obraz (make bzImage), oraz zbuduje moduły (make modules).

      • Używając 2 poniższych poleceń, etap po etapie, zachowując przy tym większą kontrolę nad całym procesem kompilacji oraz umożliwiając łatwe zdiagnozowanie której części procesu kompilacji dotyczy ew. błąd.
        make bzImage (bzImage wyzwala vmlinux – wystarczy zajrzeć do Makefile znajdującego się w podkatalogu arch/`uname -m`/)
        make modules

      Teraz przechodzimy do instalacji nowego jądra. Instalujemy moduły następującym poleceniem:

      • make modules_install

      Tutaj możemy również skorzystać z jednego polecenia, lub wykonać cały zabieg ręcznie:

      • make install – to polecenie skopiuje obraz jądra do /boot, doda odpowiedni wpisy do bootloadera. Podczas instalacji wykorzystywany jest skrypt ~/bin/installkernel, /sbin/installkernel lub jądro jest instalowane w $(INSTALL_PATH) i automatycznie uruchamiany jest wyzwalacz dla LILO (dodanie wpsiu do booltoadera).

        Lub poprzez:

      • cp arch/`uname -m`/boot/bzImage /boot/jajko-wersja oraz ręczne dodanie wpisów do bootloadera. Jeśli korzystamy z LILO, modyfikujemy plik /etc/lilo.conf, zachowując składnię przedstawioną poniżej:

        image=/boot/nazwa_obrazu opcje #sciezka do obrazu jadram oraz ew. opcje (dla jądra, dla framebuffera, czy def. softlevelu)
        label=nowe_jajko #label czyli tresc etykiety w menu LILO
        initrd=/boot/initrd-2.2.12-20.img #opcjonalnie
        read-only
        root=/dev/sda1 #tutaj podajemy partycję główną



    W przypadku GRUB'a edytujemy /etc/grub.conf, /boot/grub/menu.lst/boot/grub/grub.conf, wg schematu:

    title nowe_jajko
    root (hd0,0)
    kernel sciezka_do_obrazu_jadra opcje
    initrd sciezka_do_initrd



        Jeśli istnieje taka potrzeba – należy jeszcze utworzyć plik initrd, np. przy pomocy narzędzia mkinitrd

        Po kazdej rekompilacji jądra (a dokładnie – przebudowie modułów) należy pamiętać iż sterowniki dostarczane w postaci zewnętrznych modułów, jak np. nvidia-drivers, wymagają reinstalacji.