Ideen - Werkstatt - Projekte - Basteln - Hausbau

Linux Wegweiser

Kompilieren des Linux Kernels

Dieser Artikel erklärt wie und warum ein Linux Kernel kompiliert werden soll.

Inhalt

1.1 Was ist der Linux Kernel?
1.2 Welche Vorteile bringt das eigene Kompilieren?
1.3 Voraussetzungen

2.1 Installation der Kernel-Quellen
2.2 Konfiguration des Kernels vorbereiten
2.3 Konfiguration des Kernels
2.3.1 Treiber als Modul oder in den Kernel

3 Kompilieren

4.1 Installation der Module
4.2 Installation des Kernels
4.3 Installation der Init-Ramdisk
4.4 Konfiguration des Bootloaders Grub

5 Quellen

1.1 Was ist der Linux Kernel?

Der Kernel ist die Schnittstelle zwischen Anwendungsprogrammen und der Hardware des Systems. Er kümmert sich darum, dass die Anwendungsprogramme auf die vorhandene Hardware zugreifen können, und zwar ohne die Details der Hardware zu kennen und ohne Rücksicht auf andere Programme nehmen zu müssen.
Beispiel
Wenn ein Programm eine Datei auf die Festplatte schreiben möchte, dann kann bzw. darf es nicht direkt mit der Festplatte kommunizieren. Um seine Daten direkt auf die Festplatte zu schreiben, müsste ein Programm den exakten Festplatten- und Controllertyp kennen. Außerdem müsste das Programm sicherstellen, dass nicht gleichzeitig ein zweites Programm die Festplatte benutzt. Das wäre vielleicht umständlich! Statt dessen gibt das Programm den Inhalt der Datei an den Kernel weiter, der den Schreibvorgang durchführt. Der Kernel kennt die Hardware des Systems und sorgt für geordnetes "Miteinander". Auch wenn alle anderen Programme "me too!" rufen.
Die zuvor erwähnte Schnittstellenfunktion gilt insbesondere für den Arbeitsspeicher und den Prozessor. Jedes Programm soll seinen eigenen Speicherbereich haben, den andere Programme in der Regel weder lesen noch beschreiben sollen. Und was ist, wenn mehr Speicher benötigt wird als physikalisch vorhanden ist? Der Kernel kümmert sich darum.

1.2 Welche Vorteile bringt das eigene Kompilieren?

Heutige Linux-Distributionen bringen in der Regel sehr gute "All-round"-Kernel mit: Sie laufen auf praktisch allen PC-Systemen. Dennoch gibt es einige Gründe, den Kernel neu zu erstellen:
  • Neue Fähigkeiten. Der Linux Kernel wird von vielen Programmieren unter der Koordination von Linus Torvalds weiter entwickelt. So erhält der Kernel immer wieder neue Fähigkeiten und Verbesserungen.
  • Maßanfertigung. Da die Standard-Kernel meist "alles" können, sind sie in der Regel (zu) groß. Wenn man nicht benötigte Features entfernt, verbessert man die Performance meist nur marginal. Allerdings erhält man einen kleineren und stabileren Kernel.
  • An Prozessor anpassen. Der Kernel kann an verschiedene Prozessoren angepasst werden, um deren spezielle Fähigkeiten möglichst gut zu nutzen. Wird Linux auf einem weniger gebräuchlichen Prozessor installiert, wird meist ein Kernel aufgespielt, der keine speziellen Features voraussetzt (z.B. ein 386er Kernel), da die Distribution keinen besser passenden Kernel mitbringt.
  • Treiber: Als Module oder in den Kernel. Linux kann Treiber (Schnittstellen zu einer bestimmten Hardware) direkt in den Kernel integrieren oder in Form von "Modulen" nachträglich bzw. bei Bedarf laden. Linux-Distributionen bringen fast alle Treiber als Module mit. So ist der Kernel selbst relativ klein, kann aber je nach verwendeter Hardware die passenden Module nachladen. Ein sehr schönes Konzept. Trotzdem ist es sinnvoll, den Treiberwald etwas aufzuräumen und zu ordnen: Wird ein bestimmter Treiber grundsätzlich gebraucht (z.B. für die Festplatte), kommt er in den Kernel. Wird er gelegentlich gebraucht (z.B. Scanner), ist er in einem Modul besser aufgehoben und verbraucht nur Arbeitsspeicher, wenn er tatsächlich benötigt wird. Wird ein Treiber mit Sicherheit nicht benötigt, so muss er gar nicht erst kompiliert werden.
  • Diskless Clients. Wenn ein Rechner ohne Festplatte per Netzwerk booten soll, ist der Kernel mit speziellen Optionen zu übersetzen.
  • Verwendung spezieller Hardware. Manche spezielle, meist aber seltene, Hardware erfordert einen speziellen Kernel.

1.3 Voraussetzungen

Zum Kompilieren eines Kernel benötigt man eine ganze Reihe von Softwarepaketen. Die meisten sind in der Regel aber schon bei der Installation des Systems aufgespielt worden. Was unter Umständen übrig bleibt:
  • Ein Kompiler, Paketname "gcc-c++". Diese Pakets benötigt einige weitere Pakete, die von einem Paketmanager (z.B. Yast) automatisch hinzugefügt werden.
  • Das graphische Konfigurationstool benötigt das "qt-devel" Paket, das Text-basierte Konfigurationsprogramm das "ncurses-devel" Paket. Eines von beiden reicht. Das graphische Programm ist für den Anfänger sicher etwas leichter zu bedienen und daher meine Empfehlung.
  • Der Quelltext des Kernels. Um lediglich den bereits installierten Kernel zu modifizieren, kann das Paket "kernel-source" installiert werden. Einen neueren Kernel gibt es auf http://www.kernel.org/ zum Herunterladen. Welcher Kernel momentan installiert ist, erfährt man mit dem Befehl cat /proc/version. Bei der Auswahl eines neuen Kernels ist es einfacher, den Hauptast (die ersten beiden Versionsnummern) beizubehalten. Wenn z.B. die momentan verwendete Kernelversion 2.6.16.21-0.13-smp ist, so sollte ein Kernel aus dem 2.6.xx Ast verwendet werden. Ist ein 2.4.xx Kernel installiert, so kann ein Upgrade auf 2.6 sinnvoll sein. Es verlangt aber in der Regel weitere Änderungen am System.
    Kernel-Quellen können von kernel.org entweder vollständig (Klick auf das F rechts neben dem Datum) oder als Patch, d.h. nur die Änderungen zur vorangegangenen Version, heruntergeladen werden. Bei Dateigrößen von um die 40 MB für die vollständige Version macht das Patchen Sinn, allerdings muss der Patch exakt zur vorhandenen (installierten) Version der Kernel-Quellen passen. Das Patchen wird hier nicht weiter beschrieben.

2.1 Installation der Kernel-Quellen

Die Kernel-Quellen befinden sich normalerweise im Verzeichnis /usr/src/linux, wobei 'linux' lediglich ein symbolischer Link auf das wahre Verzeichnis, z.B. linux-2.6.16.21-0.13 ist. Mehrere Kernel-Versionen können so gleichzeitig installiert sein. Neue Kernel-Quellen sollten nach dem gleichen Schema aufgespielt werden, der Link 'linux' sollte auf die jeweils verwendeten Quellen zeigen.

Werden die Kernel-Quellen mittels Paketmanager installiert, so sollten sie automatisch an der richtigen Stelle landen. Nach einem Download einer .tar.bz2 Datei sollte die Datei an der richtigen Stelle entpackt werden. Dazu wechselt man in das /usr/src Verzeichnis und entpackt die heruntergeladene Datei:

tar -xjf /Pfad-zur-tar.bz2-Datei/linux-2.x.x.x.tar.bz2
Um in das Verzeichnis /usr/src schreiben zu dürfen, sollte man 'root' sein.

In jedem Fall sollte es nun ein neues Verzeichnis /usr/src/linux-2.x.x.x (entsprechend der neuen Kernel-Version) geben, in welches man nun wechselt.

Achtung: Alle Befehle, die in den folgenden Abschnitten genannt werden, müssen innerhalb dieses Verzeichnisses ausgeführt werden.

2.2 Konfiguration des Kernels vorbereiten

Noch bevor der Kernel konfiguriert wird, sollte das Kernel-Verzeichnis mit dem Befehl

make mrproper
"gesäubert" werden. Dabei werden alle Dateien, die einer neuen Konfiguration im Wege stehen könnten entfernt. Dazu gehört auch die Konfigurationsdatei .config selbst (siehe folgender Abschnitt), die eventuell zuvor gesichert werden sollte.

Vor dem Kompilieren muss der Kernel konfiguriert werden. Der Kompiler sucht die Konfiguration in der Datei .config im jeweiligen Kernel-Verzeichnis, z.B. /usr/src/linux-2.6.18.1/.config. Wenn der Quell-Code des Kernels zum ersten Mal kompiliert wird, existiert diese Datei normalerweise noch nicht. Stattdessen findet man eine Defaultkonfiguration in der Datei arch/i386/defconfig, welche man nach .config kopiert.

Eine oftmals bessere Alternative ist jedoch die Konfiguration des aktuell laufenden Kernels zu verwenden. Diese findet man gzipped in /proc/config.gz. Das Kommando

gzip -d < /proc/config.gz > .config
erzeugt daraus wieder eine .config-Datei.

Achtung: Wenn die .config-Datei so, also ohne weitere Änderungen, benutzt werden soll, dann muss der Befehl
make oldconfig
ausgeführt werden, damit der Quellcode entsprechend .config konfiguriert wird.

Grundsätzlich ist es empfehlenswert, eine als funktionierend bekannte Konfigurationsdatei an einen sicheren Ort zu kopieren.

2.3 Konfiguration des Kernels

Jetzt kann man seine eigenen Wünsche einbringen. Dazu sollte man nicht die .config-Datei direkt editieren, sondern mittels

make xconfig
ein graphisches Konfigurationsprogramm oder mittels
make menuconfig
ein Text-basiertes starten.

Beide Programme bieten zu jeder Option eine kurze Hilfe an. Die meisten Optionen lassen sich entweder ausschalten/entfernen (es wird ein leeres Kästchen Symbol in Konfigprog. bzw. < > angezeigt), als Modul erstellen (ein Kästchen mit einem Punkt Symbol in Konfigprog. bzw. <M>) oder direkt in den Kernel integrieren (ein Kästchen mit einem Häkchen Symbol in Konfigprog. bzw. <*>).

Die Möglichkeiten zur Konfiguration sind sehr umfangreich, und man sollte Änderungen mit großer Vorsicht vornehmen. Vor allem beim Entfernen von Optionen sollte man zu 100% wissen was man tut. Die Zusammenhänge sind oft nicht sofort ersichtlich, und so kann es passieren dass z.B. der USB-Stick oder das CD-Rom nicht mehr funktioniert, weil man mit den SCSI-Einstellungen gespielt hat. Eine Kopie einer funktionierende Kernel-Konfigurationsdatei sollte man immer an einer sicheren Stelle aufbewahren.

Wenn man eine bestimmte Option nicht findet, so kann es sein, dass sie "versteckt" wurde, da sie sich noch in einem experimentellen Stadium befindet. Mit der Option "Code maturity level options" (ganz oben) und dann durch entfernen von "Prompt for development and/or incomplete code/drivers" werden alle Optionen angezeigt.

2.3.1 Treiber als Modul oder in den Kernel?

Bei Treibern hat man drei Möglichkeiten:
  1. Wird der Treiber mit Sicherheit nicht benötigt (auch nicht in naher Zukunft), dann sollte er entfernt werden. Dies beschleunigt das Kompilieren, evtl. wird der Kernel auch kleiner und dadurch stabiler (weniger Code = weniger potenzielle Schwachpunkte).
  2. Wird der Treiber gelegentlich oder in naher Zukunft benötigt, so ist er gut in einem Modul, d.h. in einer externen Datei, aufgehoben. Dieses Modul kann jederzeit geladen und benutzt werden. Wenn der Treiber gerade nicht benötigt wird, muss das Modul nicht geladen werden und belegt keinen Arbeitsspeicher.
  3. Treiber die grundsätzlich immer benötigt werden, sollten direkt in den Kernel integriert werden. Dies gilt insbesondere für Treiber für die Festplatte und das Dateisystem. Integriert man diese in den Kernel, benötigt man keine Init-Ramdisk um das Henne-Ei-Problem zu umschiffen:
    Das Henne-Ei-Problem und die Init-Ramdisk
    Beim Booten soll der Kernel Treiber von der Festplatte nachladen, aber solange die Treiber für die Festplatte nicht geladen wurden, kann der Kernel nicht auf die Festplatte zugreifen. Dies umgeht man, indem man entweder die Festplattentreiber direkt in den Kernel integriert oder in einer Init-Ramdisk auslagert, die dem Kernel vom Bootloader zugänglich gemacht wird.

3 Kompilieren

Wenn die Konfiguration erledigt ist, ist das meiste schon geschafft. Das Kompilieren benötigt zwar etwas Zeit ist aber vergleichsweise unkompliziert:

  1. Wenn ein Kernel der 2.4er Reihe kompiliert werden soll, müssen die Abhängigkeiten erstellt werden. Dies erledigt der Befehl
    make dep
    Dieser Schritt kann etwas Zeit in Anspruch nehmen und ist bei einem 2.6er Kernel nicht notwendig.
  2. Der nächste Schritt entfernt alle alten Object-Dateien, die eventuell noch übrig geblieben sind:
    make clean
  3. Nun wird der Kernel selbst erstellt und in arch/i386/boot/bzImage gespeichert:
    make bzImage
    Dieser Schritt dauert relativ lange. Wie lange, hängt nicht nur von der Prozessorgeschwindigkeit ab, sonder auch von der Festplatte, die auf viele kleine Dateien zugreifen muss. Deshalb kann es sich lohnen, diesen und den nächsten Schritt gleichzeitig auszuführen.
  4. Was noch fehlt sind die Module bzw. Treiber, die nicht in den Kernel integriert wurden. Sie werden mit dem folgenden Befehl erstellt:
    make modules
    Für eine Defaultkonfiguration dauert dies recht lange, da sehr viele Module erstellt werden müssen.

4.1 Installation der Module

Noch liegen der erzeugten Module innerhalb des Kernel-Quellcodes. Um sie zu verwenden, müssen sie unterhalb von /lib/modules liegen. Da Module verschiedener Kernel-Versionen inkompatibel sind, gibt es für die Module jeder Kernel-Version ein eigenes Unterverzeichnis, z.B. /lib/modules/2.6.18.1. Das Kopieren (was als root erledigt werden muss) übernimmt der folgende Befehl:

su
make modules_install

4.2 Installation des Kernels

Die Installation des Kernels selbst ist unter Umständen etwas aufwändiger. Beim Booten wird über den Bootloader (z.B. grub) der zu bootende Kernel gewählt und gestartet. Ergo muss der Bootloader über den neuen Kernel Bescheid wissen. Die folgenden Abschnitte beschreiben die Installation für ein SuSE System (mit Versionen 9 und 10 müsste es so klappen).

Der neue Kernel befindet sich in arch/i386/boot/bzImage und muss in das /boot Verzeichnis kopiert werden. Dabei sollte der Kernel umbenannt werden, um verschiedene Versionen parallel betreiben zu können:

cp arch/i386/boot/bzImage /boot/vmlinuz-KERNEL_VERSION
KERNEL_VERSION sollte den kompilierten Kernel möglichst genau beschreiben, z.B. '2.6.18.1' oder '2.6.18.1-ext3_in_kernel'.

4.3 Installation der Init-Ramdisk

Wenn nicht alle zum Booten notwendigen Treiber in den Kernel integriert wurden (was ich jedoch empfehle) benötigt der Kernel eine Init-Ramdisk. Dies erledigt das Programm mkinitrd, welches je nach Distribution unterschiedliche Parameter benötigt. Genaueres steht in den man-pages von mkinitrd und initrd.

mkinitrd -k vmlinuz-KERNEL_VERSION -i initrd-KERNEL_VERSION

4.4 Konfiguration des Bootloaders Grub

Grub ist der am häufigsten verwendete Bootloader. Deshalb beschränke ich mich hier auf dessen Konfiguration. Wenn ein neuer Kernel kompiliert wurde, möchte man den Kernel in der Regel erst Testen, und bei Bedarf zum alten Kernel zurückkehren. Dazu muss man lediglich einen zusätzlichen Eintrag in der Konfigurationsdatei von Grub vornehmen. Beim Booten bietet einem Grub dann die Wahl, den alten oder den neuen Kernel zu booten.

Die Grub-Konfigurationsdatei /boot/grub/menu.lst erhält für jedes zu bootende System einige Zeilen die das Systembeschreiben. Der Standard-Eintrag könnte wie folgt aussehen:

title SUSE Linux 10.1
root (hd0,3)
kernel /boot/vmlinuz root=/dev/hda4 vga=0x31a resume=/dev/hda1
initrd /boot/initrd
Die erste Zeile enthält das Schlüsselwort 'title' gefolgt vom Namen des Systems wie er beim Booten in der Auswahlliste auftauchen soll. Die legt fest, auf welcher Festplatte und Partition sich das root-Dateisystem befindet. Die dritte Zeile bestimmt den Kernel der gebootet werden soll und die notwendigen Parameter. Die vierte Zeile enthält die init-Ramdisk, auf die verzichtet werden kann, wenn alle zum Booten notwendigen Treiber in den Kernel integriert wurden.

Um einen neuen Kernel zu testen, kopiert man diesen Block und ändert 'title', 'kernel' und 'initrd' entsprechend. Welcher Block per default gebootet wird, legt der Parameter default # im Dateikopf fest. default 0 bedeutet, dass der erste Konfigurationsblock als Default gelten soll.

title SUSE Linux 10.1
root (hd0,3)
kernel /boot/vmlinuz root=/dev/hda4 vga=0x31a resume=/dev/hda1
initrd /boot/initrd

title SUSE Linux 10.1 - Test des neuen Kernels
root (hd0,3)
kernel /boot/vmlinuz-KERNEL_VERSION root=/dev/hda4 vga=0x31a resume=/dev/hda1
Behält man die alte Konfiguration als Default bei, muss bei einem Neustart der neue Eintrag SUSE Linux 10.1 - Test des neuen Kernels ausgewählt werden, um den neuen Kernel zu booten. Wenn alles funktioniert, muss lediglich die default # Zeile angepasst werden.



5 Quellen

Kernel Rebuild Guide (englisch)
Wikipedia (Linux Kernel)