Forum: Mikrocontroller und Digitale Elektronik volatile Funktionen


von Bernd K. (bkohl)


Lesenswert?

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;
}

von S. R. (svenska)


Lesenswert?

Ich sehe in deinem Beispielcode kein "volatile".

von Einer K. (Gast)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

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.

von Teo D. (teoderix)


Lesenswert?

Das wird sich da sicher nur auf den Rückgabewert beziehen.

von Peter D. (peda)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

Ergänzung: Mit volatile Funktionen in C++ meine ich natürlich 
Mitgliedsfunktionen einer Klasse.

von Stefan F. (Gast)


Lesenswert?

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
function foo()
2
{
3
  bar++;
4
  if (whatever) bar=bar+5
5
  if (whatelse) bar=bar/2;
6
  bar++;
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.

von Oliver S. (oliverso)


Lesenswert?

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

von Bernd K. (bkohl)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Programmierer (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Programmierer (Gast)


Lesenswert?

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
#include <thread>
2
#include <iostream>
3
#include <vector>
4
#include <chrono>
5
#include <string>
6
7
void threadFun (int index, volatile int& counter) {
8
  std::this_thread::sleep_for (std::chrono::milliseconds { index * 10 });
9
  ++counter;
10
  std::cout << (std::to_string (counter) + "\n");
11
}
12
13
int main () {
14
  volatile int counter = 0;
15
  
16
  std::vector<std::thread> threads;
17
  for (int i = 0; i < 100; ++i)
18
    threads.emplace_back (threadFun, i, std::ref (counter));
19
  
20
  for (auto& t : threads)
21
    t.join ();
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
#include <thread>
2
#include <iostream>
3
#include <vector>
4
#include <chrono>
5
#include <string>
6
#include <atomic>
7
8
void threadFun (int index, std::atomic<int>& counter) {
9
  std::this_thread::sleep_for (std::chrono::milliseconds { index * 10 });
10
  int res = counter.fetch_add (1, std::memory_order_relaxed);
11
  std::cout << (std::to_string (res+1) + "\n");
12
}
13
14
int main () {
15
  std::atomic<int> counter {0};
16
  
17
  std::vector<std::thread> threads;
18
  for (int i = 0; i < 100; ++i)
19
    threads.emplace_back (threadFun, i, std::ref (counter));
20
  
21
  for (auto& t : threads)
22
    t.join ();
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
#include <thread>
2
#include <iostream>
3
#include <vector>
4
#include <chrono>
5
#include <string>
6
#include <mutex>
7
8
void threadFun (int index, int& counter, std::mutex& mtx) {
9
  std::this_thread::sleep_for (std::chrono::milliseconds { index * 10 });
10
  
11
  int res;
12
  {
13
    std::lock_guard<std::mutex> l (mtx);
14
    
15
    res = ++counter;
16
  }
17
  
18
  std::cout << (std::to_string (res) + "\n");
19
}
20
21
int main () {
22
  int counter = 0;
23
  std::mutex mtx;
24
  
25
  std::vector<std::thread> threads;
26
  for (int i = 0; i < 100; ++i)
27
    threads.emplace_back (threadFun, i, std::ref (counter), std::ref (mtx));
28
  
29
  for (auto& t : threads)
30
    t.join ();
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.

von Michael D. (nospam2000)


Lesenswert?

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

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Johannes schrieb:
> Volatile hilft ein wenig bei single CPU wenn man write ordering möchte.

Mit "write ordering" hat Volatile nichts zu tun.

von Programmierer (Gast)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Einer K. (Gast)


Lesenswert?

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)

von Cyblord -. (cyblord)


Lesenswert?

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.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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

von Programmierer (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Programmierer (Gast)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

@Stefan ⛄ F.

Das stimmt halt nicht. Schau weiter oben, ein beliebtes Element zur 
Synchronisierung ist z.B.
1
asm volatile("": : :"memory")

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.

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

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.

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

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?

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

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

von Cyblord -. (cyblord)


Lesenswert?

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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

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

von Programmierer (Gast)


Lesenswert?

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!

von Cyblord -. (cyblord)


Lesenswert?

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.

: Bearbeitet durch User
von Johannes (Gast)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Johannes (Gast)


Lesenswert?

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
// NOTE: The following volatiles are not strictly required, but I'm not sure how this works and hopefully they make things a little bit more secure.

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.

von Programmierer (Gast)


Lesenswert?

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?

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Kosmos (Gast)


Lesenswert?

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?

von Rolf M. (rmagnus)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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
int atomic_fetch_add_int (int* obj, int arg) {
2
  int res;
3
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
4
    res = *obj;
5
    obj += arg;
6
  }
7
  return res;
8
}
Braucht nichtmal Assembler.

von Rolf M. (rmagnus)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von Programmierer (Gast)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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 ;-)

von Stefan F. (Gast)


Lesenswert?

Das folgende Beispiel demonstriert, wozu volatile gut ist und dass man 
volatile nicht immer durch einen atomic-block ersetzen kann:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
uint8_t i=255;
6
7
ISR(PCINT0_vect)
8
{
9
    i=255;
10
}
11
12
int main(void) 
13
{
14
    // Port C = Ausgang
15
    PORTC = 255;
16
17
    // Interrupt bei Signal von PB0 
18
    PCICR = (1<<PCIE0);
19
    PCMSK0 = (1<<PCINT0);
20
    sei();
21
22
    do
23
    {
24
        i=i-1;
25
        PINB; // damit die Schleife nicht wegoptimiert wird
26
    }
27
    while (i>0);
28
29
    // Ergebnis ausgeben
30
    PORTC=i;
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
000000b4 <main>:
2
  b4:   8f ef           ldi     r24, 0xFF
3
  b6:   88 b9           out     0x08, r24
4
  b8:   81 e0           ldi     r24, 0x01
5
  ba:   80 93 68 00     sts     0x0068, r24
6
  be:   80 93 6b 00     sts     0x006B, r24
7
  c2:   78 94           sei
8
  c4:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
9
10
  c8:   81 50           subi    r24, 0x01       ; subtrahiere 1 von R24
11
  ca:   80 93 00 01     sts     0x0100, r24     ; i = R24
12
  ce:   93 b1           in      r25, 0x03       ; PINB lesen
13
  d0:   81 11           cpse    r24, r1         ; ist R24 = 0?
14
  d2:   fa cf           rjmp    .-12            ; Rücksprung nach 0xc8
15
16
  d4:   18 b8           out     0x08, r1        ; Gib 0 auf PORTC aus
17
  d6:   80 e0           ldi     r24, 0x00
18
  d8:   90 e0           ldi     r25, 0x00
19
  da:   08 95           ret

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
000000b4 <main>:
2
  b4:   8f ef           ldi     r24, 0xFF
3
  b6:   88 b9           out     0x08, r24
4
  b8:   81 e0           ldi     r24, 0x01
5
  ba:   80 93 68 00     sts     0x0068, r24
6
  be:   80 93 6b 00     sts     0x006B, r24
7
  c2:   78 94           sei
8
9
  c4:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
10
  c8:   81 50           subi    r24, 0x01       ; subtrahiere 1 von R24
11
  ca:   80 93 00 01     sts     0x0100, r24     ; i = R24
12
  ce:   83 b1           in      r24, 0x03       ; PINB lesen
13
  d0:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
14
  d4:   81 11           cpse    r24, r1         ; ist R24 = 0?
15
  d6:   f6 cf           rjmp    .-20            ; Rücksprung nach 0xc4
16
17
  d8:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
18
  dc:   88 b9           out     0x08, r24       ; Gib R24 auf PORTC aus
19
  de:   80 e0           ldi     r24, 0x00
20
  e0:   90 e0           ldi     r25, 0x00
21
  e2:   08 95           ret

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
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/atomic.h>
5
6
uint8_t i=255;
7
8
ISR(PCINT0_vect)
9
{
10
    i=255;
11
}
12
13
int main(void) 
14
{
15
    // Port C = Ausgang
16
    PORTC = 255;
17
18
    // Interrupt bei Signal von PB0 
19
    PCICR = (1<<PCIE0);
20
    PCMSK0 = (1<<PCINT0);
21
    sei();
22
23
    do
24
    {
25
        ATOMIC_BLOCK(ATOMIC_FORCEON)
26
        {
27
            i=i-1;
28
        }
29
        PINB; // damit die Schleife nicht wegoptimiert wird
30
    }
31
    while (i>0);
32
33
    // Ergebnis ausgeben
34
    PORTC=i;
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
000000b4 <main>:
2
  b4:   8f ef           ldi     r24, 0xFF
3
  b6:   88 b9           out     0x08, r24
4
  b8:   81 e0           ldi     r24, 0x01
5
  ba:   80 93 68 00     sts     0x0068, r24
6
  be:   80 93 6b 00     sts     0x006B, r24
7
  c2:   78 94           sei
8
9
  c4:   f8 94           cli
10
  c6:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
11
  ca:   81 50           subi    r24, 0x01       ; subtrahiere 1 von R24
12
  cc:   80 93 00 01     sts     0x0100, r24     ; i = R24
13
  d0:   78 94           sei
14
  d2:   83 b1           in      r24, 0x03       ; PINB lesen
15
  d4:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
16
  d8:   81 11           cpse    r24, r1         ; ist R24 = 0?
17
  da:   f4 cf           rjmp    .-24            ; Rücksprung nach 0xc4
18
19
  dc:   18 b8           out     0x08, r1        ; Gib 0 auf PORTC aus
20
  de:   80 e0           ldi     r24, 0x00
21
  e0:   90 e0           ldi     r25, 0x00
22
  e2:   08 95           ret

Man könnte nun versucht sein, den Automic-Block zu vergrößern:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/atomic.h>
5
6
uint8_t i=255;
7
8
ISR(PCINT0_vect)
9
{
10
    i=255;
11
}
12
13
int main(void) 
14
{
15
    // Port C = Ausgang
16
    PORTC = 255;
17
18
    // Interrupt bei Signal von PB0 
19
    PCICR = (1<<PCIE0);
20
    PCMSK0 = (1<<PCINT0);
21
    sei();
22
23
    ATOMIC_BLOCK(ATOMIC_FORCEON)
24
    {
25
        do
26
        {
27
            i=i-1;
28
            PINB; // damit die Schleife nicht wegoptimiert wird
29
        }
30
        while (i>0);
31
32
        // Ergebnis ausgeben
33
        PORTC=i;
34
    }
35
}

Aber auch das generiert fehlerhaften Code:
1
000000b4 <main>:
2
  b4:   8f ef           ldi     r24, 0xFF
3
  b6:   88 b9           out     0x08, r24
4
  b8:   81 e0           ldi     r24, 0x01
5
  ba:   80 93 68 00     sts     0x0068, r24
6
  be:   80 93 6b 00     sts     0x006B, r24
7
  c2:   78 94           sei
8
  c4:   f8 94           cli
9
  c6:   80 91 00 01     lds     r24, 0x0100     ; R24 = i
10
11
  ca:   81 50           subi    r24, 0x01       ; subtrahiere 1 von R24
12
  cc:   80 93 00 01     sts     0x0100, r24     ; i = R24
13
  d0:   93 b1           in      r25, 0x03       ; PINB lesen
14
  d2:   81 11           cpse    r24, r1         ; ist R24 = 0?
15
  d4:   fa cf           rjmp    .-12            ; Rücksprung nach 0xca
16
17
  d6:   18 b8           out     0x08, r1        ; Gib 0 auf PORTC aus
18
  d8:   78 94           sei
19
  da:   80 e0           ldi     r24, 0x00
20
  dc:   90 e0           ldi     r25, 0x00
21
  de:   08 95           ret

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.

von Programmierer (Gast)


Lesenswert?

Du hast einen ATOMIC_BLOCK zu wenig. Versuch mal:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/atomic.h>
5
6
uint8_t i=255;
7
8
ISR(PCINT0_vect)
9
{
10
    i=255;
11
}
12
13
int main(void) 
14
{
15
    // Port C = Ausgang
16
    PORTC = 255;
17
18
    // Interrupt bei Signal von PB0 
19
    PCICR = (1<<PCIE0);
20
    PCMSK0 = (1<<PCINT0);
21
    sei();
22
23
    do
24
    {
25
        ATOMIC_BLOCK(ATOMIC_FORCEON)
26
        {
27
            i=i-1;
28
        }
29
        PINB; // damit die Schleife nicht wegoptimiert wird
30
    }
31
    while (i>0);
32
33
    int iRead;
34
    ATOMIC_BLOCK(ATOMIC_FORCEON)
35
    {
36
        iRead = i;
37
    }
38
39
    // Ergebnis ausgeben
40
    PORTC=iRead;
41
}
Hab grad keinen Compiler zur Hand.

von Programmierer (Gast)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

Programmierer schrieb:
> Du hast einen ATOMIC_BLOCK zu wenig. Versuch mal

Ja geht:
1
000000b4 <main>:
2
  b4:   8f ef           ldi     r24, 0xFF       ; 255
3
  b6:   88 b9           out     0x08, r24       ; 8
4
  b8:   81 e0           ldi     r24, 0x01       ; 1
5
  ba:   80 93 68 00     sts     0x0068, r24     ; 0x800068 <__TEXT_REGION_LENGTH__+0x7e0068>
6
  be:   80 93 6b 00     sts     0x006B, r24     ; 0x80006b <__TEXT_REGION_LENGTH__+0x7e006b>
7
  c2:   78 94           sei
8
9
  c4:   f8 94           cli
10
  c6:   80 91 00 01     lds     r24, 0x0100     ; 0x800100 <__data_start>
11
  ca:   81 50           subi    r24, 0x01       ; 1
12
  cc:   80 93 00 01     sts     0x0100, r24     ; 0x800100 <__data_start>
13
  d0:   78 94           sei
14
  d2:   83 b1           in      r24, 0x03       ; 3
15
  d4:   80 91 00 01     lds     r24, 0x0100     ; 0x800100 <__data_start>
16
  d8:   81 11           cpse    r24, r1
17
  da:   f4 cf           rjmp    .-24            ; 0xc4 <main+0x10>
18
19
  dc:   f8 94           cli
20
  de:   80 91 00 01     lds     r24, 0x0100     ; 0x800100 <__data_start>
21
  e2:   78 94           sei
22
  e4:   88 b9           out     0x08, r24       ; 8
23
  e6:   80 e0           ldi     r24, 0x00       ; 0
24
  e8:   90 e0           ldi     r25, 0x00       ; 0

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.

von Einer K. (Gast)


Lesenswert?

volatile ist selektiver als asm volatile("": : :"memory")

von Johannes (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Johannes (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Johannes schrieb:
> Doch das ist tatsächlich so. Probier es aus.

Oder gucke dir einfach mein obiges Beispiel an, ich habe es ausprobiert.

von Johannes (Gast)


Lesenswert?

Noch eine Sache dazu, wie Registerzugriffe ohne volatile aussehen 
könnten:
1
#include <iostream>
2
#include <cstdint>
3
4
template<typename T>
5
struct Register
6
{
7
  Register(uint64_t addr) : _addr(addr) {}
8
  
9
  Register<T>& operator=(const T& value)
10
  {
11
    // TODO: barrier
12
    *(T*)_addr=value;
13
    return *this;
14
  }
15
  
16
  operator T() const
17
  {
18
    // TODO: barrier
19
    return *(T*)_addr;
20
  }
21
22
private:
23
  uint64_t _addr;
24
};
25
26
template<typename T, uint64_t TAddr>
27
struct CRegister
28
{
29
  CRegister<T, TAddr>& operator=(const T& value)
30
  {
31
    // TODO: barrier
32
    *(T*)TAddr=value;
33
    return *this;
34
  }
35
  
36
  operator T() const
37
  {
38
    // TODO: barrier
39
    return *(T*)TAddr;
40
  }
41
  
42
  operator Register<T>() const
43
  {
44
    return Register<T>(TAddr);
45
  }
46
};
47
48
49
CRegister<int, 0x10000> Reg1;
50
CRegister<int, 0x10004> Reg2;
51
CRegister<int, 0x10008> Reg3;
52
53
void write_argument_register(Register<int> reg)
54
{
55
  reg=1234;
56
}
57
58
int main()
59
{
60
  Reg1=123;
61
  write_argument_register(Reg2);
62
  write_argument_register(Reg3);
63
  
64
  return 0;
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.

von S. R. (svenska)


Lesenswert?

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.

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von mh (Gast)


Lesenswert?

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"?

von Programmierer (Gast)


Lesenswert?

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
#include <cstdint>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/atomic.h>
5
#include <atomic>
6
7
std::atomic<std::uint8_t> i { 255 };
8
9
ISR(PCINT0_vect)
10
{
11
    i.store (255, std::memory_order_relaxed);
12
}
13
14
int main(void) 
15
{
16
    // Port C = Ausgang
17
    PORTC = 255;
18
19
    // Interrupt bei Signal von PB0 
20
    PCICR = (1<<PCIE0);
21
    PCMSK0 = (1<<PCINT0);
22
    sei();
23
24
    while (i.fetch_sub (1, std::memory_order_relaxed) > 1);
25
26
    // Ergebnis ausgeben
27
    PORTC = i.load (std::memory_order_relaxed);
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
#include <cstdint>
2
#include <atomic>
3
4
std::uint32_t test (std::atomic<std::uint32_t>& i) {
5
  while (i.fetch_sub (1, std::memory_order_relaxed) > 1);
6
7
  return i.load (std::memory_order_relaxed);
8
}

wird kompiliert zu (für Cortex-M3):
1
00000000 <test(std::atomic<unsigned int>&)>:
2
   0:  e850 3f00   ldrex  r3, [r0]
3
   4:  1e5a        subs  r2, r3, #1
4
   6:  e840 2100   strex  r1, r2, [r0]
5
   a:  2900        cmp  r1, #0
6
   c:  d1f8        bne.n  0 <test(std::atomic<unsigned int>&)>
7
   e:  2b01        cmp  r3, #1
8
  10:  d8f6        bhi.n  0 <test(std::atomic<unsigned int>&)>
9
  12:  6800        ldr  r0, [r0, #0]
10
  14:  4770        bx  lr

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
#include <cstdint>
2
3
std::uint32_t test (std::uint32_t& i) {
4
  while (i > 0) {
5
    __asm__ volatile (""); // Schleife nicht wegoptimieren
6
    --i;
7
  }
8
9
  return i;
10
}
Wird zu
1
00000000 <test(unsigned int&)>:
2
   0:  6803        ldr  r3, [r0, #0]
3
   2:  b113        cbz  r3, a <test(unsigned int&)+0xa>
4
   4:  3b01        subs  r3, #1
5
   6:  d1fd        bne.n  4 <test(unsigned int&)+0x4>
6
   8:  6003        str  r3, [r0, #0]
7
   a:  2000        movs  r0, #0
8
   c:  4770        bx  lr
9
   e:  bf00        nop
Kein erneutes Laden. std::atomic hat hier also das "volatile" ersetzt, 
und das ohne Interruptsperre (erzeugt der GCC sowieso nicht 
automatisch).

von Rolf M. (rmagnus)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.