`printf` ohne OS implementieren – Nutzung der C-Standardbibliothek in einer Bare-Metal-Umgebung
(popovicu.com)- Einführung in eine Methode, mit der sich mithilfe der Newlib-Bibliothek auch ohne Betriebssystem C-Standardfunktionen inklusive
printfnutzen 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,_closelassen sich fortgeschrittene Funktionen wieprintfnutzen - 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
printfist intern auf einfache primitive Funktionen wie_writeaufgebaut- 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=medanyverwendet medanyist 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-newlibverwendet 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,_closewerden implementiert und an Newlib angebunden _sbrkarbeitet, indem es den Heap-Speicher vom Punkt_endbis_stack_bottomerweitert
Application example: input and output
- In der
main-Funktion könnenprintfundscanfverwendet werden; auch Eingabewerte werden korrekt verarbeitet - Echo wird zwar nicht unterstützt, aber über
scanflassen sich Zeichenketten einlesen und ausgeben - Es wird eine eigene Runtime implementiert, die den Stack initialisiert, die BSS-Sektion mit Nullen füllt und danach
mainaufruft
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,.bssangeordnet; der Heap ist von_endbis 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=medanyverwendet werden, damit Maschinencode erzeugt werden kann, der Adressen oberhalb von0x80000000verarbeiten 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.ldwerden eine minimale Newlib-Konfiguration und eine benutzerdefinierte Runtime verwendet - Beim Ausführen von
make debugfunktionieren Ein- und Ausgabe über UART in der QEMU-Konsole korrekt - Über
qemu_debug.loglässt sich der tatsächliche Instruktions-Trace prüfen
Conclusion
- Mit Newlib wurde eine Struktur umgesetzt, in der sich auch ohne Betriebssystem
printf,scanf,mallocusw. 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.