C-Standardbibliotheken auf Bare Metal

24

Ich entwickle hauptsächlich auf Geräten, die Linux portiert haben, sodass die Standard-C-Bibliothek viele Funktionen bietet, indem sie Systemaufrufe implementiert, die ein standardisiertes Verhalten aufweisen.

Für Bare Metal gibt es jedoch kein zugrunde liegendes Betriebssystem. Gibt es einen Standard bezüglich der Implementierung einer Bibliothek oder müssen Sie die Besonderheiten einer Bibliotheksimplementierung neu lernen, wenn Sie auf eine neue Karte wechseln, die ein anderes BSP bietet?

TheMeaningfulEngineer
quelle
4
Falsche Seite für Ihre Frage.
ott--
8
Ich stimme dafür, diese Frage als "Off-Topic" zu schließen, da sie zum Stapelüberlauf gehört .
uint128_t
1
Generell verzichten Sie darauf. Warum brauchen Sie solche Dinge ohne ein Betriebssystem, um sie zu unterstützen? memcpy und so sicher. Dateisysteme, nicht notwendigerweise, obwohl sie mit fopen, close usw. implementiert sind, sind beispielsweise gegen RAM trivial. printf () ist sehr, sehr schwer, tonnenweise Code erforderlich, verzichten Sie darauf. Jedes I / O ersetzen oder verzichten. newlib ist ziemlich extrem, aber hilft, wenn Sie nicht darauf verzichten können, aber Sie müssen das System trotzdem auf dem Backend implementieren, brauchen Sie also die zusätzliche Ebene?
old_timer
12
Während es sich bei dieser Frage um Software handelt, handelt es sich um eine sehr spezifische Frage der eingebetteten Programmierung, die von SO im Allgemeinen abgelehnt wird. Da wir hier bereits einige gute Antworten haben, ist eine Migration nicht angebracht.
Dave Tweed
1
Obwohl newlib in einer Antwort weiter unten erwähnt wird, ist newlib-nano möglicherweise auch nützlich - es soll eine abgespeckte Version für die Verwendung in eingebetteten Systemen mit eingeschränkten Ressourcen sein. Ich benutze es in Projekten auf Cortex M0-MCUs. Eine Reihe von Compilern (Atollic TrueSTUDIO ist einer davon) bieten die Möglichkeit, newlib oder newlib-nano zu verwenden.
JJMILBURN

Antworten:

20

Ja, es gibt einen Standard, einfach die C-Standardbibliothek . Die Bibliotheksfunktionen erfordern kein "ausgewachsenes" Betriebssystem oder ein Betriebssystem überhaupt, und es gibt eine Reihe von Implementierungen, die auf "Bare-Metal" -Code zugeschnitten sind, wobei Newlib vielleicht das bekannteste ist.

Am Beispiel von Newlib müssen Sie eine kleine Untergruppe von Kernfunktionen schreiben, hauptsächlich wie Dateien und Speicherzuordnung in Ihrem System gehandhabt werden. Wenn Sie eine gemeinsame Zielplattform verwenden, hat wahrscheinlich bereits jemand diese Aufgabe für Sie erledigt.

Wenn Sie Linux verwenden (wahrscheinlich auch OSX und vielleicht sogar cygwin / msys?) Und tippen man strlen, sollte es einen Abschnitt mit der Bezeichnung "Etwas" geben CONFORMING TO, der Ihnen sagt, dass die Implementierung einem bestimmten Standard entspricht. Auf diese Weise können Sie herausfinden, ob etwas, das Sie verwendet haben, eine Standardfunktion ist oder ob es von einem bestimmten Betriebssystem abhängt.

Rohr
quelle
1
Ich bin neugierig, wie ein stdlibGerät funktioniert , stdioohne vom Betriebssystem abhängig zu sein. wie fopen(), fclose(), fread(), fwrite(), putc()und getc()? und wie funktioniert es malloc(), ohne mit dem Betriebssystem zu sprechen?
Robert Bristow-Johnson
4
Unter Newlib befindet sich eine Ebene namens "libgloss", die ein paar Dutzend Funktionen für Ihre Plattform enthält (oder Sie schreiben). Zum Beispiel: a getcharund putcharwelche wissen über den UART Ihrer Hardware Bescheid; dann schichtet Newlib darüber printf. Die Datei-E / A stützt sich ebenfalls auf einige Grundelemente.
Brian Drummond
Ja, ich habe den 2. Absatz der Pipe nicht sorgfältig gelesen. Neben dem Umgang mit stdinund stdoutund stderr (das sich um putchar()und kümmert getchar()), das I / O von / zu einem UART leitet, müssen Sie auch dafür Klebstoff schreiben, wenn Ihre Plattform über Dateispeicher verfügt, z. B. mit einem Flash. und Sie müssen die Mittel zu malloc()und haben free(). Ich denke, wenn Sie sich um diese Probleme kümmern, können Sie Portable C in Ihrem eingebetteten Ziel ausführen (weder argvnoch argc).
Robert Bristow-Johnson
2
Newlib ist auch riesig, wenn Sie mit MCUs mit 1 oder 2 KB Code-Speicherplatz zu tun haben ...
Brian Drummond
2
@dwelch Sie erstellen kein eigenes Betriebssystem, sondern die C-Bibliothek. Wenn Sie das nicht wollen, dann ist es unnötig groß.
Pipe
8

Gibt es einen Standard bezüglich der Implementierung einer Bibliothek oder müssen Sie die Besonderheiten einer Bibliotheksimplementierung neu lernen, wenn Sie auf eine neue Karte wechseln, die ein anderes BSP bietet?

Zunächst definiert der C-Standard etwas, das als "freistehende" Implementierung bezeichnet wird, im Gegensatz zu einer "gehosteten" Implementierung (mit der die meisten von uns vertraut sind, dh mit dem vollen Umfang der C-Funktionen, die vom zugrunde liegenden Betriebssystem unterstützt werden).

Eine "freistehende" Implementierung muss nur eine Teilmenge der C-Bibliotheks-Header definieren, nämlich diejenigen, die keine Unterstützung benötigen, oder sogar die Definition von Funktionen (sie tun lediglich #defines und typedefs):

  • <float.h>
  • <iso646.h>
  • <limits.h>
  • <stdalign.h>
  • <stdarg.h>
  • <stdbool.h>
  • <stddef.h>
  • <stdint.h>
  • <stdnoreturn.h>

Wenn Sie den nächsten Schritt in Richtung einer gehosteten Implementierung machen, werden Sie feststellen, dass es nur sehr wenige Funktionen gibt, die wirklich "das System" in irgendeiner Weise verbinden müssen, wobei der Rest der Bibliothek über diesen "Grundelementen" implementiert werden kann ". Bei der Implementierung der PDCLib habe ich mich bemüht, sie in einem separaten Unterverzeichnis zu isolieren, um sie beim Portieren der lib auf eine neue Plattform leichter identifizieren zu können (Beispiele für den Linux-Port in Klammern):

  • getenv()( extern char * * environ)
  • system()( fork()/ execve()/ wait())
  • malloc()und free()( brk()/ sbrk())
  • _Exit()( _exit())
  • time() (noch nicht implementiert)

Und für <stdio.h>(wohl die am meisten "am Betriebssystem beteiligten" der C99-Header):

  • eine Möglichkeit, eine Datei zu öffnen ( open())
  • ein Weg, um es zu schließen ( close())
  • eine Möglichkeit, es zu entfernen ( unlink())
  • eine Möglichkeit, es umzubenennen ( link()/ unlink())
  • eine Möglichkeit, um es zu schreiben ( write())
  • eine Art, daraus zu lesen ( read())
  • eine Möglichkeit, sich darin neu zu positionieren ( lseek())

Bestimmte Details der Bibliothek sind fakultativ, da der Standard lediglich eine standardmäßige Implementierung anbietet , eine solche Implementierung jedoch nicht erforderlich macht.

  • Die time()Funktion kann legal zurückkehren, (time_t)-1wenn keine Zeitmessmechanik verfügbar ist.

  • Die für beschriebenen Signalhandler <signal.h>müssen nur durch einen Aufruf von aufgerufen werden. raise()Es ist nicht erforderlich, dass das System tatsächlich so etwas wie an die Anwendung sendetSIGSEGV .

  • Der C11-Header <threads.h>, der (aus offensichtlichen Gründen) stark vom Betriebssystem abhängig ist, muss überhaupt nicht bereitgestellt werden, wenn die Implementierung Folgendes definiert __STDC_NO_THREADS__:

Es gibt weitere Beispiele, aber ich habe sie momentan nicht zur Hand.

Der Rest der Bibliothek kann ohne Hilfe der Umgebung implementiert werden. (*)


(*) Einschränkung: Die PDCLib-Implementierung ist noch nicht abgeschlossen, daher habe ich möglicherweise ein oder zwei Dinge übersehen. ;-)

DevSolar
quelle
4

Standard C wird tatsächlich unabhängig von der Betriebsumgebung definiert. Es wird keine Annahme getroffen, dass ein Host-Betriebssystem vorhanden ist, und diejenigen Teile, die vom Host abhängig sind, werden als solche definiert.

Das heißt, der C-Standard ist schon ziemlich blank.

Natürlich sind diese von uns so geliebten Sprachteile, die Bibliotheken, oft der Ort, an dem die Kernsprache das Hosting bestimmter Inhalte vorantreibt. Daher das typische Cross-Compiler-Zeug "xxx-lib", das für viele Bare-Metal-Plattform-Tools zu finden ist.


quelle
3

Newlib minimal lauffähiges Beispiel

Hier stelle ich ein hochautomatisiertes und dokumentiertes Beispiel zur Verfügung, das Newlib in Aktion in QEMU zeigt .

Mit newlib implementieren Sie Ihre eigenen Systemaufrufe für Ihre Baremetal-Plattform.

Im obigen Beispiel haben wir zum Beispiel ein Beispielprogramm exit.c:

#include <stdio.h>
#include <stdlib.h>

void main(void) {
    exit(0);
}

und in einer separaten C-Datei common.cimplementieren wir das exitmit ARM Semihosting :

void _exit(int status) {
    __asm__ __volatile__ ("mov r0, #0x18; ldr r1, =#0x20026; svc 0x00123456");
}

Die anderen typischen Systemaufrufe, die Sie implementieren, sind:

  • writeErgebnisse an den Host ausgeben. Dies kann entweder mit:

    • mehr semihosting
    • eine UART-Hardware
  • brkfür malloc.

    Schont das Baremetall, da wir uns nicht um Paging kümmern müssen!

TODO Ich frage mich, ob es realistisch ist, eine vorbeugende Planung der Ausführung von Systemaufrufen zu erreichen, ohne auf ein vollwertiges RTOS wie Zephyr oder FreeRTOS umzusteigen .

Das Coole an Newlib ist, dass es alle nicht-OS-spezifischen Dinge string.himplementiert, die Sie mögen , und dass Sie nur die OS-Stubs implementieren können.

Außerdem müssen Sie nicht alle Stubs implementieren, sondern nur die, die Sie benötigen. ZB wenn Ihr Programm nur benötigt exit, dann müssen Sie kein print.

Der Newlib-Quelltextbaum enthält bereits einige Implementierungen, einschließlich einer ARM-Semihosting-Implementierung newlib/libc/sys/arm, die Sie jedoch größtenteils selbst implementieren müssen. Es bietet jedoch eine solide Basis für die Aufgabe.

Die einfachste Möglichkeit, Newlib einzurichten, besteht darin, einen eigenen Compiler mit Crosstool-NG zu erstellen. Sie müssen lediglich angeben, dass Sie Newlib als C-Bibliothek verwenden möchten. Mein Setup erledigt das automatisch für Sie mit diesem Skript , das die newlib configs verwendet, die bei vorhanden sind crosstool_ng_config.

Ich denke, C ++ wird auch funktionieren, aber TODO testet es.

Ciro Santilli ist ein Schauspieler
quelle
3
@Downvoters: Bitte erläutern Sie, damit ich Informationen lernen und verbessern kann. Hoffentlich Zukunft Leser können den Wert der nur einleitenden Newlib Setup im Internet verfügbar sehen , dass nur funktioniert :-)
Ciro Santilli新疆改造中心法轮功六四事件
2

Wenn Sie es baremetal benutzen, entdecken Sie einige nicht implementierte Abhängigkeiten und müssen mit ihnen umgehen. Bei all diesen Abhängigkeiten geht es darum, die Interna entsprechend der Persönlichkeit Ihres Systems abzustimmen. Zum Beispiel, als ich versucht habe, sprintf () zu verwenden, das malloc () verwendet. Malloc hat das Funktionssymbol "t_sbrk" als Hook-In-Code, der vom Benutzer implementiert werden muss, um die Hardware-Einschränkungen durchzusetzen. Hier kann ich es implementieren oder mein eigenes malloc () erstellen, wenn ich glaube, dass ich ein besseres für die eingebettete Hardware machen kann, hauptsächlich für andere Zwecke, nicht nur für sprintf.

Ayhan
quelle
Warum sollte sprintf malloc () brauchen?
Supercat
Ich weiß es nicht. Ich denke, Ihr Punkt ist der Puffer, den es bereits hat, nicht wahr? Aber auch printf sollte kein malloc brauchen. Vielleicht, um einige interne Variablen dynamisch zuzuweisen, wenn die Berechnung der angeforderten Ausgabe schwerer ist als die Voraussicht der gestapelten Zuweisung (dynamische Funktionsvariablen)? Ich bin sicher, dass Sprintf Malloc (arm-none-eabi-newlib) benötigt. Jetzt habe ich experimentiert, ein einfaches Programm verwendet Sprintf auf dem Computer (glibc). Es hat nie malloc genannt. Dann verwendet printf. Es hieß Malloc. Malloc war falsch und gab immer 0 zurück. Aber es hat gut funktioniert. Sie sollten einen String und eine Dezimalvariable ausgeben. @supercat
Ayhan
Ich habe selbst einige Versionen von printf oder ähnlichen Methoden erstellt, die auf die von meinen Anwendungen verwendeten Formate zugeschnitten sind. Die dezimale Ausgabe erfordert einen Puffer, der lang genug ist, um die längste mögliche Zahl zu speichern. Andernfalls akzeptiert die Basisroutine eine Struktur, deren erstes Element eine Ausgabefunktion ist, die zusammen mit den auszugebenden Daten einen Zeiger auf diese Struktur akzeptiert. Solch ein Design macht es möglich, printf-Varianten hinzuzufügen, die auf Konsolen, Sockets usw. im Fluchstil ausgegeben werden. Ich habe in so etwas noch nie ein Bedürfnis nach "malloc" gehabt.
Supercat