14 Punkte von GN⁺ 2025-05-06 | Noch keine Kommentare. | Auf WhatsApp teilen
  • Einführung in eine Methode, mit der sich mithilfe der Newlib-Bibliothek auch ohne Betriebssystem C-Standardfunktionen inklusive printf nutzen lassen
  • In einer auf der RISC-V-Architektur basierenden Bare-Metal-Umgebung werden UART-Treiber und Speicherallokationsfunktionen direkt implementiert und an Newlib angebunden
  • Schon durch die Implementierung eines minimalen Satzes an System-Call-Funktionen wie _write, _sbrk, _close lassen sich fortgeschrittene Funktionen wie printf nutzen
  • Anleitung zum Erstellen einer Newlib-basierten Toolchain zusammen mit der RISC-V-GCC-Toolchain sowie zum Verfassen von automatisierten Build- und Linker-Skripten
  • Im Ergebnis wurde erfolgreich eine printf-Umgebung mit UART-Ausgabe, scanf-Eingabe und dynamischer Speicherallokation aufgebaut

Software abstractions and C standard library

  • In einem normalen OS arbeiten bei einem printf-Aufruf verschiedene Abstraktionsschichten wie Kernel-System-Calls, Terminal-Schicht und Font-Rendering zusammen
  • In einer Bare-Metal-Umgebung ist direkte Ein-/Ausgabesteuerung ohne Betriebssystem nötig, wofür eigene Treiberimplementierungen erforderlich sind
  • Newlib bietet eine erweiterbare Struktur, bei der statt der vollständigen C-Standardbibliothek nur die Minimalfunktionen implementiert werden

Newlib concept

  • printf ist intern auf einfache primitive Funktionen wie _write aufgebaut
  • In Newlib sind anfangs alle Funktionen als Dummys definiert; implementiert man nur die benötigten Teile, können die übrigen mit Standardwerten weiterverwendet werden
  • Wenn Entwickler nur die benötigten Funktionen implementieren, können sie die C-Bibliotheksfunktionen flexibel nutzen

Cross-compilation toolchain

  • Für das Cross-Compiling von x86_64/Linux nach RISC-V ist ein direkter Build aus den GCC-Quellen erforderlich
  • Es wird eine Toolchain aufgebaut, bei der Newlib als Standard-C-Bibliothek gesetzt ist, sodass sich Binärdateien für RISC-V bauen lassen

Toolchain details

  • Beim Build der Toolchain werden die Optionen --prefix, --enable-multilib, --disable-gdb, --with-cmodel=medany verwendet
  • medany ist eine Einstellung, die auf RISC-V den Zugriff auf Speicherbereiche mit hohen Adressen ermöglicht
  • Nach Abschluss des Builds können der Cross-Compiler und die Newlib-Bibliothek unter /opt/riscv-newlib verwendet werden

Implementing the memory and UART building blocks

  • Die Implementierung von Senden und Empfangen von Zeichen erfolgt über den direkten Zugriff auf die 16550A-UART-Hardwareadresse in einer QEMU-Umgebung
  • Ersatzfunktionen für System-Calls wie _write, _sbrk, _close werden implementiert und an Newlib angebunden
  • _sbrk arbeitet, indem es den Heap-Speicher vom Punkt _end bis _stack_bottom erweitert

Application example: input and output

  • In der main-Funktion können printf und scanf verwendet werden; auch Eingabewerte werden korrekt verarbeitet
  • Echo wird zwar nicht unterstützt, aber über scanf lassen sich Zeichenketten einlesen und ausgeben
  • Es wird eine eigene Runtime implementiert, die den Stack initialisiert, die BSS-Sektion mit Nullen füllt und danach main aufruft

Linker script

  • Die Startadresse der Ausführung ist 0x80000000, und an dieser Stelle wird der Runtime-Code platziert
  • Der Speicher wird in der Reihenfolge .text, .rodata, .data, .bss angeordnet; der Heap ist von _end bis vor den Stack definiert
  • Der Stack hat eine feste Größe von 64 KB, die oberste Adresse ist 0x80000000 + 64MB
  • Über die ASSERT-Anweisung werden Kollisionen zwischen Heap und Stack verhindert

The ‘gotcha’ moment

  • Bei der Toolchain-Konfiguration muss --with-cmodel=medany verwendet werden, damit Maschinencode erzeugt werden kann, der Adressen oberhalb von 0x80000000 verarbeiten kann
  • Wenn C-Bibliothek und Anwendungscode unterschiedliche Adressmodelle verwenden, treten Linker-Fehler auf

Running the app

  • Über ein Makefile lassen sich Cross-Compile und QEMU-Ausführung automatisieren
  • Mit den Optionen -specs=nosys.specs, -nostartfiles, -T link.ld werden eine minimale Newlib-Konfiguration und eine benutzerdefinierte Runtime verwendet
  • Beim Ausführen von make debug funktionieren Ein- und Ausgabe über UART in der QEMU-Konsole korrekt
  • Über qemu_debug.log lässt sich der tatsächliche Instruktions-Trace prüfen

Conclusion

  • Mit Newlib wurde eine Struktur umgesetzt, in der sich auch ohne Betriebssystem printf, scanf, malloc usw. verwenden lassen
  • Die zentrale Strategie ist, auf Basis der Building-Block-Struktur von Newlib nur die benötigten Funktionen mit minimalem Aufwand zu implementieren
  • Später lassen sich zusätzliche Funktionen wie Dateisystem oder Speicherverwaltung ergänzen, und dank beibehaltener Bibliothekskompatibilität ist Wiederverwendung auch auf Bare Metal möglich
  • Das gesamte Projektergebnis ist mit rund 220 KB vergleichsweise klein und effizient

GitHub-Quellcode: popovicu/bare-metal-cstdlib

Noch keine Kommentare.

Noch keine Kommentare.