38 Punkte von GN⁺ 2025-09-16 | 1 Kommentare | Auf WhatsApp teilen
  • Es wird die Erfahrung geteilt, auf der RISC-V-Architektur einen Prototyp-Kernel für ein Time-Sharing-Betriebssystem implementiert zu haben
  • Das Konzept und die Funktionsweise eines Time-Sharing-Kernels werden praxisorientiert erläutert; statt in C wurde die Implementierung in Zig vorgenommen, um die Reproduzierbarkeit zu erhöhen
  • Es wird ein Unikernel-Ansatz verfolgt, bei dem Kernel und User-Code in einer einzigen Binärdatei gebündelt werden, und eine Schichtenstruktur genutzt, die für Konsolenausgabe und Timer-Steuerung auf OpenSBI setzt
  • Threads laufen im User-Modus (U-mode), während der Kernel im Supervisor-Modus (S-mode) per Timer-Interrupt den Kontextwechsel durchführt und per Systemaufruf die Grenze überquert
  • Der Kernpunkt ist eine Technik, bei der durch den Austausch des vom Interrupt-Prolog/-Epilog aufgebauten Stack-Frames die Registersätze und CSR eines anderen Threads wiederhergestellt und so der Kontrollfluss umgeschaltet werden
  • Auf Basis einer QEMU-VM und aktuellem OpenSBI wird eine für alle reproduzierbare Lernumgebung bereitgestellt; zudem wird das Virtualisierungsspektrum von Threads, Prozessen und Containern konzeptionell verbunden, was das Material als Grundlage für Lehre und Praxisübungen wertvoll macht

Überblick

  • Vorgestellt wird der Prozess, einen Time-Sharing-Betriebssystem-Kernel auf der RISC-V-Architektur direkt zu implementieren
  • Die Hauptzielgruppe sind Einsteiger in Systemsoftware und Rechnerarchitektur, Studierende sowie Engineers mit Interesse am Verständnis von Low-Level-Abläufen
  • Dieses Experiment verwendet statt der Sprache C die Sprache Zig, was die Reproduzierbarkeit der Übungen erhöht und die Installation vereinfacht
  • Der finale Code ist im Repository popovicu/zig-time-sharing-kernel veröffentlicht; es kann leichte Abweichungen zum Text geben
    • Es wird empfohlen, die Repository-Version statt der Codeauszüge im Artikel als Single Source of Truth zu betrachten
    • Für die Übungen erleichtert es die Einrichtung, sich bei Linker-Skript und Build-Optionen an dem Repository zu orientieren

Empfohlene Lektüre

  • Der Artikel setzt Grundlagen der Rechnerarchitektur wie Register, Speicheradressierung und Interrupts voraus
    • Als vorbereitende Materialien werden Bare metal on RISC-V, SBI-Boot-Prozess und Beispiele für Timer-Interrupts empfohlen
    • Der Artikel zur Mikro-Linux-Distribution ist optional hilfreich, um die Philosophie der Trennung von Kernel- und User-Space zu verstehen

Unikernel

  • Es wird eine Unikernel-Konfiguration verwendet, bei der Anwendung und OS-Kernel in eine einzelne ausführbare Datei gelinkt werden
    • So wird die Komplexität von Loader und Linker zur Laufzeit vermieden und eine Vereinfachung erreicht, bei der User-Code gemeinsam mit dem Kernel in den Speicher geladen wird
    • Für Lehr- und Reproduktionszwecke bietet dies Vorteile bei einfacher Verteilung und konsistenter Umgebung

SBI-Schicht

  • RISC-V verwendet ein Rechtemodell mit M/S/U-Modus; in diesem Experiment läuft OpenSBI im M-Modus und der Kernel im S-Modus
    • Konsolenausgabe und die Steuerung des Timer-Geräts werden an SBI delegiert, um Portabilität zu gewährleisten
    • Falls SBI nicht verfügbar ist, wird UART MMIO als Fallback genutzt; für die Übungen wird jedoch aktuelles OpenSBI empfohlen

Ziel des Kernels

  • Zur Vereinfachung werden nur statische Threads unterstützt, und Threads bestehen aus Funktionen, die nicht terminieren
    • Threads laufen im U-Modus und senden Systemaufrufe an den Kernel im S-Modus
    • Es wird Time-Sharing-Scheduling für einen Single-Core implementiert, sodass bei jedem Timer-Tick auf einen anderen Thread umgeschaltet werden kann

Virtualisierung und was genau ein Thread ist

  • Time-Sharing-Threading ist eine Form der Virtualisierung, die auf einem einzelnen Kern mehrere Aufgaben parallelisiert, ohne das Programmiermodell zu verändern
    • Anders als beim kooperativen Scheduling erfolgt der Wechsel ohne explizites yield, sondern per Timer-Interrupt
    • Threads besitzen jeweils einen eigenen, unantastbaren Registersatz und Stack, während der übrige Speicher geteilt werden kann

Stack und Speichervirtualisierung

  • Threads müssen einen eigenen Stack besitzen; nach Aufrufkonvention ist er essenziell für lokale Variablen, die Sicherung von ra und den Erhalt des Ausführungskontexts
    • Das Virtualisierungsspektrum reicht von Thread < Prozess < Container < VM, wobei sich Isolationsgrad und Sicht (view) unterscheiden
    • Unter Linux werden Container durch die Kombination von Kernel-Mechanismen wie chroot und cgroups umgesetzt

Einen Thread virtualisieren

  • Das minimale Virtualisierungsziel dieses Experiments ist die Unveränderlichkeit des Programmiermodells, der Schutz von Registern und einigen CSR sowie die Zuweisung individueller Stacks
    • Es wird betont, warum sinnvolle Berechnungen unmöglich werden, wenn die Registersicht nicht geschützt ist
    • Durch das Seeden anfänglicher Werte wie a0 auf dem Stack lässt sich die Argumentübergabe beim Thread-Start kompakt handhaben

Interrupt-Kontext

  • Interrupts lassen sich als funktionsaufrufähnliches Modell verstehen, bei dem Register durch Prolog/Epilog auf dem Stack gesichert und wiederhergestellt werden
    • Damit asynchrone Timer-Interrupts die Register nicht beschädigen, ist die Einhaltung der Sicherungs-Konvention zwingend
    • Das Beispiel-Assembly sichert und restauriert zusätzlich zur Erhaltung von x0–x31 auch CSR wie sstatus, sepc, scause, stval

Implementierung (High-Level)

Nutzung der Interrupt-Stack-Konvention

  • Der Hauptteil der Interrupt-Routine befindet sich zwischen Prolog und Epilog; wird sp auf einen anderen Speicherbereich umgeschaltet, wird dadurch der Registersatz eines anderen Kontexts wiederhergestellt
    • Das entspricht einem Kontextwechsel und ist die Kernidee der Time-Sharing-Implementierung in diesem Experiment
    • Periodische Timer-Interrupts greifen regelmäßig ein und führen Hauptfluss und Interrupt-Fluss im Wechsel aus

Trennung von Kernel- und User-Space

  • Die Grenze S-Modus-Kernel / U-Modus-User bleibt erhalten; Interrupts und Systemaufrufe werden im S-Modus-Trap-Handler verarbeitet
    • Der Boot-Ablauf verläuft in der Reihenfolge OpenSBI im M-ModusInitialisierung des Kernels im S-ModusStart der Threads im U-Modus
    • Periodische Timer-Interrupts ermöglichen Scheduling und Kontextwechsel

Implementierung (Code)

Assembly-Start

  • In startup.S wird eine minimale Sequenz aufgebaut, die nach BSS-Initialisierung und dem Setzen des initialen Stack-Pointers zu Zigs main springt
    • Der Kernel-Einstiegspunkt verwendet zur Anbindung an die C-ABI die Konvention export

Haupt-Kernel-Datei und I/O-Treiber

  • kernel.zig prüft in main zunächst die OpenSBI-Konsolenfunktion und fällt bei Misserfolg auf UART MMIO zurück
    • sbi.debug_print setzt die Register a0/a1/a6/a7 gemäß dem ECALL-Protokoll und ruft darüber auf
    • Nach dem Setzen des Timers wird der S-Modus-Interrupt-Handler registriert und Ticks werden aktiviert

S-Modus-Handler und der Kontextwechsel

  • Der Handler wird mit Zigs Konvention naked geschrieben, sodass ein vollständiger Prolog/Epilog einschließlich CSR-Sicherung manuell aufgebaut wird
    • Im Hauptteil wird handle_kernel(sp) aufgerufen; durch Austausch gegen den zurückgegebenen sp wird entschieden, ob ein Wechsel stattfindet
    • Über scause wird zwischen ECALL aus dem U-Modus und Timer-Interrupt unterschieden und entsprechend verzweigt

Die Threads im User-Space

  • Der User-Code ist zusammen mit dem Kernel in einer einzigen Binärdatei enthalten; die Beispiel-Threads wiederholen String-Ausgabe → Delay-Loop
    • syscall.debug_print legt die Systemaufrufnummer 64 in a7 sowie Puffer/Länge in a0/a1 und führt dann ECALL aus
    • Bei der Thread-Initialisierung werden Rücksprungadresse und initiale Registerwerte auf dem Stack vorbereitet, sodass bei der ersten Rückkehr Argumente sofort nutzbar sind

Den Kernel ausführen

  • Der Build erfolgt mit zig build; ausgeführt wird in QEMU mit virt-Maschine + nographic + OpenSBI fw_dynamic
    • Beim Booten erscheinen nach dem OpenSBI-Banner periodische, nach Thread-ID getrennte Ausgaben im Wechsel
    • Wird mit -Ddebug-logs=true gebaut, werden Interrupt-Quelle, aktueller Stack sowie Queueing-/Dequeueing-Logs detailliert angezeigt

Fazit

  • Dieses Experiment modernisiert einen Lehr-Kernel mit der Kombination RISC-V + OpenSBI + Zig und erhöht so Reproduzierbarkeit und Lesbarkeit
    • Zwar gibt es Vereinfachungen wie minimale Fehlerbehandlung und überdimensionierte Stacks, der Fokus liegt jedoch auf dem Erlernen des Wesens des Kontextwechsels und der Privilegientrennung
    • Eine Portierung auf reale Maschinen ist möglich, sofern Linker-/Treiberkonstanten angepasst werden und SBI-Verfügbarkeit sichergestellt ist

Zusätzliche Notiz: Ordnung des Virtualisierungsspektrums

  • Threads: vor allem Virtualisierung von Registern und Stack, hohe Wahrscheinlichkeit geteilter Speicherbereiche
  • Prozess: Adressraumvirtualisierung für Speicherisolierung, mit der Möglichkeit mehrerer Threads im Inneren
  • Container: eine Isolationseinheit, die durch die Kombination von Dateisystem- und Netzwerk-Namespaces und ähnlichen Mechanismen entsteht
  • VM: zielt auf vollständige Virtualisierung der Hardware insgesamt

Zusammenfassung der wichtigsten Implementierungspunkte

  • Kontextwechsel durch Austausch des Interrupt-Stacks
  • Bewahren/Wiederherstellen des vollständigen Zustands einschließlich CSR im S-Modus-Trap-Handler
  • Doppelte Ausgabepfade mit SBI zuerst, UART MMIO als Fallback
  • Einfaches Scheduling rund um statische Threads, Single-Core und Time-Slices
  • Klare U/S-Grenze durch ECALL-basierte Systemaufrufe

1 Kommentare

 
GN⁺ 2025-09-16
Hacker-News-Kommentare
  • Ähnliches kann man in Paketform mit "Operating System in 1000 Lines of Code" erleben. Ich habe das vor einiger Zeit mit Zig nachgebaut, wobei ich die C-Code-Snippets nach Zig übersetzt habe, und es hat sehr viel Spaß gemacht. Mein Code und die VODs sind hier: https://github.com/kristoff-it/kristos/

    • Ich frage mich, wie viel man über Zig wissen muss, um diese Übung machen zu können. Falls man Zig noch nie verwendet hat: Wie könnte man das realistisch angehen? Ich würde mich über Ratschläge freuen, ausgehend von vorhandenen C++-Kenntnissen.
  • Das ist ein separater Beitrag des Autors selbst: https://news.ycombinator.com/item?id=45236479. Der Autor hat den Tiny OS Kernel selbst noch einmal gebaut und wollte gezielt mit RISC-V und einer OpenSBI-Umgebung experimentieren, wobei er statt klassischem C Zig verwendet hat. Ich denke, man könnte dem genauso gut in C oder Rust folgen. Der gesamte Prozess ist etwas grob geraten, aber als Experiment und Einstieg, um die Entwicklung von OS-Kerneln und Computerarchitektur kennenzulernen, ist es gedacht. Ich finde, das ist ein Projekt, mit dem man an einem Wochenende gut herumspielen kann. Den vollständigen Walkthrough und den Github-Link findet man oben.

  • Solche Projekte sind wirklich beeindruckend. Linux ist letztlich auch nur ein Kernel, aber diese Arbeit hat den Weg dafür geebnet, Open-Source-Unix auf Milliarden Geräten zu installieren. Ich finde das wirklich großartig.

    • Noch lustiger ist, dass Torvalds bei der allerersten Ankündigung von Linux in einer Mail schrieb, es sei "nur ein Hobby und werde nicht groß und professionell wie GNU" https://groups.google.com/g/comp.os.minix/c/dlNtH7RRrGA/m/SwRavCzVE7gJ

    • Ich halte solche Projekte nicht für <i>unglaublich</i> beeindruckend. Wie man einen minimalistischen Multitasking-Kernel baut, ist seit Jahrzehnten ein bekannter Weg. Einen Kernel zu bauen, der bootet und einfache Aufgaben erledigt, ist etwas, das man mit einer gewissen Klugheit und Ausdauer hinbekommt. Auf RISC-V ist es etwas komplizierter als auf x86, aber Informationen zur Hardware-Initialisierung sind leicht zu finden (siehe https://wiki.osdev.org/RISC-V_Meaty_Skeleton_with_QEMU_virt_board). Auch bei diesem Projekt sagt der Autor selbst, dass es im Grunde "eine Übung aus einem Betriebssystemkurs noch einmal gemacht" sei. Ich denke, jeder mit einem Abschluss in Software Engineering sollte das schaffen können. Natürlich wird es Bugs oder unvollständige Teile geben, aber Multiprozesse oder Prozessisolation per MMU sind an sich heute nichts mehr, was besonders schwer wäre.

    • So wie Linux hat auch Stallmans Beginn im Jahr 1984, einen C-Compiler und Unix-Werkzeuge zu schreiben, letztlich den Weg dafür geebnet, Open-Source-Unix auf Milliarden Maschinen zu installieren.

  • Zig ist wirklich gut für die OS-Entwicklung, und RISC-V ebenso. Ich habe mit genau derselben Aufgabe auf x86 angefangen, bin aber schnell von der Menge an Legacy-Boilerplate ermüdet. Auf der RISC-V-Seite gibt es davon fast nichts, was den Einstieg viel leichter macht. https://github.com/Fingel/aeros-v

    • Wenn man auf x86 anfängt, finde ich nicht, dass es so viel Boilerplate gibt, solange man einen guten Bootloader verwendet. Multiboot-Loader übernehmen normalerweise den Real Mode, und da die meisten Leute in den Protected Mode wollen, muss man nur ein paar Tabellen aufsetzen und dann springen. Wenn man die alten Interrupt-Controller abschalten will, muss man etwas mehr anfassen, aber dafür hat man den Vorteil, auf Desktop-PCs booten zu können (mit etwas Vorsicht bei der Konsolen-Schnittstelle). Mein Hobby-OS nutzte BIOS-Boot und ein paar VGA-Funktionen, und ich hatte wegen der schlechten Kompatibilität damit zu kämpfen. Eine serielle Konsole ist viel einfacher, aber heutige Computer haben oft keinen seriellen Port mehr.

    • Im Grunde holt es die Sicherheit von Object Pascal oder Modula-2 zurück und verpackt sie neu in C-Syntax. C hatte nie etwas wirklich Besonderes an sich, außer dass es sich dank der UNIX-Lizenz weit verbreitet hat.

    • Ich würde das auch gern selbst ausprobieren und frage mich, in welcher Umgebung ihr den RISC-V-Kernel ausführt. Nutzt ihr nur Qemu oder gibt es konkrete echte Hardware, die ihr empfehlen würdet?

  • Ich finde die RISC-V-ISA wirklich außergewöhnlich zugänglich. Die Dokumentation ist hervorragend, es gibt sehr viele Beispiele und auch eine ganze Reihe von Emulatoren. Selbst unkomprimierter Maschinencode lässt sich gut lesen. Ich schreibe gerade sogar ein eigenes Buch für meine Tochter und baue darin ein kleines OS mit Forth und Time-Sharing https://punkx.org/projekt0/book/part1/os.html. Mit x86 hätte ich das vermutlich gar nicht erst versucht. Dabei lerne ich gleichzeitig Forth und RISC-V-Assembly, und es macht wirklich Spaß. Wenn man schon immer einmal ein Spielzeug-OS von Grund auf bauen wollte, ist jetzt genau die richtige Zeit dafür (so spannend wie in den 1980ern). Besorgt euch ein günstiges RISC-V-Board (rp2350 usw.) und ladet relevante Handbuchabschnitte bei einer KI wie Claude hoch; das hilft enorm, wenn man feststeckt.

    • Ich habe gerade nach den Board-Preisen in meinem Land gesucht und war überrascht, wie viel günstiger sie sind, als ich erwartet hatte. Ich bin gerade zu 90 % dabei, mir einfach zum Spaß eines zu kaufen und damit herumzuspielen.
  • Solche Versuche sind immer unterhaltsam und interessant. Ich würde sogar dazu ermutigen, auch eigene Kryptografie oder andere schwierige Dinge auszuprobieren. Der Rat, "keine eigene Kryptografie zu implementieren", bedeutet für die Praxis nur, dass man nichts verwenden sollte, das nicht erprobt ist. Für Experimente und Forschung ist das nicht gefährlich, also kann man ruhig frei herumprobieren. Wir brauchen mehr Betriebssysteme und mehr Auswahl.

  • (Zitat aus einem spanischen Gerichtsurteil) Dass HTTP blockiert wird, kann ja vielleicht noch sein, aber das hier ist wirklich zu viel ...

    • Ich habe gehört, dass in Spanien Cloudflare (und vermutlich auch andere) wegen Problemen rund um Fußballübertragungen versehentlich blockiert werden. Ich frage mich, wie die Leute dort so etwas umgehen. Nutzen sie VPNs? Wenn wichtige IPs gesperrt werden, müsste das doch auch die Arbeit beeinträchtigen.

    • Als ich in dem Urteil las, dass das von der spanischen Profiliga und Telefónica Audiovisual Digital angestoßen wurde, dachte ich nur: Diese Leute sind Kriminelle.

    • ... wie bitte? Hat eine spanische Fußballorganisation das Recht, den Internetzugang eines ganzen Landes einzuschränken?

  • Ich frage mich, wie man günstig an RISC-Hardware kommt.

    • Auf AliExpress wird ein Board namens Milk-V Duo S für 10 Dollar verkauft, und es taucht in letzter Zeit oft in meinen Empfehlungen auf. Die wichtigsten Spezifikationen sind ein aufgerüsteter SG2000-Master, 512 MB RAM, mehr IO, bei einigen Modellen WI-FI6/BT5 (außer den 512M-Basic/eMMC-Modellen), ein USB-2.0-Host-Port, 100-Mbit-Ethernet mit PoE-Unterstützung, Dual-MIPI-CSI sowie ein Umschalter zwischen RISC-V- und ARM-Boot. https://aliexpress.com/w/wholesale-Milk%2525252dV-Duo-S.html

    • Ich habe schon einige Boards, aber eines, das ich interessant fand und deshalb unterstützt habe, ist das VisionFive 2 Lite https://www.kickstarter.com/projects/starfive/visionfive-2-lite-unlock-risc-v-sbc-at-199/description. Ich habe kein VisionFive2 der ersten Generation, aber der Ruf ist gut und das Ökosystem scheint zu wachsen. Es gibt noch unfertige Teile, aber ich hoffe, dass es bald ausgeliefert wird. Persönlich benutze ich ein PolarFire SoC Discovery Kit. Das ist ein Board mit Quad-Core-RISC-V und FPGA, etwas teuer (130 Dollar) und sicher nicht für jeden geeignet, aber interessant ist, dass das Board billiger ist als der Chip selbst. https://www.microchip.com/en-us/development-tool/MPFS-DISCO-KIT Die Microchip-Dokumentation und Toolchain sind altbacken und nicht besonders gut, aber wenn man sich einmal daran gewöhnt hat, ist es wirklich sehr einfach, Bare-Metal-RISC-V-Code darauf laufen zu lassen. Es gibt hilfreiche Linux-/Bare-Metal-Beispiele.

    • Ich würde empfehlen, auch ohne echte Hardware erst einmal mit einem Emulator auf x86- oder Apple-Maschinen zu beginnen. Damit entwickelt es sich meist schneller als auf echten Boards, und mit etwas wie QEMU kann man sofort loslegen. https://www.qemu.org/docs/master/system/target-riscv.html

    • Auch der Raspberry Pi Pico 2 ist in Ordnung, weil er RISC-V unterstützt. https://www.raspberrypi.com/products/raspberry-pi-pico-2/

    • Danke an alle für die Antworten. Den Leuten, die die Frage downgevotet haben, schulde ich allerdings keinen Dank.