Forum: Mikrocontroller und Digitale Elektronik printf Implementierung - reentrant


von Klaus B. (nuccleon)


Lesenswert?

Bin auf der Suche nach einer re-entranten Implementierung von printf. 
Google brachte bereits einige Ergebnisse.

http://www.sparetimelabs.com/tinyprintf/index.html
http://www.efgh.com/software/gprintf.htm

Hat jemand bereits Erfahrung mit diesen Implementierungen gemacht? Kennt 
jemand nocht weitere?

Wichtig ist mir in erster Linie die Performance - weniger die Codegröße. 
Das ganze soll auf einem ARM9 laufen.

Bin gespannt auf die Antworten

von Peter D. (peda)


Lesenswert?

Klaus B. schrieb:
> Bin auf der Suche nach einer re-entranten Implementierung von printf.

Willst Du etwa sowas erreichen:

"Text 1", "Text 2" ergibt:
"TexText 2t 1"

Ich hab sowas mal versehentlich gemacht, printf im Main und im 
Interrupt.
printf sollte sich tunlichst nicht selber unterbrechen.


Peter

von Klaus B. (nuccleon)


Lesenswert?

Genau das habe ich mit snprintf aus der DLIB von IAR erreicht ;-)
Ich verstehe ehrlich gesagt nicht weshalb printf nicht reentrant 
implementiert ist.

Ich verwende ein Multitaskingsystem wo einige Male ..printf verwendet 
wird.

Jetz bin ich eben auf der Suche nach ner thread sicheren 
Implementierung.

von Klaus W. (mfgkw)


Lesenswert?

Klaus B. schrieb:
> Genau das habe ich mit snprintf aus der DLIB von IAR erreicht ;-)
> Ich verstehe ehrlich gesagt nicht weshalb printf nicht reentrant
> implementiert ist.

Steht zumindest bei AVR in der Doku zu avrlibc:

printf(), printf_P(), vprintf(), vprintf_P(), puts(), puts_P()

Alters flags and character count in global FILE stdout. Use only in one 
thread. Or if returned character count is unimportant, do not use the 
*_P versions.

Note: Formatting to a string output, e.g. sprintf(), sprintf_P(), 
snprintf(), snprintf_P(), vsprintf(), vsprintf_P(), vsnprintf(), 
vsnprintf_P(), is thread safe. The formatted string could then be 
followed by an fwrite() which simply calls the lower layer to send the 
string.

Dementsprechend auch die Abhilfe: sprintf() verwenden und den String 
anderweitig zu stdout schaufeln.

Könnte hier doch ähnlich sein?

von Klaus W. (mfgkw)


Lesenswert?

Peter Dannegger schrieb:
> "Text 1", "Text 2" ergibt:
> "TexText 2t 1"

Die beiden Texte aus unterschiedlichen Threads?

Das ist ja eigentlich normales Verhalten, und hat mit den
üblichen reentrant-Problemen eigentlich nichts zu tun.

Wenn man eine Resource (stdout) von 2 Threads aus nutzt, würde
ich eigentlich nichts anderes erwarten.

Abhilfe wäre ein Thread, der durch Pipes o.ä. von den den
threads mit der Ausgabe gefüttert wird und z.B. anhand
von \n synchronisiert.

von Klaus B. (nuccleon)


Lesenswert?

Hm, das Problem ist, ich verwende snprintf und hab trotzdem kryptische 
Ausgaben :-)
Ich bin kurz davor jeden Aufruf von snprintf u.ä. mit ner Mutex zu 
schützen.

von Peter D. (peda)


Lesenswert?

Klaus B. schrieb:
> Ich verstehe ehrlich gesagt nicht weshalb printf nicht reentrant
> implementiert ist.

Das sagt einem schon die Logik. Informationskanäle dürfen generell nicht 
unterbrochen werden.
Dein Handy-Gespräch darf auch nicht unterbrochen werden, Ein Datei 
schreiben auf die Festplatte nicht mit ner anderen Datei gemischt werden 
usw.

Informationen müssen immer komplett gesendet werden, sonst verlieren sie 
ihren Sinn.
Sie müssen ihre Reihenfolge beibehalten.


Eine Ausnahme sind Fernsehsendungen, die dürfen unterbrochen werden 
(Werbung).


Peter

von Klaus B. (nuccleon)


Lesenswert?

Klaus Wachtler schrieb:
> Wenn man eine Resource (stdout) von 2 Threads aus nutzt, würde
> ich eigentlich nichts anderes erwarten.

Das ist klar. Nur hab ich den Eindruck dass sprintf ect mit 
irgendwelchen static oder globale Variablen / Buffern arbeiten :-(

z.B. Führt der folgender Code
1
char buffer[32];
2
sprintf(buffer, "%s %s", "Text 1", "Text 2");

zu folgender Ausgabe:
"TexText 2t 1".

:-(

Ich habe das Gefühl, dass wenn während der sprintf Ausführung ein 
Kontextwechsel stattfindet und sprintf innerhalb dieses Kontextes auch 
verwendet wird, die unterbrochene Ausführung von sprintf zu der 
Fehlerhaften Ausgabe führt. (Das war jetzt im grunde die definition von 
reentrant)

von Klaus B. (nuccleon)


Lesenswert?

Peter Dannegger schrieb:
> Das sagt einem schon die Logik. Informationskanäle dürfen generell nicht
> unterbrochen werden.

Hier geb ich dir natürlich Recht. Eine Ausgabe wie "TexText 2t 1" auf 
stdout ist erklärbar.

Der char buffer[32] jedoch gehört exklusiv einer Task!

Peter Dannegger schrieb:
> Eine Ausnahme sind Fernsehsendungen, die dürfen unterbrochen werden
> (Werbung).

:-) Sehr schön.

von Peter D. (peda)


Lesenswert?

Klaus B. schrieb:
> Ich verwende ein Multitaskingsystem wo einige Male ..printf verwendet
> wird.

Das Problem hat man oft, daß verschiedene Tasks etwas ausgeben sollen.
Ich teile dazu das LCD in Bereiche auf, jede Task hat ihr eigenes 
Fenster, wo sie nach belieben reinschreiben darf.
Sie tut das aber nicht direkt, sondern schreibt in einen SRAM-Bereich. 
Und dieser SRAM wird dann zyklisch an das LCD ausgegeben.
Jedes sprintf hat also seinen eigenen Pointer, wo es reinschreiben darf.


Peter

von Klaus B. (nuccleon)


Lesenswert?

Peter Dannegger schrieb:
> Jedes sprintf hat also seinen eigenen Pointer, wo es reinschreiben darf.

Genau so mache ich das auch.

Klaus B. schrieb:
> char buffer[32];
> sprintf(buffer, "%s %s", "Text 1", "Text 2");

buffer ist nicht global, sondern gehört genau einer Task (keine Sorge, 
buffer liegt nicht auf dem Stack).

Ich vermute sprintf verwendet irgendwelche globale bzw static Variablen 
(sowas wie errno).

Ich befürchte dass mit folgendem abgändertem Code die Probleme weg sind.
1
int save_sprintf ( char * str, const char * format, ... )
2
{
3
/* Pseudocode:
4
5
   sema_take();
6
   sprintf();
7
   sema_give();
8
*/
9
}
10
11
char buffer[32];
12
save_sprintf(buffer, "%s %s", "Text 1", "Text 2");

Ich möchte aber vermeiden, bei jedem sprintf auch noch ne Mutex zu 
nehmen und wieder geben. Deshalb suche ich eine reentrante 
implementierung von sprintf :-)

von Matthias H. (experimentator)


Lesenswert?

Hallo Klaus,

ich habe zwar keine Ahnung von ARM9, aber Probleme mit Reentranz sind 
nach meiner Erfahrung oft eher nicht reproduzierbar (das macht sie ja 
gerade so tückisch!). Wenn Du regelmäßig geshredderte Ausgaben hast, 
dann ist es möglich, daß Du ein ganz anderes Problem hast (falscher 
Formatstring, falsche Datenübergabe, Zeigerfehler, überschriebene 
Daten).

Zu Deinem Beispiel von 21:11:
Wer erzeugt denn Interrupts, die einen Taskwechsel erzeugen können und 
wer behandelt die? Ist vielleicht ein Interrupthandler nicht sauber beim 
Speichern und Restore des Zustands der CPU?
Oder versucht die Library, den Aufruf zu parallelisieren und versemmelt 
es? Versehentlich Single-threaded-Version der Library gelinked?

von Klaus B. (nuccleon)


Lesenswert?

Matthias H. schrieb:
> Versehentlich Single-threaded-Version der Library gelinked?

So weit ich weiß liefert IAR keine multithread versionen der library 
mit. Werd ich morgen überprüfen.

Im Interrupt verwende ich kein printf o.ä. Allerdings in 
unterschielichen Tasks.

Das Problem ist in der Tat sehr schwer zu reproduzieren. Ich kann 
allerdings beobachten, dass dieses Problem umso häufiger auftritt, je 
höher die Systemlast (und der damit verbundenen häufigeren 
Kontextwechseln) ist, weshalb ich auf ein reentrancy Problem tippe.

Ich verstehe allerdings nach wie vor nicht, weshalb sprintf nicht 
reentrant ist. Schließlich wird ein "Arbeitsbuffer" mit übergeben. Ich 
kann mir nicht vorstellen welche Art von globalen Variablen bei sprintf 
benötigt werden, die solch einen Effekt verursachen. (Errno ist global, 
wird aber meines Wissens von sprintf nicht gesetzt!? Irgendwelche Zeiger 
zum positionieren im String? Indices?)

Ich werd es auch nicht rausfinden, da ich bezweifle dass IAR die sourcen 
Ihrer stdio lib herausrücken ;-)

Matthias H. schrieb:
> Wenn Du regelmäßig geshredderte Ausgaben hast,
> dann ist es möglich, daß Du ein ganz anderes Problem hast (falscher
> Formatstring, falsche Datenübergabe, Zeigerfehler, überschriebene
> Daten).

Eher unregelmäßig, also nicht reproduzierbar...

von Klaus W. (mfgkw)


Lesenswert?

Klaus B. schrieb:
> Ich verstehe allerdings nach wie vor nicht, weshalb sprintf nicht
> reentrant ist.

Ich sehe auch keinen zwingenden Grund.

von Peter D. (peda)


Lesenswert?

Schreib mal ein Programm ohne sprintf und eins mit sprintf und 
vergleiche die SRAM-Belegung.


Peter

von Klaus W. (mfgkw)


Lesenswert?

Klaus B. schrieb:
> Errno ist global,
> wird aber meines Wissens von sprintf nicht gesetzt!?

Stimmt.

von holger (Gast)


Lesenswert?

>Ich kann mir nicht vorstellen welche Art von globalen
>Variablen bei sprintf benötigt werden, die solch einen
>Effekt verursachen.

Eher lokale Variablen, und damit hast du die Arschkarte
gezogen wenn du sprintf() nicht atomar aufrufst. sprintf()
wird ja nicht als Kopie verwendet sondern als eine Funktion
im Speicher. sprintf() in mehrere Tasks zu legen ist genau das
gleiche wie sprintf() in einem oder mehreren Interrupts
aufzurufen.

von chris. (Gast)


Lesenswert?

Diese Variante funktioniert ohne statischen Buffer,
http://www.menie.org/georges/embedded/printf-stdarg.c

Das Problem, daß der Interrupt das andere Printf unterbricht, und
eine Zeile inmitten deinem anderen Autput hast, wirst du trotzdem haben.

von Klaus W. (mfgkw)


Lesenswert?

holger schrieb:
> sprintf()
> wird ja nicht als Kopie verwendet sondern als eine Funktion
> im Speicher.

Du bist mir hoffentlich nicht böse, wenn ich davon kein Wort
verstanden habe?

von Klaus W. (mfgkw)


Lesenswert?

chris. schrieb:
> Das Problem, daß der Interrupt das andere Printf unterbricht, und
> eine Zeile inmitten deinem anderen Autput hast, wirst du trotzdem haben.

Das wird er nicht haben, weil er ja nicht printf, sondern s(n)printf 
benutzt.

von holger (Gast)


Lesenswert?

>> sprintf()
>> wird ja nicht als Kopie verwendet sondern als eine Funktion
>> im Speicher.

>Du bist mir hoffentlich nicht böse, wenn ich davon kein Wort
>verstanden habe?

Nein. Ach komm Klaus das hast du verstanden.

Was er nicht gesagt hat ist wie sein Programm auf dem ARM
aussieht. Auf einem ARM9 kann man ja z.B. Linux laufen
lassen. Da kann man einzelne Programme kompilieren und
jedes davon benutzt sprintf(). Jedes Programm hätte dann
eine Kopie von sprintf() und alles ist gut. Wenn er nur
ein Programm benutzt und sprintf() dann in mehrere Tasks
legt dann gibt es auch nur ein sprintf().

von Klaus W. (mfgkw)


Lesenswert?

Jetzt (glaube ich) weiß ich, was du meinst.
Es geht dir wohl nicht um die Funktion (als Code irgendwo),
sondern um ihre Variablen.

Jedes halbwegs moderne OS, sogar Windows, nutzt Code mehrfach.
Und zwar quer über alle Prozesse und Threads, solange die
Funktionen in einem shared object bzw. einer DLL liegen.
Das macht auch keine Probleme - alleine deshalb, weil auf
den Code ja nur lesend zugegriffen wird.

Trotzdem bekommt die Funktion in jedem Thread und in jedem
Prozess entsprechend viele Instanzen seiner lokalen
Variablen (automatische Variablen, die auf dem Stack).
Auch das klappt (auch wenn es keine "Kopien" voneinander
sind).

Schwierig wird es, wenn eine Funktion globale Variablen nutzt
oder lokale statische.

Dann tritt der Unterschied zwischen Prozessen und Threads
auf, den du vrmtl. meinst.
Hat man mehrere Prozesse mit je einem Thread, dann geht
alles gut, weil die statischen Variablen einmal je Prozess
existieren.
Bei mehreren Threads in einem Prozess kneift es dann.
Und genau das ist es, was nicht rentrante Funktionen
ausmacht: sie haben globale oder statische Variablen.

Soweit nichts neues.

Die Frage ist jetzt, wieso sprintf solchen statischen
Speicher braucht. Dafür gibt es keinen mir ersichtlichen
Grund. Bspw. strtok kommt gemäß seiner Definition nicht
ohne aus, weil es sich von Aufruf zu Aufruf etwas merken
muß.
sprintf sollte es aber eigentlich schaffen, wenn nicht
jemand Quatsch programmiert hat.

von Mark B. (markbrandis)


Lesenswert?

Klaus Wachtler schrieb:
> wenn nicht jemand Quatsch programmiert hat.

Ich hab gehört das kommt gar nicht so selten vor wie man denkt :-)

von Klaus B. (nuccleon)


Lesenswert?

holger schrieb:
> Nein. Ach komm Klaus das hast du verstanden.

Wenn ich ehrlich bin, nein :-)
Lokale Variablen landen schließlich auf dem Stack. Jede Task hat ihren 
eigenen Stack. Wo sollte also das Problem sein?

Klaus Wachtler schrieb:
> Die Frage ist jetzt, wieso sprintf solchen statischen
> Speicher braucht. Dafür gibt es keinen mir ersichtlichen
> Grund. Bspw. strtok kommt gemäß seiner Definition nicht
> ohne aus, weil es sich von Aufruf zu Aufruf etwas merken
> muß.
> sprintf sollte es aber eigentlich schaffen, wenn nicht
> jemand Quatsch programmiert hat.

Ja genau, jetzt sind wir beeinander :-)

chris. schrieb:
> Diese Variante funktioniert ohne statischen Buffer,
> http://www.menie.org/georges/embedded/printf-stdarg.c

Werde ich mir gleich mal ansehen, danke.

von Matthias H. (experimentator)


Lesenswert?

Hallo Klaus,

bzgl. der Interrupts meinte ich nicht einen Aufruf von printf, sondern 
daß vielleicht ein  Interrupthandler entweder nicht alle von printf 
benutzten Register sichert oder wild im Speicher herumschreibt.

Speicherkorruption könnte theoretisch auch allgemein eine Ursache für so 
ein Verhalten sein, also daß vielleicht ein möglicherweise völlig 
anderer Programmteil den von der Implementation von printf benutzten 
Speicher kaputtschreibt.

von Peter (Gast)


Lesenswert?

Matthias H. schrieb:
> sondern
> daß vielleicht ein  Interrupthandler entweder nicht alle von printf
> benutzten Register sichert oder wild im Speicher herumschreibt.

ein Interrupthandler muss alle Register wieder herstellen die er selber 
verwendet sonst könnte man kaum sinnvoll programmieren, ausser man 
reserviert Register nur für die Interupts aber das ken ich nur von ASM 
programmen.

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Klaus Wachtler schrieb:
> Die Frage ist jetzt, wieso sprintf solchen statischen
> Speicher braucht. Dafür gibt es keinen mir ersichtlichen
> Grund.

Den Grund stelle ich mir ganz einfach vor. Bei einem 
Mikroporozessor-Programm (kein Multithreading/-tasking) geht alles 
nacheinander. Printf und Konsorten haben einen(!) lokalen Zielzeiger. 
printf wird, abgesehen von INT, komplett bearbeitet. Damit spart man 
Speicher. Aus diesem Grund kann printf auch nicht alle Ausgabeformate 
umsetzen. Dafür gibt es dann andere Bibliotheken, die man bei Bedarf 
verwendet.

Um eine threadsichere Ausgabe zu haben, muss eine andere Bibliothek her, 
oder man schreibt sich sein printf selber. In diesem Fall mit alle 
Variablen auf dem Stack (global oder threadlokal).

Für mich also einfach eine Frage der effizienz.

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Peter schrieb:
> ein Interrupthandler muss alle Register wieder herstellen die er selber
Du meinst sollte

> verwendet sonst könnte man kaum sinnvoll programmieren,
Manche machen das nicht und wundern sich später.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.