Man kann Funktionen auch volatile deklarieren. Was passiert, wenn das
Hauptprogramm und die ISR gleichzeitig auf diese Funktion zugreifen?
Ich nehme an das volatile wird die ISR nicht hindern,
dazwischenzugrätschen.
z.B.:
uint8_t pinmode(uint8_t mode){
static uint8_t encuse=RTX;
switch(mode){
case ENC:encuse=ENC;RXI_D; break;
case RTX:encuse=RTX;EO_NPD;pbold|=(1<<RXPin);RXI_E; break;
case PRCL: break;
}
return encuse;
}
Ich sehe in deinem Beispielcode kein "volatile".
Gast
#6335072
Statische Variablen in Funktionen sind böse, da diese die Wiedereintrittsfähigkeit(Reentrance) vernichten. Bernd K. schrieb: > Was passiert, wenn das > Hauptprogramm und die ISR gleichzeitig auf diese Funktion zugreifen? Chaos. Flickwerk: Im Hauptprogramm die Interrupts VOR dem Aufruf verbieten. Danach wieder erlauben
Gast
#6335077
S. R. schrieb: > Ich sehe in deinem Beispielcode kein "volatile". Ich auch nicht. Und auch nicht in der Dokumentation des gcc, wo es um "function attributes" geht. Was soll das denn sein? Abgesehen davon geht es bei volatile Objekten keinesfalls um Locking, sondern darum, die Zugriffe auf das RAM durch Nutzung von Registern abzukürzen.
Gast
#6335087
Ich empfehle einemal ein vollständiges, ohne Fehler und Warnungen compilierbares, Codebeispiel zu posten. Falls ich nämlich nicht (wieder mal) eine Neuerung in C verpasst habe, dann gibt es keine "volatile Funktionen". Allenfalls solche die einen volatile Rückgabewert haben. In C++ gibt es in der Tat volatile Funktionen. Aber geht es hier um C oder C++? Deswegen die Frage nach einem Codebeispiel.
Das wird sich da sicher nur auf den Rückgabewert beziehen.
Bernd K. schrieb: > Man kann Funktionen auch volatile deklarieren. Was soll das bewirken? Hast Du mal ein Beispiel? Bernd K. schrieb: > Ich nehme an das volatile wird die ISR nicht hindern, > dazwischenzugrätschen. Richtig. Volatile schaltet nur die Optimierung einer Variablen aus. Mit Interrupts hat das nichts zu tun.
Gast
#6335091
Ergänzung: Mit volatile Funktionen in C++ meine ich natürlich Mitgliedsfunktionen einer Klasse.
Gast
#6335092
Abgesehen davon geht es bei volatile Objekten keinesfalls um Locking, sondern darum, die Zugriffe auf das RAM weniger zu optimieren. Wenn du z.B. in einer Funktion eine globale (nicht volatile) Variable mehrmals änderst, kann der Compiler das in CPU Registern machen und erst ganz zum Schluss ins RAM zurück schreiben:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Hier würden die Zwischenergebnis nur in einem Register stehen. Unter Umständen geht der Compiler sogar noch einen Schritt weiter und hält die Variable ausschließlich im Register, statt im RAM. Wäre die Variable volatile, würde jede einzelne Zeile eine read-modify-write Operation auf das ARM sein.
Bernd K. schrieb: > Man kann Funktionen auch volatile deklarieren. In C eigentlich nicht. In C++ gibts das für member-Funktionen. Allerdings haben volatile und ISRs in dem von dir genannten Kontext nichts miteinander zu tun. Oliver
Vielen Dank für die Antworten. Entschuldigung, dass ich erst jetzt antworte. Die Notifikationen sind auf meiner Dienst-Email eingegangen. In C gibt es also keine volatile Funktionen - hatte das offenbar in einem C++ -Beispiel aufgeschnappt. Vielen Dank auch an "ufuf" für den Hinweis, das statische Variablen die Reentrance einer Funktion zerstören. Mein Problem konnte ich durch atomare Programmierung cli/sei endlich lösen. (Zündzeitpunkts-Steuerung mit 3kHz-Differntial-Winkelencoder und Soft-UART auf einem ATtiny85). Bitte melden falls einer am Code interessiert ist.
Gast
#6336893
In C++ tut volatile auch nicht das, was du davon erwarten würdest. Du siehst das zu abstrakt - volatile ist, wie schon geschrieben wurde, nur ein Hilfsmittel um dem Compiler einige Optimierungen beim Zugriff abzugewöhnen, mehr nicht. Wenn man nicht weis, was der Compiler da tut, hilft volatile einem auch nicht. volatile gibt überhaupt gar keine Garantie zu irgendwas. Selbst Zugriffe sind damit nicht automatisch atomar, d.h. wenn ISR und normaler Code gleichzeitig auf eine volatile Variable zugreifen, kann dabei trotzdem Datenmüll rauskommen. (Z.B. int auf AVR oder double auf ARM) Streich volatile aus deinem Wortschatz und sag das am besten jedem weiter, der es trotzdem benutzt.
Johannes schrieb: > Streich volatile aus deinem Wortschatz und sag das am besten jedem > weiter, der es trotzdem benutzt. Das wäre allerdings wenig zielführend. Sinnvoller wäre: Nutze volatile nur für genau das, für das es gedacht ist, und verstehe, was es macht, und was nicht. Ganz ohne geht es bei Memory-mapped Registern oder auch ISRs u.ä. halt nicht. Oliver
Johannes schrieb: > volatile gibt überhaupt gar keine Garantie zu irgendwas Das ist falsch ! > Streich volatile aus deinem Wortschatz und sag das am besten jedem > weiter, der es trotzdem benutzt. Und das ist gefährlicher Unsinn.
Gast
#6336920
Johannes schrieb: > Streich volatile aus deinem Wortschatz und sag das am besten jedem > weiter, der es trotzdem benutzt. Volatile ist durchaus wichtig. Wenn du den Zweck nicht verstanden hast, dann ist das dein Problem.
Gast
#6336932
War ja klar, dass jetzt wieder die Deppen kommen die Aussagen aus dem Kontext zerren nur damit sie ihren Senf dazu abgeben können. Für einen Anfänger ist volatile gefährlich. Es gibt mehr Leute die es falsch benutzen als solche, die es richtig benutzen. Von daher ist für einen Anfänger der Rat, darauf zu verzichten, sicherlich nicht falsch. Wenn er mal in die Situation kommt sich mit write ordering oder read/write barriers zumschlagen zu müssen, hat er auch verstanden was volatile wirklich tut. Aber auch da gibt es bessere Lösungen als volatile. Und gerade im Kontext mit mehreren CPUs ist volatile dann plötzlich noch viel gefährlicher als auf einem µC mit nur einer CPU.
Gast
#6336953
Johannes schrieb: > dann plötzlich noch viel gefährlicher Was für ein Schwachfug! Nichts gefährliches ist da dran! Einzig Notwendigkeiten hängen da dran. Notwendigkeiten volatile einzusetzen. Aus meiner Sicht: Ein Leben ohne volatile ist nicht denkbar. Zumindest wenn ISR und Multicore CPUs ins Spiel kommen Und ja, dieser Thread behandelt ein ISR Problem. Also ist volatile hier im Rennen! Nicht so wie anfangs gedacht, aber im Rennen.
Gast
#6336966
Normalerweise verwendet man auf multithreaded Systemen eine write barrier, weil volatile halt einfach nicht sauber funktioniert. Aber ich sag doch, dass die meisten volatile falsch einsetzen - und es noch nicht einmal wissen. Volatile hilft ein wenig bei single CPU wenn man write ordering möchte. Wenn man dann halbwegs weis, welche Zugriffe vom Compiler so umgesetzt werden das sie in einer Instruktion erledigt sind, kann man damit funktionierende Synchronisationsmechanismen bauen. Wenn man das aber nicht weis tut man Murks. Es kommt Code heraus der in 99% der Fälle sauber funktioniert, aber dann einmal in der Woche halt doch nicht.
Bernd K. schrieb: > nehme an das volatile wird die ISR nicht hindern, dazwischenzugrätschen. Das volatile ist sogar eher andersrum: du sagst damit dem Compiler, dass du selber die Verantwortung für diese Variable übernehmen willst und er gefälligst optimierenderweise seine Finger weg lassen soll. Insofern kannst du dir da nur selber "reingrätschen". Johannes schrieb: > Für einen Anfänger ist volatile gefährlich. Ein volatile ist nur dann "gefährlich", wenn man meint, damit würde der Compiler das schon richten. Und natürlich ist ein volatile zwingend nötig, wenn eine (Haupt-)Schleife auf einen Interrupt reagieren soll. Und der Interrupt sich nicht nur mit sich selber beschäftigt. Johannes schrieb: > volatile gibt überhaupt gar keine Garantie zu irgendwas. Selbst Zugriffe > sind damit nicht automatisch atomar Ein Semaphorenproblem samt inkonsistenter Daten hat eben nichts mit dem Abschalten der Compileroptimierung durch ein volatile zu tun. Die Semaphore garantiere ich, indem ich beim Manipulieren einer Variablen dafür sorge, dass das zu diesem Zeitpunkt nur von einer Stelle aus passiert und nicht eine ISR (oder gar eine weitere ISR) oder auch ein anderer Prozessorkern gleichzeitig lesend oder schreibend darauf zugreifen will. Und das volatile sagt dem Compiler nur, dass sich die Variable jederzeit "von aussen" ändern kann und sie deshalb nicht lokal optimiert werden darf. Das muss man erkannt und verstanden haben, dass da zwei völlig andere Wirk- und Fehlermechnismen greifen.
Gast
#6336970
Arduino Fanboy D. schrieb: > Zumindest wenn ISR und Multicore CPUs ins Spiel kommen "volatile" ist in C und C++ zur Synchronisation auf Multicore-CPUs bzw. in Multithreading-Umgebungen ungeeignet (in Java hingegen nicht, da hat es aber eine andere Bedeutung!). In "normalem" C- oder C++-Code, der auf einem Betriebssystem wie Linux läuft, ist "volatile" an Datentypen immer falsch (im Sinne von: bringt nichts, macht wahrscheinlich nur das Programm langsamer), außer bei "__asm__ volatile" aber das ist eine andere Art von "volatile". Auf manchen CPU-Architekturen kann "volatile" zur Multithreading-Synchronisation reichen, aber das ist dann unportabel, und es gibt immer eine bessere, korrekte & portable Lösung. Besonders hinterhältig ist, dass ein Multithreading-Programm mit "volatile" den Anschein haben kann zu funktionieren (à la "funktioniert in 99,999% der Aufrufe"), aber dennoch falsch ist und unter bestimmten nicht kontrollierbaren Umständen dann doch versagt. Für den Zugriff auf Memory-Mapped Register ist "volatile" meist erforderlich. Für die Synchronisation zwischen ISRs&Main-Loop (auf der selben CPU!) kann es sinnvoll sein, wenn nur einzelne Variablen verändert werden, welche die CPU atomar bearbeiten kann. Oft sind aber Interrupt-Sperren nötig. Zur Vollständigkeit halber kann man C++-Member-Funktionen mit "volatile" definieren (typischerweise in Libraries), wenn dieser aber außerhalb der beiden genannten Use Cases (I/O und ISRs) aufgerufen werden, ist das wieder falsch, wie im 1. Absatz gesagt.
Programmierer schrieb: > "volatile" ist in C und C++ zur Synchronisation auf Multicore-CPUs bzw. > in Multithreading-Umgebungen ungeeignet Das hat der C- oder C++- Standard auch nie behauptet. Allerdings wird’s halt aus Gewohnheit falsch verwendet. C++ kippt daher ja auch das Kind mit dem Bade aus, siehe Beitrag "avr-gcc-10 - "volatile deprecated [-Wvolatile]" Warnungen" Oliver
Gast
#6337022
Oliver S. schrieb: > Das hat der C- oder C++- Standard auch nie behauptet. Eben. Das wissen nur viele nicht... Oliver S. schrieb: > Allerdings wird’s > halt aus Gewohnheit falsch verwendet. Ja. Und eben weil es manchmal so aussieht, als würde es funktionieren. Ein kleines Beispiel:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
Über "sleep" wird versucht, die Ausführung der "++counter" Zeile in den einzelnen Threads nacheinander zu ordnen. Bei meinen Tests werden die Zahlen 1-100 ausgegeben, sieht also so aus als wäre es korrekt. Ist aber gleich in dreierlei Hinsicht falsch: * Es gibt keine Garantie dass "sleep_for" eine Nacheinander-Ausführung von "++counter" bewirkt. * "++counter" ist nicht atomar; es kann sein dass mittendrin ein anderer Thread diese Variable schreibt und das Ergebnis beliebig falsch ist. * Die auf "counter" geschriebenen Werte müssen keineswegs in den anderen Threads sichtbar sein. Es ist sehr wohl möglich, dass einfach 100x die Zahl "1" ausgegeben wird, weil jeder Thread nur die initiale 0 sieht. Eine Lösung ist die Verwendung von atomics:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
Dies löst direkt die letzten beiden Probleme; das Addieren kann nicht schief gehen, und das garantierte Memory-Ordering sorgt dafür, dass der Ergebnis-Wert garantiert bei den anderen Threads ankommt. Die Reihenfolge der Additionen ist immer noch nicht fix, was sich im Ergebnis aber nicht niederschlägt, weil so garantiert 100x eine 1 addiert wird und so die Zahlen 1-100 ausgegeben werden (allerdings ggf. in falscher Reihenfolge). Oder man macht es ganz klassisch mit einem Mutex:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
Löst das Problem ebenso (ggf. langsamer), denn Mutexe bewirken ebenfalls eine garantierte Memory-Order, sodass die geschriebenen Werte bei den anderen Threads sichtbar sind. Beide Lösungen sind portabel und funktionieren immer (sofern ich nichts falsch gemacht habe) und brauchen kein "volatile" und verlassen sich somit nicht auf schmutzige Tricks.
Bernd K. schrieb: > Man kann Funktionen auch volatile deklarieren Wenn du wirklich wissen willst, was das bedeutet, dann sieh dir folgendes Video an: https://www.youtube.com/watch?v=KJW_DLaVXIY. TL;DR: man kann an vielen Stellen 'volatile' verwenden (überall dort wo man auch 'const' verwenden kann), d.h. aber noch lange nicht, dass es eine Auswirkung hat und schon garnicht, dass es die Auswirkung hat, die man sich erhofft. Oliver S. schrieb: > C++ kippt daher ja auch das Kind mit dem Bade aus, siehe Eigenlich war das Ziel, dass volatile an den Stellen zu einer Warning führt, wo es keinen Sinn macht. Michael
Johannes schrieb: > Streich volatile aus deinem Wortschatz und sag das am besten jedem > weiter, der es trotzdem benutzt. Ein volatile zuviel kann ein Programm etwas ineffizienter machen. Ein volatile zuwenig kann es zum völligen Versagen bringen. Was ist wohl besser? Das Problem, dass manche nicht genau über die Wirkungsweise von volatile Bescheid wissen, wird nicht dadurch behoben, dass man dieses Feature ignoriert oder gar verbietet. Ganz im Gegenteil: Nur wer es benutzt, wird sich ggf. auch mit seinen Details beschäftigen und damit etwas dazulernen.
Gast
#6337060
Johannes schrieb: > Volatile hilft ein wenig bei single CPU wenn man write ordering möchte. Mit "write ordering" hat Volatile nichts zu tun.
Gast
#6337097
Yalu X. schrieb: > Ein volatile zuwenig kann es zum völligen Versagen bringen. > > Was ist wohl besser? Zweiteres. Ein Programm welches nicht völlig versagt, sondern "meistens" funktioniert, kann gefährlich werden; ein Programm welches überhaupt nicht funktioniert wird nicht auf die Welt losgelassen. Wenn man auf mehrere zusammengehörige "volatile" Variablen hintereinander schreibt, und ein Interrupt genau dazwischen kommt, kann die ISR inkonsistente Werte sehen. Ähnliche Probleme mit "int" oder "float" auf AVR sind bekannt, wenn Variablen nur "halb" geschrieben werden. Man sollte sich also sehr genau überlegen, ob "volatile" hier hilft, oder nicht doch lieber Interrupt-Sperren. Rein zufällig enthält das "cli()" Makro der avr-libc auch eine Optimizer-Barrier, sodass das volatile hier sogar unnötig ist.
Programmierer schrieb: > Zweiteres. Ein Programm welches nicht völlig versagt, sondern "meistens" > funktioniert, kann gefährlich werden; ein Programm welches überhaupt > nicht funktioniert wird nicht auf die Welt losgelassen. Mit dieser Begründung kann man ein typisches Mikrocontrollerprogramm (mit memory-mapped I/O) nie auf die Welt loslassen, weil es ohne volatile einfach nicht funktioniert. > Wenn man auf mehrere zusammengehörige "volatile" Variablen > hintereinander schreibt, und ein Interrupt genau dazwischen kommt, kann > die ISR inkonsistente Werte sehen. Das ist das bereits diskutierte Problem mit der Atomizität. Dieses Problem wird aber nicht durch das Weglassen von volatile gelöst, sondern durch die Verwendung geeigneter Konstrukte, die die Atomizität gewährleisten.
Volatile braucht man, um z.B. auf Hardware mit Autoincrement zuzugreifen. Man liest immer von der gleichen Adresse, aber kriegt nacheinander die Daten. Z.B. CAN oder UART machen das so. Für Interrupts benutzt man besser die <atomic.h>. Wie der Name schon sagt, sorgt sie für den atomaren Zugriff. Daß Zugriffe nicht wegoptimiert werden, ist dabei implizit.
Gast
#6337108
Yalu X. schrieb: > Mit dieser Begründung kann man ein typisches Mikrocontrollerprogramm > (mit memory-mapped I/O) nie auf die Welt loslassen, weil es ohne > volatile einfach nicht funktioniert. Die Begründung bezieht sich natürlich nur auf ISR-Synchronisation. Dass es für Memory-Mapped-IO nötig ist schrieb ich ja bereits. Die Alternative wäre hier übrigens Inline-Assembly, was aber eher hässlicher ist. Yalu X. schrieb: > Das ist das bereits diskutierte Problem mit der Atomizität. Dieses > Problem wird aber nicht durch das Weglassen von volatile gelöst, sondern > durch die Verwendung geeigneter Konstrukte, die die Atomizität > gewährleisten. Diese Konstrukte wie eben cli()/sei() machen das volatile aber überflüssig, weshalb man es hier von Anfang an ganz weglassen kann. Das Suchen und Vermeiden von volatile in einer Codebasis ist hier also eine sinnvolle Vorgehensweise.
Programmierer schrieb: > Yalu X. schrieb: >> Ein volatile zuwenig kann es zum völligen Versagen bringen. >> >> Was ist wohl besser? > > Zweiteres. Ein Programm welches nicht völlig versagt, sondern > "meistens" funktioniert, kann gefährlich werden Schon recht. Nur war das überhaupt keine Option, die zur Wahl gestanden hätte. Es ging um "etwas langsamer als nötig" vs. "funktioniert nicht". > Wenn man auf mehrere zusammengehörige "volatile" Variablen > hintereinander schreibt, und ein Interrupt genau dazwischen kommt, kann > die ISR inkonsistente Werte sehen. Diese Garantie gibt volatile ja auch nicht. Es ist vollkommen sinnlos, sich (künstlich?) darüber aufzuregen, daß volatile nicht das tut, was du dir wünschst, sondern nur das, was es garantiert.
Gast
#6337129
Axel S. schrieb: > Schon recht. Nur war das überhaupt keine Option, die zur Wahl gestanden > hätte. Es ging um "etwas langsamer als nötig" vs. "funktioniert nicht". Und die Abstufung "funktioniert meistens" gibt es nicht? Axel S. schrieb: > Diese Garantie gibt volatile ja auch nicht. Es ist vollkommen sinnlos, > sich (künstlich?) darüber aufzuregen, daß volatile nicht das tut, was du > dir wünschst, sondern nur das, was es garantiert. Niemand regt sich auf... Es geht nur darum, dass viele "volatile" falsch verstehen, und annehmen, es würde so etwas garantieren. Daher kann man nochmal klarstellen, was es nicht garantiert.
Programmierer schrieb: > Diese Konstrukte wie eben cli()/sei() machen das volatile aber > überflüssig, weshalb man es hier von Anfang an ganz weglassen kann. Das > Suchen und Vermeiden von volatile in einer Codebasis ist hier also eine > sinnvolle Vorgehensweise. Das ist Unsinn. Weil eine Speicherstelle nicht nur durch einen Interrupt sondern eben auch durch interne (z.B. Timer) oder externe (z.B IO) Peripherie verändert werden kann. Und ohne volatile wird dann z.B. in einer Schleife immer nur der Alte Wert gelesen und nicht der aktuelle. Allein die ständige Reduktion hier von volatile auf die Synchronisation ist der komplett falsche Ansatz.
Gast
#6337143
Cyblord -. schrieb: > Weil eine Speicherstelle nicht nur durch einen Interrupt > sondern eben auch durch interne (z.B. Timer) oder externe (z.B IO) > Peripherie verändert werden kann. Du redest also von Memory-Mapped IO? Dass man hier volatile braucht habe ich von Anfang an gesagt: Programmierer schrieb: > Für den Zugriff auf Memory-Mapped Register ist "volatile" meist > erforderlich. Interessant wird es bei DMA. Dann braucht man aber sowieso ein Konstrukt, um beim Lesen den Cache zu löschen ("invalidate") bzw. beim Schreiben eben rauszuschreiben ("clean"), um nicht die "stale" Daten aus dem Cache zu lesen bzw. um dem DMA-Agent die aktuellen Daten zu geben. Dieses Konstrukt kann dann auch eine Optimization-Barrier beinhalten, sodass "volatile" auch nicht mehr gebraucht wird. Wenn man für einen Prozessor ohne (aktivierten) Cache oder mit Cache und DMA mit Cache-Coherency ("Snooper") kompiliert, enthält das Konstrukt dann nur noch die Barrier.
Cyblord -. schrieb: > Und ohne volatile wird dann z.B. in > einer Schleife immer nur der Alte Wert gelesen und nicht der aktuelle. Nein, im Falle des cli()-Makros passiert das wegen der darin enthaltenen memory-Barrier nicht. Ob es immer sinnvoll ist, bei jedem Speicherzugriff die Interrupts komplett zu sperren, ist natürlich auch fraglich. Man muß sich halt Gedanken machen. Oliver
Gast
#6337150
Cyblord -. schrieb: > Allein die ständige Reduktion hier von volatile auf die Synchronisation > ist der komplett falsche Ansatz. Ja! Volatile und Atomic, Mutex, Semaphor, sind unterschiedliche Instrumente, welche erst im richtigen Zusammenspiel die angenehme/fehlerfreie Melodie abliefern. Davon abgesehen: Mit jedem Sprachmittel kann man Mist bauen! Das spricht nicht gegen das Sprachmittel. (denn dann müsste man wohl alle abschaffen/verbieten)
Programmierer schrieb: > Du redest also von Memory-Mapped IO? Dass man hier volatile braucht habe > ich von Anfang an gesagt: Nun deine, von mir zitierte, Aussage war recht allgemein gehalten. Vielleicht hast du das nicht so gemeint, aber so geschrieben. Aber auch im Zusammenspiel mit Interrupts und Atomic sieht doch der Standardanwendungsfall für volatile so aus: Es gibt einen Hauptschleife welche in jedem Durchgang auf einen "Messwert" zugreift und damit was tut (z.B. Displayausgabe). Dann gibt es einen Interrupt der diesen Messwert erzeugt. Volatile ist notwendig da der Messwert sonst nie in der Hauptschleife aktualisiert wird. Atomic ist notwendig damit der Interrupt nicht mitten im Lesevorgang des Messwerts anspringt und so die Hauptschleife teilweise alten Wert und teilweise neuen Wert liest (bei Datentypen die nicht atomar kopiert werden können). Und ich sage mal, diese Anwendungsfälle treten häufig auf. Damit ist deine Aussage widerlegt, man bräuchte mit Atomic kein volatile. > Nein, im Falle des cli()-Makros passiert das wegen der darin enthaltenen > memory-Barrier nicht. cli ist ja nur das Implementierungsdetails eines Atomic Blocks. Es ist schlechter Stil sich auf genau diese Implementierung und genau diesen Seiteneffekt davon zu verlassen. Man verwendet die atomic Makros ja damit man diese Details erst gar nicht sieht. Und auf der nächste Architektur ist atomic evt. ganz anders umgesetzt. Nur mit volatile stellt man den gewünschten Effekt unabhängig davon sicher.
Gast
#6337163
Cyblord -. schrieb: > Es gibt einen Hauptschleife welche in jedem Durchgang auf einen > "Messwert" zugreift und damit was tut (z.B. Displayausgabe) IMO ist das schlechtes Design, aber egal. Cyblord -. schrieb: > Volatile ist notwendig da der Messwert sonst nie in der Hauptschleife > aktualisiert wird. Wird er wohl. Zum Auslesen des Werts benutzt du eines der avr-libc-"atomic" Makros wie ATOMIC_BLOCK (für andere Prozessoren kann man sich leicht Äquivalente bauen). Dieses enthält eine Optimization Barrier. Der Compiler ist damit gezwungen, den neuen Wert aus dem Speicher zu laden. Cyblord -. schrieb: > Damit ist deine Aussage widerlegt, man bräuchte mit Atomic kein > volatile. Nö! std::atomic kann man zumindest auf Cortex-M so portieren dass es auch reicht (mithilfe von LDREX/STREX). Die Optimization Barrier ist dann in std::atomic mit Compiler-Builtins hartkodiert. Oder eben ATOMIC_BLOCK, was auch eine Barrier enthält.
Gast
#6337166
Cyblord -. schrieb: > Und auf der nächste Architektur ist atomic evt. ganz anders umgesetzt. Die sind eh nicht standardisiert. Entweder man verwendet ein korrekt portiertes std::atomic (braucht garantiert kein volatile) oder portiert ATOMIC_BLOCK eben so, dass es die Optimization-Barrier enthält und kein volatile braucht.
Programmierer schrieb: > Yalu X. schrieb: >> Ein volatile zuwenig kann es zum völligen Versagen bringen. >> >> Was ist wohl besser? > > Zweiteres. Ein Programm welches nicht völlig versagt, sondern "meistens" > funktioniert, kann gefährlich werden; ein Programm welches überhaupt > nicht funktioniert wird nicht auf die Welt losgelassen. FALSCH! Klassischer Irrtum! Ein fehlendes volatile KANN es zum völligen Versagen bringen, es tut es aber nicht IMMER! Und genau DAS ist das Problem! Fehler, die nur sporadisch und unter sehr speziellen Bedingungen auftreten! Viel Spaß beim Debugging! Wenn ein Fehler, welcher Art auch immer, der ein Programm immer oder sehr oft zum Versagen bringt, ist er harmlos, denn dann wird er schnell gesehen und verhindert meist ein "ist jetzt zur Auslieferung fertig".
Gast
#6337182
Falk B. schrieb: > Und genau DAS ist das Problem! Fehler, die nur sporadisch und unter sehr > speziellen Bedingungen auftreten! Viel Spaß beim Debugging! Knick in der Optik? Genau das hab ich geschrieben. Unter "völlig versagen" war wohl gemeint "stürzt sofort ab, lässt sich gar nicht erst testen" und das Gegenteil wäre "scheint zu funktionieren aber man weiß nicht genau ob es immer funktioniert". Und da ist natürlich ersteres besser. Ein Programm mit viel "volatile" kann durchaus dieses Problem haben, welches sich nicht mit noch mehr "volatile" beheben lässt.
Programmierer schrieb: > Axel S. schrieb: >> Diese Garantie gibt volatile ja auch nicht. Es ist vollkommen sinnlos, >> sich (künstlich?) darüber aufzuregen, daß volatile nicht das tut, was du >> dir wünschst, sondern nur das, was es garantiert. > > Niemand regt sich auf... Es geht nur darum, dass viele "volatile" falsch > verstehen, und annehmen, es würde so etwas garantieren. Du scheinst das ja auch anzunehmen, obwohl du gleichzeitig weißt, dass es nicht so ist. Du behauptest ja immerhin, volatile würde nicht "sauber funktionieren".
Gast
#6337185
Programmierer schrieb: > Diese Konstrukte wie eben cli()/sei() machen das volatile aber > überflüssig, Nein. Beispiel: Eine Variable die von einer ISR verändert wird und im Hauptprogramn benutzt wird (lesend oder schreibend, spielt keine Rolle). Im Hauptprogramm würde die Variable evtl. für immer (in einem CPU Register) gecached werden weil der Compiler davon ausgeht, daß die ISR nie aufgerufen wird. Um das zu verhindern, dafür ist volatile da. Es ersetzt kein locking, es kann auch nicht durch locking ersetzt werden. Wer mal (wie ich) zum ersten mal auf das Problem stößt, für den kann ein Blick ins Assembler Listing sehr aufschlussreich sein.
Stefan ⛄ F. schrieb: > Programmierer schrieb: >> Diese Konstrukte wie eben cli()/sei() machen das volatile aber >> überflüssig, > > Nein. Doch. So, wie die cli()/sei()-Makros für den AVR implementiert sind (und um die ging es in dem Zusammenhang), schon. Oliver
Gast
#6337205
Stefan ⛄ F. schrieb: > Im Hauptprogramm würde die Variable evtl. für immer (in einem CPU > Register) gecached werden weil der Compiler davon ausgeht, daß die ISR > nie aufgerufen wird. Nö. ATOMIC_BLOCK verhindert das. std::atomic auch, wenn korrekt portiert. ATOMIC_BLOCK-Äquivalente für andere Prozessoren sollten es auch verhindern; wenn nicht, baut man sich seine eigenen. Aber ganz zu Anfang schrieb ich sogar, dass volatile hier nötig sein kann: Programmierer schrieb: > Für die Synchronisation zwischen ISRs&Main-Loop (auf der > selben CPU!) kann es sinnvoll sein, wenn nur einzelne Variablen > verändert werden, welche die CPU atomar bearbeiten kann. Oft sind aber > Interrupt-Sperren nötig. Und die Interrupt-Sperre macht volatile eben überflüssig. Rolf M. schrieb: > Du behauptest ja immerhin, volatile würde nicht "sauber > funktionieren". Das tue ich nicht. Allerdings verlassen sich viele unsaubere Programme auf Dinge, die volatile nicht garantiert. Einfach nur "volatile" an möglichst viele Variablen tackern ist keine saubere Programmierung.
Gast
#6337209
@Stefan ⛄ F. Das stimmt halt nicht. Schau weiter oben, ein beliebtes Element zur Synchronisierung ist z.B.
1 | |
Dadurch schiebt der Compiler alle gecachten Werte vorher in die entsprechenden Variablen und liest sie hinterher wieder neu ein. Mir scheint diese Diskussion hier teilt sich in 2 Lager auf: Die einen kennen Volatile, wissen was es tut und warum es mitunter sehr böse sein kann. Die anderen haben es zwar immer benutzt, aber nie wirklich verstanden und sich auch nie nach Alternativen umgesehen. Diese Leute heulen jetzt und verstehen die Welt nicht mehr: "Aber nimmt man doch immer...", "Alternativlos..." usw. Ein schönes Spiegelbild dafür, wie es in der Gesellschaft insgesamt läuft. Btw. wenn die exakte Zugriffssemantik auf ein Register oder ähnliches in Hardware so wichtig ist, dann benutzt man halt einfach inline asm. Da hat man volle Kontrolle was abläuft. Das ist dann natürlich eine Methode und ein wenig weniger komfortabel als einen volatile pointer zu dereferenzieren, aber C/C++ ist einfach die falsche Spielwiese für solche Experimente. Das sind produktive Programmiersprachen und Mikrocontroller sind nur ein kleiner Teil vom Einsatzgebiet. Man möchte eine saubere Sprache bei der klar ist wann was wie passiert, und volatile liegt da eindeutig in einer Grauzone. Abgesehen von µC hat das einfach keinen Sinn.
Immerhin sorgt die drohende „Abkündigung“ von volatile in C++ dafür, daß mancher sich seinen Code daraufhin mal anschaut, und erstaunliches findet. Z.B: https://www.kdab.com/getting-rid-of-volatile-in-some-of-qt/ Das lässt für den Rest der weltweiten Codebasis Böses erahnen ;) Oliver
Programmierer schrieb: > Cyblord -. schrieb: >> Und auf der nächste Architektur ist atomic evt. ganz anders umgesetzt. > > Die sind eh nicht standardisiert. Das spielt keine Rolle. Die Zusicherung lautet das dieser Block atomar ausgeführt wird. Das reicht. Das Konstrukt ist dann austauschbar gegen jedes andere Konstrukt mit dieser Zusicherung. Es müssen aber keine Annahmen über die Memory Barrier gemacht werden.
Programmierer schrieb: > Das tue ich nicht. Allerdings verlassen sich viele unsaubere Programme > auf Dinge, die volatile nicht garantiert. Gibts dafür ne Quelle oder ist das halt so eine Behauptung? Weil es deine Meinung stützt dass jeder ausser dir leider ein bisschen doof ist?
Gast
#6337232
Cyblord -. schrieb: > Das spielt keine Rolle. Die Zusicherung lautet das dieser Block atomar > ausgeführt wird. Wer sichert das zu? Nur weil die avr-libc zufällig ein ATOMIC_BLOCK Makro hat welches diese Funktion bietet ohne sie zu dokumentieren, muss man jetzt annehmen dass man zwar immer so ein Makro zur Verfügung hat, welches aber keine Barrier enthält? Wenn es die avr-libc gar nicht gäbe, könnte man sich dann darauf verlassen dass sich immer so ein Makro konstruieren lässt? Verwende einfach statt ATOMIC_BLOCK cli/sei. Das hat die Barrier garantiert & dokumentiert. Lässt sich natürlich ebenfalls mit Barrier portieren. Cyblord -. schrieb: > Gibts dafür ne Quelle oder ist das halt so eine Behauptung? Wurde schon gepostet: Oliver S. schrieb: > https://www.kdab.com/getting-rid-of-volatile-in-some-of-qt/ > > Das lässt für den Rest der weltweiten Codebasis Böses erahnen ;) Ganz genau. Wenn selbst Qt es nicht hinbekommt (bekam)...
Programmierer schrieb: > Wer sichert das zu? Die API Beschreibung des Makros. > Nur weil die avr-libc zufällig ein ATOMIC_BLOCK > Makro hat welches diese Funktion bietet ohne sie zu dokumentieren, muss > man jetzt annehmen dass man zwar immer so ein Makro zur Verfügung hat, > welches aber keine Barrier enthält? Nein muss man nicht. Aber man kann jedes Konstrukt verwenden welches diese Zusicherung macht. Was ist daran unverständlich für dich. Je weniger Annahmen und Zusicherungen ich brauche, desto einfacher kann ich Code portieren. > Wenn es die avr-libc gar nicht gäbe, > könnte man sich dann darauf verlassen dass sich immer so ein Makro > konstruieren lässt? Nein, wo habe ich das behauptet? > Verwende einfach statt ATOMIC_BLOCK cli/sei. Das hat die Barrier > garantiert & dokumentiert. Lässt sich natürlich ebenfalls mit Barrier > portieren. Kann man machen, nur wozu gibt es Abstraktion? Das Atomic kontrukt zeigt dem Leser des Codes was beabsichtigt ist, und belästigt den Nutzer nicht mit den Details seiner Implementierung.
Programmierer schrieb: > Rolf M. schrieb: >> Du behauptest ja immerhin, volatile würde nicht "sauber funktionieren". > > Das tue ich nicht. Stimmt, war jemand anders. Sorry, hab ich verwechselt. > Allerdings verlassen sich viele unsaubere Programme auf Dinge, die volatile > nicht garantiert. Einfach nur "volatile" an möglichst viele Variablen tackern > ist keine saubere Programmierung. Klar. Da sind wir uns denke ich alle einig. Johannes schrieb: > Die einen kennen Volatile, wissen was es tut und warum es mitunter sehr > böse sein kann. …und wo es sinnvoll ist. > Die anderen haben es zwar immer benutzt, aber nie wirklich verstanden > und sich auch nie nach Alternativen umgesehen. Warum muss man sich denn nach Alternativen umsehen? Es tut das, was es soll. Wenn man was anderes erwartet, wird man natürlich enttäuscht. Das liegt dann aber eben daran, dass man es - wie du selbst sagst - nie verstanden hat. > Btw. wenn die exakte Zugriffssemantik auf ein Register oder ähnliches in > Hardware so wichtig ist, dann benutzt man halt einfach inline asm. Da > hat man volle Kontrolle was abläuft. Du willst auf einem µC für jeden einzelnen Registerzugriff extra inline-Assembler schreiben, nur um dir zu ersparen, die Register als volatile zu definieren? > Das ist dann natürlich eine Methode und ein wenig weniger komfortabel > als einen volatile pointer zu dereferenzieren, aber C/C++ ist einfach > die falsche Spielwiese für solche Experimente. Eben. Deswegen lernt man, was volatile macht und was nicht und setzt es dann entsprechend ein, statt einfach irgendwas zu erwarten und mal zu probieren, ob das so funktioniert. > Das sind produktive Programmiersprachen und Mikrocontroller sind nur ein > kleiner Teil vom Einsatzgebiet. So klein dürfte der nicht sein.
Rolf M. schrieb: > So klein dürfte der nicht sein. Mir kommt es so vor als hätte hier jemand was gegen C auf Microcontrollern. Als wäre C nur was für Linux-Treiber und alle anderen sollen gefälligst ASM verwenden auf ihren unpotenten Microcontrollern.
Gast
#6337244
Cyblord -. schrieb: > Kann man machen, nur wozu gibt es Abstraktion? Das Atomic kontrukt zeigt > dem Leser des Codes was beabsichtigt ist, und belästigt den Nutzer nicht > mit den Details seiner Implementierung. Bei der avr-libc ist es unglücklicherweise ein Implementierungs-Detail. Andere Implementationen können (und sollten) explizit garantieren, dass eine Optimization Barrier enthalten ist, genau wie andere Synchronisations-Mechanismen es auch tun (std::atomic, std::mutex usw). Ein Atomic-Block-Makro ohne Optimization-Barrier macht wenig Sinn (außer man will eben unbedingt einen Grund haben die Barrier explizit mit "volatile" zu erreichen), weshalb man sich nicht auf ein solches einschränken muss. Im allerschlimmsten Fall bastelt man sich eben selbst eines auf Basis der vorhandenen Möglichkeiten.
Programmierer schrieb: > Bei der avr-libc ist es unglücklicherweise ein Implementierungs-Detail. > Andere Implementationen können (und sollten) explizit garantieren, dass > eine Optimization Barrier enthalten ist Aber garantiert hin oder her. Der Einsatz von volatile an dieser Stelle ist trotzdem nicht falsch. Es ist zwar vielleicht nutzlos weil seine Funktionalität schon vorweg genommen wurde, aber niemand setzt hier falsche Erwartungen in volatile oder gefährdet damit die Funktionalität des Programmes.
Gast
#6337249
@Rolf M. Sorry aber der Teil ist tatsächlich klein. Der Großteil der Systembibliotheken auf Linux ist in C/C++ geschrieben, Linux Kernel, Android Kernel, BSD, Gnome, GObject, KDE, Linpack, JVMs, etc. pp. Auf Systemen mit Betriebssystem und mehreren Kernen (also alle ARM SoCs der letzten Jahre) ist volatile auch gefährlich ohne Nutzen, da braucht man richtige Synchronisationsmechanismen. Das betrifft alle Smartphones, RPis usw. Klar kann man mit volatile auch sinnvolle Sachen anstellen. Aber das Qt Beispiel zeigt, dass die Gefahr einfach zu groß ist damit was falsch zu machen. Andere Programmiersprachen haben wegen sowas keine sichtbaren Zeiger. C/C++ sind zwar komplexer, sollten desswegen aber sicher nicht einfach alles erlauben was geht. Man muss auf dem uC auch sicher nicht nach jedem Registerzugriff ein sync machen, oder? Reicht doch häufig nach einer Gruppe von Zugriffen. Aber ich bin bei dir, das eine schönere Lösung hier wünschenswert wäre. Volatile ist aber halt aus vielen Gründen keine gute Lösung. > Warum muss man sich denn nach Alternativen umsehen? Es tut das, was es soll. Als Entwickler muss man sich immer auf den aktuellen Stand halten. Die Welt dreht sich weiter. Nur weil Quecksilberschalter mal populär waren, muss man die ja heute auch nicht mehr einsetzen. Abgesehen davon tut es statistisch gesehen für die meisten nicht was es soll, sie benutzen es einfach falsch.
Gast
#6337252
Cyblord -. schrieb: > Der Einsatz von volatile an dieser Stelle > ist trotzdem nicht falsch. Technisch nicht falsch. Aber sinnloser Code verwirrt den Leser, und das ist mindestens genauso schlimm. Man kann auch überall im Code Zeilenumbrüche oder nie benutzte Variablen einstreuen, aber das ist auch keine besonders gute Idee. Johannes schrieb: Alles absolut richtig. Danke dafür!
Johannes schrieb: > Abgesehen davon tut es statistisch gesehen für die meisten nicht was es > soll, sie benutzen es einfach falsch. Was du damit auch immer genau meinst. Bei AVRs wird volatile erstmal hauptsächlich für Memory-Mapped Peripherie benutzt. Und da tut es genau was es soll. Ein sehr geringer Teil der Programme verwendet volatile überhaupt noch darüber hinaus.
Gast
#6337257
Btw. ich bin tatsächlich kein Freund von C auf dem Mikrocontroller, ich würde viel häufiger C++ verwenden ;-). Allein namespaces sind ein ziemlich nützliches Feature, ebenso templates.
Programmierer schrieb: > Technisch nicht falsch. Aber sinnloser Code verwirrt den Leser Sehe ich nicht so. Das volatile zeigt dem Leser genau was erwartet wird. Und das stimmt dann auch. Ohne volatile muss er erst auf den atomic block und dann auf das dahinterliegende cli vordringen um zu verstehen warum das Programm überhaupt funktioniert. D.h. er muss über C Kentnisse hinaus noch die Implementierungsdetails der Architektur kennen.
Gast
#6337264
Cyblord -. schrieb: > Sehe ich nicht so. Das volatile zeigt dem Leser genau was erwartet wird. > Und das stimmt dann auch. Und der Leser weiß dass das "volatile" nicht etwa gebraucht wird, um noch innerhalb des ATOMIC_BLOCKS irgendwie eine Schreib-Reihenfolge zu garantieren, sondern wirklich nur weil man sich auf die Standard-Bibliothek nicht verlassen kann? Cyblord -. schrieb: > Ohne volatile muss er erst auf den atomic block und dann auf das > dahinterliegende cli vordringen um zu verstehen warum das Programm > überhaupt funktioniert. D.h. er muss über C Kentnisse hinaus noch die > Implementierungsdetails der Architektur kennen. Oder einfach nur die Doku des Makros lesen. Wie gesagt ist es unglücklich dass die avr-libc das nicht vernünftig dokumentiert. Für Systeme mit korrekter Portierung von std::atomic kann man das einfach in C++-Büchern nachlesen, dann ist es sogar portabel.
Programmierer schrieb: > unglücklich dass die avr-libc das nicht vernünftig dokumentiert. Für > Systeme mit korrekter Portierung von std::atomic Die avrlibc dokumentiert das sehr wohl. Und das die als C-Library ein std::atomic nicht korrekt portiert, ist ihr auch nicht vorzuwerfen ;) Oliver
Gast
#6337280
Imho besteht das Problem beim "Reinklatschen" von volatile darin, dass man den Code überspezifiziert. Wie Programmierer schon angesprochen hat, niemand weis hinterher welche der Sachen tatsächlich erforderlich sind und welche nicht. Wenn man solchen Code vorgesetzt bekommt und erweitern soll, oder halt seinen eigenen Code nach 3 Jahren wieder verstehen will, ist das etwas knifflig. Am Ende reißt man es ein und schreibt's neu, weil man sich nur so sicher sein kann, das es tut, was es soll. Man kann da ein wenig mit Kommentaren nachhelfen, aber spätestens da würde ich mir dann doof vorkommen. Spätestens bei sowas wie
1 | |
würde ich den Code löschen und sauber neu schreiben. Oder als Code Reviewer den Change ablehnen und darauf bestehen, dass da Klarheit geschaffen wird.
Gast
#6337281
Oliver S. schrieb: > Die avrlibc dokumentiert das sehr wohl. Oh, wo denn? Hier leider nicht: https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html#gaaaea265b31dabcfb3098bec7685c39e4 Oliver S. schrieb: > Und das die als C-Library ein > std::atomic nicht korrekt portiert, ist ihr auch nicht vorzuwerfen ;) Logisch, std::atomic ist als Abkürzung für "std::atomic in C++ und _Atomic in C" zu verstehen; implementiert die avr-libc letzteres?
Programmierer schrieb: > Axel S. schrieb: >> Schon recht. Nur war das überhaupt keine Option, die zur Wahl gestanden >> hätte. Es ging um "etwas langsamer als nötig" vs. "funktioniert nicht". > > Und die Abstufung "funktioniert meistens" gibt es nicht? Die gibt es. Aber sie ist in "funktioniert nicht" mit eingeschlossen. Und nochmal: die Optionen waren "ein volatile zuviel" vs. "ein volatile zu wenig". Letzteres ist das, was kaputt geht. Es gibt im Kontext dieser Wahlmöglichkeiten genau gar keinen Grund, nochmal verschiedene Grade von Kaputtheit zu unterscheiden. > Es geht nur darum, dass viele "volatile" falsch > verstehen, und annehmen, es würde so etwas garantieren. Daher kann man > nochmal klarstellen, was es nicht garantiert. Das ist aber etwas ganz anderes, als den Leuten zu sagen, sie sollten volatile bitte ganz schnell vergessen. Programmierer schrieb: > Oliver S. schrieb: >> Die avrlibc dokumentiert das sehr wohl. > > Oh, wo denn? Hier leider nicht: > > https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html#gaaaea265b31dabcfb3098bec7685c39e4 >> Creates a block of code that is guaranteed to be executed atomically Was fehlt dir denn da? >> Und das die als C-Library ein >> std::atomic nicht korrekt portiert, ist ihr auch nicht vorzuwerfen ;) > > Logisch, std::atomic ist als Abkürzung für "std::atomic in C++ und > _Atomic in C" zu verstehen; Sagt wer? Ich lese da "std::atomic" und das ist eindeutig C++ > implementiert die avr-libc letzteres? Natürlich nicht. Das kann so nur der Compiler implementieren.
Gast
#6337311
Axel S. schrieb: > Es gibt im Kontext dieser > Wahlmöglichkeiten genau gar keinen Grund, nochmal verschiedene Grade von > Kaputtheit zu unterscheiden. Doch, die Kaputtheit die nur manchmal zuschlägt ist gefährlicher, weshalb man diese separat betrachten kann. Axel S. schrieb: > Das ist aber etwas ganz anderes, als den Leuten zu sagen, sie sollten > volatile bitte ganz schnell vergessen. Das hab ich auch nicht. Axel S. schrieb: > Was fehlt dir denn da? Der Hinweis dass im Block auftretende Lese-Zugriffe nicht vor den Block verschoben werden können, und dass im Block auftretende Schreibzugriffe nicht nach den Block verschoben werden können ("optimization barrier"). Praktisch dass der Blockanfang Acquire-Semantik hat und das Block-Ende Release-Semantik. Axel S. schrieb: > Sagt wer? Ich, denn ich war einfach nur zu faul es auszuführen. Die beiden Mechanismen sind äquivalent, als Leser kann man diesen Zusammenhang also erkennen. Axel S. schrieb: > Natürlich nicht. Das kann so nur der Compiler implementieren. Wenn, dann typischerweise aber in Zusammenarbeit mit der libc. Ich vermute dass man es zumindest teilweise in C mit Inline-Asm hinbekommen könnte. Es wird wahrscheinlich ohnehin darauf hinauslaufen, dass die _Atomic-Funktionen einfach ATOMIC_BLOCK benutzen, da der AVR keine Hardware-Atomics hat.
Programmierer schrieb: >> Es gibt im Kontext dieser Wahlmöglichkeiten genau gar >> keinen Grund, nochmal verschiedene Grade von >> Kaputtheit zu unterscheiden. > > Doch, die Kaputtheit die nur manchmal zuschlägt ist > gefährlicher, weshalb man diese separat betrachten kann. Wenn ich die Wahl habe zwischen "ein volatile zu viel" und "ein volatile zu wenig", dann ist die erste Wahl korrekt. Denn zuviel volatile macht garantiert nichts kaputt, was nicht vorher schon kaputt war. Dagegen führt ein benötigtes volatile weglassen zu einem kaputten Programm. > Axel S. schrieb: >> Das ist aber etwas ganz anderes, als den Leuten zu sagen, >> sie sollten volatile bitte ganz schnell vergessen. > > Das hab ich auch nicht. Stimmt, das war der Johannes. Dem du explizit zugestimmt hast. Du bevorzugst ein garantiert kaputtes Programm (zuwenig volatile) gegenüber einem zu langsamen, aber funktionierendem Programm (zuviel volatile).
Johannes schrieb: > @Rolf M. > > Sorry aber der Teil ist tatsächlich klein. Der Großteil der > Systembibliotheken auf Linux ist in C/C++ geschrieben, Linux Kernel, > Android Kernel, Der Android-Kernel ist der Linux-Kernel. > BSD, Gnome, GObject, KDE, Linpack, JVMs, etc. pp. Das ist der Teil, den du siehst. Aber es gibt auch extrem viel Code, der nicht so offensichtlich ist, und davon läuft viel auf µCs, z.B. in deiner Kaffeemaschine oder dem ESP deines Autos. Selbst am und im PC wird es etliche µCs geben, z.B.im Controller deiner SSD oder dem Tastaturcontroller, im Monitor und dem Drucker. Selbst so Dinge wie die Ansteuerung der bunten Show-LEDs in der Grafikkarte wird einen eigenen µC haben. In einem Handy wird es auch jede Menge Mikrocontroller geben. Und das wird überwiegend C-Code sein. µCs kommen halt in etwas mehr Komponenten vor als nur der selbstgebastelten Propeller-Clock. > Man muss auf dem uC auch sicher nicht nach jedem Registerzugriff ein > sync machen, oder? Reicht doch häufig nach einer Gruppe von Zugriffen. Kommt immer ganz drauf an, würde ich sagen. Wesentlich ist auf jeden Fall, dass mehrere aufeinanderfolgende Zugriffe auf das selbe Register auch wirklich alle stattfinden und der Compiler nicht alle bis auf einen wegoptimiert, wie er es bei einer regulären Variable tun würde. > Aber ich bin bei dir, das eine schönere Lösung hier wünschenswert wäre. > Volatile ist aber halt aus vielen Gründen keine gute Lösung. > >> Warum muss man sich denn nach Alternativen umsehen? Es tut das, was es > soll. > > Als Entwickler muss man sich immer auf den aktuellen Stand halten. Die > Welt dreht sich weiter. Nur weil Quecksilberschalter mal populär waren, > muss man die ja heute auch nicht mehr einsetzen. Wenn es was überzeugendes gibt, das alle_ Anwendungsfälle auf _allen Plattformen abdeckt, das gleichzeitig einen erkennbaren Vorteil bietet, dann bin ich auch dafür. Nur sehe ich das im Moment nicht so wirklich. > Abgesehen davon tut es statistisch gesehen für die meisten nicht was es > soll, sie benutzen es einfach falsch. Das ist jetzt Ansichtssache, was du unter "soll" verstehst. Für mich heißt es, dass es sich entsprechend der Spezifikation verhält und nicht unbedingt so, wie sich das der Anwender ggf. wünschen würde.
Rolf M. schrieb: > Der Android-Kernel ist der Linux-Kernel. Nein. Der Android-Kernel ist ein aktuell gehaltener, Google-eigener Fork des Linux-Kernels. Man kann kein CDD-konformes Android mit einem Mainline-Linux bauen.
Gast
#6337376
Arduino Fanboy D. schrieb: > Statische Variablen in Funktionen sind böse, da diese die > Wiedereintrittsfähigkeit(Reentrance) vernichten. Kannst Du das ein bisschen näher erläutern? Würde mich mal interessieren. In einer AVR Appnote steht, dass man static-Variablen innerhalb von Funktionen vermeiden soll, da für diese 1) Speicher an ungünstigen Stellen bereitgestellt werden muss und 2) Zusätzlicher Code zum Lesen und Schreiben dieser Variablen erzeugt wird, was wiederum zusätzliche Zyklen erfordert. Soweit verständlich. Aber was hat es mit dem Wiedereintritt auf sich?
Programmierer schrieb: > Axel S. schrieb: >> Was fehlt dir denn da? > > Der Hinweis dass im Block auftretende Lese-Zugriffe nicht vor den Block > verschoben werden können, und dass im Block auftretende Schreibzugriffe > nicht nach den Block verschoben werden können ("optimization barrier"). Und der Teil, der garantiert, dass die Zugriffe, die innerhalb des Blocks stehen, überhaupt alle wirklich stattfinden. Gerade das ist ja die Hauptaufgabe von volatile. > Axel S. schrieb: >> Natürlich nicht. Das kann so nur der Compiler implementieren. > > Wenn, dann typischerweise aber in Zusammenarbeit mit der libc. Die eigentliche Funktionalität muss dabei aber im Compiler stecken. > Ich vermute dass man es zumindest teilweise in C mit Inline-Asm hinbekommen > könnte. Wie soll das gehen? Der Code innerhalb des Blocks wird vom Compiler generiert, und der muss diesen dann anders generieren. eine Funktion in einer ISR und gleichzeitig auch außerhalb einer ISR aufgerufen werden könnte. Oder wenn die Funktion sich selbst rekursiv aufruft.
Gast
#6337415
S. R. schrieb: > Wenn ich die Wahl habe zwischen "ein volatile zu viel" und "ein volatile > zu wenig", dann ist die erste Wahl korrekt. Zum Glück hat man diese Wahl nie. S. R. schrieb: > Dagegen führt ein benötigtes volatile weglassen zu einem kaputten > Programm. Wenn man std::atomic nutzt, hat man dieses Problem gar nicht erst. S. R. schrieb: > Du bevorzugst ein garantiert kaputtes Programm (zuwenig volatile) > gegenüber einem zu langsamen, aber funktionierendem Programm (zuviel > volatile). Nein. Meine Präferenz-Reihenfolge: * Ein Programm mit std::atomic, std::mutex & Co (funktioniert garantiert) ganz ohne volatile * Ein Programm mit viel zu wenig Synchronisation, welches so kaputt ist dass es sich gar nicht testen lässt, weil es dann nie released wird und keinen Schaden anrichten kann (nutzlos) * Ein Programm mit exakt der richtigen Menge an "volatile" (schwierig zu erstellen und warten) * Ein Programm mit etwas zu wenig Synchronisation, welches in manchmal nicht funktioniert (gefährlich). Rolf M. schrieb: > Und der Teil, der garantiert, dass die Zugriffe, die innerhalb des > Blocks stehen, überhaupt alle wirklich stattfinden. Sie finden ggf. nicht einzeln statt. Wenn man mehrere, einzelne Zugriffe braucht, braucht man mehrere Atomic-Blöcke, und daraus werden dann einzelne Zugriffe kompiliert. Rolf M. schrieb: > Wie soll das gehen? Der Code innerhalb des Blocks wird vom Compiler > generiert, und der muss diesen dann anders generieren. Hä? Reden wir von der selben Sache? Eine Atomic-Addition könnte bei AVR so aussehen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
Braucht nichtmal Assembler.
Programmierer schrieb: > Rolf M. schrieb: >> Und der Teil, der garantiert, dass die Zugriffe, die innerhalb des >> Blocks stehen, überhaupt alle wirklich stattfinden. > > Sie finden ggf. nicht einzeln statt. Was meinst du damit? Mir geht es konkret um sowas wie z.B. eine Schleife, in der so eine atomare Operation durchgeführt wird. Der Optimizer könnte auf die Idee kommen, diese wiederholten Zugriffe auf den RAM zu entfernen und den Wert nur in einem Register zu halten. Bei der Dokumentation von ATOMIC_BLOCK sehe ich keine Garantie, dass sowas nicht passiert. > Rolf M. schrieb: >> Wie soll das gehen? Der Code innerhalb des Blocks wird vom Compiler >> generiert, und der muss diesen dann anders generieren. > > Hä? Reden wir von der selben Sache? Vielleicht nicht. > Eine Atomic-Addition könnte bei AVR so aussehen: > int atomic_fetch_add_int (int* obj, int arg) { > int res; > ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { > res = *obj; > obj += arg; > } > return res; > } > Braucht nichtmal Assembler. Ich dachte, es geht um die Umsetzung des Makros ATOMIC_BLOCK so, dass es volatile überflüssig macht, und das ohne spezielle Unterstützung seitens des Compilers.
Programmierer schrieb: >> Dagegen führt ein benötigtes volatile weglassen >> zu einem kaputten Programm. > > Wenn man std::atomic nutzt, hat man dieses Problem gar nicht erst. Aha. Du hast gerade bewiesen, dass du den Unterschied zwischen std::atomic und volatile nicht kennst. Die tun nämlich nicht dasselbe und ersetzen einander nicht. Super.
Gast
#6337431
Rolf M. schrieb: > Der Optimizer könnte auf die Idee kommen, diese wiederholten Zugriffe > auf den RAM zu entfernen und den Wert nur in einem Register zu halten. Macht er aber nicht, wenn der Zugriff einzeln im ATOMIC_BLOCK steht und nicht die ganze Schleife. Das haben wir doch jetzt schon 25x diskutiert. Rolf M. schrieb: > Bei der Dokumentation von ATOMIC_BLOCK sehe ich keine Garantie, dass > sowas nicht passiert Darum geht es doch die ganze Zeit ?♂️ es ist aber so, da das sei() innerhalb des Makros einen Memory Clobber enthält. Das fehlt lediglich in der Doku. Rolf M. schrieb: > Ich dachte, es geht um die Umsetzung des Makros ATOMIC_BLOCK so, dass es > volatile überflüssig macht, Nein, ein solches Makro macht keinen Sinn. Ich rede von einer Implementation von std::atomic bzw _Atomic.
Gast
#6337434
S. R. schrieb: > Die tun nämlich nicht_ dasselbe und ersetzen einander _nicht. Genau das sagte ich in meinem 1. Post. Super. Auf Standard konformen "hosted" Implementationen von C und C++ ersetzt atomic sehr wohl volatile. Für ISR Synchro sollte es auch weitgehend volatile ersetzen. Für Memory Mapped IO nicht.
Gast
#6337438
S. R. schrieb: > Die tun nämlich nicht dasselbe > und ersetzen einander nicht. Ja sie tun nicht das selbe. Aber atomics ersetzt nahezu alle volatiles, wo diese fälschlicherweise eingesetzt werden/wurden ;-)
Gast
#6337447
Das folgende Beispiel demonstriert, wozu volatile gut ist und dass man volatile nicht immer durch einen atomic-block ersetzen kann:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
Das Programm soll die Variable i von 255 auf 0 herunter zählen und dann auf Port C ausgeben. Wenn aber zwischendurch der Interrupt ausgelöst wird, soll sie zurück auf 255 gesetzt werden. Ein Blick in den Assembler-Code zeigt, dass dieses Programm nicht funktioniert:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
Der Fehler entsteht durch den Rücksprung nach 0xc8. Hier wird R24 wiederholt verringert ohne die RAM Zelle i erneut zu lesen. Unterhalb der while Schleife wird dann einfach fest 0 auf PORTC ausgegeben (r1 ist immer 0). Was die ISR macht, wird einfach völlig ignoriert. Das Schlüsselwort volatile (vor uint8_t i=255;) behebt diesen Fehler:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
Dieses mal wird die Variable bei jedem Zugriff erneut aus dem RAM gelesen, so dass die Änderung durch die ISR wirksam ist. Mit einem Atmoc-Block kann man das nicht lösen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
Jetzt wird zwar innerhalb der Schleife i immer schön neu aus dem RAM gelesen, aber zum Schluss wird doch wieder einfach 0 auf PORTC ausgegeben:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
Man könnte nun versucht sein, den Automic-Block zu vergrößern:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
Aber auch das generiert fehlerhaften Code:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
Die Schleife kann nicht mehr durch die ISR unterbrochen werden. Folglich verkürzt der Compiler die Ausgabe auf PORTC wieder auf den festen Wert 0. Andere Werte können gar nicht mehr vorkommen. Ich sehe hier keine Chance, dieses kleine Programm ohne volatile ans Laufen zu bekommen. Ich habe mir für die Mikrocontroller-Programmierung folgende Regeln gemerkt: 1) Wenn eine Variable in eine ISR beschrieben und außerhalb gelesen wird (oder umgekehrt), muss sie volatile sein. 2) Wenn die Variable größer ist, als die CPU in einem Rutsch lesen/schreiben kann, dann braucht man außerdem noch einen atomic-block (oder cli/sei). Ansonsten kann man beim Lesen inkonsistente Daten bekommen, weil nur eine Hälfte aktualisiert wurde. Bei AVR betrifft das alle Variablen, die größer als 8bit sind.
Gast
#6337457
Du hast einen ATOMIC_BLOCK zu wenig. Versuch mal:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
Hab grad keinen Compiler zur Hand.
Gast
#6337467
... ist so natürlich ineffizienter. Echtes std::atomic erlaubt über die Memory Order Parameter präzisere Angaben darüber, wann welche Synchronisation erforderlich ist, um unnötige Operationen zu vermeiden. ATOMIC_BLOCK ist hier eher Holzhammer.
Na ja, in dem Fall würden zwei Atomic-Blöcke, oder eine Memory Barrier
vor der letzten Zeile per cli(), asm volatile("": : :"memory"), oder
__sync_synchronize das Problem lösen, auch ohne volatile.
Oliver
Gast
#6337479
Programmierer schrieb: > Du hast einen ATOMIC_BLOCK zu wenig. Versuch mal Ja geht:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
Programmierer schrieb: > ATOMIC_BLOCK ist hier eher Holzhammer. Sieht man. Ich habe das Gefühl, dass hier jetzt das atomic-block nur noch wegen seinem Seiteneffekt genutzt wird. Außerdem haben wir jetzt zu 50% der Schleifen-Zeit alle Interrupts gesperrt, was gar nicht nötig wäre. Die primäre Funktion des atomic-block ist laut der Doku offenbar: > The term "Atomic" in this context refers to the unability > of the respective code to be interrupted." Ich will ja gar nicht verbieten, das Interrupts dazwischen kommen. Was aber nicht geht ist, dass die Änderungen an i durch die ISR komplett ignoriert werden. In den weiteren Erklärungen zeigen sie, wann und warum man atomic-block mit volatile kombinieren soll. Quelle: https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html Ich würde mich lieber daran halten, um unangenehme Überraschungen nach Compiler-Upgrade zu vermeiden.
Gast
#6337489
volatile ist selektiver als asm volatile("": : :"memory")
Gast
#6337517
Nein du kehrst die Auswirkungen von volatile unter den Teppic. volatile sorgt bei jedem *einzelnen* Zugriff dafür, dass der Wert neu gelesen wird. Geht mit atomic einwandfrei nachzubilden, muss dann aber bei jedem *einzelnen* Zugriff auch angegeben werden - zumindest mit dem avr libc Konstrukt.
Programmierer schrieb: > Auf Standard konformen "hosted" Implementationen von > C und C++ ersetzt atomic sehr wohl volatile. > Für ISR Synchro sollte es auch weitgehend volatile ersetzen. Das ist falsch. > Für Memory Mapped IO nicht. Das ist richtig. mh schrieb: > Aber atomics ersetzt nahezu alle volatiles, > wo diese fälschlicherweise eingesetzt werden/wurden ;-) Darum ging es aber nicht. Es ging um die Aussage vom Johannes, dass alle volatiles falsch sind und man niemals volatile verwenden sollte, weil atomics das können. Das ist falsch. Johannes schrieb: > volatile sorgt bei jedem einzelnen Zugriff dafür, dass der Wert neu > gelesen wird. Geht mit atomic einwandfrei nachzubilden, Das ist falsch.
Gast
#6337591
Doch das ist tatsächlich so. Probier es aus. https://en.cppreference.com/w/cpp/language/cv > Every access (read or write operation, member function call, etc.) made > through a glvalue expression of volatile-qualified type is treated as a > visible side-effect [...] Im allgemeinen wäre es schön, wenn du auch Beispiele liefern würdest. Ich mach mir schließlich auch die Mühe. Ohne wenigstens irgend einen Beleg ist deine Aussage leider nichts wert.
Gast
#6337593
Johannes schrieb: > Doch das ist tatsächlich so. Probier es aus. Oder gucke dir einfach mein obiges Beispiel an, ich habe es ausprobiert.
Gast
#6337640
Noch eine Sache dazu, wie Registerzugriffe ohne volatile aussehen könnten:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
Ist ein bare minimum sample. In C++ erzeugt das keinerlei overheat verglichen mit direktem Zugriff auf die Werte, ich hab mir die Assemblerausgabe angesehen. Die Kapselung in Klassen erlaubt einem nun aber, ganz unabhängig vom Compiler, die Zugriffssicherungen zu implementieren die man möchte. Das könnte atomic sein, inline asm oder was auch immer.
Johannes schrieb: > Doch das ist tatsächlich so. Probier es aus. Mir ging es um das "Geht mit atomic einwandfrei nachzubilden" von dir. Das ist falsch. Ein atomic gibt nicht die gleichen Garantien wie ein volatile, also kann man das eine nicht mit dem anderen ersetzen. Atomic garantiert, dass der Zugriff nicht geteilt wird. Volatile garantiert, dass der Zugriff stattfindet. Im Übrigen ist dein Beispiel aus meiner Sicht vor allem ein Zeichen für "viel Boilerplate für erstaunlich wenig Nutzen". Davon hab ich auf Arbeit schon genug.
Programmierer schrieb: > Axel S. schrieb: >> Es gibt im Kontext dieser >> Wahlmöglichkeiten genau gar keinen Grund, nochmal verschiedene Grade von >> Kaputtheit zu unterscheiden. > > Doch, die Kaputtheit die nur manchmal zuschlägt ist gefährlicher, > weshalb man diese separat betrachten kann. Du faselst. Schlag das Wort "Kontext" nach. >> Was fehlt dir denn da? > > Der Hinweis dass im Block auftretende Lese-Zugriffe nicht vor den Block > verschoben werden können, und dass im Block auftretende Schreibzugriffe > nicht nach den Block verschoben werden können ("optimization barrier"). Das heißt, du erwartest, daß die Dokumentation nicht nur sagt: "das erzeugt einen atomic block", sondern daß sie außerdem noch erläutert, was ein atomic block ist? Das ist unsinnig. Irgendwas muß man voraussetzen. Man kann nicht an jeder Stelle anfangen, alles vom Urschleim her durchzudeklinieren. >> Natürlich nicht. Das kann so nur der Compiler implementieren. > > Wenn, dann typischerweise aber in Zusammenarbeit mit der libc _Atomic kann auch als type qualifier verwendet werden. Dann muß der Code für den Zugriff auf den entsprechenden Typ atomar sein. Das kann sinnvoll nur der Compiler so erzeugen. > vermute dass man es zumindest teilweise in C mit Inline-Asm > hinbekommen könnte. Das ist eine Binsenweisheit. C und ASM sind gleichmächtig. Der entscheidende Unterschied ist das Abstraktionsniveau. Selbstverständlich kann man bei jedem Zugriff auf einen "großen" Datentyp jedesmal einen ATOMIC_BLOCK drum herum bauen (oder in ASM eine CLI/SEI Klammer). Aber es ist wesentlich sauberer, einfach den Typ als _Atomic zu markieren und es den Compiler automatisch erzeugen zu lassen. > Es wird wahrscheinlich ohnehin darauf hinauslaufen, dass die > _Atomic-Funktionen einfach ATOMIC_BLOCK benutzen _Atomic ist mehr, als das was ATOMIC_BLOCK bietet.
Gast
#6337693
S. R. schrieb: > Darum ging es aber nicht. Es ging um die Aussage vom Johannes, dass alle > volatiles falsch sind und man niemals volatile verwenden sollte, weil > atomics das können. Das ist falsch. Das konnte ich deinem Beitrag nicht entnehmen. S. R. schrieb: > Mir ging es um das "Geht mit atomic einwandfrei nachzubilden" von dir. > Das ist falsch. Ein atomic gibt nicht die gleichen Garantien wie ein > volatile, also kann man das eine nicht mit dem anderen ersetzen. Du hast nicht genau gelesen, was er geschrieben hat. Er behauptet an der Stelle nicht, dass man mit atomics alle volatiles ersetzen kann. Er spricht von einer Eigenschaft, die ersetzt werden kann. S. R. schrieb: > Atomic garantiert, dass der Zugriff nicht geteilt wird. > Volatile garantiert, dass der Zugriff stattfindet. Das ist beides nicht vollständig und der zweite Teil ist nichtmal korrekt. Axel S. schrieb: > Das heißt, du erwartest, daß die Dokumentation nicht nur sagt: "das > erzeugt einen atomic block", sondern daß sie außerdem noch erläutert, > was ein atomic block ist? Wenn eindeutig ist, was ein atomic block ist, muss es nicht in der Doku stehen. Wie genau lautet denn die 100% immer korrekte Definiton von "atomic block"?
Gast
#6337841
Stefan ⛄ F. schrieb: > Ich habe das Gefühl, dass hier jetzt das atomic-block nur noch wegen > seinem Seiteneffekt genutzt wird. Richtig. Aber da der ATOMIC_BLOCK eben ein ungenauer Holzhammer ist kann er nicht für Datentypen, die sowieso atomisch sind (uint8_t) differenzieren. S. R. schrieb: > Das ist falsch. Nein. S. R. schrieb: > Das ist richtig. Wobei es auch hier Alternativen gibt, die aber nicht unbedingt besser sind. S. R. schrieb: > Atomic garantiert, dass der Zugriff nicht geteilt wird. > Volatile garantiert, dass der Zugriff stattfindet. atomic auch. Du solltest mal was über Memory Ordering lesen: https://en.cppreference.com/w/cpp/atomic/memory_order Axel S. schrieb: > sondern daß sie außerdem noch erläutert, > was ein atomic block ist? Das ist unsinnig. Irgendwas muß man > voraussetzen. Man kann nicht an jeder Stelle anfangen, alles vom > Urschleim her durchzudeklinieren. Da "atomic block" eben keineswegs ein Standard-C oder -C++ -Feature ist, sondern ein avr-libc-Kuriosum, durchaus. Außerdem fing Cyblord damit an, über die lückenhafte Doku zu mäkeln. Axel S. schrieb: > Das kann > sinnvoll nur der Compiler so erzeugen. Stimmt. In C++ braucht man "nur" eine Klasse std::atomic zu bauen, da ist das ggf. anders. Axel S. schrieb: > C und ASM sind gleichmächtig. Nö. ASM kann plattformspezifische Dinge tun, die sinnvollerweise nicht Teil von C sind, da sie sich nicht portabel abbilden lassen. Axel S. schrieb: > _Atomic ist mehr, als das was ATOMIC_BLOCK bietet. Ja, es ist präziser. Ich meinte, dass die konkrete Implementation der _Atomic / std::atomic Operationen vermutlich in C oder C++, ggf. mit etwas Inline-Asm, abbildbar ist. Entweder muss man die dann manuell aufrufen (ohne _Atomic) oder der Compiler hier nur als Weiche fungieren. Aber das ist Haarspalterei, ist für die Diskussion kaum relevant. Hätte man eine std::atomic Implementation für AVR, könnte man o.g. Programm so schreiben:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
Für _Atomic geht es ähnlich, keine Lust es nachzuschlagen. std::atomic<std::uint8_t> sollte automatisch wissen dass std::uint8_t atomisch geschrieben werden kann, und dementsprechend keine Interruptsperre einbauen. Eine gute Implementation vorausgesetzt sollte hier für AVR der selbe Code herauskommen wie bei volatile. Da aber der AVR-GCC bzw. avr-libc anscheinend keine Implementation von std::atomic / _Atomic bietet, muss man sich mit der Krücke ATOMIC_BLOCK behelfen, welche zwar auch funktioniert aber eben immer den Worst Case annimmt und die Interrupts sperrt, weil sie nicht zwischen unterschiedlichen Datentypen differenzieren kann. Besser verdeutlichen lässt sich das Ganze mit ARMv7, welches Support für Atomics hat der auch vom GCC unterstützt wird:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
wird kompiliert zu (für Cortex-M3):
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
Wie zu sehen ist, wird erst die Zahl in einer Schleife atomisch dekrementiert. Wenn ein anderer Thread (oder ISR) dazwischenfunkt und die Variable verändert, schlägt das "strex" fehl und es wird erneut versucht (Zeile "a"). Interessant ist Zeile "12": Hier wird der Wert erneut geladen, denn der Compiler weiß, dass sich Atomics extern verändern können, und erzeugt eine neue "ldr" Instruktion. Zum Vergleich, so sieht es aus ohne atomic:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
Wird zu
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
Kein erneutes Laden. std::atomic hat hier also das "volatile" ersetzt, und das ohne Interruptsperre (erzeugt der GCC sowieso nicht automatisch).
Programmierer schrieb: > Rolf M. schrieb: >> Der Optimizer könnte auf die Idee kommen, diese wiederholten Zugriffe >> auf den RAM zu entfernen und den Wert nur in einem Register zu halten. > > Macht er aber nicht, wenn der Zugriff einzeln im ATOMIC_BLOCK steht und > nicht die ganze Schleife. Das haben wir doch jetzt schon 25x diskutiert. Es ging darum, dass die DOKUMENTATION das nicht erwähnt. Mein Text war die Antwort auf die Frage, welche Information in der Doku fehlt. Du hast leider die beiden zusammengehörenden Sätze auseinandergerissen und dann separat beantwortet. > Das fehlt lediglich in der Doku. Genau. Und das war das einzige, was ich damit sagen wollte. > Rolf M. schrieb: >> Ich dachte, es geht um die Umsetzung des Makros ATOMIC_BLOCK so, dass es >> volatile überflüssig macht, > > Nein, ein solches Makro macht keinen Sinn. Ich rede von einer > Implementation von std::atomic bzw _Atomic. Ok, dann haben wir tatsächlich von verschiedenen Dingen geredet.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.