Hallo,
in einem Embedded-Betriebssystem gibt es eine API-Funktion, die Text auf
einer Debug-Schnittstelle (RS232) ausgeben kann. Damit das ganze
möglichst komfortabel abläuft, bekommt der Entwickler von der OS-API
eine Art printf() an die Hand gegeben, mit dem er seine Debugausgaben in
gewohnter Weise formatieren kann. Das klappt soweit auch alles
wunderbar. Der Applikationsentwickler kann überall und jederzeit eine
Debug-Ausgabe erzeugen, z.B. auch innerhalb einer ISR. Da gibt es
natürlich ein großes Problem: Die Performance wird unterirdisch
schlecht,
wenn...
1. der String zunächst aufwändig mit printf() formatiert werden muss.
2. der formatierte String laanngsam über die Serielle rausklickert.
Problem 2 ist kein Problem, denn ich kann ja einfach statt printf() ein
sprintf() nutzen und die gepufferten und formatierten Daten zu einem
späteren Zeitpunkt von einen niederprioren Debug-Task über die Serielle
rausschreiben lassen, wenn das Betriebssystem gerade keine anderen
wichtigen Dinge zu tun hat und/oder einen RS232-Treiber schreiben, der
über DMA die Daten selbständig rausschaufelt.
Der Grund meines Postings ist Problem 1:
Wie kann ich erreichen, dass die Formatierung erst durch den Debug-Task
ausgeführt wird und dieser sich aus einem Puffer den Format-String und
die va_list nimmt und dann erst printf() anwendet und nicht etwa
innerhalb einer ISR oder einem zeitkritischen Task? Also quasi so, dass
erstmal alle relevanten Daten für die Debugausgabe (format + va_list) in
der zeitkritischen Task oder ISR nur zwischengepuffert werden und zu
einem späteren Zeitpunkt vom Debug-Task mit printf ausgewertet werden.
Ich hab bis jetzt keine Möglichkeit gefunden, die Argumente für printf
irgendwie zwischenzuspeichern und zu einem beliebigen späteren Zeitpunkt
zu verwenden? Ich glaube ich suche so eine Art
1
// Dumpen einer va_list in einen Puffer damit das Programm schnell weiterlaufen kann
2
charbuffer[100];
3
charoutputbuffer[100];
4
Dump_va_list(buffer,va_listargs)
5
// (...) viele Instruktionen später in einem weniger zeitkritischen Moment
6
// va_list aus einem Puffer heraus erzeugen
7
Get_va_list(char*buffer,va_listargs)
8
// Gepuffertes format + va_list auf printf anwenden und formatierten Text in outputbuffer schreiben.
gar nicht (denke ich)
Das Problem ist ja wenn jemand soetwas macht
char* s = "irgend ein string"
printf("%s", s );
und die willst jetzt die parameter zwischen speicher, dann musst du ja
auch s komplett kopieren, denn es könnte ja später nicht mehr da sein.
Dafür musst du aber erstmal den Typvon s ermitteln. Dafür musst du aber
den Formatstring parsen.
ist ein Problem: du hast zweimal denselben wert im va_args, einmal musst
du aber ein strcopy (Wohin?) laufen lassen, einmal nur einen Pointer
kopieren.
d.H. dein Dump_va_list muss den Format-String kennen, parsen, auswerten.
Damit ist aber der Vorteil gegenüber einem sprintf nur noch minimal,
wenn überhaupt.
Edit: Peter war schneller ;)
Alexander I. schrieb:> Gibt's da was?
Nein. Du müsstest den Formatstring dazu parsen und die va_list
entsprechend auswerten. Die Liste selbst wird beim Beenden der Funktion,
in der sie erzeugt wurde, ungültig. Um den Inhalt der Liste dumpen zu
können, bräuchtest du aber Informationen über die Elemente, die bekommst
du bei printf() aber nur durch den Formatstring. Wenn du den aber
sowieso parst, kannst du auch gleich die Formatierung fertig machen.
Ich würde in den zeitkritischen Pfaden auf die volle Flexibilität von
printf() verzichten und mir Debug-Funktionen schreiben, die einen
Formatstring und fest (z.B.) immer drei Integer-Argumente erhalten (wenn
eins nicht gebraucht wird dann eben 0 angeben):
Der Formatstring entspricht printf(), es können aber eben nur eine
bestimmte Anzahl Parameter mit vorausbestimmten Typen verwendet werden.
Diese Parameter können dann ohne weiteres für später abgespeichert
werden. Nur Vorsicht bei etwaigen Zeigern auf lokale Variablen!
Andreas
Zu Peter:
das ist das eine, das andere Problem ist, daß man eine
irgendwie gerettete Parameterliste nicht wieder zu einer
va_list zusammenbauen kann, zumindest kenne ich keinen
halbwegs legalen Weg.
Zumal der Nutzen auch nicht besonders groß wäre, weil
das Parsen und Retten kaum weniger aufwändig wäre als
gleich printf laufen zu lassen.
Ich würde in so einem Fall eher auf printf verzichten.
Unportable Methoden, die auf eine bestimmte Umgebung zugeschnitten
sind, könnte ich mir in gewissem Maße vorstellen: man müsste die
Parameterliste bspw. aus dem Stack in einen Speicherbereich "zur
Seite" legen, wobei man halt so viel auf Verdacht kopieren muss,
dass der maximal mögliche Fall abgedeckt ist.
Dazu muss man aber zuvor erst einmal analysieren, wie die betreffende
C-Implementierung derartige Listen überhaupt hinterlegt.
Wirklich schön ist das trotzdem nicht.
Alexander I. schrieb:> Ich hab bis jetzt keine Möglichkeit gefunden, die Argumente für printf> irgendwie zwischenzuspeichern und zu einem beliebigen späteren Zeitpunkt> zu verwenden?
Ich denke eine va_list kann man außer mit Assembler-Tricksereien nicht
einfach so zwischenspeichern. Je nach ABI kann ein Teil der Parameter in
Registern übergeben werden. Um einen Eindruck von dem, was va_start(),
va_end() und va_arg() machen kannst Du ja mal einen Blick in die
aktuelle Manualpage zu stdarg werfen.
Ein möglicher Ausweg wäre, den Formatstring "grob" zu parsen und die
Argumente in einen "Variant"-Datentyp zu kopieren. In C-ähnlichem
Pseudocode meine ich etwa sowas:
enum {
VT_ptr,
VT_int,
VT_sonstwas
};
struct VARIANT {
int type; /* One of the VT_xxx constants */
union {
int ival;
char *ptrval;
type *someval;
};
};
struct DELAYED_PRINTF_DATA {
char *fmt;
ARRAY_OF_VARIANT args;
};
void fast_debug_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
DELAYED_PRINTF_DATA data;
while (*fmt) {
VARAINT current_arg;
if (*fmt == '%') {
/* Format spec can be e.g. "%02d". Skip the "%02" stuff to
get to the conversion specifier (the 'd' in this example) */
skip_formatting_qualifiers;
if (*fmt == 'd' || *fmt == 'x' || *fmt == ...) {
current_arg.type = VT_int;
current_arg.ival = va_arg(ap, int);
VARIANT_ARRAY_append(data.args, current_arg);
} else if (*fmt == 's') {
currrent_arg.type = VT_ptr;
current_arg.ptrval = va_arg(ap, char *);
VARIANT_ARRAY_append(data.args, current_arg);
} else ........
};
fmt++;
};
DELAYED_PRINTF_DATA_add_to_round_robin_list(&data);
return;
};
Stephan
Das sehe ich wie meine Vor-"schreiber". Der Aufwand für die Aufbereitung
ist genauso gross, wie der, wenn Du printf/sprintf direkt aufrufst.
Also kannst Du grundsätzlich nur die Ausgabe verzögern.
Sowas in der Richtung habe ich schon geahnt. Vor allem eure Beispiele
mit den Zeigern sind einleuchtend...
Bisher hab ich tatsächlich ein paar vorgefertigte Funktionen definiert
mit ein paar Integer-Platzhaltern etc. damit man ohne format-parsen
auskommt.
Ich denke, ich werde die printf-Funktionalität weiterhin lassen aber
einen deutlichen Hinweis setzen, dass dieser ungewollte Verzögerungen
verursachen kann. Da es neben mir nur 1-2 andere Entwickler gibt, die
das nutzen, kann ich die Echtzeit-Verantwortung auch an die Entwickler
abgeben...
Wo wir gerade beim Thema printf sind: Gibt's ein <xy>sprintf, wo man
eine Puffer-Größe mit angibt und der Rest einfach abgeschnitten wird
bevor es einen Pufferüberlauf gibt?
z.B.
Alexander I. schrieb:> Gibt's ein <xy>sprintf, wo man> eine Puffer-Größe mit angibt und der Rest einfach abgeschnitten wird> bevor es einen Pufferüberlauf gibt?
ja gibt es, snprintf
Ich habe diese Funktion mit Abstrichen implementiert.
Tatsächlich ist das Umkopieren und Parsen des Formatstrings auf meine
Plattform (QNX 6.3/PPC) sehr viel schneller als die direkte Fomratierung
mit snprintf.
Die Einzelzeile benötigt etwa 200 Mikrosekunden für die Formatierung.
Die Umspeicherung benötigt weniger als 20 Mikrosekunden.
Die Ausgabe erfolgt in einem zweiten Thread. Dadurch werden die
Logausgaben serialisiert ohne das andere Threads lange Warten müssen und
die Zeit für das Schreiben auf Ramdisk (oder Syslog) fällt dann an, wenn
der niedrigpriore Prozess die Zeit bekommt.
Bei mehreren Cores kann man im Echtzeitthread ohne ein zu enges
Formatierungskorsett Meldungen rausschicken, die dann halt ein paar
hundert Millisekunden später raus geschrieben werden. Das ist bei der
Fehlersuche sehr hilfreich, da im Falle des Falles Material zu dem was
geschehen ist vorliegt. Oft lassen sich halt Ereignisse am Testplatz
nicht hinreichend gut nachstellen. BTW: Klick-Klick-Debuggen ist nicht.
Das FPGA will 2000mal pro Sekunde ausgelesen werden, sonst gibt es halt
Fehlerflags und andere annormale Ereignisse.
Die Abstriche: Da sich kein va_args generieren läßt, können nur
32Bit-Werte konvertieren. Schon bei 64Bit-Integer muß ein
plattformspezifischer Trick helfen. Double und Float funktionieren
nicht, da sie in anderer Form übergeben werden.
Die va_list ist letztendlich ein Stackformat. Mit (halbwegs) legalen
C-Mitteln kommt zur Sicherung nur ein setjmp()/longjmp() in Frage, dazu
müssten aber auch alle über Zeiger referenzierten Variablen gesichert
werden (sonst sind die möglicherweise anschließend verschwunden).
Ich will nicht ausschließen, daß es geht, aber ich möchte das nicht
programmieren müssen.
Alexander I. schrieb:> in einem Embedded-Betriebssystem gibt es eine API-Funktion, die Text auf> einer Debug-Schnittstelle (RS232) ausgeben kann. Damit das ganze> möglichst komfortabel abläuft, bekommt der Entwickler von der OS-API> eine Art printf() an die Hand gegeben, mit dem er seine Debugausgaben in> gewohnter Weise formatieren kann. Das klappt soweit auch alles> wunderbar. Der Applikationsentwickler kann überall und jederzeit eine> Debug-Ausgabe erzeugen, z.B. auch innerhalb einer ISR.
Ich würde erstens die Benutzung des Debug-printf's in der ISR
unterbinden. Wenn eine Debugausgabe wirklich gebraucht wird, dann:
- In der ISR die auszugebene Variablen zwischenspeichern und ein Flag
setzen
- In der main-Loop (oder in einem separaten Task/Thread) Flag abfragen
und erst dann ausgeben.
Zweitens kann man sich fragen ob eine printf()-ähnliche Debugausgabe im
embedded-Umfeld überhaupt notwendig ist. Ich bin in vielen Projekten
schon wunderbar zurechtgekommen mit fest vordefinierte Debugmeldungen.
Auf der serielle Console wurden nur (binär) Task- oder Modul-Id,
Funktion Id und Funktion sub-Id und ggf noch eine Wert mit feste Größe
(zB 4 Bytes) übertragen.
Am PC lief dann ein kleiner Skript, dass diese Werte ausgelesen hat und
benutzerfreundlich dargestellt. Damit wird der ganze printf()-Kram am uC
überflüssig.
Also etwa so:
Rufus Τ. F. schrieb:> Das Thema war vor fünfeinhalb Jahren aktuell. Ob va_arg & Co. so lange> mitmachen?
Hm, könnte man Threads nicht irgendwann (zB. nach einer gewissen Zeit
von Inaktivität, oder nach max 1 Jahr) automatisch schliessen? Das
würde solche Ausgrabungen deutlich einbinden :-I
EDIT:
> Nochmal: Alexander schrieb das vor fünf(!) Jahren!
Ja ja, ist mir erst nach deiner Post aufgefallen. Mea maxima culpa etc
:-)
Jörg W. schrieb:> Nochmal: Alexander schrieb das vor fünf(!) Jahren!
Also, irgendwie verstehe ich euch (Moderatoren) nicht:
1) Wenn ihr nicht wollt, dass man Leichen ausgräbt, dann verhindert dass
doch einfach! 6 Monate, 1 Jahr, 2 Jahre nach dem letzten Post ist der
Thread dann einfach Read-Only.
2) Da schreibt jemand eine "neue" Lösung zu einem älteren Problem. Warum
sollte dass nicht interessant sein? Sicher nicht für den ursprünglichen
TO. Dann kommen noch neue Informationen von Johann und Eric. Finde ich
gut!
Mal wieder was über den gcc gelernt.
PS: Ich bin sonst wirklich der Meinung, dass ihr einen tollen Job macht!
Aber das Thema Leichenfledderei von Threads ist einfach nur inkonsequent
umgesetzt.
Klaus R. schrieb:> Da schreibt jemand eine "neue" Lösung zu einem älteren Problem. Warum> sollte dass nicht interessant sein?
Deshalb haben wir sie ja stehen lassen.
Aber es ist jetzt nicht sinnvoll, dem TE noch vermeintlich gute
Ratschläge auf das Problem zu geben, welches er vor 5 Jahren hatte.