6. Linux Kernel¶
6.1. Was ist der Linux Kernel?¶
Der Linux-Kernel ist Kernkomponente des Linux-Betriebssystem. Es ist die Schnittstelle zwischen der Hardwareebene (Hardwarekomponenten z.B. CPU) und der Benutzerebene (Software) und nach den Bedürfnissen des Nutzers modifizierbar. Somit kommuniziert die Software über den Kernel mit der Hardware, wodurch Hardwareunterschiede nicht berücksichtigt werden müssen.
6.1.1. Aufgaben¶
Die Hauptaufgabe des Kernels ist die Kommunikation zwischen Hardware und Software. - Systemverwaltung: Aufteilung des Arbeitsspeicher - Prozessmanagement: Bestimmt die Dauer und den Zeitpunkt für die Nutzung des Prozessors - Datenträgerverwaltung: Verwaltung der Zugriffsrechte auf das Dateisystem und hilft beim organisieren des Dateisystems
6.2. Aufbau¶
Der Linux-Kernel besteht aus mehreren Schichten und Modulen, die zusammenarbeiten, um die Kernfunktionen des Betriebssystems bereitzustellen. Diese Schichten umfassen den Kernel-Modus und den Benutzermodus. Der Kernel-Modus erlaubt den Zugriff auf alle Systemressourcen, während der Benutzermodus eingeschränkten Zugriff hat und hauptsächlich für Anwendungen gedacht ist. Die Prozessverwaltung ist eine wichtige Komponente, die die Erstellung, Planung und Terminierung von Prozessen managt. Die Speicherverwaltung ist für die Zuweisung und Verwaltung des physischen und virtuellen Speichers verantwortlich. Das Dateisystem organisiert und verwaltet die Daten auf den Speichermedien, während die Gerätetreiber dem Kernel die Kommunikation mit der Hardware ermöglichen. Der Netzwerk-Stack handhabt die Netzwerkkonnektivität und Protokolle. Schließlich beinhaltet der architekturabhängige Code spezifische Anpassungen für verschiedene Hardwarearchitekturen. Diese modulare und flexible Struktur ermöglicht es, den Linux-Kernel an eine Vielzahl von Anforderungen und Geräten anzupassen.
6.3. Wie funktioniert ein Linux-Kernel?¶
Die im Kernel enthaltenen Treiber aktivieren die Hardwarekomponenten. Anschließend wird das Speichermedium, das die Linux-Installation enthält, eingebunden und das Init-System der Linux-Installation aufgerufen. Dieses Init-System lädt die Benutzeroberfläche unter Verwendung der Kernel-Treiber. Gleichzeitig wird die Kernelumgebung eingerichtet, in der die Programme ausgeführt werden.
6.4. Kernelarten¶
Es gibt verschiedene Arten von Linux-Kerneln, die jeweils unterschiedliche Architektur- und Designprinzipien aufweisen. Der traditionelle Linux-Kernel ist monolithisch, das bedeutet, alle grundlegenden Dienste des Betriebssystems wie Speicherverwaltung, Dateisysteme und Netzwerktreiber laufen im Kernel-Modus. Dies bietet den Vorteil einer hohen Leistung und einer direkten Kommunikation zwischen den Komponenten. Im Gegensatz dazu führt ein Mikrokernel nur das Notwendigste im Kernel-Modus aus, während andere Dienste wie Dateisysteme und Netzwerke als Benutzerprozesse laufen. Dies erhöht die Stabilität und Sicherheit, kann jedoch die Leistung beeinträchtigen. Beispiele für Mikrokernel sind die Kernels von MINIX und QNX. Ein Hybridkernel kombiniert Elemente von monolithischen und Mikrokernel-Designs. Dabei laufen grundlegende Dienste im Kernel-Modus und weniger kritische Dienste im Benutzermodus. Windows NT ist ein Beispiel für einen Hybridkernel. Ein weiteres, experimentelles Konzept ist der Exokernel, bei dem der Kernel die Hardware abstrahiert und den Anwendungen direkten Zugriff ermöglicht. Dies kann die Leistung maximieren, erfordert jedoch spezialisierte Anwendungen. Jede Kernelart hat ihre eigenen Vor- und Nachteile, die je nach Anwendungsfall und Anforderungen berücksichtigt werden müssen.
6.5. Warum einen Linux-Kernel selbst kompilieren?¶
Gerade wenn man besondere Hardware besitzt die evtl. nur wenig Ressourcen zur Verfügung hat, ist es sinnvoll sich den Kernel selbst zu compilieren, etwa für Embedded oder sehr alte Systeme. Der default Kernel enthält viele Module, die für spezielle Hardware unnötig sind und somit nur Plattenspeicher bzw. Arbeitsspeicher verbrauchen würden. Wenn man den Kernel selbst compiliert, kann man Module nach eigenen Ansprüchen auswählen und so die Ressourcennutzung optimieren. Besitzt man beispielsweise eine bestimmte Graphikkarte kann man deren Treiber selbst mit dem Kernel abstimmen, um die optimale Performance zu erreichen.
Ein weiterer Grund könnte sein, dass man den Kernel performanter machen will, indem man einen anderen Compiler verwendet als den Standard GCC. Gerade wenn man eine Intel CPU besitzt, könnte Intels C/C++ Compiler den Kernel schneller machen, da dieser spezifische Optimierungen basierend auf Features von Intel Prozessoren bietet.
Zusammengefasst kann man also sagen, dass das eigenständige Compilieren einen kleineren und performanteren Kernel ergibt, der auf die eigene Systemhardware spezialisiert ist.
6.6. Linux-Kernel selbst kompilieren¶
6.6.1. Quell-Code¶
Um den Linux-Kernel selber zu kompilieren wird zunächst der Quell-Code für den Kernel benötigt. Diesen kann man von vielen verschiedenen Quellen bekommen, normalerweise holt man sich ihn von https://kernel.org. Auf der Seite bekommt man einen Überblick über die verschiedenen Versionen des Kernels:
(Abbildung 1)
mainline steht für die Kernel Version, die von Linus Torvalds gepflegt wird.
stable steht für das aktuelle, noch unterstützte Release (wird von Greg Kroah-Hartman gepflegt)
longterm steht für ein älteres Release, was keine neuen Features mehr bekommt, allerdings noch Bugfixes und Securityupdates bekommt
linux-next steht für den bleeding edge Kernel, das heißt, dass hier die neuesten Patches reinkommen, die später in den mainline Kernel gemerged werden. Dieser muss nicht immer kompilierbar sein.
Man kann sich nun entweder einen Tarball herunterladen oder sich doch dazu
entscheiden das Repository mit git zu klonen.
Wenn man das neuesten Mainline Repository haben will, dann führt man folgendes aus:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
und wenn man ein stable Repository haben will, dann das:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
Die beiden Repositories unterscheiden sich in der Größe, was dem geschuldet
ist, dass im Mainline Repo nur VERSION, PATCHLEVEL und EXTRAVERSION
geändert werden, nicht aber SUBLEVEL. Version und Patchlevel heißen auch
MAJOR und MINOR Versionsnummer.
(Abbildung 2)
VERSION spielt nur eine untergeordenete Rolle, da diese nur erhöht wird,
wenn Linus Torvalds mal wieder keine Finger und Zehen mehr hat, um die
PATCHLEVEL zu zählen (s. https://lkml.org/lkml/2022/10/2/255). Bei einer
Erhöhung des Letzteren erhält der Kernel neue Features bzw. es werden
Features deprecated, d.h. sie werden in naher Zukunft entfernt (z.B. ReiserFS:).
Es gibt aber auch viele verschiedene Linux Kernel, die alle auf dem Original aufbauen. Hier eine kleine Liste:
Linux-ck (Für Arch basierte Distros ein Repo)
Diese Forks haben alle einige kleine Änderungen zum Standart Kernel.
6.6.2. Patches¶
Man kann den Kernel aber auch manuell mithilfe von Patches modifizieren. Dazu lädt man sich den zunächst den Linux Quellcode seiner Wahl runter. Anschließend lädt man sich einen Patch herunter. Wir haben uns beispielsweise für diesen Patch entschieden. Mit diesem ist es möglich die CPU-Architektur auszuwählen, für die GCC den Code optimieren soll.
Zunächst Tarball und Patch holen; Tarball extrahieren:
$ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.33.tar.xz && tar -xvf linux-6.6.33.tar.xz
$ wget https://raw.githubusercontent.com/graysky2/kernel_compiler_patch/master/more-uarches-for-kernel-6.1.79-6.8-rc3.patch
In das Verzeichnis wechseln:
$ cd linux-6.6.33
Und den Patch anwenden:
$ patch -p1 -i ../more-uarches-for-kernel-6.1.79-6.8-rc3.patch
6.6.3. Makeopts und Maketargets¶
Der Linux Kernel wird mithilfe von Makefiles kompiliert, d.h. man kann
theoretisch einfach nur make in die Konsole tippen und man erhält ein
fertiges Kernel Image. Es gibt aber auch die Möglichkeit mit den Makeopts
den Kompilierprozess zu steuern. Makeopts sind im Grunde nur
Umgebungsvariablen, die in der Makefile ausgewertet werden. Eine der
wichtigsten ist -j. Damit wird gesteuert, wie viele Threads benutzt werden
sollen, um den Kernel zu kompilieren. Beispiel:
# Kernel wird mit 32 Threads gebaut
$ make -j 32
# Kernel wird mit allen verfügbaren Threads gebaut
$ make -j $(nproc)
Um den Kernel mit LLVM und nicht mit GCC zu kompilieren, setzt man LLVM=1
in die Makeopts:
$ make LLVM=1
Man kann den Kernel auch für andere Architekturen kompilieren. Dazu muss zunächst die Architektur und einen Cross Compiler einstellen.
# Beispiel für einen ARMv8 Prozessor
$ make CROSS_COMPILE=aarch64-unknown-linux-gnu- ARCH=arm64
Benutzt man LLVM, wird CROSS_COMPILE= nicht benötigt:
# Beispiel für einen ARMv8 Prozessor
$ make LLVM=1 ARCH=arm64
Makeopts können auch als Umgebungsvariablenexportiert werden, d.h. man muss
sie nicht jedes mal mit angeben, wenn man make aufruft. Beispiel:
# Beispiel für einen ARMv8 Prozessor
$ make LLVM=1 ARCH=arm64
ist dasselbe wie:
# Beispiel für einen ARMv8 Prozessor
$ export LLVM=1 ARCH=arm64
$ make
Mit den Maketargets definiert man, was genau gemacht werden soll. Die Wichtigsten sind:
all Damit werden alle Targets gebaut, die in
make helpmit einem*markiert sind.clean Löschen aller übersetzten C Dateien
mrproper Wie
clean, aber zusätzlich Löschung aller Backup und Config Dateiendefconfig Erstellung einer Standart Konfigurationsdatei, basierend auf der Hostarchitektur
install Installieren des Kernels auf das Hostsystem
modules_install Installieren der Module auf das Hostsystem
6.6.4. Kernel-Config¶
Nachdem man nun den Quell-Code hat, die Patches seiner Wahl eingespielt und
seine Makeopts exportiert hat, geht es nun ans Kompilieren. Zunächst wird
eine Konfigurationsdatei benötigt, in der steht, was in den Kernel kommt, was
als Modul kompiliert wird und was ausgelassen wird. Eine Standartdatei kann
mit dem Maketarget defconfig erstellt werden.
$ make defconfig
Diese wird anschließend in das Root-Directory des Repos als .config
gespeichert.
Will man diese nun bearbeiten, kann man dies grafisch machen mit:
$ make xconfig
Oder terminalbasiert mit:
$ make menuconfig
Mit einem Texteditor sollte man die .config niemals bearbeiten, da viele
Optionen Abhängigkeiten besitzen und die Config Programme diese automatisch
auflösen.
(Abbildung 3)
Das ist die Ansicht, die man sieht, wenn man menuconfig startet.
Mit den Pfeiltasten lässt sich das Menü navigieren, mit Enter steigt man in das Menü hinein, mit Escape kommt man wieder raus. Mit ? erfährt man mehr über den ausgewählten Menüpunkt. Will man etwas suchen, geht das mit / und einem Suchbegriff.
6.6.4.1. Config Status¶
Eine Option kann man mit Leertaste togglen. Dabei gibt es 2 verschiedene Arten von Optionen:
Option 1 kann man nur in den Kernel einbauen oder ganz rauslassen und
Option 2 lässt sich zusätzlich als Modul bauen, welches man später vom
Userspace nachladen kann. Ein Sternchen steht dafür, dass die Option in das
Kernel Image reingebaut wird und bei einem M wird die Option als Modul
gebaut.
6.6.4.2. Konfiguration¶
Nun kann man den Kernel konfigurieren.
Den Patch, den wir eingespielt haben, finden wir als Konfiguration unter
Processor type and features CPU-Type. Dort findet man unter
Processor family eine kleine Liste von CPUs, für die GCC den Code
optimiert. Standartmäßig ist Generic-x86-64 angegeben, was vollkommen
ausreichend ist und von den meisten CPUs heute ausgeführt werden kann.
Vor dem Patch:
(Abbildung 4)
Nach dem Patch:
(Abbildung 5)
Man sieht, dass die Liste um einiges größer geworden ist und man auch herstellerspezifische CPUs auswählen kann.
Unter General architecture-dependent options findet sich hier die Option Link Time Optimization (LTO). In dieser Liste kann man normalerweise nichts auswählen, kompiliert man den Kernel jedoch mit LLVM, kann man hier einstellen welche LTO man haben will. LTO sorgt für eine Verkleinerung des Kernelimages sowie weitere Optimierungen zur Link-Zeit.
(Abbildung 6)
Unter Device Drivers kann man beispielsweise die Unterstützung für Hardware
deaktivieren, die man sowieso nicht im System eingebaut hat. Dadurch wird das
Kernelimage kleiner und verbraucht weniger Arbeitsspeicher. Wenn man aber
Hardware auf seinem Rechner hat die nicht in der defconfig aktiviert wurde,
kann man das hier jetzt machen.
6.6.5. Kompilieren¶
Wenn man nun seinen Kernel-Config fertig konfiguriert hat, kann man nun seinen Kernel kompilieren. Dazu lediglich(Makeopts nicht vergessen!):
$ make
Wenn man den Kernel nun auf seinem eigenen Rechner installieren will:
# Wichtig: Als Root ausführen!
root~# make install
Anschließend die Konfigurationsdatei vom Bootloader neu generieren(bei den Meisten ist das GRUB):
root~# grub-mkconfig -o /boot/grub/grub.cfg
6.7. Fazit¶
In den meisten Fällen lohnt es sich nicht den Linux Kernel selber zu kompilieren, da der Performancezuwachs oft nur sehr gering ist. Auch die Geräteunterstützung der Distro Kernel ist im Normalfall so gut, dass alle Geräte unterstützt werden. Nur in seltenen Fällen oder im Embedded Bereich lohnt sich der Aufwand, da diese meistens seltene Hardware benutzen.
6.8. Literatur¶
Linux-Kernel geeksforgeeks: https://www.geeksforgeeks.org/kernel-in-operating-system/ (06.06.2024).
Basiswissen zum Linux-Kernel: https://www.heise.de/select/ct/2017/6/1489331084099960 (06.06.2024).
Benefits of Building the Linux Kernel: https://www.baeldung.com/linux/kernel-building-benefits (06.06.2024).
Kernel LLVM https://docs.kernel.org/kbuild/llvm.html (12.06.2024)
Kernel Patches https://docs.kernel.org/process/applying-patches.html (12.06.2024)