Hallo,
Ich habe schon etwas mit nem AVR rumgespielt und alles war soweit ganz
ok.
Jetzt habe ich aber gelesen, dass ein C-Compiler Code umordnen darf,
über mehrere Zeilen hinweg, sodass dann z.B. bei
1
...
2
bla1();
3
cli(),
4
bla2();
5
bla3();
6
sei();
7
bla4();
8
...
heraus kommen kann, dass bla1() und bla4() auch innerhalb des
cli()/sei()-Blocks ausgeführt werden.
Das wird wohl nur gemacht, wenn am Ende das Selbe heraus kommt. Was aber
egal zu sein scheint, ist, wann was genau berechnet wird... Und das ist,
so wie ich das sehe, das Problem. Bei µCs ist es eben oft nicht egal,
sondern wichtig zu wissen, wann was berechent wird.
Hab ich das soweit erstmal richtig erfasst?!
Ich kann jedenfalls nicht nachvollziehen, wann der Compiler was machen
darf und wann nicht. Das machts auch schwer, ihm zu sagen, wann er was
lassen soll...
Ich habe schon mal von volatiles gehört (.."nutzen bei ISRs"..), von
sequence points, von mem-barriers, ...
Das Problem ist, man hört so viel..
Volatiles haben irgendwas mit Nebeneffekten zutun (so wie der Rest auch,
oder?). Nur erkenne ich noch nicht, wann was wohl Nebeneffekte besitzt.
Ich habs zwar schon gelesen, aber noch nicht richtig verstanden, was
volatile mit der Variablen genau macht.
Naja, sehe ich das richtig, dass es einmal ein reordering vom Compiler
gibt und einaml eines in der CPU/Hardware?! Auch dabei hab ich noch nich
verstanden, welche Methode wofür gedacht ist..
Ich hoffe erstmal die hardware-Reorder kann mir erstmal egal sein.
Also wie bringe ich den Compiler dazu, den Code auch wirklich so
auszuführen, wie er da steht?
Wie wärs z.B. mit "{}"? Würde das helfen? Es müsste bestenfalls etwas
sein, was nicht den Code aufbläht, als Workaround oder so, sondern
einfach dem Compiler sagt:"Diesen Bereicht hier nicht umordnen!" Ist
sowas nicht vorgesehen, oder wie?
Und noch was wegen der volatiles, da man die wohl so oder so braucht:
Wie performance-hungrig sind die eigentlich?
Ich habe mir dazu ein "sleep()" mit und ohn volatile-Zugriff gebaut. Und
das mit war schon erheblich langsamer!
Also was ist, z.B., wenn ich eine Situation habe, in der eine volatile
nicht als solche behandelt werden muss (gibt es soeine Situation
überhaupt)? Was ist z.B. mit dem Zugriff auf eine volatile innerhalb
einer ISR, in der ja keiner weiteren IRQs auftreten können?? Was also,
wenn ich temporär dafür garantieren kann, dass sie nicht volatile sein
muss (was ich zumidest glaube in diesem Bsp..) ??
Hans W. schrieb:> Hab ich das soweit erstmal richtig erfasst?!
Nein!
Du siehst eine Problem, wo keins ist.
Ja, der Kompiler darf umsortieren, aber ist dabei auch Beschränkungen
unterworfen!
Denn Hinten, am Ende, muss das gleiche bei raus kommen.
Hans W. schrieb:> volatile
Verhindert dass Variablen in Register gehalten werden!
Das ist wichtig wenn es asynchron zum normalen Programmablauf, weitere
Verarbeitungen gibt.
Z.B. Interrupts, oder Preamtives Multitasking.
Hans W. schrieb:> Jetzt habe ich aber gelesen, dass ein C-Compiler Code umordnen darf,> über mehrere Zeilen hinweg, sodass dann z.B. bei
Nö, darf er nicht beliebig. Stichwort für C ist "Sequence Point". Die
cli/sei Makros sind entsprechend dekoriert.
Hans W. schrieb:> Volatiles haben irgendwas mit Nebeneffekten zu tun
Sie zwingen den Compiler vor allem zu echten Speicherzugriffen, und zwar
"vor Ort". Sonst packt der möglichst alles in CPU Register - die sind
teilweise deutlich schneller, kann man aus einem Interrupt aus aber
nicht wirklich gezielt beeinflussen.
Hans W. schrieb:> heraus kommen kann, dass bla1() und bla4() auch innerhalb des> cli()/sei()-Blocks ausgeführt werden.> Das wird wohl nur gemacht, wenn am Ende das Selbe heraus kommt. Was aber> egal zu sein scheint, ist, wann was genau berechnet wird... Und das ist,> so wie ich das sehe, das Problem. Bei µCs ist es eben oft nicht egal,> sondern wichtig zu wissen, wann was berechent wird.
Das ist blödsinn. Wo hast du denn so was her?
Hans W. schrieb:> Volatiles haben irgendwas mit Nebeneffekten zutun (so wie der Rest auch,> oder?). Nur erkenne ich noch nicht, wann was wohl Nebeneffekte besitzt.> Ich habs zwar schon gelesen, aber noch nicht richtig verstanden, was> volatile mit der Variablen genau macht.
Volatile sagt dem Compiler erst mal nur, dass die Variable auch von ganz
wo anders verändert werden wird. Das führt leider dazu, dass so gut wie
keine Optimierungen mehr durchgeführt wernde, was wiederum zu
langsameren Code führt
>Ja, der Kompiler darf umsortieren, aber ist dabei auch Beschränkungen>unterworfen!>Denn Hinten, am Ende, muss das gleiche bei raus kommen.
Das meine ich doch auch. Nur, dass es egal zu sein scheint, wann welcher
"Zwischenschritt" berechnet wird..!?
>Verhindert dass Variablen in Register gehalten werden!>Das ist wichtig wenn es asynchron zum normalen Programmablauf, weitere>Verarbeitungen gibt.>Z.B. Interrupts, oder Preamtives Multitasking.
Hm.. Wenn ich jetzt also im der main() bin und irgendwelche 'normalen'
Vars im Register habe, werden die dann aus den Registern im Ram
"Zwischengespeichert" wenn ein IRQ kommt? Und in der ISR werden
volatiles bei jedem Lesezugriff neu aus dem RAM ins Register eingelesen,
sowie nach jeden einzelnen Schreibzugriff innerhalb der ISR in den RAM
geschrieben?
Kann man das evtl. innerhalb der ISR ausstellen? Sprich, wenn ich mir
sicher sein kann, dass niemand die Vars gerade ändern kann?!
>Nö, darf er nicht beliebig. Stichwort für C ist "Sequence Point". Die>cli/sei Makros sind entsprechend dekoriert.
Wie kodiert man die denn? Ist das eine Art Hardware-Workaround, indem
man ihm eine CUP-Operation mitgibt, die ein Sequence Point darstellt,
oder eher 'virtuell' als eine Art Compiler-Flag??
Ich würde sowas auch gerne können, ohne Workaround..
>Das ist blödsinn. Wo hast du denn so was her?
Aus irgendeinem Forum. Der Beitrag war schon etwas älter.. Ein
Arduino-Forum..
>Volatile sagt dem Compiler erst mal nur, dass die Variable auch von ganz>wo anders verändert werden wird. Das führt leider dazu, dass so gut wie>keine Optimierungen mehr durchgeführt wernde, was wiederum zu>langsameren Code führt
Was, wenn ich in der ISR nur schreibe und in der main nur lese? Brauche
ich dann eine volatile?
Oder mal anders: Gibt es Situationen, in denen ich theoretisch ne
Volatile brauche, aber praktisch nicht, weil ...?!
Danke schon mal für eure Antworten!
Hans W. schrieb:> Nur, dass es egal zu sein scheint, wann welcher> "Zwischenschritt" berechnet wird..!?
Natürlich ist das egal!
Denn der Compiler sorgt dafür, dass das richtige dabei raus kommt.
Die Funktionen dürfen ruhig Seiteneffekte haben.
Der Kompiler sorgt dafür, dass diese Seiteneffekte in der richtigen
Reihenfolge auftreten.
Das ist garantiert!
Du machst dir einen Kopf um NICHTS!
Hans W. schrieb:> Was, wenn ich in der ISR nur schreibe und in der main nur lese? Brauche> ich dann eine volatile?
Natürlich!
Sach mal, was ist mit dir los?
An tausend Stellen im Netz steht geschrieben, wofür der Kram gut ist.
Wie man ihn verwendet.
Und du spielst hier den *****....
Warum?
Was hast du davon?
Hans W. schrieb:>>Das ist blödsinn. Wo hast du denn so was her?> Aus irgendeinem Forum. Der Beitrag war schon etwas älter.. Ein> Arduino-Forum..
Glaube ich dir nicht.
So ein Blödsinn wird da nicht verzapft!
Hans W. schrieb:>>Denn Hinten, am Ende, muss das gleiche bei raus kommen.> Das meine ich doch auch. Nur, dass es egal zu sein scheint, wann welcher> "Zwischenschritt" berechnet wird..!?
Die Zwischenschritte müssen überhaupt nicht berechnet werden, wenn das
Ergebnis bereits bekannt ist. Wichtig ist, dass sich das Programm so
verhält wie aufgeschrieben.
> Hm.. Wenn ich jetzt also im der main() bin und irgendwelche 'normalen'> Vars im Register habe, werden die dann aus den Registern im Ram> "Zwischengespeichert" wenn ein IRQ kommt?
Ja. Guckst du Doku von Chip.
> Und in der ISR werden volatiles bei jedem Lesezugriff neu aus> dem RAM ins Register eingelesen, sowie nach jeden einzelnen> Schreibzugriff innerhalb der ISR in den RAM geschrieben?
Ja. Guckst du Doku von Compiler.
> Kann man das evtl. innerhalb der ISR ausstellen? Sprich, wenn ich mir> sicher sein kann, dass niemand die Vars gerade ändern kann?!
Nein. Eine Variable ist "volatile" oder sie ist es nicht. Du darfst aber
eine temporäre Variable benutzen.
>>Nö, darf er nicht beliebig. Stichwort für C ist "Sequence Point". Die>>cli/sei Makros sind entsprechend dekoriert.> Wie kodiert man die denn? Ist das eine Art Hardware-Workaround, indem> man ihm eine CUP-Operation mitgibt, die ein Sequence Point darstellt,> oder eher 'virtuell' als eine Art Compiler-Flag??
Dem Compiler wird mitgeteilt, dass hier eine Besonderheit stattfindet,
auf die er achten muss. Dann generiert er passenden Code. Das Stichwort
lautet "atomics", und für nähere Details guckst du in die Compiler-Doku.
> Ich würde sowas auch gerne können, ohne Workaround..
Das ist kein Workaround, sondern der vorgegebene Weg.
Alternativ kannst du auch Assembler programmieren, da pfuscht dir keiner
ins Handwerk.
>>Volatile sagt dem Compiler erst mal nur, dass die Variable auch von ganz>>wo anders verändert werden wird. Das führt leider dazu, dass so gut wie>>keine Optimierungen mehr durchgeführt wernde, was wiederum zu>>langsameren Code führt> Was, wenn ich in der ISR nur schreibe und in der main nur lese?> Brauche ich dann eine volatile?
Ja, denn sonst wird die Variable möglicherweise in der ISR geändert,
aber main weiß davon nichts.
> Oder mal anders: Gibt es Situationen, in denen ich theoretisch ne> Volatile brauche, aber praktisch nicht, weil ...?!
Ja. Wenn dein Compiler scheiße ist (oder du Optimierungen verbietest),
dann liegt jede Variable im RAM, ist also auch dann "volatile", wenn du
es nicht hinschreibst.
Zitat:
#define sei ( )
Enables interrupts by setting the global interrupt mask. ... also
implies a memory barrier ...
cli ebenso.
Da ist also nichts zu befürchten, der Compiler darf an diesen Punkten
nur so vorbei-umordnen, dass es keinen Unterschied macht:
Hans W. schrieb:> Was aber> egal zu sein scheint, ist, wann was genau berechnet wird... Und das ist,> so wie ich das sehe, das Problem. Bei µCs ist es eben oft nicht egal,> sondern wichtig zu wissen, wann was berechent wird.
Das ist sozusagen nur ein Problem in der Extrapolation.
1
intx=furchtbarAufwaendigeRechnung();
2
/* 30 Minuten minus Berechnungsdauer abwarten */
3
while(!printNow())Wait();
4
5
printf("%d",x);/* hat 5 Minuten verzögerung, weil das idle vorgezogen wurde */
gibt es so in der Praxis eigentlich relativ selten. Um sich dagegen
abzusichern, müsste man aber den Compiler in der Tat zwingen, den Wert
auch echt berechnet zu haben: KEIN Problem ist es nur deshalb, weil man
in den meisten Kommandofolgen, irgendwo eine hat, die eine
Speicherbarriere impliziert. Dafür genügt schon eine Funktion, die der
Compiler nicht in der selben Datei mitübersetzt. Das deshalb, weil er
von einer solchen Funktion nicht weiß, auf welche Variablen sie
zugreift.
> Hab ich das soweit erstmal richtig erfasst?!
Ja, das scheint mir der Fall zu sein.
> Ich kann jedenfalls nicht nachvollziehen, wann der Compiler was machen> darf und wann nicht. Das machts auch schwer, ihm zu sagen, wann er was> lassen soll...
Er darf alles ändern, solange sich das beobachtbare Verhalten des
Programms nicht ändert. Meine persönliche Meinung ist allerdings auch,
dass der Ausnahmenkatalog der Sprachdefinition definitiv zu lang wird.
> Ich habe schon mal von volatiles gehört (.."nutzen bei ISRs"..), von> sequence points, von mem-barriers, ...> Das Problem ist, man hört so viel..
Versuchs mit YouTube :)
> Volatiles haben irgendwas mit Nebeneffekten zutun (so wie der Rest auch,> oder?).
Eine Variable als volatile zu deklarieren, besagt, dass wirklich JEDER
Zugriff auf sie auch echt als solcher stattfinden soll und der Compiler
keine Annahmen macht, wie, dass sie ja eigentlich noch den selben Wert
wie vorher haben müsste, oder dass er von zwei Schreibzugriffen den
ersten ja eigentlich auch weglassen könnte.
> Naja, sehe ich das richtig, dass es einmal ein reordering vom Compiler> gibt und einaml eines in der CPU/Hardware?! Auch dabei hab ich noch nich> verstanden, welche Methode wofür gedacht ist..
Ja, richtig. Die CPU kann Anweisungen auch neu ordnen. Allerdings
bekommt man davon weit weniger mit, da man hier wirklich eine BlackBox
von außen betrachtet. Beides hat den selben Zweck, die Performance zu
optimieren.
> Also wie bringe ich den Compiler dazu, den Code auch wirklich so> auszuführen, wie er da steht?
Für viele Sachen gibt es Compilerflags. Allerdings muss man hier Kosten
und Nutzen abwägen. Für 90% des Application-Codes ist es idR egal, wenn
man mal hier mal dort ein paar CPU-Cyclen verliert.
> Wie wärs z.B. mit "{}"? Würde das helfen? Es müsste bestenfalls etwas> sein, was nicht den Code aufbläht, als Workaround oder so, sondern> einfach dem Compiler sagt:"Diesen Bereicht hier nicht umordnen!" Ist> sowas nicht vorgesehen, oder wie?
So nicht, nein. Allerdings gibt es Möglichkeiten, kritische Bereiche
gegeneinander abzugrenzen. Das sind die Speicherbarrieren.
> Und noch was wegen der volatiles, da man die wohl so oder so braucht:> Wie performance-hungrig sind die eigentlich?
Kommt auf das konkrete Beispiel an, sind aber potentiell viele
redundante Lade- und SPeichervorgänge.
> Ich habe mir dazu ein "sleep()" mit und ohn volatile-Zugriff gebaut. Und> das mit war schon erheblich langsamer!> Also was ist, z.B., wenn ich eine Situation habe, in der eine volatile> nicht als solche behandelt werden muss (gibt es soeine Situation> überhaupt)?
Es gibt nichts, was es nicht gibt :)
Man kann das volatile hinzu- oder wegcasten. Man kann den volatilen Wert
in eine lokale Variable ohne volatile Laden und ggf. später explizit
zurückschreiben.
> Was ist z.B. mit dem Zugriff auf eine volatile innerhalb> einer ISR, in der ja keiner weiteren IRQs auftreten können??
Da wäre ich vorsichtig und würde genau die Compiler-Doku lesen, ob da
echt keine anderen Interrupts dazwischen kommen können.
Ich sehe aber, dass die Verwendung von "volatile" nicht ganz klar zu
sein scheint: Wenn die Sorge der Unterbrechbarkeit gilt, ist ein
volatile idR entweder unnötig oder nicht genug. Volatile Zugriffe sind
NICHT atomar und auch keine Speicherbarrieren (außer bei Microsoft...)!
> Was also,> wenn ich temporär dafür garantieren kann, dass sie nicht volatile sein> muss (was ich zumidest glaube in diesem Bsp..) ??
Dann den Wert in eine lokale Variable laden, damit rechnen und
zurückschreiben
>Schau mal in die atomic.h.
Habe ich eben getan.. Aber so wie ich das sehe, ist diese 'mem-barrier'
doch eine asm-Anweisung, also etwas, das sich im code wieder finden
wird, und keine bloße Compiler-Anweisung..
Gibt es einen Grund dafür, dass man das in HW machen muss und nicht
quasi virtuell?
Andererseits kennt der Compiler ja Sequence Points.. Kann ich nicht
einen absichtlich dafür Nutzen?
Z.B. Kram mit "{}" einschließen, das wäre fein.. Gibts einen Grund warum
das nicht so funktioniert?
>Das ist garantiert!
Die haben Test gemacht, asm verglichen und sich darüber beschwert, dass
dem nicht so ist..
>So ein Blödsinn wird da nicht verzapft!
..weil alle Beiträge von dir kommen??
>Glaube ich dir nicht.
Och.
Was ist denn los? Ach..
>Heute ist Freitag...
...
>Sach mal, was ist mit dir los?>>An tausend Stellen im Netz steht geschrieben, wofür der Kram gut ist.>Wie man ihn verwendet.
Ja lies doch einfach:
>Das Problem ist, man hört so viel..
Ich bin mit dem Kram aufm normalen PC nie in Berührung gekommen - wer
hätte das gedacht. Darum bin ich jetzt etwas geschockt..
Gibt es bei 'normalen x86-single-core' Anwendungen eig. auch eie
Verwengung für Volatiles??
>Ja. Guckst du Doku von Chip.
Ah ok. Und wie ist das, der Vollständig keit halber, bei normalen
Funktionsaufrufen; ist da das Zwischenspeichern etc alleine Aufgabe des
Compilers?
Reicht es, bei Nebeneffekten zwischen hardwareseitig und softwareseitig
zu unterscheiden??
Damit meine ich:
-wenn Fkt hw-seitig aufgerufen wird -> Fkt stets "Blackbox" -> volatile
etc
-wenn sw-seitig -> Fkt dem Compiler komplett und jederzeit bekannt
?!
>Nein. Eine Variable ist "volatile" oder sie ist es nicht. Du darfst aber>eine temporäre Variable benutzen.
Daran dachte ich auch schon, ist aber unschön.. z.B. bei großen Typen.
Wie siehts denn aus mit casten??
Habe ich ausch schon gesehen, weiß aber nicht was wohin zu casten,
welche Konsequenzen hat..
>Das ist kein Workaround, sondern der vorgegebene Weg.>Alternativ kannst du auch Assembler programmieren, da pfuscht dir keiner>ins Handwerk.
Das wäre schön manchmal.. Ich hab mal in die delay_basic.h geguckt..
1
void
2
_delay_loop_1(uint8_t __count)
3
{
4
__asm__ volatile (
5
"1: dec %0" "\n\t"
6
"brne 1b"
7
: "=r" (__count)
8
: "0" (__count)
9
);
10
}
11
12
void
13
_delay_loop_2(uint16_t __count)
14
{
15
__asm__ volatile (
16
"1: sbiw %0,1" "\n\t"
17
"brne 1b"
18
: "=w" (__count)
19
: "0" (__count)
20
);
21
}
Kann mir vllt. jemand kurz als Beispiel erklären, was da steht, wenns
nicht zu lange dauert?!
Ich denke, wenn ich rein in asm programmiere, muss ich mich komplett
selbst um das Speichermanagement kümmern, oder? Also welche var gerade
in welchem register gecached wird etc?!?
Gibt es in asm eigentlich portable Funktionen, die irgendwie selbst
etwas um die Register kümmern?
Ich frage mich nämlich, wie sich der asm-Code hier in C integriert:
Irgendwie muss das ja zu dem automatischen Speichermanagement (oder
besser Cachemanagement) von C passen..
>Ja, denn sonst wird die Variable möglicherweise in der ISR geändert,>aber main weiß davon nichts.
Hm, wie kann das denn exemplarisch aussehen ohne volatile?
-Ich habe: glob. Var., ISR(), main()
-in ISR(): g.V. wird aus Ram in Reg geladen, Reg.Var. wird verändert,
beim Verlassen der ISR wird reg.Var in Ram geschrieben..
- in main(): Tja, wie genau wird jetzt der alte Zustand der Register, so
wie er vor dem Interrupt war wieder hergestellt??? ..Offenbar ja nicht
aus dem Ram, sonst bräuchte ich hier ja kein volatile, oder??
Hans W. schrieb:> Die haben Test gemacht, asm verglichen und sich darüber beschwert, dass> dem nicht so ist..
Bei der Formulierung der Doku von sei() und cli() wundert mich das
etwas. Scheint so, als ob da eine etwas andere Interpretation des
Begriffs vorliegt, siehe hier:
Beitrag "Schwerer Bug in AVR-GCC 4.1.1"
Grund ist, dass die "Memory barrier", dort nur ein mem-clobber ist statt
einer atomic_thread_fence.
Schockierend daran ist, dass die Argumentation auf "avr-gcc" ist, dass
ja eben damals noch keine Standard-Methode gegeben hätte, die
Ausführungsreihenfolge zu forcieren - als ob __asm("cli") Standard
gewesen wäre... Und in der Form mit mem-clobber steht es immer noch
genau so da.
Kommunismus funktioniert einfach nicht...
Hans W. schrieb:> Ich bin mit dem Kram aufm normalen PC nie in Berührung gekommen - wer> hätte das gedacht. Darum bin ich jetzt etwas geschockt..
Das ist jetzt nicht überraschend, da die Compiler früher nicht so stark
optimiert haben.
Für ein paar schöne Hintergründe kannst du dir Herb Sutters Vortrag zum
Thema anschauen. Da geht's zwar um C++, aber er erklärt es ganz gut:
https://www.youtube.com/watch?v=A8eCGOqgvH4.
> Gibt es bei 'normalen x86-single-core' Anwendungen eig. auch eie> Verwengung für Volatiles??
Ja, nämlich bei Verwendung von Signalen (es gilt dasselbe wie bei
Interrupts auf einem AVR).
>>Ja. Guckst du Doku von Chip.> Ah ok. Und wie ist das, der Vollständig keit halber, bei normalen> Funktionsaufrufen; ist da das Zwischenspeichern etc alleine Aufgabe des> Compilers?
Ja, denn der Compiler kennt den Funktionsaufruf.
Ein Interrupt führt übrigens nicht dazu, dass alle Register gesichert
werden, sondern im Zweifelsfall nur genau eins (der PC). Den Rest
sichert der Compiler im ISR-Anfang. Der Unterschied ist, dass die
Registerwerte zu diesem Zeitpunkt ungültige, halbgeschriebene oder
temporäre Werte enthalten können, über die der Compiler keine Aussage
machen kann.
> Reicht es, bei Nebeneffekten zwischen hardwareseitig und softwareseitig> zu unterscheiden??
Keine Ahnung. Es gibt noch so Dinge wie "Sprünge durch
Funktionspointer", da weiß der Compiler nur, dass ein Sprung
stattfindet, aber er kennt die Zielfunktion zur Compilezeit nicht.
>>Nein. Eine Variable ist "volatile" oder sie ist es nicht. Du darfst aber>>eine temporäre Variable benutzen.> Daran dachte ich auch schon, ist aber unschön.. z.B. bei großen Typen.
Um den Wert der Variablen zu benutzen, muss der Compiler ihn sowieso in
ein Register laden. Eine temporäre Variable zieht diesen Vorgang nur
vor, damit du mehrfach auf den Wert zugreifen kannst, ohne ständig
Speicherzugriffe zu haben.
>>Das ist kein Workaround, sondern der vorgegebene Weg.>>Alternativ kannst du auch Assembler programmieren,>> da pfuscht dir keinerins Handwerk.> Das wäre schön manchmal..> Ich hab mal in die delay_basic.h geguckt..
Für _delay_loop_1 steht da:
label:
dec R18
brne label
Für _delay_loop_2 steht da:
label:
sbiw R18, 1
brne label
In beiden Fällen werden sowohl der Labelname als auch das zu verwendende
Register vom Compiler zugewiesen und das Register (bzw. Registerpaar)
mit dem Zählerwert vorgeladen.
Was die Schnipsel tun, guckst du Datenblatt; kriegst du hin.
Wie Inline-Assembler funktioniert, guckst du Doku zum Compiler.
> Ich denke, wenn ich rein in asm programmiere, muss ich mich komplett> selbst um das Speichermanagement kümmern, oder?
Ja.
> Also welche var gerade> in welchem register gecached wird etc?!?
Ja.
> Gibt es in asm eigentlich portable Funktionen, die irgendwie selbst> etwas um die Register kümmern?
Ja. Deren ABI (Binärschnittstelle) definierst du aber selbst.
> Ich frage mich nämlich, wie sich der asm-Code hier in C integriert:> Irgendwie muss das ja zu dem automatischen Speichermanagement (oder> besser Cachemanagement) von C passen..
Dafür sind die ganzen Dekorationen um den Assemblercode herum da.
Außerdem ist der Code selbst nur ein Template, wo der Compiler die
verwendeten Registerwerte selbst einsetzt.
>>Ja, denn sonst wird die Variable möglicherweise in der ISR geändert,>>aber main weiß davon nichts.> Hm, wie kann das denn exemplarisch aussehen ohne volatile?
Ich schrieb, dass du ein volatile brauchst.
1
charflag_set=0;
2
3
intmain(){
4
while(!flag_set){/* warte */}
5
puts("Hallo!");
6
}
7
8
voidISR_Handler(){
9
flag_set=1;
10
}
Mit Optimierungen hast du in main() eine Endlosschleife, weil flag_set
nicht volatile ist.
Danke nochmal für die ausführlichen Antworten! Ich habe darum noch etwas
experimentiert und mir ein 'paar' Gedanken dazu gemacht, was wann warum
wohl genau passiert unter der Haube und mit dem Compiler..
Aber das muss ich noch etwas destilieren, bevor ich euch das antue..
Um wirklich experiementieren zu können und dann nicht jede Kleinigkeit
nachfragen zu müssen, ist eig. erstmal eine Frage wichtig:
Kann ich mir am PC, in einem normalen Programm alle paar Sekunden
Interrupts simulieren (sprich nen TOV)?
Sodass sich der Compiler so ähnlich verhält, wie auf dem AVR, was den
volatile-Kram angeht (also nicht weiß, wann eine "ISR" aufgerufen
wird).. Das wäre wunderbar, um etwas rumzuexpeimentieren und mir meine
Fragen evtl. selbst zu beantworten..
Ach
@Heiko Lewin: Da bin ich jetzt zum Schluss nicht mehr mit gekommen..
Und danke für
https://www.nongnu.org/avr-libc/user-manual/optimization.html#optim_code_reorder
Gerade ein sehr schönes Bsp. für 'wie es nicht laufen sollte'.
"memory-clobber"??
Heißt doch, cli() und sei() sind doch keine 'undurchdringlichen'
Barrieren..
Auch keine sequence-points..?!?
Kann doch nicht wahr sein...
Also ist es doch ein Workaround, der nicht mal immer wie erwartet
funktioniert, da man den Compiler zu etwas bringen will, das er von sich
aus nicht kann..
Das nervt schon etwa.
Hans W. schrieb:> Also ist es doch ein Workaround, der nicht mal immer wie erwartet> funktioniert, da man den Compiler zu etwas bringen will, das er von sich> aus nicht kann..
Würde der Compiler wissen, wann deine Interrupts kommen, könnte er da
auch problemlos gegenwirken. Da er aber keine Glaskugel hat. Muss Du ihm
sagen, wo er besonders aufpassen muss.
Vielleicht liege ich ja falsch....
Damals musste ich lernen:
> Man programmiert grundsätzlich nur gegen Schnittstellen,> niemals gegen die Implementierung.
Es hat Jahre gedauert, bis ich die Wichtigkeit des Spruches, in aller
Schärfe, erkannt habe!
Dazu brauchte es einen starrköpfigen Charakter.
Einer, der sich gegen jede Konvention gesperrt hat.
Das ist allerdings völlig nach hinten losgegangen.
Wurde aus dem Team entfernt.
Mit seinem Code hatten wir danach noch viele Probleme.
OK, bezieht sich klar auf OOP.
Aber kann man, meines bescheidenen Erachtens nach, auch auf diesen Fall
anwenden.
Hans W. schrieb:> "memory-clobber"??> Heißt doch, cli() und sei() sind doch keine 'undurchdringlichen'> Barrieren..
Steht doch in dem Link:
> Unfortunately, at the moment, in avr-gcc (nor in the C standard),> there is no mechanism to enforce complete match of written> and executed code ordering
Die habe sich für eine Lösung entschieden, von der schon damals klar
war, dass sie unzureichend ist. Das genannte Problem ließe sich seit C11
sogar standardkonform lösen. Wenn ich beruflich mit dem Zeug zu tun
hätte, würde ich in Betracht ziehen, mich dem mal anzunehmen. Das ist im
Grunde einfach nur peinlich...
>Würde der Compiler wissen, wann deine Interrupts kommen, könnte er da>auch problemlos gegenwirken. Da er aber keine Glaskugel hat. Muss Du ihm>sagen, wo er besonders aufpassen muss.
Das mache ich ja auch gerne, leider kann ich ihm nicht sagen wann!
Der kann und soll ja die meiste Zeit machen, was er will, nur eben nicht
immer.
Ich würde gerne sagen: "Jetzt synchronisieren!"
>Die habe sich für eine Lösung entschieden, von der schon damals klar>war, dass sie unzureichend ist. Das genannte Problem ließe sich seit C11>sogar standardkonform lösen. Wenn ich beruflich mit dem Zeug zu tun>hätte, würde ich in Betracht ziehen, mich dem mal anzunehmen. Das ist im>Grunde einfach nur peinlich...
Ich habe mich schon gefragt, wie aktuell die Infos auf der Seite sein
könnten.. Der dort verlinkte Thread von hier ist ja schon etwas älter...
Wie sähe denn eine C11 lösung aus? Bin sonst eher mit C++ unterwegs..
Hier noch ein paar Gedanken...
Macht volatile eig. ausschließlich bei glbalen Variablen Sinn?
Um zu klären, ob es ein Workaround ist, oder nicht:
Erfüllt die asm-Anweisung "membarrier" einen Zweck, wenn man nur in asm
programmiert?
Mal was ganz grundlegendes: Warum heißt memory-barrier eig
memory-barrier? Warum nicht eher sequence-point?
Wie siehts mit volatile aus, wenn ich z.B. eine resetVars() aus der
main() und aus eier ISR() aufrufe? Müssen die zu 'resettenden' Vars dann
plötzlich alle volatile sein?
Also kurz: Muss alles an Variablen, was nicht gerade nur den scope der
ISR hat, aber von einer ISR direkt und indirekt benutzt wird, volatile
sein?
..Ich hab mir hier mal ein testprogramm ( mit einer resetVars() )
geschrieben. mit und ohne volatile. macht iwie keinen unterschied..
..Ist also UB, das man erst bemerkt wenns zu spät ist, stimmts.....?
Wozu gibt es volatile bei Methoden? Bzw. warum werden nicht alle
Methoden automatisch volatile bei volatilen Klassen?
Es gibt ja die cv-qualifier: Ob eine Klasse konstant sein kann, is doch
eig Sache der Klasse, darum auch ob sie entsprechende Methoden anbietet
oder nicht (ein const timer; bringt jetzt nicht so viel..). Aber ob eine
Klasse volatile ist, oder nicht, ist doch eig nicht sach der Klasse,
oder? Komisches Konzept..
Was bedeutet es eig wenn eine Fkt ein volatiles Objekt erwartet? Denn
eig wird ja, wenn das Arg keine Referenz ist, eine Kopie an die Fkt
übergeben, aber wozu volatile bei einer Kopie???
Gibt es verlässliche, bestimmte Zeitpunkte, an denen der Cach neu aus
dem Ram eingelesen werden muss? Z.B. eben zu Beginn einer fkt()?
Diese volatile-Sache klingt so, wie eine Hau-Drauf-Lösung. Gibts nicht
einen gesunden Mittelweg?
Wenn ich Compiler wäre, würde ich mir bei einer Var vorher überlegen, ob
sie von außen änderbar ist und ihr ggf ne echte Adresse im Ram zuweisen.
Dann würde ich sie für ne Berechnung ins Register laden, aber solange
drin lassen, wie es geht. Sprich, solange ich mir sicher sein kann, dass
ihr Ram sich nicht geändert hat. ..Statt sie bei jedem Windhauch neu zu
laden & schreiben.. Dann muss eben der Programmierer das manuell machen,
aber der schreibt ja schließlich auch den restlichen Code!
Wie kann ich dieses Verhalten erzielen??
Hans W. schrieb:> Wenn ich Compiler wäre, würde ich mir bei einer Var vorher überlegen, ob> sie von außen änderbar ist und ihr ggf ne echte Adresse im Ram zuweisen.
Das wäre theoretisch möglich. Nur besteht ein Programm in den meisten
Fällen aus mehrere Objekten, die erst später durch den Linker
zusammengefasst und erst dann lauffähig ist. Der Compiler kann also
garnicht wissen, was in anderen Bibliotheken passiert. Das ist nunmal
die Aufgabe des Programmierers; der soll schließlich auch mal mitdenken.
Und denke daran, dass volatile nicht nur für einen kleinen
Mikrocontroller erfunden wurde.
Hans W. schrieb:> Also kurz: Muss alles an Variablen, was nicht gerade nur den scope der> ISR hat, aber von einer ISR direkt und indirekt benutzt wird, volatile> sein?
Der Zugriff muss atomar sein. Nutze am besten eine der atomic-Klassen.
http://en.cppreference.com/w/c/atomic
Oder aber sichere den Zugriff durch memory barriers
http://en.cppreference.com/w/c/atomic/atomic_thread_fence
Die haben auch den Vorteil, dass man die Konsistenz über mehrere
Variablen hinweg absichern kann. Je nachdem, welche memory_order du da
angibst erlaubst/verbietest du dem Compiler, Zugriffe in einer Richtung
über die Barriere zu schieben.
http://en.cppreference.com/w/c/atomic/memory_order
In deinem Fall brauchst du ein "acquire/release" oder
memory_order_acq_rel also eine Barrier, die das in keiner Richtung
erlaubt.
PS: Vorsicht! Angaben ohne Gewähr. Ich habe die Dinger in der Praxis
selbst noch nie verwendet, da jedes normale Mutex das schon von selbst
regelt.
>Umsteigen auf Pascal
Ich hab zu Beginn mal darüber nachgedacht, ob ich jetzt free pascal oder
c++ lernen soll.. ....Man trifft einfach immer die falsche Wahl....
>Das wäre theoretisch möglich. Nur besteht ein Programm in den meisten>Fällen aus mehrere Objekten, die erst später durch den Linker>zusammengefasst und erst dann lauffähig ist. Der Compiler kann also>garnicht wissen, was in anderen Bibliotheken passiert. Das ist nunmal>die Aufgabe des Programmierers; der soll schließlich auch mal mitdenken.
Das ist aber was anderes. Zwar nicht über mehrere Dateien hinweg, aber
innerhalb einer merkt er sich das für gewöhnlich schon. Das ermöglicht
ja gerade die Optimierungen, sodass eben gerade nicht ständig der Ram
genutzt werden muss (zumindest verstehe ich das so).
>Und denke daran, dass volatile nicht nur für einen kleinen>Mikrocontroller erfunden wurde.
Ne, ich glaube für nen kleien µC wurde da gar nix erfunden...
...Außer vielleicht jetzt mit c11 (welch Überleitung)...
Die c11 atomar-Geschichte muss ich mir erst noch ansehen. Keine Ahnung
wie man die nutzt und was genau das ist..
Mit atomar meinst du jetzt "am Stück weg, wenns länger als uint8_t
ist"?! D.h. wenn ich eine längere Var als volatile kennzeichne, genügt
das nicht?! Also:
1
volatile uint16_t var;
2
3
ISR(){
4
...var...;
5
}
6
7
main(){
8
cli(); // nötig damit atomar??
9
...var...;
10
sei();
11
}
Also, worauf müssen wir jetzt beim AVR achten:
1. evtl. Umsortieren verhindern
2. volatile Variablen nicht durch IRQs zerpflücken lassen
3. volatile-light-Variablen
1.) mit mem-barriers, die aber nur mit volatiles funktionieren... ODER
mit c11-atomics (vielleicht)
2.) immer cli() + sei() ODER irgendwie mit c11-atoms
3.) vielleicht irgenwie mit casts
Habe ich was vergessen? Bzw. was wäre jeweils das Beste?
Hans W. schrieb:> Habe ich was vergessen? Bzw. was wäre jeweils das Beste?
cli() und sei() umdefinieren, so dass statt mem-clobbers
atomic_thread_fences setzen, und dann einfach mit normalen Variablen
arbeiten.
>cli() und sei() umdefinieren, so dass statt mem-clobbers>atomic_thread_fences setzen, und dann einfach mit normalen Variablen>arbeiten.
Wie/Wo/Was?! Sind die C11-atomics hier das Allheilmittel? Kann ich die
evtl. für 1.), 2.) und 3.) verwenden?
Kannst du mir zufällig kurz erklären, was der Unterschied zw.
mem-clobber und atomic (..was sich wohl doch nicht direkt auf atomaren
Zugriff bezieht..) ist? Wenn man das überhaupt kurz erklären kann..
Ich habe mal in die stdatomic.h reingeschaut. Weit kommt man ja nicht,
um zu sehen, was sich hinter den Funktionen verbirgt. Warum wurde diese
Neuerung überhaupt in Funktionen realisiert?
Hans W. schrieb:> Also ist es doch ein Workaround, der nicht mal immer wie erwartet> funktioniert, da man den Compiler zu etwas bringen will, das er von sich> aus nicht kann..
Der C-Standard kennt ursprünglich weder Interrupts noch andere
Seiteneffekte realer Hardware. Beschrieben wird das Verhalten einer
virtuellen Maschine, die so natürlich real nicht existiert.
Du kannst natürlich Optimierungen abschalten, dann brauchst du kein
volatile mehr.
Hans W. schrieb:> Wie gehe ich mit Klassen um, die nicht volatile sein können, da sie> keine volatilen Methoden haben?
Es gibt keine volatile-Funktionen in C (die einzige Ausnahme ist eine
GCC-Erweiterung, die "volatile void function()" als "kehrt nicht zurück"
betrachtet - das ist aber obsolet.
Für C++ und Klassenmethoden gelten andere Regeln.
Hans W. schrieb:> Macht volatile eig. ausschließlich bei glbalen Variablen Sinn?
Nein, es macht auch Sinn, wenn bestimmte Speicherzugriffe in einer
bestimmten Reihenfolge stattfinden müssen. Daher sind Hardwareregister
ebenfalls volatile.
Hans W. schrieb:>>Umsteigen auf Pascal> Ich hab zu Beginn mal darüber nachgedacht, ob ich jetzt free pascal oder> c++ lernen soll.. ....Man trifft einfach immer die falsche Wahl....
Pascal kennt kein volatile, weil dort jede Variable volatile ist.
Wie gesagt, du darfst den Optimierer auch abschalten, dann brauchst du
dir darüber keine Gedanken mehr zu machen. Über Performance allerdings
auch nicht. :-)
Hans W. schrieb:> Kannst du mir zufällig kurz erklären, was der Unterschied zw.> mem-clobber und atomic (..was sich wohl doch nicht direkt auf atomaren> Zugriff bezieht..) ist?
Ich empfehle den von mir verlinkten Vortrag von Herb Sutter, "atomic
weapons". Da beschreibt er genau, was ein Atomic ist, warum man es
braucht und welche Arten es gibt.
Ein mem-clobber ist nur der große Holzhammer, der dem Compiler mitteilt,
dass sämtliche in Registern gehaltene Variablen gerade ungültig wurden
und vor der nächsten Verwendung neu aus dem Speicher gelesen werden
müssen.
S. R. schrieb:> Ein mem-clobber ist nur der große Holzhammer, der dem Compiler mitteilt,> dass sämtliche in Registern gehaltene Variablen gerade ungültig wurden> und vor der nächsten Verwendung neu aus dem Speicher gelesen werden> müssen.
Nein, das heißt es gerade nicht. Es sagt nur: "Der Speicher hat sich
geändert". Deshalb dürfen volatile-Zugriffe nicht an dem Clobber vorbeit
verschoben werden.
Heiko L. schrieb:> Nein, das heißt es gerade nicht.> Es sagt nur: "Der Speicher hat sich geändert".
Daraus folgt direkt, dass der Compiler sämtliche in Registern gehaltenen
non-volatile Variablen vor der nächsten (lesenden) Verwendung verwerfen
muss. Denn deren Werte im Speicher könnten sich geändert haben.
>Der C-Standard kennt ursprünglich weder Interrupts noch andere>Seiteneffekte realer Hardware. Beschrieben wird das Verhalten einer>virtuellen Maschine, die so natürlich real nicht existiert.
Ich dachte C sei sehr hardwarenah gebaut, aber daran hat keiner gedacht?
>> Wie gehe ich mit Klassen um, die nicht volatile sein können, da sie>> keine volatilen Methoden haben?>Für C++ und Klassenmethoden gelten andere Regeln.
Ich meine hier wirklich c++. Ich habs mit einer Klasse versucht.. Mir
virtuelelr Vererbung und mehr Rumgebastel geht es wohl, aber dann muss
ich mir wieder Gedanken machen, was das für Auswirkungen auf die
Performance hat...usw usw usw.....
Ich dachte jetzt kommen die stärken von C/C++. Ist wohl eher das
Gegenteil..
>Nein, es macht auch Sinn, wenn bestimmte Speicherzugriffe in einer>bestimmten Reihenfolge stattfinden müssen. Daher sind Hardwareregister>ebenfalls volatile.
Stimmt.. Du meinst jetzt casts, nehme ich an. Aber ich meinte eigentlich
'physische' volatile Variablen. Braucht man die sonst wo?
Und das mit der Reihenfolge gilt auch nur für die volatile Variablen
untereinander (und für Aufrufe irgendwelcher anderer Blackboxen), wenn
ich das jetzt richtig verstanden habe..?!
WaAaRUM GIBT ES DAS NICHT EINFACH AUCH FÜR NORMALE VARS?!?
...Eine Art "virtual volatile" z.B... (wäre der Name noch frei?)
Dann könnte ich mir einfach eine Funktion schreiben, in der ich zu
Beginn etwas in eine Blackbox schicken kann. Wärend ich drauf warte,
dass ich was erhalte, etwas "Nonvolatiles" berechnen. Und mir, wegen
des, von mir selbst intelligent gewählten timings, sicher sein, dass ich
am Ende was von der Blackbox erhalten habe. Jetzt kann ich mir ja nicht
sicher sein, ob alle non-volatilen Sachen nicht vor oder hinter die
volatilen Anweisungen rutscht, wodurch diese dann quasi zeitgleich
ausgeführt würden!
>Pascal kennt kein volatile, weil dort jede Variable volatile ist.
Hab mir schon gedacht, dass es da einen Haken gibt.. ;)
>Ich empfehle den von mir verlinkten Vortrag von Herb Sutter, "atomic>weapons". Da beschreibt er genau, was ein Atomic ist, warum man es>braucht und welche Arten es gibt.
Ach ja... Hätt ich fast vergessen. Aber sind diese atomics eigentlich
performance-hungrig?? Ich weiß nicht mehr, wie sicher man sich bei
dieser Frage sein kann..
Gibts den mem-clobber denn nicht auch in klein, für eine bestimmte
Variable?
Das wäre gut für ein "volatile-light" (was die Speicherzugriffe
angeht)..
Und wegen der Reihenfolge: Können non-volatile jetzt nicht dran vorbei
geschoben werden, oder nur nicht volatile?
Wenn das neu Laden mehr oder weniger die Funktionsweise des clobber ist,
was genau macht dann eine mem-barrier ??
>Und genau das ist, was ich tue. Wenn ich will, dass etwas sicher und>berechenbar funktioniert, dann schreibe ich es in Assembler...
..und es nicht zu groß werden kann..
Ein paar Sachen an C/C++, besonders letzterem, sind ja schon ganz nett.
S. R. schrieb:> Heiko L. schrieb:>> Nein, das heißt es gerade nicht.>> Es sagt nur: "Der Speicher hat sich geändert".>> Daraus folgt direkt, dass der Compiler sämtliche in Registern gehaltenen> non-volatile Variablen vor der nächsten (lesenden) Verwendung verwerfen> muss. Denn deren Werte im Speicher könnten sich geändert haben.
Ich denke nicht. Wenn da ein Ausdruck
1
x*x+3*x;
Ausgewertet wird, kann das cli() z.B. mitten in die Berechnung geschoben
werden und es kann bzw. muss trotzdem mit dem alten x weitergerechnet
werden.
Um der Fairness genüge zu tun: Abgesehen vom Zeitverhalten ist das
Resultat so, als ob es so wäre, wie du sagst.
Volatile beim lesen bedeutet, es jedesmal neu zu lesen, wenn das im Code
steht.
Volatile beim Schreiben bedeutet, es jedesmal zu schreiben, wenn ist im
Code steht. {i++; i++;} erfordert lesen, schreiben, lesen, schreiben und
nicht nur einmal i um 2 erhöhen.
Die Synchronisation zwischen Interrupt und main (oder allgemein:
asynchrone Prozesse) hat mit volatile nichts zu tun, die geht
(prinzipiell) auch ohne.
Nur weil auf manchen Prozessoren manche Befehle auch atomar möglich sind
(i++), missbrauchen viele volatile zur Synchronisation.
Problematik beim lesen: bei if(i>0)...else if(i<0) ... else ... Kann es
sein, dass kein Zweig aufgerufen wird. Hier bräuchte man eine nicht
volatile Hilfsvariable.
Heiko L. schrieb:> Ich denke nicht. Wenn da ein Ausdruck> x*x + 3*x;> Ausgewertet wird, kann das cli() z.B. mitten in die Berechnung geschoben> werden und es kann bzw. muss trotzdem mit dem alten x weitergerechnet> werden.
Wie willst du mittig in den Ausdruck ein cli() (oder einen anderen
clobber) schieben?
Das geht an sich nur, wenn ein Interrupt oder eine andere asynchrone
Unterbrechung auftritt, aber davon merkt dieser Ausführungsstrang ja
nichts.
Hans W. schrieb:> Ich dachte C sei sehr hardwarenah gebaut, aber daran hat keiner gedacht?
Entweder, du spezifizierst eine Programmiersprache anhand eines
Computers (wie Assembler das tut), oder anhand eines Computermodells
(wie jede andere Sprache das tut). Portabilität gibt es nur durch eine
gewisse Abstraktion.
Bei C ist dieses Modell relativ einfach gebaut und bildet daher eine
große Menge an unterschiedlichen Architekturen ziemlich exakt ab.
Hans W. schrieb:> Ich dachte jetzt kommen die stärken von C/C++.> Ist wohl eher das Gegenteil..
C und C++ geben dir scharfe Waffen.
Wenn du dafür nicht stark, schlau oder willens genug bist, gibt es genug
Alternativen, mit denen du deine Probleme ebenfalls lösen kannst -
Pascal wurde mehrfach genannt, Basic erfreut sich auf AVRs ebenfalls
weiter Verbreitung, für PCs die gesamte Latte an Java, C#, etc. -
jeweils mit ihren eigenen Fähigkeiten und Einschränkungen.
Hans W. schrieb:> Aber ich meinte eigentlich> 'physische' volatile Variablen. Braucht man die sonst wo?
Man braucht sie überall da, wo man asynchrone Unterbrechungen des
Programms hat. Das sind Interrupts, Signale, Multithreading. Wenn du die
alle ausschließt, dann nein, man braucht sie nicht.
Für Multithreading sind Atomics hinreichend, aber die gab es vor einigen
Jahren noch nicht in der jetzigen Form, ebenso wie massive
Superskalarität in Prozessoren.
Hans W. schrieb:> Dann könnte ich mir einfach eine Funktion schreiben, in der ich zu> Beginn etwas in eine Blackbox schicken kann. Wärend ich drauf warte,> dass ich was erhalte, etwas "Nonvolatiles" berechnen.
Das funktioniert so nicht. Deine CPU führt nacheinander Instruktionen
aus, und wenn da ein Speicherzugriff drin ist, dann muss die CPU
entweder warten oder sie sortiert in Hardware um (Out-Of-Order
Execution). Das macht sie immer, und das kannst du nicht abschalten.
Hans W. schrieb:> Jetzt kann ich mir ja nicht sicher sein, ob alle non-volatilen> Sachen nicht vor oder hinter die volatilen Anweisungen rutscht,> wodurch diese dann quasi zeitgleich ausgeführt würden!
Was willst du überhaupt?
Real was erreichen oder rumtheoretisieren?
Hans W. schrieb:> Gibts den mem-clobber denn nicht auch in klein,> für eine bestimmte Variable?
Wenn sich der Speicher geändert hat, dann hat sich "irgendwas" im
Speicher geändert. Wenn sich nur eine bekannte Variable geändert hat,
dann caste sie nach volatile und fertig...
> Und wegen der Reihenfolge: Können non-volatile jetzt nicht dran vorbei> geschoben werden, oder nur nicht volatile?
Schau dir den Vortrag zu Atomics an. Da beschreibt er, was wo wie
geschoben werden darf und welche Speichermodelle was erlauben.
Hint: Das ist auf verschiedenen Architekturen unterschiedlich.
> Wenn das neu Laden mehr oder weniger die Funktionsweise des clobber ist,> was genau macht dann eine mem-barrier ??
Eine Barriere verhindert das Verschieben von Instruktionen vor oder
hinter eine bestimmte Grenze, so dass du Konsistenz wahren kannst. Ein
Clobber sagt an, dass die Konsistenz verloren gegangen ist.
c-hater schrieb im Beitrag #5427998:
> Und genau das ist, was ich tue. Wenn ich will, dass etwas sicher und> berechenbar funktioniert, dann schreibe ich es in Assembler...
Da bist Du nicht alleine.
Die Bemühung mit Hochsprache abstrahierend zu vereinfachen läuft leider
oft aufs Gegenteil hinaus.
Vernebelt durch die zusätzliche Komplexitätsebene die Sicht. Ist
aufwendiger zu erlernen und anzuwenden. Mit Asm bleibt alles in eigener
Hand.
Vorhandene Hardware ist stets vollumpfänglich mit maximalem Speed
programmierbar.
Niemand bezweifelt, dass Assembler ab und zu Vorteile bietet.
Aber dieses Attribut kann man auch allen anderen Sprachen anheften.
Uns sei es nur die Befriedigung persönlicher Vorlieben.
Ich möchte auf C++, auf meinen AVR Zwergen nicht verzichten.
Aber deswegen muss man/ich doch nicht zum C++ Priester werden, oder?
Arduino F. schrieb:> Uns sei es nur die Befriedigung persönlicher Vorlieben.
Reale Vorteile und Vorlieben sind verschiedene Paar Schuhe.
> Ich möchte auf C++, auf meinen AVR Zwergen nicht verzichten.
Du meinst die Arduino Syntax?
Zu C und C++ generell kann man nur sagen: Wäre auf AVRs nicht nötig
gewesen!
Arduino F. schrieb:> Dachte ich mir doch....
Und ich dachte mir Du meinst "nur" C++ welches Arduinoprogrammen
zugrundeliegt :)
Ok, mag einfacher und schneller programmiert als in Asm sein. Natürlich
zu gewissen Kosten.
Also ich bin noch etwas verwirrt, auch wenns schon besser ist als
vorher.. Ich denke, an nem konkreten Bsp. kann man das besser
nachvollziehen. Ein Code sagt evtl. mehr als 1000 Worte..
Also wie ca. würdet ihr den foglenden, 'nackten' Code umgestalten, damit
meine 3 Punkte von oben erfüllt werden? Gibt bestimmt mehr als eine
Lösung..
1
int fktVar; // SOLL: Berücksichtigen, dass Var (evtl) flüchtig (*)
2
void fkt(void){
3
fktVar++;
4
}
5
6
klasse obj; // SOLL: Berücksichtigen, dass Klasse flüchtig (aber ohne "volatile") (**)
7
int16_t var; // SOLL: Sicherstellen, dass var atomar benutzt (in main()),
8
// damit ISR nicht dazwischen funken kann! (***)
9
ISR(){
10
ADC_START(); // hiernach viel Zeit nutzen um Kram zu berechnen... // SOLL: Sicherstellen, dass ADC-Kram am Anfang & Ende
11
obj.write();
12
obj.read();
13
var++;
14
fkt();
15
16
...non-volatile Kram, der gerne umsortiert werden darf...
17
18
ADC_GET(); // schreibt z.B. den PWM-Wert
19
}
20
int main(void){
21
while(1){
22
obj.write();
23
//obj.read();
24
var++;
25
fkt();
26
}
27
}
(*): Da primitiver Typ: evtl einfach "volatile"
(**): Da Klasse: "volatile" unpraktisch! Sollte lieber 'normales' Objekt
sein, dass sich dann einfach an gegebener Stelle, nur wenn nötig, mit
dem Ram synchronisiert (in die eine oder andere Richtung, je nach
Bedarf).
Dazu bräuchte es einen nicht in der Reihenfolge vertauschbaren
"lies/schreib diese eine Variable aus/in den Ram"-Operation.
(***): Müssen volatile Vars nicht eigentlich implizit atomar ("am
Stück") sein? Wenn andernfalls die ISR kommt und mit einer halb
aktualisierten Var rechnet, kann ja eig. nur Müll dabei heraus kommen..
Ich würde mich über jede Lösungsmöglichkeit freuen! Wäre bestimmt
interessant zu sehen, wieviele Möglichkeiten hier auftauchen und welche
Vorteile sie jew. haben..
S. R. schrieb:> Wie wäre es mit einem Lock, also einer primitiven (und atomar> setz-/löschbaren) volatile-Variablen?
Mit einer atomar setz und löschbaren variable alleine kann man kein Lock
erstellen.
MaWin schrieb:> Mit einer atomar setz und löschbaren variable alleine> kann man kein Lock erstellen.
Stimmt, allerdings gingen wir hier ja von eingeschränkten Bedingungen
aus (Single-Threaded, nur zwischen ISR und Mainloop, etc.). Da reicht
das.
Worauf genau bezieht sich das mit dem 'Lock' jetzt?
Ist was wieder etwas neues, bei dem es schon daran scheitert, dass ich
nicht weiß, wie ich es benutze und was genau es tut.. Oder ein anderer
Name für etwas bekanntes?
Kannst du mir vielleicht das Prinzip einfach grob an Hand des Codes
zeigen? Das würde dann evtl. schon alle Fragen klären..
Ich glaube, ich habe bislang oft erst das Prinzip gelernt und dann
nachher gesagt:"Ah, das nennt sich xyz."..
S. R. schrieb:> nur zwischen ISR und Mainloop, etc.). Da reicht das.
Damit ist höchstens denkbar, dass der Interrupt sich ohne etwas zu tun
beendet, wenn dieses Flag gesetzt ist.
Und es ist aufwendiger umzuseten als ein ganz einfaches cli/sei.
>https://de.wikipedia.org/wiki/Lock
Hm, ich sehe nicht, wie uns das Prinzip weiter hilft.. Ist das nicht
noch schlimmer als ein volatile, wenn es dazu füren kann, dass die ISR
einfach 'ausfällt'?!
Ich meinte, wobei hilft der Lock denn jetzt genau? Und dann stellt sich
natürlich noch die Frage, wie man das in c/c++ zu realisieren hat..
Würde mir, glaube ich , wirklich sehr helfen, wenn wir eine Kopie des
obigen Codes so ändern (in Kurzschreibweise/Pseudocode), dass er
funktioniert.
Da in deinem Beispiel der einzige Unterschied zwischen einem normalen
main-loop Durchgang und dem ISR äußerst klein ist: Mache das ohne
Interrupts. Die sind kompliziert...
Prüfe einfach zu Beginn des Main-loops ob das entsprechende
Interrupt-Flag gesetzt ist und schiebe die nötigen Anweisungen dort ein.
Immer die gleiche, wiederkehrende Diskussion ...
Um meinen Ex-Prof. Meyer-Wachsmuth zu zitieren: "Da schreiben wir uns
ein kleines Assembler-Unterprogramm". Sagt eigentlich alles.
>Ich denke du solltest einfach mal ein paar grundlegende Artikel lesen:
Ein paar der Sachen sagen mir schon was. Ich habe das Gefühl, ich mache
nichts anderes als alles zu lesen. Das einmal praxisnah durchzuarbeiten,
hilft mir mehr.
>Prüfe einfach zu Beginn des Main-loops ob das entsprechende>Interrupt-Flag gesetzt ist und schiebe die nötigen Anweisungen dort ein.
Die Möglichkeit habe ich schon längst als funktionierend abgehakt. Ich
überlege auch schon, ob ich es letztendlich so mache (weil ich nicht
weiß wie sonst), aber dann wäre ich genau so schlau wie vorher ..also
keine Option. ;)
Ich finde den obigen Weg schöner: Ich möchter der ISR höchste Priorität
geben, sodass etwas sofort berechnet wird, wenn es angefragt wird und
nicht erst wenn es zufällig in der main() mal dran kommt.
Hier mal mein Vorschlag. So (ähnlich) stelle ich mir das vor:
1
volatile int fktVar; // SOLL = IST: Berücksichtigen, dass Var (evtl) flüchtig (*)
2
void fkt(void){
3
fktVar++;
4
}
5
6
klasse obj; // SOLL = IST: Berücksichtigen, dass Klasse flüchtig (aber ohne "volatile") (**)
7
int16_t var; // SOLL = IST: Sicherstellen, dass var atomar benutzt (in main()),
8
// damit ISR nicht dazwischen funken kann! (***)
9
10
ISR(){
11
12
ADC_START(); // hiernach viel Zeit nutzen um Kram zu berechnen... // SOLL = IST: Sicherstellen, dass ADC-Kram am Anfang & Ende
13
14
_KRAM_HOCH_SCHIEBEN_VERBOTEN();
15
16
17
obj.write(6);
18
_WRITE_TO_RAM(obj);
19
20
int x = obj.read();
21
22
var++;
23
fkt();
24
25
...non-volatile Kram, der gerne umsortiert werden darf...
26
27
28
_KRAM_RUNTER_SCHIEBEN_VERBOTEN();
29
30
ADC_GET(); // schreibt z.B. den PWM-Wert
31
32
}
33
34
int main(void){
35
while(1){
36
37
_READ_FROM_RAM(obj);
38
int x = obj.read();
39
obj.write(6);
40
_WRITE_TO_RAM(obj);
41
42
_ATOMAR_AN(); // ..ist das bei volatile überhaupt nötig?
43
var++;
44
_ATOMAR_AUS();
45
46
fkt();
47
48
}
49
}
Jetzt müsste man nurnoch die ganzen _PSEUDOCODE_MAKROS() durch richtiges
c ersetzen und allen wären glücklich. ....Wenn c denn etwas zum Ersetzen
anbietet...
>Echt jetzt? Du hast noch nicht alle Infos, die du brauchst?!?
Ich glaube, was jedenfalls noch fehlt, ist, wie man mit flüchtigen
Klassen umgehen soll offiziell(die man intern nicht ändern kann!)..
Ich bin mir nicht sicher!Ich habe zu viele, unsortierte Infos.. Dazu
kommt, dass man was, das nach A aussieht für B verwendet (was ich echt
nervig finde!). Außerdem, c11, oder nicht c11..?! Blicke da noch nicht
ganz durch. ..Darum die Idee mit dem Beispielcode. ..Das sollte helfen.
Hier mal meine "Infos" (würd mich wundern, wenn das alles so stimmt):
Geht nur auf die eine c11-Art (Oder vielleicht mit mem-barrier, die es
aber nicht gibt, also mit mem-clobber. Das allerdings funktioniert dann
nur mit volatile Vars):
_KRAM_HOCH_SCHIEBEN_VERBOTEN();
_KRAM_RUNTER_SCHIEBEN_VERBOTEN();
Mittels mem-clobber (oder so):
_READ_FROM_RAM(obj);
Keine Ahnung wie:
_WRITE_TO_RAM(obj);
Irgendwie mit atomics (wobei es da was von c und von avr gibt!?)
_ATOMAR_AN();
_ATOMAR_AUS();
...
_WRITE_TO_RAM(obj);
fehlt jedenfalls noch, selbst wenn der Rest passt.
>Einfach mal Google anwerfen.
Das musst du nicht. Ich habe den Code doch hier gepostet.. ;)
Man muss sogar nur nen Hand voll Makros ersetzen, wenn die 'Syntax'
soweit passt..
>Es gibt millionen Beispiele.
Das ist es ja gerade.. Millionen Beispiele aus millionen von Jahren von
millionen von Leuten, ob Pro, oder ..Semi-Pro..
Und weil 'dazu kommt, dass man was, das nach A aussieht für B
verwendet', findet man beim Googeln alles und nichts.
MaWin schrieb:>> wenn es dazu füren kann, dass die ISR>> einfach 'ausfällt'?!>> Ich weiß jetzt nicht wo du das her hast, aber ein Lock führt sicher> nicht dazu.
Also, da ist mir auch nicht ganz klar, wie Du erreichen willst, daß die
ISR bzw. deren Funktion ggf. nicht einfach "ausfällt".
In dem von Dir verlinkten Wiki-Artikel steht zum Lock:
1
Ist ein Mutex ausgesprochen, dann darf ein anderer Prozess oder Thread nicht zugreifen. Der andere Prozess/Thread kann dann
2
3
nichts weiter ausführen, als auf den Zugriff zu warten, der unter Mutex steht, oder
4
andere Aufgaben ausführen und auf den Zugriff warten oder
5
den Zugriff verwerfen.
D.h. main setzt z.B. das Lock, um zu verhindern, daß die ISR in einen
nicht-atomaren Variablenzugriff pfuscht. Nun wird ein Interrupt
ausgelöst und die ISR stellt fest, daß auf die Variable ein Mutex
ausgesprochen ist - und dann?
Thomas E. schrieb:> D.h. main setzt z.B. das Lock, um zu verhindern, daß die ISR in einen> nicht-atomaren Variablenzugriff pfuscht. Nun wird ein Interrupt> ausgelöst und die ISR stellt fest, daß auf die Variable ein Mutex> ausgesprochen ist - und dann?
Sowas hat man schon seit Ewigkeiten mit ner lockless queue gemacht.
Meistens als Ringbuffer implementiert. Das geht solange, wie man eine
einzige Quelle und einen einzigen Verbraucher hat, was bei ISR/main der
Fall ist.
Thomas E. schrieb:> D.h. main setzt z.B. das Lock, um zu verhindern, daß die ISR in einen> nicht-atomaren Variablenzugriff pfuscht. Nun wird ein Interrupt> ausgelöst und die ISR stellt fest,
Interrupt service routines werden normalerweise lokal vom lock-acquire
innerhalb des kritischen Bereichs deaktiviert.
Sonst hätte man ein Deadlock.
> daß auf die Variable ein Mutex> ausgesprochen ist - und dann?
Denn der zweite Prozess auf eine bereits gelockte Ressource trifft, dann
muss sie eben warten, bis der erste Prozess die Ressource wieder
freigibt.
Aber wenn der erste Prozess die Ressource freigibt, geht es mit dem
zweiten Prozess auch weiter.
Es fällt auf gar keinen Fall etwas aus.
Die ganze Diskussion um Locks hilft aber wenig auf AVR, weil der AVR ein
Uniprozessorsystem ist. Dort reicht eine einfache Deaktivierung der
Interrupts.
Nop schrieb:> Sowas hat man schon seit Ewigkeiten mit ner lockless queue gemacht.
Ok, aber ist das nicht schon wieder etwas ganz anderes, als ein simpler
Mutex bzw. Lock?
Mir ging es ja darum, daß MaWin schreibt, man könne mit dem
Lock-Mechanismus auf die Variable das Problem losen und das Lock würde
nicht zum möglichen Ausfall der ISR-Funktion führen.
Thomas E. schrieb:>> Sowas hat man schon seit Ewigkeiten mit ner lockless queue gemacht.>> Ok, aber ist das nicht schon wieder etwas ganz anderes, als ein simpler> Mutex bzw. Lock?
Es ist ein Mechanismus, der ohne Lock auskommen kann.
> Mir ging es ja darum, daß MaWin schreibt, man könne mit dem> Lock-Mechanismus auf die Variable das Problem losen
Ich denke nicht, dass ich das geschrieben habe. Ein Lock ist auf dem AVR
gar nicht nötig. Das Deaktivieren der lokalen Interrupts reicht hier.
Aber man sollte es als Programmierer locks trotzdem verstehen, da es
ein sehr grundlegender Mechanismus ist, der beim Verständnis von
nebenläufiger Programmierung hilft.
> und das Lock würde nicht zum möglichen Ausfall der ISR-Funktion führen.
Wenn Interrupts deaktiviert sind und irgendwann wieder reaktiviert
werden, dann werden aufgelaufene Interrupts nachgeholt.
Es fällt nichts aus.
MaWin schrieb:> Ich denke nicht, dass ich das geschrieben habe.
Sorry, dann habe ich das hier vielleicht fehlinterpretiert oder einen
falschen Zusammehang hergestellt:
MaWin schrieb:>> wenn es dazu füren kann, dass die ISR>> einfach 'ausfällt'?!>> Ich weiß jetzt nicht wo du das her hast, aber ein Lock führt sicher> nicht dazu.MaWin schrieb:> Denn der zweite Prozess auf eine bereits gelockte Ressource trifft, dann> muss sie eben warten, bis der erste Prozess die Ressource wieder> freigibt.
Eben - aber warten geht nicht innerhalb einer ISR - das blockiert ja
auch den unterbrochenen Main-Prozess und damit ist das System entgültig
blockiert.
Also m.E. fuktioniert dieser Mutex-Mechanismus grundsätzlich nicht
zwischen Main und ISR.
MaWin schrieb:> Dort reicht eine einfache Deaktivierung der> Interrupts.
Ich würde statt "reicht" sogar sagen, man muss es über die Sperrung der
Interrupts machen. Oder sich eben andere Lösungen wie Queues o.ä.
ausdenken.
Thomas E. schrieb:> Eben - aber warten geht nicht innerhalb einer ISR - das blockiert ja> auch den unterbrochenen Main-Prozess und damit ist das System entgültig> blockiert.>> Also m.E. fuktioniert dieser Mutex-Mechanismus grundsätzlich nicht> zwischen Main und ISR.
Genau. Und das habe ich ja bereits gesagt. Ein Lock muss auch lokale
Interrupts deaktivieren, damit es nicht zu diesem Deadlock kommt.
> MaWin schrieb:>> Dort reicht eine einfache Deaktivierung der>> Interrupts.>> Ich würde statt "reicht" sogar sagen, man muss es über die Sperrung der> Interrupts machen. Oder sich eben andere Lösungen wie Queues o.ä.> ausdenken.
Ein (primitives) Lock deaktiviert auch lokale Interrupts.
Weil es auf AVR nur eine CPU gibt, reicht es auf AVRnur das zu tun.
Ich hoffe das war jetzt endlich verständlich. :)
Und jetzt liest du bitte die Artikel. Darin sind auch Beispiele.
Bei der Entwicklung von Software, reicht es nicht, nur wie ein
Codeäffchen Programmschnipsel zu kopieren und einzufügen. Man muss die
zugrundelegenden Algorithmen und Mechanismen auf abstrakter Ebene
verstehen. Genau deshalb ist dein Ansatz "ersetzt mir mal bitte hier die
Makros" falsch und nicht zielführend. Wenn du die Mechanismen verstanden
hast, dann weißt du automatisch selbst, was dort eingetragen werden
muss.
MaWin schrieb:> Wenn Interrupts deaktiviert sind und irgendwann wieder reaktiviert> werden, dann werden aufgelaufene Interrupts nachgeholt.> Es fällt nichts aus.
Das sehe ich anders - nur das jeweils letzte Interrupt-Ereignis eines
Interrupt-Typs wird bearbeitet. Es gibt keine "Interrupt-Queue" pro
Interrupt-Typ.
Dieter F. schrieb:> MaWin schrieb:>> Wenn Interrupts deaktiviert sind und irgendwann wieder reaktiviert>> werden, dann werden aufgelaufene Interrupts nachgeholt.>> Es fällt nichts aus.>> Das sehe ich anders - nur das jeweils letzte Interrupt-Ereignis eines> Interrupt-Typs wird bearbeitet. Es gibt keine "Interrupt-Queue" pro> Interrupt-Typ.
Wieviele INT's (eines "Types") sind denn innerhalb des bzgl. Interrupt
zu sperrenden Zeitraums zu erwarten? Wie lange wird der gesperte
Zeitraum vermutlich sein?
Auf AVR in typischem Queue schreiben/lesen wohl eins und >μs.
Carl D. schrieb:> Wieviele INT's (eines "Types") sind denn innerhalb des bzgl. Interrupt> zu sperrenden Zeitraums zu erwarten? Wie lange wird der gesperte> Zeitraum vermutlich sein?> Auf AVR in typischem Queue schreiben/lesen wohl eins und >μs.
Ich verfüge leider über keine Glaskugel - und kenne auch keinen
"typischen Queue".
>Ja dann mach das doch.
Habe ich schon längst (s.o.)... Und hast du vielleicht auch was konkret
DAZU beizutragen?
>Warum sollen wir dir dein Programm entwickeln?
Ja übertreib mal nicht. Ein paar Makros machen noch keinen Programm.
Wobei, für jemanden der nur in asm schreibt, ist das wahrscheinlich
schon ein ganzes...
K.A., wen du mit 'wir' meinst, aber dann rede wenigstens von 'euch',
denn ich bin offenbar nicht der einzige, den das interessiert.
Nen Programmierer, der sich gegen Codeschreiben sträubt. Was es nicht
alles gibt.
>Die ganze Diskussion um Locks hilft aber wenig auf AVR, weil der AVR ein>Uniprozessorsystem ist. Dort reicht eine einfache Deaktivierung der>Interrupts.
...Wegen solcher Meinungsverschiedenheiten, wäre ein Bsp. gut.. Sieh die
Programmiersprache doch mal als ...Sprache... an, in der man sich nicht
nur mit der Maschine unterhalten kann!
>Es fällt nichts aus.
Es wird verzögert. Ein temporärer Ausfall! Auch nicht optimal. Und die
Queue ist nicht besonders lang...
> Genau deshalb ist dein Ansatz "ersetzt mir mal bitte hier die Makros" falsch und
nicht zielführend.
1. Thomas Elger =/= Hans W.
2. Schwachsinn! Dass man letztlich die Maschine verstehen sollte ist
klar. Nur wie man da hin kommt, ist wohl nicht deine Angelegenheit. Dass
es nicht zielführend sei, ist komplett falsch. ..Ich wäre schon weiter.
Und du hättest dir viel schreibarbeit gespart (statt es einfach zu tun,
redest du lieber drum herum - alles klar).
Außerdem wären es 2 Schritte auf einmal: 1. Was?, 2. Wie implementiere
ich es in C? (Die eig Herausforderung...)
Zielführender geht gar nicht!
Wie gesagt, versuch mal, die Programmiersprache als Sprache zu sehen.
Wie gesagt, die ISR soll höchste Priorität haben! Wenn ich das mit Locks
mache, kann ich auch einfach Flags setzen...
Entweder sehe ich das falsch, oder das wurde bei dem Vorschlag nicht
berücksichtigt.
Hans W. schrieb:> Wie gesagt, die ISR soll höchste Priorität haben!
Dann darfst du Interrupt niemals deaktivieren.
Konsequenz: Du darfst niemals kritische Sektionen in deinem Programm
haben.
Konsequenz: Beschäftige dich mit lockless queues o.ä.
Hans W. schrieb:> Es wird verzögert. Ein temporärer Ausfall!
Die Definition von "Echtzeitsystem" besagt nicht, dass das System sofort
schnellstmöglich reagieren muss. Sie besagt, dass das System innerhalb
einer vorgegebenen Zeit reagieren muss.
Willst du innerhalb von möglichst wenigen Takten reagieren können, sind
Interrupts sowieso die falsche Wahl. Oder du machst grundsätzlich was
falsch.
Dieter F. schrieb:> Das sehe ich anders - nur das jeweils letzte Interrupt-Ereignis eines> Interrupt-Typs wird bearbeitet. Es gibt keine "Interrupt-Queue" pro> Interrupt-Typ.
Wenn die vorhandene CPU-Zeit nicht ausreicht, um alles zu rechnen bevor
der nächste Interrupt kommt, dann hast du einen zu kleinen Controller
gewählt, oder deinen Algorithmus falsch gewählt.
Mit Locks selbst und dass diese irgendwie "Interrupts verlieren" hat es
jedenfalls nichts zu tun.
Hans W. schrieb:>>Es fällt nichts aus.> Es wird verzögert. Ein temporärer Ausfall!
Du willst um alles in der Welt recht haben, auch wenn du falsch liegst.
Richtig?
MaWin schrieb:> Wenn die vorhandene CPU-Zeit nicht ausreicht, um alles zu rechnen bevor> der nächste Interrupt kommt, dann hast du einen zu kleinen Controller> gewählt, oder deinen Algorithmus falsch gewählt.MaWin schrieb:> Du willst um alles in der Welt recht haben, auch wenn du falsch liegst.> Richtig?
Schön, dass Du Dir selbst antwortest :-)
>Dann darfst du Interrupt niemals deaktivieren.
Mir würde hier tatsächlich auch 'so gut wie niemals' reichen. Sprich,
für 1-2 kurze Operationen wird das schon gehen (z.B. für atomaren
Zugriff), aber nicht für ein halbes Programm..
Besonders wenn es nötig ist; ich weiß noch nicht genau:
Muss ich jetzt in der main() cli() und sei() um >8bit-volatiles, die in
der ISR benötigt werden, setzen? ..Damit sie nicht von der ISR
zerpflückt werden?
>Konsequenz: Beschäftige dich mit lockless queues o.ä.
Werd ich mir anschauen, danke.
Kennt sich hier jemand zufällig mit gcc inline asm aus?!? ..Und kann mir
kurz erklären, bei welcher Aufgabe man dem Compiler hilft, wenn man
Input- und Output-Constraints setzt?
>Willst du innerhalb von möglichst wenigen Takten reagieren können, sind>Interrupts sowieso die falsche Wahl. Oder du machst grundsätzlich was>falsch.
Und was ist mit Timer-IRQs? Genau dazu ist ein Timer doch da, bzw.
dessen IRQ. Jedenfalls möchte ich ihn so gut es geht dazu benutzen.
>Wenn die vorhandene CPU-Zeit nicht ausreicht, um alles zu rechnen bevor>der nächste Interrupt kommt, dann hast du einen zu kleinen Controller>gewählt, oder deinen Algorithmus falsch gewählt.
EBEN!.......
>Mit Locks selbst und dass diese irgendwie "Interrupts verlieren" hat es>jedenfalls nichts zu tun.
Hats ja auch nicht.
Wobei... "verlieren" heißt ja nicht, dass es weg ist, sondern, dass es
immer da ist wo man selbst IM MOMENT nicht ist.. Hmmm
>Schön, dass Du Dir selbst antwortest :-)
Mir antwortet er jedenfalls nicht wirklich...
Hans W. schrieb:>> Dann darfst du Interrupt niemals deaktivieren.> Mir würde hier tatsächlich auch 'so gut wie niemals' reichen.
Dann hat die ISR nicht mehr die garantiert höchste Priorität...
Hans W. schrieb:> Muss ich jetzt in der main() cli() und sei() um >8bit-volatiles,> die in der ISR benötigt werden, setzen? ..Damit sie nicht von der ISR> zerpflückt werden?
Ja, denn alles, was nicht inhärent atomisch ist, muss vor asynchronen
Zugriffen geschützt werden.
Hans W. schrieb:> Kennt sich hier jemand zufällig mit gcc inline asm aus?!? ..Und kann mir> kurz erklären, bei welcher Aufgabe man dem Compiler hilft, wenn man> Input- und Output-Constraints setzt?
Du hilfst dem Register-Allocator, wenn du ihm die Freiheit gibst, statt
eines bestimmten Registers (z.B. r16) selbst ein Register (aus einer
Gruppe, z.B. r16..r31) aussuchen zu dürfen. Dann braucht er
möglicherweise nichts umherkopieren, wenn der Wert schon in einem
Register steht.
Außerdem hilfst du dem Codegenerator, wenn du ihm erlaubst, eine
Compiletime-Konstante fix einzusetzen, statt dafür ein Register
verschwenden zu müssen.
Und zu guter Letzt hilfst du auch noch dem Compiler, wenn er weiß,
welche Register (und evtl. der RAM) von deinem Assemblercode zerstört
werden - und vor allem, welche nicht. Denn dann braucht er nichts
sichern.
Hans W. schrieb:>>Willst du innerhalb von möglichst wenigen Takten reagieren können, sind>>Interrupts sowieso die falsche Wahl.> Und was ist mit Timer-IRQs?
Mit einem Timer-IRQ kannst du in regelmäßigen Abständen eine Leitung
abfragen und darauf reagieren.
Mit einem Pin-Change-Interrupt kannst du in einigen Takten auf ein
Leitungsereignis reagieren (Stichwort Interrupt-Latenz).
Willst du in möglichst wenigen (einstellig) Takten auf ein Ereignis
reagieren, musst du die Leitung pollen und darfst keine Unterbrechungen
haben.
Und wenn das noch immer ein Problem ist, hast du die falsche Hardware.
> Genau dazu ist ein Timer doch da, bzw. dessen IRQ.> Jedenfalls möchte ich ihn so gut es geht dazu benutzen.
Wieviel ist "möglichst wenig", wieviel ist "sehr schnell", wie kritisch
ist deine Echtzeit, und wieviel ist an der ganzen Diskussion nur
Gedankenexperiment? Verschiedene Anforderungen erfordern verschiedene
Lösungen.
>Dann hat die ISR nicht mehr die garantiert höchste Priorität...>Ja, denn alles, was nicht inhärent atomisch ist, muss vor asynchronen>Zugriffen geschützt werden.
Eine höhere Priorität geht dann doch nciht, ohne zu riskieren, dass das
Programm anfängt zu spinnen..?!
>Du hilfst dem Register-Allocator, wenn du ihm die Freiheit gibst, statt>eines bestimmten Registers (z.B. r16) selbst ein Register (aus einer>Gruppe, z.B. r16..r31) aussuchen zu dürfen. Dann braucht er>möglicherweise nichts umherkopieren, wenn der Wert schon in einem>Register steht.
Hm, meine ich was anderes? Ich dachte dazu nehme ich die
[Variablennamen] von C.
Ich eine eigentlich sowas wie "r", "+rm", "=m",... Mehrere 'Buchstaben'
werden oder-verknüpft, soviel weiß ich schon.
Kannst du mit einmal erklären, was "+r" und "=m" letztlich für
Konsequenzen haben? Dann hab ich es schwarz-auf-weiß und muss mich nicht
fragen, ob ich es richtig verstanden habe..
>Außerdem hilfst du dem Codegenerator, wenn du ihm erlaubst, eine>Compiletime-Konstante fix einzusetzen, statt dafür ein Register>verschwenden zu müssen.
-> Bei den Inputs : "i" ???
Kann man auch #0x123 schreiben?
>Und zu guter Letzt hilfst du auch noch dem Compiler, wenn er weiß,>welche Register (und evtl. der RAM) von deinem Assemblercode zerstört>werden - und vor allem, welche nicht. Denn dann braucht er nichts>sichern.
->"Clobbering"?!
Ich kann ja "r15",.., oder "memory" nutzen.
Warum kann ich nicht bestimmten ram, sonder nur den ges. 'memory'
clobbern?
Und kann ich statt "r15" auch "Das Register dieser C-Variablen"
clobbern?
>Mit einem Timer-IRQ kannst du in regelmäßigen Abständen eine Leitung>abfragen und darauf reagieren.>Willst du in möglichst wenigen (einstellig) Takten auf ein Ereignis>reagieren, musst du die Leitung pollen und darfst keine Unterbrechungen>haben.
Achso nein, ich meinte lediglich als interner Taktgenerator für Code,
der dann für einen Output sorgt.
Ich wollte mich erstmal um Output-Latenzen kümmern, bevor noch der Input
dazu kommt.
>Wieviel ist "möglichst wenig", wieviel ist "sehr schnell", wie kritisch>ist deine Echtzeit, und wieviel ist an der ganzen Diskussion nur>Gedankenexperiment?
Einen dringenden Anwendungsfall gibt es zwar noch nicht, aber das könnte
sich ja schnell ändern. Mein Codebeispiel von oben wartet immernoch auf
Beachtung. Das wäre so das konkreteste was mir einfällt. Der Code
beschäftigt mich jedenfalls.
Hans W. schrieb:> Eine höhere Priorität geht dann doch nciht, ohne zu riskieren, dass das> Programm anfängt zu spinnen..?!
Nö... du kannst die Kommunikation zwischen ISR und Mainloop auch mit
einer Queue abwickeln, deren Indizes ausschließlich uint8_t (und damit
atomisch) sind. Dann hast du garantiert zu jedem Zeitpunkt Konsistenz.
Hans W. schrieb:> Hm, meine ich was anderes? Ich dachte dazu nehme ich die> [Variablennamen] von C.
Dein Assemblercode referenziert Register, keine Variablen. Die
Verknüpfung beider machst du über den Input-Parameter.
Bestimmte Instruktionen können nur mit bestimmten Registern arbeiten,
z.B. kann ADIW nur auf r24:r25, r26:r27, r28:r29 und r30:r31 angewendet
werden, ADD aber auf alle Register. Das musst du dem Compiler natürlich
sagen, sonst nimmt er möglicherweise ein unpassendes Register.
Hans W. schrieb:> Warum kann ich nicht bestimmten ram,> sonder nur den ges. 'memory' clobbern?
Frag die gcc-Entwickler.
Wenn man inline-asm für Performance nutzt, sollte man ohnehin keine
Seiteneffekte haben (d.h. es clobbert nix oder nur Register), und wenn
man den CPU-Status beeinflussen will, dann betrifft es sowieso
"irgendwas" im Speicher.
Variablen leben meist in Registern, nicht im Speicher. Deren Speicher
muss nicht geclobbert werden (wenn sich das Register ändert ist, ist der
Speicher sowieso ungültig). Wenn du eine Linked-List o.ä. änderst, dann
sind Pointer ungültig, deren Adressen du dem Compiler nicht sinnvoll
(zur Compilezeit!) mitteilen kannst, und welche Zwischenwerte der
Compiler in Registern gehalten hat, um die Adressen auszurechnen, weißt
du sowieso nicht.
> Und kann ich statt "r15" auch "Das Register dieser> C-Variablen" clobbern?
Sicher. Dein Schnipsel benutzt Variablen ja ausschließlich über
Input-Parameter (sonst machst du was falsch), und die kannst du auch
angeben. Output-Parameter gelten immer als geclobbert (werden
logischerweise immer überschrieben).
Lies einfach mal die gcc-Doku zum Inline-Assembler.
>Nö... du kannst die Kommunikation zwischen ISR und Mainloop auch mit>einer Queue abwickeln, deren Indizes ausschließlich uint8_t (und damit>atomisch) sind. Dann hast du garantiert zu jedem Zeitpunkt Konsistenz.
Auch lockless?
Ich glaube, ganz kommt man um asm nicht herum, wenns um µCs geht..
>Lies einfach mal die gcc-Doku zum Inline-Assembler.
Das habe ich mir gestern Abend noch angeschaut (die aktuelle Version,
nicht die von ~2002..).
Lass mich mal laut denken..
- C-Variablen steht immer Ram zu, auch wenn sie evtl (noch) keinen
haben. Und wenn sie erstmal eine Adresse im Speicher haben, bleibt diese
auch erhalten (aber nicht notwendigerweise ihr Inhalt), solange die Var
lebt.
- ASM-Befehle können:
(Ram, Register) -> Register, und
(Ram, Register) -> Ram
- Die Input-Constraints sagen dem Compiler, in welche Registerklasse die
cVar geladen werden soll.
...Aber was sagt ein "m"?
- Die Output-Constraints sagen, wo sie zu finden sein wird, entweder in
einem Register, oder bei ihrer Adresse.
...Ich nutze "m", aber mein Ergebnis steht in einem Register. Kann ich
das einfach so angeben und der Compiler kümmert sich um den Rest, oder
muss ich dass dann noch selbst in den Ram kopieren?
- Warum gibts es bei den Outputs sowas wie "+" und "&" ?
Stimmt etwas davpn?
Weiß jemand, was der Compiler alles über eine cVar weiß? Ich weiß, sie
ist z.B. "int" "const" und "42". Aber ich weiß auch, der Compiler weiß
mehr, aber was?
Hans W. schrieb:>>Nö... du kannst die Kommunikation zwischen ISR und Mainloop>> auch mit einer Queue abwickeln, deren Indizes ausschließlich>> uint8_t (und damit atomisch) sind.> Dann hast du garantiert zu jedem Zeitpunkt Konsistenz.> Auch lockless?
Klar, was glaubst du, wie man lockless Datenstrukturen implementiert.
:-)
> Ich glaube, ganz kommt man um asm nicht herum, wenns um µCs geht..
Den glauben kannst du in der Kirche lassen. Aber lassen wir das.
> Lass mich mal laut denken..> - C-Variablen steht immer Ram zu, [...]
Ja, laut Standard schon. Praktisch wird der Compiler (bei aktiver
Optimierung) aber weder Speicher noch Zeit durch Speicherzugriffe
verschwenden, wenn er es vermeiden kann.
Bei globalen Variablen ist das nicht der Fall.
> Und wenn sie erstmal eine Adresse im Speicher haben, bleibt diese> auch erhalten (aber nicht notwendigerweise ihr Inhalt), solange die Var> lebt.
Die Adresse im Speicher existiert immer, die dazugehörige Variable aber
nicht. Eine lokale Variable wird zudem auf dem Stack angelegt und dessen
Adresse ändert sich dynamisch im Programmverlauf.
> - ASM-Befehle können:> (Ram, Register) -> Register, und> (Ram, Register) -> Ram
Das ist architekturabhängig, außerdem hast du Immediates und die
verschiedenen sonstigen Adressierungsmodi vergessen.
Was genau geht, steht im dazugehörigen Datenblatt.
> - Die Input-Constraints sagen dem Compiler, in welche Registerklasse die> cVar geladen werden soll.
Die Input-Constraints sagen dem Compiler, in was für einer
Registerklasse die Eingabe-Variablen erwartet werden.
> ...Aber was sagt ein "m"?
Dass da eine Speicheradresse erwartet wird?
Guckst du Compiler-Doku, weil ist architekturabhängig.
> - Die Output-Constraints sagen, wo sie zu finden sein wird, entweder in> einem Register, oder bei ihrer Adresse.
Genau. Es darf übrigens auch mehr als eine Ausgabevariable geben.
> ...Ich nutze "m", aber mein Ergebnis steht in einem Register. Kann ich> das einfach so angeben und der Compiler kümmert sich um den Rest, oder> muss ich dass dann noch selbst in den Ram kopieren?
Der Compiler wird die Constraints entweder honorieren oder den Versuch
mit einer Fehlermeldung abbrechen. Aus genau diesem Grund kannst du
übrigens auch unterschiedliche Klassen für die Input-Constraints angeben
(z.B. "trage hier den Wert der Variablen ein, wenn du ihn weißt,
andernfalls trage das Register ein, wo die Variable steht".
> - Warum gibts es bei den Outputs sowas wie "+" und "&" ?
Guckst du Compiler-Doku, was die machen.
> Weiß jemand, was der Compiler alles über eine cVar weiß? Ich weiß, sie> ist z.B. "int" "const" und "42". Aber ich weiß auch, der Compiler weiß> mehr, aber was?
Ob sie zu einem gewissen Zeitpunkt lebendig ist ("ihr Wert wird noch
benötigt"), wo sie lebt (in deinem Fall vermutlich "nirgends", weil die
42 kann der Compiler jederzeit neu erzeugen), und vermutlich noch ein
paar Dinge mehr.
Schau in den Sourcode eines optimierenden Compilers, dann weißt du es.
Oder geh in die nächstgelegene Uni und zieh dir einen Compiler-Kurs
rein. Praktisch relevant ist das für dich jedenfalls nicht, wenn du
schon so fragst. :-)
>Klar, was glaubst du, wie man lockless Datenstrukturen implementiert.>:-)
Ich habe mir das zwischendurch mal angesehen. Beim Multicore ok, aber
führt das beim Singlecore (mit Interrupts als asynchrone Quelle) nicht
trotzdem zu Locks, wegen der Warteschleife, während der nichts anderes
passieren kann!?
Jedenfalls danke nochmal. Ich habe mit inline Assember begonnen, ist ja
doch übersichtlicher als ich dachte. Selbst wenn ich es nicht brauchen
werde, ist es doch ganz beruhigend, nachschauen und verstehen zu können,
ob der Schwachsinn, den der Compiler verzapft hat, ungefähr dem
Schwachsinn entspricht, den man sich vorgestellt hat..
>die vermeidet eine Warnung über die nicht genutzte Variable __s
Mehr nicht? Was soll die Funktion dann überhaupt..
Ok, eine letzte Sache habe ich mich doch noch gefragt: Wenn ich eine Var
'normal' deklariere, aber überall nach const/volatile caste, ist das
dann identisch mit einer const/volatile deklarierten?
Manchmal tauscht man zwischen ISR und MainLoop ja einfach nur ein ohe
Interruptsperre nicht atomar auslesbares Datum aus, z.B. (Arduino) ein
32bit-Timer-Wert der Millisekunden seit Reset enthält.
Will man den "lockfrei" lesen, braucht man ein atomar lese-/Schreibers
Flag, z.B. ein Bit in GPIOR0 bei AVRs, das löscht man in der MainLoop
und liest den Wert in eine temp. Variable, in der ISR setzt man immer
das "es ist was geändert"-Bit von weiter oben. Ist nun in der MainLoop
nach dem Zugriff auf den int32_t das Bit noch gelöscht, ist alles ok,
wenn nicht, einfach noch mal machen. Der 2.Versuch wird eher μs denn ms
später passieren, also erfolgreich sein.
Man braucht keine Interrupts zu sperren, hat also auch keine Verzögerung
selbiger, abgesehen von den HW bedingten und der Interrupt-Sperre
innerhalb einer ISR beim AVR.
Hans W. schrieb:> Mehr nicht? Was soll die Funktion dann überhaupt..
das, was sie eigentlich tut, tut sie obendrüber.
Interrupts wieder an und eine memory barrier dahinter, damit der
Compiler nicht auf die Idee kommt, beim Optimieren irgendwas aus der
critical region rauszuschieben.
Markus F. schrieb:> Interrupts wieder an und eine memory barrier dahinter, damit der> Compiler nicht auf die Idee kommt, beim Optimieren irgendwas aus der> critical region rauszuschieben.
Ist aber leider fehlerhaft.
1
sei();
2
//hier ist schon außerhalb der critical section, aber der Compiler bis kann hierher schieben.
3
__asm__volatile("":::"memory");
Da sei() aber selbst ein barrier hat, fällt dieser Bug nicht auf.
Carl D. schrieb:> Will man den "lockfrei" lesen, braucht man ein atomar lese-/Schreibers> Flag, z.B. ein Bit in GPIOR0 bei AVRs,
Dafür brauchst du kein I/O-Bit, eine 8 Bit-Variable geht auch (und trägt
mehr Informationen). Ich denke immer an den typischen UART-Ringpuffer:
Die ISR schreibt in eine Variable, die Mainloop in eine andere - und
zusammen synchronisieren die den Zugriff auf die eigentlichen Daten.
S. R. schrieb:> Carl D. schrieb:>> Will man den "lockfrei" lesen, braucht man ein atomar lese-/Schreibers>> Flag, z.B. ein Bit in GPIOR0 bei AVRs,>> Dafür brauchst du kein I/O-Bit, eine 8 Bit-Variable geht auch (und trägt> mehr Informationen). Ich denke immer an den typischen UART-Ringpuffer:> Die ISR schreibt in eine Variable, die Mainloop in eine andere - und> zusammen synchronisieren die den Zugriff auf die eigentlichen Daten.
Wenn ich aber Information übertragen muß, die in kein 8-Bit-Register
passt, ich aber ein zusätzliches Flag brauche, dann ist BIT die beste
Einheit.
Whatabout es ist ein anderes Problem ...
MaWin schrieb:> Da sei() aber selbst ein barrier hat, fällt dieser Bug nicht auf.
da ist m.E. kein Bug. Zumindest nicht, wenn man die Funktion wie gedacht
verwendet.
1
ATOMIC_BLOCK(ATOMIC_FORCEON)
__iSeiParam ist nicht zum direkten Aufruf gedacht. Das ist eine
__cleanup__-Funktion, die gerufen wird, wenn SREG_SAVE out of scope
geht. Da kann der Compiler nichts dazwischen schieben.
>Deshalb!
:D mehr davon
@Carl Drexler:
Das muss ich mir noch mal durchlesen.. Kann man das grob als
Doppelpuffer ansehen?!
>das, was sie eigentlich tut, tut sie obendrüber.
Ja schon. Ich habe mich etwas zu ungenau ausgedrückt. Ich meinte
eigentlich:
Warum hat die Funktion überhaupt ein Argument?
Hans W. schrieb:>> @Carl Drexler:> Das muss ich mir noch mal durchlesen.. Kann man das grob als> Doppelpuffer ansehen?!
Das wesentliche ist, den Fehler nicht zu vermeiden, denn das kostet ja
Interrupt-Response-Zeit, sondern ihn zu erkennen und richtig darauf zu
reagieren, durch (weit vor der nächsten erwartbaren Änderung) einfach
noch einen Versuch starten.
Ich muss doch noch mal nachfragen:
Sagen wir, ich habe ein Timer-IRQ jede Sekunde und eine Var ">uint8_t":
- mit cli()/sei() verschiebt sich die ISR um einige µs bis Var komplett
- mit Lock wird das eine IRQ gar nicht ausgeführt und aufs nächste
gawartet..
- lockless wird es das IRQ mit dem alten Wert von Var
ausgeführt..solange Var nicht komplett ist
...Ich glaube, das mit den Locks stimmt nicht so ganz...?!
Markus F. schrieb:> Da kann der Compiler nichts dazwischen schieben.
Natürlich kann er das. Oder was soll es den verhindern?
Und wenn er es nicht kann, warum gibt es die Barrier dann überhaupt?
MaWin schrieb:> Natürlich kann er das. Oder was soll es den verhindern?
weil attribute(cleanup(function)) eben als cleanup ausgeführt wird (wenn
__iSeiParam out of scope geht, also bei Blockende). Welchen Sinn würde
das Attribut machen, wenn der Compiler da noch was dazwimschen
schieben dürfte?
Markus F. schrieb:> Welchen Sinn würde> das Attribut machen, wenn der Compiler da noch was dazwimschen> schieben dürfte?
Warum sollte er das nicht machen dürfen?
Und warum sollte man das Barrier falsch machen, wenn es auch richtig
geht?
Hans W. schrieb:> Ich muss doch noch mal nachfragen:
Grundsätzlich:
Wenn du gleichzeitig mit mehreren Aktoren auf eine Datenstruktur
zugreifst, treten die sich auf die Füße und machen möglicherweise die
Datenstruktur kaputt.
Ein Lock verhindert solche Probleme, weil du als Programmierer manuell
sicherstellst, dass solche Zugriffe nicht stattfinden können.
Ein lockloser Ansatz sorgt dafür, dass solche Zugriffe erkannt werden
und nicht zu Fehlern führen. Üblicherweise macht man das, indem man
genau bestimmt, wie (in welcher Reihenfolge) die Datenstruktur benutzt
wird. Dazu braucht man Garantien, die C im Normalfall nicht gibt - daher
die Atomics.
Lockless heißt nicht, dass solche Zugriffe auch gelingen (im
Zweifelsfall gibt's statt Deadlock dann Livelock, weil der Versuch
unendlich oft wiederholt wird).
Mit cli/sei findet dein Interrupt während des kritischen Bereichs nicht
statt. Mit einem Lock ohne cli/sei findet der Interrupt zwar statt, darf
aber nichts tun, wenn main das Lock hält - die Daten sind verloren. Ein
Lock mit cli/sei ist in deinem Beispiel witzlos.
Mit einer Queue (locklos) kann der Interrupt seine Daten garantiert
jederzeit in die Queue schreiben. Ist die Queue voll, sind die Daten
verloren. Die Queue wird durch zwei 8 Bit-Variablen (weil: auf AVR
atomic) synchronisiert und kann daher maximal 255 Elemente halten.
Andere locklose Datenstrukturen funktionieren in deinem Fall nicht, weil
u.U. sichergestellt sein muss, dass der andere Zugreifer (dem du gerade
in die Parade fährst, d.h. die Mainloop) seinen Zugriff
fortsetzen/beenden können muss - innerhalb einer ISR kann die Mainloop
nicht arbeiten. Ob das ein Problem ist, hängt von der Datenstruktur ab.
PS: In meiner Welt ist es "der Interrupt".