Ein Mikro-Linux-Distributionspaket bauen (2023)
(popovicu.com)- 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,initramfsund einer einfachen in Go geschriebenen Shell - Stellt zum Schluss mithilfe des Projekts
u-rootvor, 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 1initist 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
initgestartet 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.xzvonkernel.orgwirdmake ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfigausgeführt
- Nach dem Herunterladen des Quellcodes
- Mit
menuconfiglassen sich die Kernel-Einstellungen visuell bearbeiten - Nach einem parallelen Build mit
make -j16wirdarch/riscv/boot/Imageerzeugt - In QEMU wird mit
qemu-system-riscv64 -machine virt -kernel arch/riscv/boot/Imagegebootet- 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 fseine Kernel Panic auf- Ursache: Es wurde kein Root-Dateisystem (
initramfs) bereitgestellt
- Ursache: Es wurde kein Root-Dateisystem (
- Ein Dateisystem kann nicht nur auf einem Datenträger, sondern auch RAM-basiert (
initramfs) aufgebaut sein initramfswird imcpio-Format paketiert und kann in QEMU mit der Option-initrdgeladen werden
initramfs aufbauen und „Hello world“ ausführen
- Die minimale Voraussetzung ist das Vorhandensein eines
/init-Binärprogramms- Nach dem Schreiben von
init.cwird es statisch gelinkt (-static) gebaut - Mit
cpio -o -H newc < file_list.txt > initramfs.cpiowird es paketiert
- Nach dem Schreiben von
- Beim Start in QEMU wird „Hello world“ ausgegeben, danach führt das Beenden von
initerneut zu einer Kernel Panic- Lösung: Eine Endlosschleife hinzufügen, damit
initnicht beendet wird
- Lösung: Eine Endlosschleife hinzufügen, damit
Eine einfache in Go geschriebene Shell hinzufügen
initstartet mitforkundexecldie/little_shelllittle_shell.goist eine einfache Shell, die Benutzereingaben annimmt und die Befehle per Echo ausgibt- Mit
GOOS=linux GOARCH=riscv64 go build little_shell.gowird sie für RISC-V gebaut
- Mit
- Sowohl
initals auchlittle_shellteilen sich die Ausgabe über UART- Standard-Ein- und -Ausgabe werden über File-Handles verwaltet und bei
forkvererbt
- Standard-Ein- und -Ausgabe werden über File-Handles verwaltet und bei
- 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:
initund 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-rootumfasst 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-rootautomatisch eininitramfs- Die Datei
/tmp/initramfs.linux_riscv64.cpiokann in QEMU ausgeführt werden
- Die Datei
- Beim Booten erscheint zusammen mit dem Banner „Welcome to u-root!“ eine Standard-Shell-Eingabeaufforderung
- Unterstützt grundlegende Befehle wie
ls,pwd,echosowie Tab-Vervollständigung
- Unterstützt grundlegende Befehle wie
Netzwerkanbindung praktisch ausprobieren
- Zu QEMU werden die Geräte
virtio-net-deviceundvirtio-rng-pcihinzugefügt- Verwendet werden die Optionen
-device virtio-net-device,netdev=usernet -netdev user,id=usernet
- Verwendet werden die Optionen
- Mit
dhclientausu-rootwird per DHCP automatisch eine IP-Adresse zugewiesen- Beispiel:
eth0erhält10.0.2.15/24
- Beispiel:
- Mit
wget http://google.comgelingt der Zugriff auf das externe Netzwerk, und der Download vonindex.htmllä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
initist nicht bloß ein einfacher Prozessstarter, sondern eine Kernkomponente für Geräteinitialisierung, Dienstverwaltung und Steuerung des Systemstarts- Im
init-Quellcode vonu-rootlassen sich verschiedene Schritte zur Einrichtung von Geräten (/dev) nachvollziehen
- Im
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
- Dort lassen sich
1 Kommentare
Hacker News-Kommentare
Ich baue seit ein paar Monaten meine eigene Micro-Linux-Distribution
Der User-Mode besteht aus genau einer einzelnen statischen Binärdatei, plus ein paar Dateien zur Unterstützung vertraulicher microVM-Container
Besonders die Struktur von initramfs ist faszinierend. Der Ablauf, bei dem der Kernel ein cpio-Archiv entpackt, in tmpfs wechselt und dann /init ausführt, wirkt fast wie Magie
Man kann auch mehrere cpio-Archive aneinanderhängen, jedes davon komprimieren, und sie werden der Reihe nach als Overlay angewendet
Durch dieses einfache und elegante Design habe ich viel gelernt, indem ich den Code zum Entpacken selbst geschrieben habe
Vor Kurzem hat qemu begonnen, uftrace auf den wichtigsten Architekturen zu unterstützen
Das ist genau die Antwort, wenn Experten fragen: „Wie soll man das debuggen?“
Mehr dazu findet sich in diesem Thread
Ich arbeite ebenfalls an einem ähnlichen Projekt — azathos
Es enthält ein selbstgeschriebenes toy init, eine Shell und einige Utilities
Zum Debuggen habe ich GNU coreutils hineingepackt, und aktuell konzentriere ich mich darauf, Fenster auf den Framebuffer zu zeichnen
Dieses Projekt ist wirklich großartig. Es erinnert mich an die Zeit 1998, als ich eine Disketten-„Distribution“ gebaut habe, mit der Windows-PC-Images per UDP-Broadcast verteilt wurden
„make bzimage“, Fehler in init-Skripten, Endlos-Reboots … so viele Erinnerungen
Interessant ist, dass die heutige Vorgehensweise gar nicht so anders ist. Ein Port für Raspberry Pi wäre spaßig und lehrreich. Vielleicht probiere ich es selbst aus
Am Ende hat ein Freund den Inhalt per sftp auf CD gebrannt, um das Problem zu lösen, aber damals konnte man nur mit 2-facher Geschwindigkeit brennen
Ich frage mich, wie schwierig es wäre, das als Cloud-Image laufen zu lassen, z. B. auf Vultr oder DigitalOcean, oder ein GUI zu starten und Firefox auszuführen
Man kann auch in eine andere Distribution booten und dann per kexec den eigenen Kernel starten, um die Installation im Arbeitsspeicher durchzuführen
Ein reales Implementierungsbeispiel ist nixos-anywhere
Das ist überraschend unkompliziert
Eine Version dieses Projekts speziell für Raspberry Pi fände ich wirklich interessant
Ich habe mich gefragt, warum man so etwas selbst baut, und ob es nicht reichen würde, Linux einfach mit Gentoo zu erkunden
Man kann den User Space anpassen, aber um Linux selbst zu lernen, ist es nicht ideal
Schon das stage3-Tarball ist praktisch auf dem Niveau einer „Mini-Distribution“
Zum Lernen ist das wirklich großartig, und wenn man schnell etwas fertig bekommen will, ist buildroot eine gute Wahl
Dank dieses Artikels habe ich wirklich viel gelernt. Vielen Dank für diesen sehr informationsreichen Beitrag