Hi,
ich habe ein Multitasking OS auf einem Atmega 644 implementiert, nun
habe ich allerdings ein Problem an dem ich hänge, avr-gcc optimiert mir
meine Tasks, die im System laufen sollen weg, bzw. teile davon.
1
unsignedchar*shared;
2
3
unsignedchar*control;
4
5
unsignedchar*test;
6
7
8
9
10
voidtask1()
11
12
{
13
14
control=os_calloc(7);
15
16
shared=os_calloc(20);
17
18
19
20
while(1);
21
22
}
23
24
25
26
27
voidtask2()
28
29
{
30
31
32
test=(unsignedchar*)" Test ";
33
34
35
36
37
38
while(!shared);
39
40
41
42
while(1)
43
44
{
45
46
os_cWrite(shared,test,6);
47
48
os_cRead(shared,control,6);
49
50
control[6]=0;
51
52
lcd_writeString((char*)control);
53
54
delayMs(os_taskOutputDelay);
55
56
}
57
58
}
Die Tasks werden vom OS Scheduler gestartet und gestoppt, nun das
Problem, task2 soll so lange warten, bis Task1 gemeinsamen Speicher
angefordert hat und anschließend immer wieder den Speicher beschreiben
und auslesen
Leider macht die Codeoptimierung nur murks, das while(!shared) wird sehr
seltsam behandelt:
1
while (!shared)
2
+000007B0: 91800177 LDS R24,0x0177 Load direct from data space
3
+000007B2: 91900178 LDS R25,0x0178 Load direct from data space
4
+000007B4: 9700 SBIW R24,0x00 Subtract immediate from word
5
+000007B5: F159 BREQ PC+0x2C Branch if equal
6
7
....
8
9
10
+000007E1: CFFF RJMP PC-0x0000 Relative jump
Naja, also komm ich automatisch immer auf diese Stelle, wo er nicht
wegspringt mehr in dem Task. Nun hab ich mir gedacht, okay machste mal
die shared variable volatile, dann sollte es ja klappen... falsch
gedacht, dass klappt auch nicht, der Code ist ähnlich toll. Nun
erweitere ich den Code der while(!shared); um ein asm volatile("nop");
der Code wird fast wieder genauso gut, die Sprungbedinnung der Schleife
ist nun nicht mehr zu einer Stelle wo nur wieder zu sich selbst
gesprungen wird, sondern (die bed. wird 1 mal richtig geprüft (aber da
ist in den seltesten Fällen schon die Speicherallokierung fertig) zu
einer Abfolge von nop und rjmp pc-0x0001. Also wieder Endlosschleife
ohne meine Schleifenbedinnung nochmals zu Prüfen.
1
27: while (!shared)
2
+000007B0: 91800177 LDS R24,0x0177 Load direct from data space
3
+000007B2: 91900000 LDS R25,0x0000 Load direct from data space
4
+000007B4: 9700 SBIW R24,0x00 Subtract immediate from word
5
+000007B5: F411 BRNE PC+0x03 Branch if not equal
6
28: asm volatile("nop");
7
+000007B6: 0000 NOP No operation
8
+000007B7: CFFE RJMP PC-0x0001 Relative jump
9
36: delayMs(os_taskOutputDelay);
10
+000007B8: 91C0010A LDS R28,0x010A Load direct from data space
Wieso kann es sein, dass avr-gcc denkt, dass die Variable niemals einen
Wert bekommt (ich meine, wieso sollte er sonst den Sprung zum
nachfolgendem Code wegoptimieren?). Stell ich die Optimierung aus, läuft
der Code, wie er sollte, allerdings ist das nicht sinnvoll für den
restlichen Code und irgendwie ist dass ja auch nicht der Sinn der Sache.
Kann es am os_calloc liegen? Das avr-gcc denkt, da kommt kein Wert
zurück? Oder liegt es daran, weil er keine Codezeile findet wo die
Funktionen explizit aufgerufen werden, diese werden nur durch eine ISR
per reti aufgerufen.
Ich würde mich freuen, wenn ihr mir helfen könntet, btw, ich nutze AVR
Studio mit WinAVR 20080610
Danke
!shared
Geht so nicht(hat der compiler nicht gewarnt?).
Zeiger mit NUL initialisieren und dann (shared==NUL).
Oder so in der Art.
Bin nicht so der C Crack.
Das Problem ist aber weiterhin existent, auch mit volatile, ich hab nun
die ganze Nacht dran gesessen und versucht herauszufinden, wieso dieser
blöde compiler mir den Code vermurkst und ich komm einfach nicht hinter
das Geheimniss.
auf (int) casten bringt auch nichts, hab ich alles schon versucht.
Argh, hier ist das Ziel volatile, nicht aber der Pointer selbst, und der
wird doch schließlich getestet.
Also:
1
unsignedchar*volatileshared;
> Wieso kann es sein, dass avr-gcc denkt, dass die Variable niemals einen> Wert bekommt
Weil der Compiler nichts von Tasks weiß. Er sieht nur den linearen
Codeablauf innerhalb der Funktion und dort wird shared in der
while-Schleife nicht verändert.
> wieso dieser blöde compiler mir den Code vermurkst
Ja, ja, immer sind die anderen schuld.
Na klar sind die anderen schuld! so was blödes aber auch, ich wusste
echt nicht, dass man volatile so benutzen kann, ich programmiere erst
seit ein paar Wochen in C.
Nun, noch eine Frage, kann ich irgendwie erzwingen, dass die variablen
die z.b. mit malloc benutzt werden das volatile richtig bekommen, auch,
wenn sie falsch deklariert worden sind wie "unsigned char * shared",
z.b. dass der Compiler trotzdem weiß, dass diese nicht wegzuoptimieren
ist? Wieso könnte man fragen... z.b. damit das niemand falsch macht.
Jan Sagichnicht wrote:
> ich programmiere erst seit ein paar Wochen in C.
Da ist ein Multitasking OS dann aber ein ziemlich ehrgeiziges Projekt.
Wenn du dich da mal nicht übernimmst.
> Nun, noch eine Frage, kann ich irgendwie erzwingen, dass die variablen> die z.b. mit malloc benutzt werden das volatile richtig bekommen, auch,> wenn sie falsch deklariert worden sind wie "unsigned char * shared",> z.b. dass der Compiler trotzdem weiß, dass diese nicht wegzuoptimieren> ist? Wieso könnte man fragen... z.b. damit das niemand falsch macht.
Erzwingen kannst du es nicht. Du könntest aber z.B. in einer
Header-Datei eigene Typen definieren, und dann den Benutzern deines OS
sagen, dass sie die verwenden sollen.
Hallo,
ich arbeite am gleichen Projekt (Uni) und habe das gleiche Problem.
Mir ist durch eure Erklärungen klarer geworden, warum das ganze nicht
funktioniert.
Die einzige Frage die wieso es andere Leute/Studenten gibt, die die
gleichen Funktionen implementieren müssen (natürlich jeder wie er will
und kann) und dieses Problem bei den gleichen Tasks nicht haben. (also
ohne unsgn. char * volatile shared)
Wenn etwas "wegoptimiert" wird, dann doch überall und nicht mal hier
oder mal da.....
Da das ganze aber auch bei uns/mir laufen sollte, frage ich mich was man
den anders machen kann um diesen Fehler zu vermeiden.
Gruß Tim
Deklarationen mit "volatile" drin werden etwas intuitiver, wenn man bei
der Deklaration das "*" in einem typedef versteckt:
typedef unsigned char * uchar_ptr;
volatile uchar_ptr shared;
ist das gleiche wie
unsigned char * volatile shared;
Du kannst natürlich auch gleich schreiben:
typedef unsigned char * volatile uchar_ptr;
uchar_ptr shared;
Tim wrote:
> Wenn etwas "wegoptimiert" wird, dann doch überall und nicht mal hier> oder mal da.....
So einfach ist es nicht.
Aus
while (shared == 0)
;
wird ohne volatile eine Totschleife, weil der Compiler weiss das sich
unterwegs nichts ändert.
Anders sieht es bei
while (shared == 0)
printf("waiting\n");
aus, weil der Compiler hier davon ausgehen muss, dass die ihm inhaltlich
nicht bekannte Funktion printf() die globale Variable "shared"
möglicherweise ändert.
Hallo,
erstmal Danke für die schnelle Antwort.
Vielleicht habe ich das Problem nicht genug konkretisiert....
Wir müssen das ganze Programmieren im Zuge eine Praktikums und der Code
wird durch TestTasks getestet...auf diese Tasks haben wir keinerlei
Einfluss....
sie müssen nur durchlaufen....
wenn sie das nun bei "vielen" anderen tun und bei uns nicht...stellt
sich ja die Frage warum es das bei uns nicht tut...
Jetzt war die Frage, ob es Gründe für dieses Verhalten gibt, bei einem
Programm ...und ob man darauf Einfluss nehmen kann.
Gruß Tim
EDIT: (da ich die zweite Antwort erst nachher gelesen habe)
Bei anderen Studenten mit den gleichen Funktionen, aber anders
implementiert läuft der Code aber genauso durch !
Das ist das was ich einfach nicht verstehen kann.
Gruß Tim
Tim wrote:
> wenn sie das nun bei "vielen" anderen tun und bei uns nicht...stellt> sich ja die Frage warum es das bei uns nicht tut...>> Jetzt war die Frage, ob es Gründe für dieses Verhalten gibt, bei einem> Programm ...und ob man darauf Einfluss nehmen kann.
Zum einen schreibt ihr ja nicht alle 100%ig den gleichen Code.
Zum anderen kann natürlich auch fehlerhafter Code zufällig meistens
richtig funktionieren.
WENN der Testtask aufgrund eines fehlenden volatile nicht richtig läuft,
is das nunmal ein Fehler im Task. Ihr könnt doch nicht mit eurem OS
sicherstellen, dass fehlerhaft programmierte Tasks trotzem richtig
ausgeführt werden :)
Aber im Grunsatz habe ihr natürlich Recht.
So einen Fall kann man nicht abfangen, man muss ja nicht falschen Code
zum laufen bringen (hoffe ich)....
Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft
und bei uns nicht.
Natürlich haben die nicht den gleichen Code, aber der Fehler des
"wegoptimierens" müsste doch auch bei denen bestehen bleiben....
Das ist mein Verständnisproblem :)
Trotzdem natürlich Danke für die Hilfen...
Gruß Tim
A. K. wrote:
> Anders sieht es bei> while (shared == 0)> printf("waiting\n");> aus, weil der Compiler hier davon ausgehen muss, dass die ihm inhaltlich> nicht bekannte Funktion printf() die globale Variable "shared"> möglicherweise ändert.
Schlechtes Beispiel: das Verhalten von printf() darf dem Compiler
sehr wohl bekannt sein (sofern er in einem "hosted environment"
arbeitet). Beispielsweise ersetzt der GCC ein printf("Hi!\n")
locker durch puts("Hi!"). Auch die Annahme, dass der Compiler nichts
über die Interna einer externen Funktion weiß (und daher den Zugriff
auf "shared" nicht weglassen darf) geht spätestens dann den Bach
runter, wenn man mit -fwhole-program --combine eine globale Optimierung
über alle Quelldateien (die dann auf einer Kommandozeile stehen müssen)
erreichen möchte.
> Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft> und bei uns nicht.> Natürlich haben die nicht den gleichen Code, aber der Fehler des> "wegoptimierens" müsste doch auch bei denen bestehen bleiben....
Ja, aber dieser Fehler führt ja nicht in jedem Fall zu fehlerhaften
Verhalten. Wenn an der Stelle mit der while-Schleife shared bereits
ungleich Null ist, dann funktioniert der Code ja trotz des Fehlers. Und
ob shared dort Null ist oder nicht, hängt wiederum davon ab, in welcher
Reihenfolge und mit welcher Zeitscheibengröße die Tasks ausgeführt
werden.
Jörg Wunsch wrote:
> Schlechtes Beispiel: das Verhalten von printf() darf dem Compiler> sehr wohl bekannt sein
Ja, er kann es wissen. Ich weiss auch dass ein globaler Optimizer das
diesbezügliche Verhalten aller Funktionen vom gesamten Quellcode kennen
kann. Nur wollte ich Tim nicht gleich erschlagen. Mir ging es darum, ein
einfaches Beispiel zu bringen, warum 2 Leute bei gleichen Problem zu
unterschiedlichem Verhalten kommen können.
A. K. wrote:
> einfaches Beispiel zu bringen, warum 2 Leute bei gleichen Problem zu> unterschiedlichem Verhalten kommen können.
Manche übersetzen auch (effektiv) mit -O0 ;-)
Tim wrote:
> Mich wundert es einfach nur, das es bei 2 anderen "Teams" einfach läuft> und bei uns nicht.>> Natürlich haben die nicht den gleichen Code, aber der Fehler des> "wegoptimierens" müsste doch auch bei denen bestehen bleiben....
Dann zeigt doch mal den echten Code.
Der C-Sourcecode ganz oben passt ja nicht zu den beiden ASM-Listings...
C-Sourcecode:
while (!shared);
^
ASM-Listing #1:
while (!shared)
^
+000007B0: 91800177 LDS R24,0x0177 Load direct from data
space
ASM-Listing #2:
27: while (!shared)
^
+000007B0: 91800177 LDS R24,0x0177 Load direct from data
space
Wenn im ausführbaren Programm tatsächlich das ; fehlt (bzw. dessen
Umsetzung), wird es zur Laufzeit
a/ bei noch ungültigem shared (NULL) zur Ausführung des
Schreiben/Lesen while(1)s kommen.
b/ bei bereits gültigem shared (~NULL) zur Beendigung von task2()
kommen. Was ja nicht vorkommen darf!
Du erwartest nicht wirklich, daß im Ausführbaren Programm ein ; steht
oder die Kommentare in einem aggressiv optimierten Programm aufs Byte
genau platziert sind?
Nein, ich erwarte es nicht.
Ich kann allerdings nur zu dem meinen Senf abgeben, was der OP
geschrieben hat. Das was ich geschrieben habe, könnte das Problem
umzingeln. So gut oder so wenig wie die anderen Antworten auch.
In die Optimierungstheorien habe ich mich nicht eingelesen. Das Feld
überlasse ich anderen Experten u.a. auch Dir (no pun intended).
Allerdings weiss ich nicht welche Optimierung der OP verwendet. Für mich
lesen sich die Ausführungen des OP dazu wie Spekulationen, solange nicht
die Optimierungsstufe angegeben ist bzw. ein Übersetzungsversuch ohne
Optimierung (wie von dir vorgeschlagen?) ein anderes Ergebnis bringt.
Ob mein Elaborat tatsächlich zutreffend ist, kann im Moment nur der OP
prüfen. Wenn er möchte, dass ich/andere es prüfe/n, kann er mir/uns den
echten Sourcecode und am besten auch die ASM-Listings anhängen, darum
bitte ich ja oben, und dann prüfe ich das selber.
ADD: Was ich erwarte ist, dass im gemischten C/ASM Listing komplette
Sourcecodezeilen enthalten sind, d.h. auch ein ; am Zeilenende enthalten
ist. Was hätte das gemischte Listing sonst für einen Sinn?
Keine Angst, da steht schon ein ; im Sourcecode. Ich programmier zwar
noch nicht so lange in C, aber Programmieren können wir schon. Die
Optimierungsstufe egal welche hat diesen Code erzeugt, außer O0
natürlich, dort wurde die Schleifen, wie man sie auch per Hand in asm
implementieren würde umgesetzt, also jedesmal bed. geprüft. Mir ist nun
auch klar, wie das ganze abgelaufen ist und warum das wegoptimiert
wurde.
Also weiß ich gar nicht warum immernoch so viel diskutiert wird
Stefan B. wrote:
> In die Optimierungstheorien habe ich mich nicht eingelesen. Das Feld> überlasse ich anderen Experten u.a. auch Dir (no pun intended).> Allerdings weiss ich nicht welche Optimierung der OP verwendet. Für mich> lesen sich die Ausführungen des OP dazu wie Spekulationen, solange nicht> die Optimierungsstufe angegeben ist bzw. ein Übersetzungsversuch ohne> Optimierung (wie von dir vorgeschlagen?) ein anderes Ergebnis bringt.
-O0 ist leider nicht immer möglich, weil uU das Programm dann zu groß
wird.
Mit hilft meistens ein Blick in die Compilerausgabe mit "-fverbose-asm
-dp -save-temps", weil da mehr info drin ist als in das asm-Ausgabe des
Assemblers (-Wa,-...)
Was mit schon mal spanisch vorkommt ist
> while (shared == 0);
und alle dazu gleichbedeutenden Formulierungen, weil auf shared nicht
atomar zugegriffen wird. Das sollte zwar nur sporadisch zu Fehlern
führen, aber denonch:
funktioniert auch alles wunderbar.
Es war ja eigentlich nur die Frage danach warum man den Fehler den wir
bekommen nicht immer bekommt....andere Projekte mit den gleichen
Funktionen weisen diesen "Compiler-Fehler" oder "Optimierungs-Fehler"
nicht auf...
das ist das einzig verwirrende was bleibt....
mit
1
while(!shared)
oder
1
while(shared==NULL)
oder
1
while(shared==0)
fragen wir ja nur ab ob der Pointer gesetzt wurde...
wie gesagt, programmieren ist nicht das Problem :)
Gruß Tim
>> funktioniert auch alles wunderbar.
Was nicht heisst, daß es korrekt ist ;-)
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Interrupt-Routinen_und_Registerzugriffe> Es war ja eigentlich nur die Frage danach warum man den Fehler den wir> bekommen nicht immer bekommt....andere Projekte mit den gleichen> Funktionen weisen diesen "Compiler-Fehler" oder "Optimierungs-Fehler"> nicht auf...
Es sind ziemlich sicher keine Fehler, behaupte ich jetzt mal. Es ist C.
Die Sprache C hat keine Vorstellung vom Interrupts oder kennt Konzepte
wie synchronized in Java!
Der Compiler arbeitet deterministisch. Wenn iht also die gleiche
Compilerversion mit den gleichen Schaltern und Kommandozeilen-Parametern
auf die gleiche Quelle anwendet, kommt auch das gleiche raus. Und wenn
ihr diese Compilerausgabe mit der gleichen binutils-Version und den
gleichen binutils-Schaltern assembliert und linkt und das elf und hex
daraus erstellt, sind die auch identisch. Sofern in beiden Fällen die
verwendeten Betriebbsystembibliotheken (.dll, .a, .so, ...) ebenfalls
gleich arbeiten.
Falls ihr Hinweise auf einen gcc-Fehler habt, dann hilft eine
präcompilierte Quelle und die Compilerausgabe; beides bekommst bei
zusätzlicher Angabe von -v -E bei den gcc-Optionen.
> das ist das einzig verwirrende was bleibt....>> mit
1
while(!shared)
> oder
1
while(shared==NULL)
> oder
1
while(shared==0)
fragen wir ja nur ab ob der Pointer
> gesetzt wurde...
Nein, ihr verlasst euch später auch darauf, daß shared einen sinnvollen
Wert hat, was nicht unbedingt gegeben ist. Siehe "atomic" oben.
Die while-Schleife hat nur ein paar Instruktionen, es ist also nicht
unwahrscheinlich, daß zu einem ungünstigen Zeitpunkt geschedult wird und
os_cWrite() mit einem korrupten shared aufgerufen wird.
Zudem: ohne volatile darf ein compiler