- Erklärt Schritt für Schritt, wie man durch direktes Bauen des Linux-Kernels und das Einrichten eines minimalen User Space eine „Mikro-Linux-Distribution“ erstellt
- Behandelt von Grund auf die Rolle des Betriebssystem-Kernels, die Bestandteile einer Linux-Distribution und die Beziehung zwischen Kernel und User Space
- Verwendet als Beispiel die RISC-V-Architektur (QEMUs Maschine
riscv64 virt), aber dieselben Prinzipien lassen sich auch auf andere Architekturen wie x86 anwenden
- Baut eine minimal lauffähige Linux-Umgebung auf, einschließlich des
init-Prozesses, initramfs und einer einfachen in Go geschriebenen Shell
- Stellt zum Schluss mithilfe des Projekts
u-root vor, wie man eine tatsächlich nützliche Mikro-Distribution erstellt, und endet als Einführungshandbuch zum Verständnis des gesamten Aufbaus eines Linux-Systems
Was ist ein Betriebssystem-Kernel?
- Der Kernel ist die Kernkomponente des Betriebssystems, die für die Verwaltung von Hardware-Ressourcen und die Steuerung der Programmausführung zuständig ist
- Er bietet Multitasking-Verwaltung, sodass es auch in einer Single-Core-Umgebung so aussieht, als liefen mehrere Programme gleichzeitig
- Der Kernel abstrahiert die Steuerung von Ein-/Ausgabegeräten, damit Anwendungen nicht direkt mit Hardware-Adressen oder Registerwerten arbeiten müssen
- Ein Programm fordert zum Beispiel einfach an, „eine Nachricht auf die Standardausgabe zu schreiben“, und der Kernel übernimmt die tatsächliche Interaktion mit der Hardware
- Über die Dateisystem-Schnittstelle stellt er eine höherwertige Form des Datenzugriffs bereit
- Dateien sind nicht nur Daten auf einem Datenträger, sondern fungieren als logische Schnittstelle zur Kommunikation mit dem Kernel
- Der Kernel stellt Modelle zur Isolation und Kommunikation zwischen Prozessen bereit, sodass Anwendungen unabhängig voneinander laufen oder zusammenarbeiten können
- Der Linux-Kernel ist Open Source, läuft auf vielen verschiedenen Architekturen und ist weltweit einer der am weitesten verbreiteten Kernel
Was ist eine Linux-Distribution?
- Mit dem Linux-Kernel allein können Benutzer weder einen Webbrowser noch GUI-Apps ausführen; es braucht mehrere Schichten von Software-Infrastruktur über dem Kernel
- Dinge wie Netzwerkkonfiguration, IP-Zuweisung und VPN-Verwaltung werden nicht vom Kernel, sondern von übergeordneten Programmen im User Space übernommen
- Daher wird eine Linux-Distribution als Kombination aus Kernel + User-Space-Infrastruktur definiert
- Eine Distribution umfasst zusätzlich zu den Grundfunktionen des Kernels Pakete, Werkzeuge, Konfigurationen und den Initialisierungsprozess (
init)
- Die Komplexität von Distributionen ist sehr unterschiedlich: von minimalistischen Setups wie Arch Linux bis zu benutzerfreundlichen Varianten wie Ubuntu
Infrastruktur außerhalb des Kernels: User Space und der init-Prozess
- Sobald der Kernel den Bootvorgang abgeschlossen hat, startet er zuerst den
init-Prozess mit PID 1
init ist der Vorfahr aller späteren User-Space-Prozesse und startet der Reihe nach die Dienste und Werkzeuge des Systems
- Die Gesamtheit der Prozesse und Werkzeuge, die von
init gestartet werden, bildet den eigentlichen inhaltlichen Kern einer Linux-Distribution
- Je komplexer eine Distribution wird, desto eher sammelt sie unnötige Funktionen an und wird deshalb manchmal als „bloated“ kritisiert
- Umgekehrt lässt sich mit einer angepassten Mikro-Distribution ein schlankes System aufbauen, das nur die nötigsten Funktionen enthält
Einen Linux-Kernel für RISC-V bauen
- In einer
x86-Umgebung wird mithilfe einer Cross-Compile-Toolchain ein Kernel für RISC-V gebaut
- Nach dem Herunterladen des Quellcodes
linux-6.5.2.tar.xz von kernel.org wird make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig ausgeführt
- Mit
menuconfig lassen sich die Kernel-Einstellungen visuell bearbeiten
- Nach einem parallelen Build mit
make -j16 wird arch/riscv/boot/Image erzeugt
- In QEMU wird mit
qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Image gebootet
- Im Boot-Log lassen sich Meldungen wie Erkennung der SBI-Schicht, UART-Initialisierung und Aktivierung von printk prüfen
Das erste Hindernis: kein Root-Dateisystem
- Während des Kernel-Boots tritt durch den Fehler
VFS: Unable to mount root fs eine Kernel Panic auf
- Ursache: Es wurde kein Root-Dateisystem (
initramfs) bereitgestellt
- Ein Dateisystem kann nicht nur auf einem Datenträger, sondern auch RAM-basiert (
initramfs) aufgebaut sein
initramfs wird im cpio-Format paketiert und kann in QEMU mit der Option -initrd geladen werden
initramfs aufbauen und „Hello world“ ausführen
- Die minimale Voraussetzung ist das Vorhandensein eines
/init-Binärprogramms
- Nach dem Schreiben von
init.c wird es statisch gelinkt (-static) gebaut
- Mit
cpio -o -H newc < file_list.txt > initramfs.cpio wird es paketiert
- Beim Start in QEMU wird „Hello world“ ausgegeben, danach führt das Beenden von
init erneut zu einer Kernel Panic
- Lösung: Eine Endlosschleife hinzufügen, damit
init nicht beendet wird
Eine einfache in Go geschriebene Shell hinzufügen
init startet mit fork und execl die /little_shell
little_shell.go ist eine einfache Shell, die Benutzereingaben annimmt und die Befehle per Echo ausgibt
- Mit
GOOS=linux GOARCH=riscv64 go build little_shell.go wird sie für RISC-V gebaut
- Sowohl
init als auch little_shell teilen sich die Ausgabe über UART
- Standard-Ein- und -Ausgabe werden über File-Handles verwaltet und bei
fork vererbt
- Das Ergebnis ist eine grundlegende Linux-Umgebung, in der „Hello from init“ und die Shell-Eingaben abwechselnd ausgegeben werden
Zusammenfassung der Rolle des Kernels
- Hardware-Abstraktion: User-Programme können Ausgaben erzeugen, ohne Details zu UART oder Geräten zu kennen
- Bereitstellung höherwertiger Schnittstellen: Zugriff auf andere Binärdateien (
little_shell) über das Dateisystem
- Prozess-Isolation:
init und die Shell laufen in getrennten Speicherbereichen
- Der Kernel bietet auf komplexer Hardware eine stabile und hoch portable Ausführungsgrundlage
Definition eines Betriebssystems
- Man kann entweder nur den Kernel als Betriebssystem betrachten oder die gesamte Distribution als Betriebssystem
- Wichtig ist, die Grenzen der Rollen und die Interaktionsstruktur von Kernel und User Space zu verstehen
Mit u-root eine tatsächlich nützliche Mikro-Distribution bauen
- Das u-root-Projekt stellt ein Go-basiertes Set von User-Space-Werkzeugen bereit
u-root umfasst einen im User Space laufenden Bootloader und eine Shell-Umgebung auf dem Linux-Kernel
- Nach der Installation erstellt der Befehl
GOOS=linux GOARCH=riscv64 u-root automatisch ein initramfs
- Die Datei
/tmp/initramfs.linux_riscv64.cpio kann in QEMU ausgeführt werden
- Beim Booten erscheint zusammen mit dem Banner „Welcome to u-root!“ eine Standard-Shell-Eingabeaufforderung
- Unterstützt grundlegende Befehle wie
ls, pwd, echo sowie Tab-Vervollständigung
Netzwerkanbindung praktisch ausprobieren
- Zu QEMU werden die Geräte
virtio-net-device und virtio-rng-pci hinzugefügt
- Verwendet werden die Optionen
-device virtio-net-device,netdev=usernet -netdev user,id=usernet
- Mit
dhclient aus u-root wird per DHCP automatisch eine IP-Adresse zugewiesen
- Beispiel:
eth0 erhält 10.0.2.15/24
- Mit
wget http://google.com gelingt der Zugriff auf das externe Netzwerk, und der Download von index.html lässt sich bestätigen
Die Bedeutung von Paketmanager und init
- Allgemeine Distributionen installieren und aktualisieren Software dynamisch über einen Paketmanager
- Diese praktische Übung folgt dagegen einem Embedded-Ansatz, bei dem das gesamte Image neu gebaut werden muss
init ist nicht bloß ein einfacher Prozessstarter, sondern eine Kernkomponente für Geräteinitialisierung, Dienstverwaltung und Steuerung des Systemstarts
- Im
init-Quellcode von u-root lassen sich verschiedene Schritte zur Einrichtung von Geräten (/dev) nachvollziehen
GitHub-Repository
- Der vollständige Code und die Beispiele aus diesem Leitfaden sind unter popovicu/linux-micro-distro verfügbar
- Dort lassen sich
initramfs-Images bauen und die Übungen nachvollziehen
Noch keine Kommentare.