LPI-Prüfungsvorbereitung LPIC-2

> # Einen Kernel compilieren

> Bewertung: 1

> Die Kandidaten sollten in der Lage sein, einen geeigneten Kernel zu compilieren, um spezifische Features des Linux-Kernels zu integrieren oder auszuschalten, je nach Notwendigkeit. Dieses Prüfungsziel beinhaltet das Compilieren und Wiedercompilieren des Kernels, das Einspielen von Updates und Anmerkung von Veränderungen in einem neuen Kernel, das Erstellen eines System-initrd Images und das Installieren neuer Kernels.

> Schlüsseldateien, Begriffe und Hilfsmittel beinhalten:

> - make
>   config, xconfig, menuconfig, oldconfig
>   mrproper
>   zImage, bzImage
>   modules, modules_install
> - mkinitrd (beide, Debian und RedHat basierte Modelle)
> - /usr/src/linux/
> - /etc/lilo.conf

> ## Erstellen eines eigenen Kernels

> Der Linux-Kernel besteht aus hunderten von einzelnen Quellcode-Dateien, die in verschiedene Unterverzeichnissen abgelegt sind. Aus diesem Grund wird von einem Kernel-Baum geredet, also einer Baumstruktur der Quellcode-Dateien.

> Normalerweise liegen diese Dateien unter /usr/src (src steht für Source - Quelle) in einem Verzeichnis das entweder linux-x.xx.xx oder kernel-source-x.xx.xx heißt. Dabei stehen statt den x.xx.xx natürlich die jeweiligen Versionsnummern. Diese Aufteilung macht es möglich, verschiedene Versionen des Kernels gleichzeitig zur Verfügung zu haben. Um einen Kernel zu compilieren, wird auf die aktuelle Version, die bearbeitet werden soll, ein Symlink gesetzt, der einfach nur linux heißt. In /usr/src steht dann z.B.:

>       drwxr-xr-x 15 root root 4096 19. Nov 17:23 kernel-source-2.4.10
>       drwxr-xr-x 14 root root 4096 16. Feb 01:29 kernel-source-2.4.14
>       lrwxrwxrwx  1 root src    20 19. Nov 20:45 linux -> kernel-source-2.4.14

> Um ein großes Projekt wie den Linux-Kernel ohne großen Aufwand zu compilieren, wird ein Programm benutzt, das alle notwendigen Schritte der Compilation anhand von Regeln ausführt. Dieses Programm heißt make und bezieht seine Regeln aus einer Datei mit Namen Makefile. Der Linux-Quellbaum enthält praktisch in jedem Verzeichnis ein solches Makefile, das wichtigste für uns ist aber das, was sich auf der Wurzel des Quellbaums, also in /usr/src/linux befindet.

> Dieses Makefile enthält verschiedenste Regeln, die jeweils als Parameter dem Befehl make mitgegeben werden. Um etwa die Regel `clean` auszuführen schreiben wir also `make clean`.

> Im Folgenden werden wir die verschiedenen Regeln des Makefiles des Linux-Kernels beschreiben und somit auch die Techniken, um einen Kernel zu compilieren.

> ### Konfiguration des Kernels

> Wie schon erwähnt, besteht der Kernel selbst aus vielen einzelnen Quelldateien. Jede dieser Dateien besteht aus - abstrakt gesehen - einer oder mehreren Fähigkeiten, die der Kernel hat, wenn diese Datei mit eingebunden wird. Da aber nicht alle Fähigkeiten erwünscht sind oder gebraucht werden (z.B braucht ein Server keine Soundkartenunterstützung) muß vor der eigentlichen Compilation festgelegt werden, was eigentlich genau compiliert werden soll. Dieser Vorgang wird als Konfiguration des Kernels bezeichnet.

> Linux bietet drei verschiedene Möglichkeiten an, diese Konfiguration vorzunehmen. Eine für simple Terminals, die keine Cursorsteuerungen können, eine für den normalen Textmodus und eine für die Konfiguration unter der graphischen Oberfläche(X11). Alle drei Methoden werden über den Befehl make aufgerufen:

> make config
>   Die älteste Methode, auch für einfache Terminals geeignet. Die Fragen werden einfach hintereinander gestellt, was bedeutet, dass immer alle Fragen beantwortet werden müssen. Mühsam!
> make menuconfig
>   Eine Dialog-gesteuerte Version, die die Auswahl wie in einem Menü ermöglicht. Ab dieser Version ist es möglich, die einzelnen Fragen auszuwählen, was es z.B. leichter macht, nur eine kleine Veränderung vorzunehmen.
> make xconfig
>   Eine Tcl/tk basierte Konfiguration unter X11. Die komfortabelste Methode, bedingt aber eben die graphische Oberfläche, die mancher Server nicht bereitstellt.

> All diesen Konfigurationsprogrammen ist gemeinsam, dass sie festlegen, welche Eigenschaften der neue Kernel haben soll. Die ausgewählte Konfiguration wird in der Datei `.config` gespeichert. Diese Datei enthält dann die Einstellungen, nach der der Kernel compiliert wird. Eine bestehende solche Datei kann mit dem Befehl make oldconfig aus einem anderen System übernommen werden.

> Bei vielen Fragen während der Konfiguration kann neben der Antwort YES oder NO noch die Antwort MODULE (M) gegeben werden. Das bedeutet, dass diese Eigenschaft des Kernels nicht fest in die ausführbare Kerneldatei integriert werden soll, sondern einzeln als Modul erstellt werden soll. Ein solches Modul kann später - während der Laufzeit des Kernels - ge- oder entladen werden. So ist es möglich, dass nicht für jede neue Hardware (z.B. Soundkarte oder Netzwerkkarte) ein neuer Kernel erstellt werden muß, sondern nur die jeweils benötigten Module geladen werden müssen.

> ### Kernel Versionsnummer setzen

> Damit ein neu kompilierter Kernel von einem bestehenden unterschieden werden kann, ist es möglich, neben den normalen Versionsnummer-Angaben auch noch eine sogenannte Extra-Versionsnummer zu setzen. Das kann im Anfangsbereich des Makefiles des Kernels geschehen. Hier finden sich die Angaben:

>       VERSION = 2
>       PATCHLEVEL = 4
>       SUBLEVEL = 18
>       EXTRAVERSION =

> Wird der Angabe EXTRAVERSION ein Wert zugewiesen, so erhält der Kernel damit einen neuen Namen. Hätten wir z.B. dort den Eintrag

>       EXTRAVERSION = -SCSI

> gemacht, dann hätte die Release-Nummer des neu erstellten Kernels jetzt

>       2.4.18-SCSI

> gelautet. Auch die Module dieses Kernels würden jetzt in das Verzeichnis `/lib/modules/2.4.18-SCSI` installiert, statt einfach nach `/lib/modules/2.4.18`.

> ### Compilieren des Kernels

> Ist der Kernel einmal konfiguriert, so muss er noch compiliert werden. Beim ersten Mal müssen noch die Abhängigkeiten überprüft werden. Das geschieht mit der Anweisung

> make dep

> Nur falls CONFIG_MODVERSIONS gesetzt ist (in `.config`), muß dieser Vorgang vor jeder Neucompilation ausgeführt werden. Sollte der Kernel schon einmal vorher compiliert worden sein und Sie wollen sicherstellen, dass keine alten Teile des Kernels in den neuen übernommen werden, so kann mit den folgenden beiden Befehlen "aufgeräumt" werden.

> make clean
>   Löscht alle Objekt-Dateien (.o) in allen Verzeichnissen. Ohne diese Anweisung werden nur die Quellcode-Dateien neu compiliert, deren Inhalt sich seit dem letzten Compiliervorgang verändert haben.
> make mrproper
>   Löscht alle Objekt-Dateien (wie make clean) und alle Konfigurationsdateien. Nach diesem Schritt ist zwingend eine Neukonfiguration (make config | menuconfig | xconfig) notwendig.

> Erst jetzt kommt der eigentliche Compiliervorgang. Die folgenden Regeln existieren:

> make zImage
>   Die alte Anweisung, die den Kernel komprimiert in einer Datei ablegt. Bei Intel-basierten Systemen liegt der fertige Kernel dann unter `./arch/i386/boot/zImage`
> make zdisk
>   Compiliert den Kernel und kopiert ihn auf eine Diskette, die so zur Bootdisk wird. Auch das die alte Methode.
> make bzImage
>   Die moderne Methode, einen großen (b wie big) Kernel herzustellen und komprimiert abzulegen. Bei Intel-basierten Systemen liegt der fertige Kernel dann unter `./arch/i386/boot/bzImage`
> make bzdisk
>   Die neue Methode, eine Bootdisk mit großem (big) Kernel zu erstellen.

> Der Vorgang des Compilierens dauert eine Weile, abhängig vom verwendeten Rechner und dessen Ressourcen. Es werden jetzt alle .c Dateien, die durch die Konfigurationsinformation angewählt wurden, in Objektdateien (.o) compiliert die dann später zum Kernel zusammengefasst (linked) werden.

> ### Compilieren der Module

> Nachdem der Kernel jetzt fertig erstellt wurde, müssen noch die Teile compiliert werden, die bei der Konfiguration als Module ausgewählt worden sind. Dazu sind die beiden folgenden Regeln notwendig:

> make modules
>   Diese Anweisung compiliert alle Module, die während der Konfiguration als Module angewählt wurden. Die Module verbleiben dann als .o Dateien im jeweiligen Verzeichnis, in dem ihr Quellcode (.c) liegt.
> make modules_install
>   Diese Anweisung installiert die compilierten Moduldateien (.o) in die systemweiten Verzeichnisse, wo sie erwartet werden. Das sind Verzeichnisse unter `/lib/modules/Kernelversionsnummer/`.

> ## Booten des neuen Kernels

> Der neue Kernel ist zwar nach all diesen oben beschriebenen Schritten jetzt fertig compiliert, wir haben aber noch keine Möglichkeit, ihn zu booten. Dazu sind noch ein paar Schritte notwendig.

> ### Kopieren der Kerneldatei

> Wenn der Kernel nicht auf eine Diskette geschrieben wurde, sondern in eine Datei (z.B. mit make bzImage), dann liegt diese Datei jetzt unter `/usr/src/linux/arch/Prozessorfamilie/boot/` und heißt bzImage oder zImage. Bei Intel-basierten Prozessoren steht für Prozessorfamilie dann ein i386. Diese Datei müssen wir jetzt an einen geeigneten Ort kopieren, wo unser Bootmanager sie dann finden kann. Dabei bieten sich zwei Stellen des Systems besonders an, entweder das Wurzelverzeichnis (alte Lösung) oder besser das Verzeichnis `/boot`, das sogar auf einer eigenen kleinen Partition liegen kann.

> Wenn Sie dieses Verzeichnis nutzen wollen, dann achten Sie darauf, dass Sie nicht einen schon existierenden Kernel überschreiben. Der Name der Kerneldatei ist frei wählbar. Wenn also beispielsweise schon ein bzImage in /boot existiert, dann nennen wir unsere neue Datei eben bzImage_2 oder wie auch immer...

> Der Vorteil dieser Architektur ist es, dass die Partition, die dann später unter /boot gemountet werden soll, unter der magischen 1024 Zylinder Grenze liegen kann. Ältere Versionen des Bootmanagers Lilo können nicht auf Partitionen zugreifen, die oberhalb dieser Grenze liegen.

> ### Konfiguration des Bootmanagers Lilo

> Der Linux-Loader (lilo) erwartet seine Einstellungen in der Datei `/etc/lilo.conf` Hier werden die Angaben über die zu bootenden Kernels eingetragen. Wenn hier mehrere Kernel angegeben sind, dann arbeitet Lilo nicht nur als Loader, sondern auch als Bootmanager. Er ermöglicht dann die freie Auswahl, welcher Kernel gebootet werden soll. Es ist in jedem Fall also ratsam, bei der Installation eines neuen Kernels den alten Eintrag in /etc/lilo.conf stehen zu lassen, bevor der neue Kernel nicht restlos ausgetestet ist. Denn falls etwas mit dem neuen Kernel schiefgehen sollte, können wir also einfach erneut den alten laden.

> Die Einträge in /etc/lilo.conf sind sehr einfach vorzunehmen. Am Beginn der Datei stehen zunächst einmal einige globale Einstellungen, die unabhängig vom zu ladenden Kernelimage sind. Dazu zählen beispielsweise

>       boot=/dev/hda            # Bootmanager auf MBR installieren
>       lba32                    # Auch Partitionen über 1024 Zylinder
>       timeout = 80             # Die Wartezeit bevor der Default-Kernel geladen wird
>       message = /boot/message  # Die Begrüßungsmeldung

> Nach diesen globalen Einstellungen folgen jetzt die Einstellungen für die zu bootenden Kernel. Jeder Kernel, den wir aktivieren wollen benötigt mindestens die drei Zeilen

> image = Kerneldateiname
>   Hier wird der absolute Pfad zu der Kerneldatei angegeben, etwa /boot/bzImage_2.
> label = Name_des_Kernels
>   Jeder Kernel bekommt einen frei wählbaren Namen, der hier angegeben wird. Dieser Name dient zur Auswahl des zu bootenden Kernels. Er muß beim Booten angegeben werden, wenn nicht der voreingestellte Kernel gebootet werden soll.
> root = Wurzelpartition
>   Hier wird die Gerätedatei angegeben, die die Wurzelpartition des Linux-Systems beschreibt, das mit diesem Kernel geladen werden soll. Also etwa /dev/hda1.

> In der Datei /etc/lilo.conf können jetzt beliebig viele Einträge dieser Art stehen. Der zuerst genannte Eintrag gilt als voreingestellt und wird gewählt, wenn beim Booten das Timeout erreicht wurde oder einfach Enter gedrückt wurde.

> Die Möglichkeit, auch die jeweils verwendete Wurzelpartition anzugeben eröffnet uns die Freiheit, auch verschiedene Linux-Systeme wie etwa SuSE und RedHat gleichzeitig auf einem Rechner zu installieren. Jede Distribution benützt ein eigenes Wurzelverzeichnis und geht sich so nicht im Weg um.

> Eine einfache lilo.conf Datei für zwei verschiedene Kernel und ein Windows-System könnte also folgendermaßen aussehen:

>       boot=/dev/hda
>       lba32
>       prompt
>       timeout = 80
>       message = /boot/message

>       image = /boot/bzImage_2
>       label = linuxneu
>       root = /dev/hdc1

>       image = /boot/bzImage
>       label = linuxorg
>       root = /dev/hdc1

>       other  = /dev/hda1
>       label  = windows

> Um diese Konfiguration jetzt tatsächlich zu aktivieren muß jetzt einmal der Befehl lilo eingegeben werden. Erst jetzt wird die entsprechende Information in den MasterBootRecord der ersten Festplatte eingetragen. Die Ausgabe von lilo sollte in unserem Beispiel folgendermaßen aussehen:

>       Added linuxneu*
>       Added linuxorg
>       Added windows

> Jetzt kann das System mit dem neuen Kernel gebootet werden.

> ## Einrichten einer initialen Ramdisk

> In manchen Fällen, etwa wenn der Kernel sehr modular aufgebaut ist, ist es notwendig beim Booten eine Ramdisk zu benutzen, um den Starvorgang erst zu ermöglichen. Wenn z.B. alle Festplatten des Systems SCSI-Platten sind, aber der Kernel die SCSI-Adapter Unterstützung als Modul laden muß, damit er auf die SCSI-Laufwerke zugreifen kann, dann haben wir ein Problem. Die Module liegen ja auf der Festplatte, die wir erst benützen können, wenn wir die Module geladen haben! Ähnliche Probleme haben wir, wenn wir von Netzwerk booten wollen, ohne unsere Module für die Netzwerkkarten geladen zu haben...

> Um dieses Problem zu lösen, bietet Linux an, beim Booten eine sogenannte initiale Ramdisk zu laden, die die entsprechenden Module beinhaltet. Auf diese Ramdisk kann der Kernel auch ohne Module zugreifen. Jetzt kann er die wichtigen Module laden um dann auf die wirklichen Festplatten (oder Netzwerkkarten) zuzugreifen. Die Ramdisk wird während des Bootvorgangs als Wurzelpartition geladen, muß also eine entsprechende Struktur besitzen.

> Wenn wir einen eigenen Kernel erstellt haben und dabei womöglich eine neuere Version der Quellen benutzen, als unsere Distribution, dann müssen wir natürlich auch in der Lage sein, eine solche initiale Ramdisk mit den gewünschten Modulen zu erstellen. Dabei existieren zwei unterschiedliche Methoden, die ihren Ursprung in den jeweiligen Distributionen von Debian und RedHat haben. Beide Befehle heißen mkinitrd haben aber vollkommen unterschiedliche Parameter. Die LPI201 Prüfung erwartet Kenntnisse beider Scripts, um sicherzustellen, dass ein Administrator in jedem Fall eine solche Ramdisk einrichten kann.

> Grundvoraussetzung für beide Fälle ist immer die Unterstützung des Kernels für RAM-Disks, und initrd (einstellbar unter Block Devices) und das CRAM-Filesystem (einstellbar unter File Systems).

>       CONFIG_BLK_DEV_RAM=y
>       CONFIG_BLK_DEV_INITRD=y
>       CONFIG_CRAMFS=y

> In jedem Fall enthält die Ramdisk ein kleines Wurzeldateisystem, auf dem eine ausführbare Datei mit Namen /linuxrc liegt. In der Regel handelt es sich dabei um ein Shellscript. Dieses Script wird abgearbeitet, wenn die Ramdisk geladen ist und sollte die entsprechenden Befehle beinhalten, die das Laden der Module bewirken. Damit eben dieses Script und die dazu notwendigen Programme auf der Ramdisk zu finden sind, gibt es die beiden Scripts von Debian und RedHat. Die Hauptaufgabe beider Scripts ist es also, das Image so zu erstellen, dass die notwendigen Module und Programme darauf auch zu finden sind.

> ### Das Debian-basierte mkinitrd-Script

> Das Script der Debian Distribution ermöglicht es, über ein Verzeichnis ziemlich genau festzulegen, was die initiale Ramdisk erledigen soll. Standardmäßig wird dieses Verzeichnis in /etc/mkinitrd angenommen. In diesem Verzeichnis befindet sich eine Datei mkinitrd.conf, in der Einstellungen vorgenommen werden können, um die Ramdisk zu konfigurieren. Hauptsächlich kann hier die Wartezeit eingestellt werden, während der sich die Abarbeitung von /linuxrc abbrechen lässt.

> Außerdem ist hier noch die Datei modules zu finden, die festlegt, welche Module tatsächlich geladen werden sollen. Mit Hilfe von beliebigen Shellscripts im Verzeichnis scripts kann dafür gesorgt werden, dass die entsprechenden Module auch tatsächlich geladen werden.

> Eine genaue Beschreibung des Debian-Scripts finden Sie auf der entsprechenden Handbuchseite von Debian.

> ### Das RedHat-basierte mkinitrd-Script

> Die RedHat Distribution bietet ebenfalls ein Script mkinitrd an, das im Wesentlichen die selben Aufgaben übernimmt, wie das von Debian. Nur wird dieses Script ausschließlich über Kommandozeilenparameter konfiguriert und nicht über ein Konfigurationsverzeichnis. Seine Anwendung ist etwas reduzierter, hauptsächlich werden die benötigten SCSI-Module automatisch geladen, die in /etc/conf.modules angegeben sind.

> Eine genaue Beschreibung finden Sie auch hier auf der entsprechenden Handbuchseite.

> ### Einbinden der Ramdisk

> Wenn das Image der Ramdisk einmal erstellt ist, so muß dem Kernel beim Booten noch mitgeteilt werden, welche Datei es enthält. Ein guter Platz für diese Images ist wiederum das Verzeichnis /boot, in dem ja auch schon unsere Kerneldateien abgelegt sind. Der Name der Ramdisk-Imagedateien ist beliebig wählbar, so dass wir für jeden Kernel eine eigene Imagedatei erstellen können.

> Damit beim Booten die jeweilige Imagedatei dann auch gefunden wird, muß in /etc/lilo.conf für jeden Kerneleintrag eine entsprechende Zeile eingefügt werden. Stand vorher also z.B. in /etc/lilo.conf

>       ...
>       image = /boot/bzImage_2
>       label = linuxneu
>       root = /dev/hdc1

>       image = /boot/bzImage
>       label = linuxorg
>       root = /dev/hdc1
>       ...

> so fügen wir jetzt für jeden Kernel die entsprechend passende Imagedatei mit dem Schlüsselwort initrd = Imagedatei ein:

>       ...
>       image = /boot/bzImage_2
>       initrd = /boot/initrd_2
>       label = linuxneu
>       root = /dev/hdc1

>       image = /boot/bzImage
>       initrd = /boot/initrd
>       label = linuxorg
>       root = /dev/hdc1
>       ...

> Nach einem erneuten Aufruf von lilo sollte jetzt beim Booten die initiale Ramdisk geladen werden und alle dort eingestellten Module werden geladen. Erst dann wird das wirkliche Wurzelverzeichnis gemountet. Das ist ja jetzt auch möglich, wenn eben ein Modul für den Zugriff auf die entsprechende Platte notwendig war.

> [ Zurück zur Startseite LPI 201 ] [ Zurück zur LPI-Seite ] [ Zurück zur Hauptseite von Linux-Praxis ] [ Guestbook ] [ Kontakt ]

> Diese und die darunterliegenden Seiten wurden erstellt von F. Kalhammer

> © 2001 by F.Kalhammer - all rights reserved.