Forum: Mikrocontroller und Digitale Elektronik AVR-C Anweisungsreihenfolge NICHT dem Compiler überlassen..


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Hans W. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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
...
bla1();
cli(),
bla2();
bla3();
sei();
bla4();
...

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..) ??

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Jim M. (turboj)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Keiner N. (nichtgast)


Bewertung
0 lesenswert
nicht lesenswert
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

von Peter D. (peda)


Bewertung
1 lesenswert
nicht lesenswert
Hans W. schrieb:
> ...
> bla1();
> cli(),
> bla2();
> bla3();
> sei();
> bla4();
> ...

Schau mal in die atomic.h.

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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!

von Arduino Fanboy D. (ufuf)


Bewertung
-3 lesenswert
nicht lesenswert
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!

: Bearbeitet durch User
von Manfred F. (manfred_f)


Bewertung
-1 lesenswert
nicht lesenswert
Arduino F. schrieb:
> Und du spielst hier den *****....
> Warum?
> Was hast du davon?

Heute ist Freitag...

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.
int x = furchtbarAufwaendigeRechnung();
/* 30 Minuten minus Berechnungsdauer abwarten  */
while(!printNow()) Wait();

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
int x = volatile_variable;
x = x*5 + x*x + 3;
...
volatile_variable = x;

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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..
void
_delay_loop_1(uint8_t __count)
{
  __asm__ volatile (
    "1: dec %0" "\n\t"
    "brne 1b"
    : "=r" (__count)
    : "0" (__count)
  );
}

void
_delay_loop_2(uint16_t __count)
{
  __asm__ volatile (
    "1: sbiw %0,1" "\n\t"
    "brne 1b"
    : "=w" (__count)
    : "0" (__count)
  );
}
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??

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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...

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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: 
Youtube-Video "C++ and Beyond 2012: Herb Sutter - atomic Weapons 1 of 2".

> 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.
char flag_set = 0;

int main() {
  while(!flag_set) { /* warte */ }
  puts("Hallo!");
}

void ISR_Handler() {
  flag_set = 1;
}

Mit Optimierungen hast du in main() eine Endlosschleife, weil flag_set 
nicht volatile ist.

von rtfm (Gast)


Bewertung
0 lesenswert
nicht lesenswert

von Hans W. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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..

von Hans W. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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...

von Hans W. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
Eine kontrete Frage wäre:
Wie gehe ich mit Klassen um, die nicht volatile sein können, da sie 
keine volatilen Methoden haben?

von Christian H. (netzwanze) Benutzerseite


Bewertung
1 lesenswert
nicht lesenswert
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.

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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...

: Bearbeitet durch User
von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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??

von Arduino Fanboy D. (ufuf)


Bewertung
-2 lesenswert
nicht lesenswert
Hans W. schrieb:
> Wie kann ich dieses Verhalten erzielen??

Umsteigen auf Pascal

von Christian H. (netzwanze) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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:
volatile uint16_t var;

ISR(){
   ...var...;
}

main(){
   cli(); // nötig damit atomar??
   ...var...;
   sei();
}


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?

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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?

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

Beitrag #5427998 wurde von einem Moderator gelöscht.
von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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
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.

: Bearbeitet durch User
von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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.

von A. S. (achs)


Bewertung
0 lesenswert
nicht lesenswert
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.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Ernst (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
S. R. schrieb:
> Wie willst du mittig in den Ausdruck ein cli() (oder einen anderen
> clobber) schieben?

Das macht der Compiler, weil er denkt, es ist dann besser...
https://www.nongnu.org/avr-libc/user-manual/optimization.html#optim_code_reorder

: Bearbeitet durch User
von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
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?

: Bearbeitet durch User
von Ernst (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
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!

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
Ernst schrieb:
> Du meinst die Arduino Syntax?
Was soll das sein?

von Ernst (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Arduino F. schrieb:
> Ernst schrieb:
> Du meinst die Arduino Syntax?
>
> Was soll das sein?

https://www.arduino.cc/reference/en/

von Arduino Fanboy D. (ufuf)


Bewertung
0 lesenswert
nicht lesenswert
Ernst schrieb:
> Arduino F. schrieb:
>> Ernst schrieb:
>> Du meinst die Arduino Syntax?
>>
>> Was soll das sein?
>
> https://www.arduino.cc/reference/en/

Dachte ich mir doch....
Du verwendest den Begriff Syntax falsch.

Die Funktionen eines Frameworks folgen vielleicht einer Syntax.
Müssen sie auch!
Sind aber keine Syntax.
Siehe: 
https://de.wikipedia.org/wiki/Syntax#Die_Syntax_formaler_Sprachen_(formale_Syntax)

von Ernst (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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..
int fktVar;    // SOLL: Berücksichtigen, dass Var (evtl) flüchtig (*)
void fkt(void){
  fktVar++;
}

klasse obj;    // SOLL: Berücksichtigen, dass Klasse flüchtig (aber ohne "volatile") (**)
int16_t var;  // SOLL: Sicherstellen, dass var atomar benutzt (in main()),
        // damit ISR nicht dazwischen funken kann! (***)
ISR(){
  ADC_START(); // hiernach viel Zeit nutzen um Kram zu berechnen... // SOLL: Sicherstellen, dass ADC-Kram am Anfang & Ende
  obj.write();
  obj.read();
  var++;
  fkt();
  
  ...non-volatile Kram, der gerne umsortiert werden darf...
  
  ADC_GET(); // schreibt z.B. den PWM-Wert
}
int main(void){
  while(1){
    obj.write();
    //obj.read();
    var++;
    fkt();
  }
}

(*): 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..

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
Wie wäre es mit einem Lock, also einer primitiven (und atomar 
setz-/löschbaren) volatile-Variablen?

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
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."..

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Kannst du mir vielleicht das Prinzip einfach grob an Hand des Codes
> zeigen?

https://de.wikipedia.org/wiki/Lock

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. 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.

Ich denke du solltest einfach mal ein paar grundlegende Artikel lesen:
https://de.wikipedia.org/wiki/Prozesssynchronisation
https://de.wikipedia.org/wiki/Mutex
https://de.wikipedia.org/wiki/Semaphor_(Informatik)

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Dieter F. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Immer die gleiche, wiederkehrende Diskussion ...

Um meinen Ex-Prof. Meyer-Wachsmuth zu zitieren: "Da schreiben wir uns 
ein kleines Assembler-Unterprogramm". Sagt eigentlich alles.

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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:
volatile int fktVar;    // SOLL = IST: Berücksichtigen, dass Var (evtl) flüchtig (*)
void fkt(void){
  fktVar++;
}

klasse obj;    // SOLL = IST: Berücksichtigen, dass Klasse flüchtig (aber ohne "volatile") (**)
int16_t var;  // SOLL = IST: Sicherstellen, dass var atomar benutzt (in main()),
        // damit ISR nicht dazwischen funken kann! (***)

ISR(){

  ADC_START(); // hiernach viel Zeit nutzen um Kram zu berechnen... // SOLL = IST: Sicherstellen, dass ADC-Kram am Anfang & Ende
  
  _KRAM_HOCH_SCHIEBEN_VERBOTEN();
  

  obj.write(6);
  _WRITE_TO_RAM(obj);
  
  int x = obj.read();
  
  var++;
  fkt();
  
  ...non-volatile Kram, der gerne umsortiert werden darf...
  
  
  _KRAM_RUNTER_SCHIEBEN_VERBOTEN();
  
  ADC_GET(); // schreibt z.B. den PWM-Wert

}

int main(void){
  while(1){
  
    _READ_FROM_RAM(obj);
    int x = obj.read();
    obj.write(6);
    _WRITE_TO_RAM(obj);
    
    _ATOMAR_AN(); // ..ist das bei volatile überhaupt nötig?
    var++;
    _ATOMAR_AUS();
    
    fkt();
    
  }
}


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...

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
Echt jetzt? Du hast noch nicht alle Infos, die du brauchst?!?

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Darum die Idee mit dem Beispielcode.

Einfach mal Google anwerfen.
Es gibt millionen Beispiele.

von Heiko L. (zer0)


Bewertung
0 lesenswert
nicht lesenswert
Ja, und nun? Hast du dir Herb Sutter angeschaut?

Dann hier noch ein interessantes Paper aus dem oben verlinktem Thread 
mit anderem Thema aber sehr informativ:
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
Enthält auch einiges an Beispielen.

Und hier nochmal die Details:
http://en.cppreference.com/w/c/atomic
http://en.cppreference.com/w/c/atomic/atomic_thread_fence
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Man muss sogar nur nen Hand voll Makros ersetzen

Ja dann mach das doch.
Warum sollen wir dir dein Programm entwickeln?

von Thomas E. (picalic)


Bewertung
0 lesenswert
nicht lesenswert
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:
Ist ein Mutex ausgesprochen, dann darf ein anderer Prozess oder Thread nicht zugreifen. Der andere Prozess/Thread kann dann

    nichts weiter ausführen, als auf den Zugriff zu warten, der unter Mutex steht, oder
    andere Aufgaben ausführen und auf den Zugriff warten oder
    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?

von Nop (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Thomas E. (picalic)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Thomas E. (picalic)


Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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 AVR nur 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.

von Dieter F. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
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.

von Carl D. (jcw2)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Dieter F. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
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".

von Hans W. (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
>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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
1 lesenswert
nicht lesenswert
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?

von Dieter F. (Gast)


Bewertung
-4 lesenswert
nicht lesenswert
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 :-)

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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...

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Dann hab ich es schwarz-auf-weiß und muss mich nicht
> fragen, ob ich es richtig verstanden habe..

https://www.microchip.com/webdoc/AVRLibcReferenceManual/inline_asm.html

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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?

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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. :-)

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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..

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Eine Sache ist mir noch aufgefallen..
Beim Umsehen in der avr atomic.h habe ich was gefunden, das ich in 
letzter Zeit häufiger gesehen habe..
static __inline__ void __iSeiParam(const uint8_t *__s)
{
    sei();
    __asm__ volatile ("" ::: "memory");
    (void)__s;
}
Welchen Zweck erfüllt die letzte Zeile dort?

Und ist jemandem klar, warum das so aussieht und wo das __ToDo her 
kommt?
#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
                         __ToDo ; __ToDo = 0 )

von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Welchen Zweck erfüllt die letzte Zeile dort?

die vermeidet eine Warnung über die nicht genutzte Variable __s

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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?

von Heimlehrgangsprogrammierer (Gast)


Angehängte Dateien:

Bewertung
2 lesenswert
nicht lesenswert
Hans W. schrieb:
> Mehr nicht? Was soll die Funktion dann überhaupt..

Deshalb!

von Carl D. (jcw2)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
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.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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.
    sei();
//hier ist schon außerhalb der critical section, aber der Compiler bis kann hierher schieben.
    __asm__ volatile ("" ::: "memory");

Da sei() aber selbst ein barrier hat, fällt dieser Bug nicht auf.

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Carl D. (jcw2)


Bewertung
0 lesenswert
nicht lesenswert
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 ...

von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
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.
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>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?

von Carl D. (jcw2)


Bewertung
0 lesenswert
nicht lesenswert
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.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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...?!

von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> Warum hat die Funktion überhaupt ein Argument?

weil __attribute__(cleanup) eins braucht.

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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?

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hans W. schrieb:
> ...Ich glaube, das mit den Locks stimmt nicht so ganz...?!

Ja, ist ist falsch.

von Hans W. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
>Ja, ist ist falsch.
Stimmt, muss "das" heißen...höh

von Markus F. (mfro)


Bewertung
0 lesenswert
nicht lesenswert
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?

von MaWin (Gast)


Bewertung
0 lesenswert
nicht lesenswert
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?

von S. R. (svenska)


Bewertung
0 lesenswert
nicht lesenswert
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".

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.