Hallo,
ich habe eine Verständnisfrage zu den _read()/_write() Funktionen, wie
sie im GCC in der Syscalls.c Sourcedatei verwendet werden.
Für den STM32 i.V.m. CubeMX wird eine syscalls.c Minimalimplementierung
bei der Projekterstellung zur Verfügung gestellt.
Die Funktionen _read() und _write() werden im allgemeinen bei allen
Funktionen, welche auf Dateien zugreifen aufgerufen. printf() greift
ebenfalls auf _write zu, scanf() analog auf _read(). printf()/scanf()
benutzen die eindeutig festgelegten Dateinummern für stdout (0) und
stdin (1), welche an _write() und _read() übergeben werden. Für
Fehlerausgaben ist die Dateinummer 2 vorgesehen. Alle darüber hinaus
gehenden Dateinummern hängen vom verwendeten Betriebs- und/oder
Dateisystem (sofern vorhanden) ab, ist das allgemein soweit korrekt?
Welche Rolle spielen hier nun die Funktionen __io_putchar() und
__io_getchar(), wie sie in der Minimalimplementierung auftauchen?
1
int_write(intfile,char*ptr,intlen){
2
intDataIdx;
3
4
for(DataIdx=0;DataIdx<len;DataIdx++)
5
{
6
__io_putchar(*ptr++);
7
}
8
returnlen;
9
}
Dass ich hier mittels einer eigenen __io_putchar() Funktion die Ausgabe
bspw. auf den UART umleiten kann ist mir klar. Mit der oben gezeigten
Standardimplementierung würden dann alle Schreibzugriffe, egal mit
welcher Dateinummer auf dem UART landen.
Mir fehlt das Verständnis, ob die __io_putchar() Funktion nur für die
Ausgabe auf stdout vorgesehen ist. Ich habe weitere Implementierungen
der _read()/_write() Funktionen gefunden, welche zumindest mal die
übergebene Dateinummer auf stdout, stdin und stderr abfragen und bei
darüber hinausgehenden Dateinummern auf einem bare-metal-System keine
Ein-/Ausgabe vornehmen. Also eine etwas sauberere Implementierung. Und
den Aufruf der __io...() Funktionen könnte man sich ja sparen und direkt
vom UART einlesen bzw. drauf ausgeben.
Ist also der Zweck der __io_putchar()/__io_getchar() Funktionen die
Ein-/Ausgabe auf einem dedizierten "Medium"? Auf einem Rechner als
Konsolenprogramm ist die Standardein-/Ausgabe ja eben genau die Konsole.
D.h. Systeme mit Betriebs-/Dateisystem verwenden in den _read()/_write
Funktionen für Dateinummern >2 eine eigene Ein-/Ausgabefunktion, bspw.
eine __file_putchar() Funktion?
Ralf
Ralf schrieb:> D.h. Systeme mit Betriebs-/Dateisystem verwenden in den _read()/_write> Funktionen für Dateinummern >2 eine eigene Ein-/Ausgabefunktion, bspw.> eine __file_putchar() Funktion?
Die o.g. _read() und _write() werden nur auf Architekturen ohne
Betriebssystem verwendet.
Hat man ein Betriebssystem, sind read() und write() eignene System
Calls, und der OS (-Kern? -Treiber?) kümert sich um die korrekte
Zuordnung der Daten zum File / Ausgabedevice.
Im minimalen Beispiel oben werden deshalb die File Nummern
weggeschmissen, weil es (z.B. bei einem UART) ohnehin nur einen
Eingabe/Ausgabe Kanal gibt.
Ralf schrieb:> D.h. Systeme mit Betriebs-/Dateisystem verwenden in den _read()/_write> Funktionen für Dateinummern >2 eine eigene Ein-/Ausgabefunktion, bspw.> eine __file_putchar() Funktion?
Kann man selbst für die Deskriptoren 0, 1 und 2 machen, wenn man will.
Dann muss man nur ein passendes freopen() am Anfang von main() haben,
damit kann man dann in Abhängigkeit vom Dateinamen ziemlich generisch
intern arbeiten. Wir interpretieren in unserer Firwmare bspw. Strings
mit Doppelpunkt am Ende als Gerätetreiber, dabei geht dann auch sowas
wie "uart0@38400:" oder auch "nul:" :.), alle anderen Dateinamen gehen
auf die SD-Karte. Das sieht dann bspw. so aus:
1
intmain(void)
2
{
3
// hardware init ...
4
5
// stdin/stdout für Binärdaten (keine CR/LF Wandlung)
6
freopen("uart0@38400:","rb",stdin);
7
freopen("uart0@38400:","wb",stdout);
8
// stderr zum Debugging ("\n" -> CR/LF)
9
#ifdef DEBUG
10
freopen("uart1@9600","w",stderr);
11
#else
12
freopen("nul:","w",stderr);
13
#endif
Man muss natürlich dann ein passendes _open() implementieren und intern
irgendwie über die zurück gegebenen file handles Buch führen.
Jim M. schrieb:> Die o.g. _read() und _write() werden nur auf Architekturen *ohne*> Betriebssystem verwendet.
Widerspricht das...
> Hat man ein Betriebssystem, sind read() und write() eignene System> Calls, und der OS (-Kern? -Treiber?) kümert sich um die korrekte> Zuordnung der Daten zum File / Ausgabedevice.
...nicht dieser Aussage? Oben sagst du, sie werden nur ohne OS
verwendet, und hier werden sie doch wieder verwendet, wobei das OS dann
eben die Dateinummer etc. abhandelt.
Oder meinst du damit, dass das OS ohne eine eigene Implementierung der
read()/write() Funktionen entscheiden kann, was wie wohin wandert?
Jörg W. schrieb:> Kann man selbst für die Deskriptoren 0, 1 und 2 machen, wenn man will.> Dann muss man nur ein passendes freopen() am Anfang von main() haben,> damit kann man dann in Abhängigkeit vom Dateinamen ziemlich generisch> intern arbeiten. Wir interpretieren in unserer Firwmare bspw. Strings> mit Doppelpunkt am Ende als Gerätetreiber, dabei geht dann auch sowas> wie "uart0@38400:" oder auch "nul:" :.), alle anderen Dateinamen gehen> auf die SD-Karte. Das sieht dann bspw. so aus:>>> [snip]>> ...>> [snap]
Das heisst, in eurer Implementierung gibt's "den" (einen) Dateinamen an
sich nicht, sondern freopen() ist selbst implementiert und interpretiert
den Dateinamen und wenn er korrekt umgesetzt werden kann, wird der UART
passend konfiguriert?
> Man muss natürlich dann ein passendes _open() implementieren und intern> irgendwie über die zurück gegebenen file handles Buch führen.
Mmmh, okay. Das heisst, freopen() ruft _open() auf? Das sind Funktionen
welche ich bis dato noch gar nicht beäugt habe, weil ich bisher auch
kein OS oder Dateisystem verwende.
Danke schonmal an euch beide, ein bisschen ist es jedenfalls schon
klarer geworden.
Das ganze ist doch die Mechanik der C-Bibliothek, oder? Gibt's dazu
Literatur, in der man das ganze Prozedere "am Stück" nachlesen kann? Ich
habe gerade das libc-Handbuch auf gnu.org gefunden, vielleicht hat
jemand noch weitere Literaturvorschläge?
Ralf
Ralf schrieb:> Mmmh, okay. Das heisst, freopen() ruft _open() auf? Das sind Funktionen> welche ich bis dato noch gar nicht beäugt habe, weil ich bisher auch> kein OS oder Dateisystem verwende.
Mit dem ARM-GCC benutzt man auf Controllern (also arm-none-eabi)
typischerweise die newlib. Diese ist ein Konglomerat diverser
nicht-GPL-lizensierter (sondern liberalerer lizensierter)
Implementierungsteile, vorrangig aus dem BSD-Umfeld. Naturgemäß setzen
diese nun auf dem Posix-Syscall-Modell auf, denn das implementiert in
einem unixoiden System das zugrunde liegende OS bereits. Daher rufen
fopen() oder auch freopen() halt open() auf (je nach Gusto auch mal mit
einem vorangestellten Unterstrich, _open()), von denen man nun wiederum
Posix-Semantik erwartet. Die einzige Erweiterung gegenüber dem
Posix-Modell ist, dass (wie ich oben angedeutet habe) das f[re]open-Flag
"b" dort als O_BINARY mit nach unten weiter gereicht wird. Daher kann
man dieses Flag günstig auch in eigenen Implementierungen verwenden, um
bspw. im UART-Treiber eine Umwandlung von "\n" nach <CR><LF>
vorzunehmen. (Unter Posix würde man das ja über einen deutlich
aufwändigeren Terminaltreiber mit termios realisieren.)
Ähnlich bspw. ist das auch bei malloc() gelagert: die Implementierung
der Bibliothek ruft hier sbrk() (oder _sbrk()) auf, von der erwartet
wird, dass sie das "Datensegment" vergrößert. Dort kann man dann einfach
selbst implementieren, was auch immer man als Heap realisieren möchte.
Vielen Dank für die Erläuterungen. Sieht aus, als ob ich da noch viel zu
lernen hab :D
> Mit dem ARM-GCC benutzt man auf Controllern (also arm-none-eabi) typischerweise
die newlib.
Guter Hinweis mit newlib, ich werd mal in der Richtung nach Lektüre
gucken. Danke.
Grüße