Forum: Mikrocontroller und Digitale Elektronik Variablenzugriff bei Multitasking/Multicore


von Fritz G. (fritz65)


Lesenswert?

Ich habe ein Projekt mit einem ESP32-S3-Controller, der zwei µC-Cores 
enthält und FreeRTOS als Betriebssystem nutzt. Mehrkernprozessoren sind 
für mich Neuland, deshalb hier eine Grundsatzfrage:

Mein System sammelt Daten aus mehreren unterschiedlichen Quellen: UDP 
über WLAN, RS232, RS485 und SPI. Jede Quelle hat einen eigenen Task, der 
sich um den Empfang der Daten kümmert. Die Daten werden dann in einer 
globalen Struktur gesammelt. Jedes Strukturelement wird nur von genau 
einem Task geschrieben, allerdings können mehrere Tasks darauf lesend 
zugreifen. Im Moment habe ich jeden Zugriff auf die Struktur, egal ob 
Lesen oder Schreiben, mit einem Mutex abgesichert.
Meine Frage: Ist das in diesem Fall wirklich notwendig? Die 
Strukturelemente sind 8, 16- oder 32-bit Variablen und sollten 
eigentlich "in einem Rutsch" geschrieben und gelesen werden, so dass es 
bei einem Einkernprozessor nicht möglich sein sollte, dass teilweise 
geschriebene Werte gelesen werden.

Nun hat der ESP32-S3 zwei Kerne. Was würde passieren, wenn ein Kern 
genau in dem Moment auf eine Speicherstelle schreibt während der andere 
diese liest?

Wäre es zumindest möglich, die Strukturelemente als Atomic zu 
deklarieren, um nicht den umständlichen Weg über den Mutex gehen zu 
müssen?

Zur Info: Queues brauche ich hier nicht, da nur der jeweils aktuelle 
Wert interessiert, nicht die lückenlose Vorgeschichte.

von Hans W. (Firma: Wilhelm.Consulting) (hans-)


Lesenswert?

Fritz G. schrieb:
> Die
> Strukturelemente sind 8, 16- oder 32-bit Variablen und sollten
> eigentlich "in einem Rutsch" geschrieben und gelesen werden, so dass es
> bei einem Einkernprozessor nicht möglich sein sollte, dass teilweise
> geschriebene Werte gelesen werden.

Bist du dir da sicher?

Wenn du z.B so schöne Struct-Union Kunstrukte verwendest um z.B. 
high-word und low-word einer 32-bit variable zu setzen, dann bist du 
zumindest in RAM-Bereichen, die kein unaligned-access mögen nicht mehr 
atomic unterwegs.

Ganz ehrlich, solche Zugriffe auf gemeinsamen Speicher gehören einfach 
geschützt.
Die Wahrscheinlichkeit, dass man irgendwann mal irgendeine Abhängigkeit 
übersieht, oder auf einmal eine 64-bit Variable ablegt ist einfach zu 
groß um sie zu ignorieren.

Solche Fehler dann zu finden ist auch nicht unbedingt super einfach weil 
extrem schwer nachzustellen.

Übrigens: ich verwende in solchen Fällen meistens Helper-Klassen in 
C++...

Verhält sich nach außen wie eine beliebige Variable (Template sei dank) 
und schreib-lesezugriffe (operator-überladung) werden über einen Mutex 
geschützt.

Minimaler mehraufwand und nie wieder ein Problem :)

73

Beitrag #7922627 wurde vom Autor gelöscht.
von Tobias (code_red)


Lesenswert?

Für sowas gibt es mutexe, semaphoren und queueing.

von Rahul D. (rahul)


Lesenswert?

Tobias schrieb:
> Für sowas gibt es mutexe, semaphoren und queueing.

Fritz G. schrieb:
> Wäre es zumindest möglich, die Strukturelemente als Atomic zu
> deklarieren, um nicht den umständlichen Weg über den Mutex gehen zu
> müssen?

So ein Dreizeiler ist aber zu unbequem...
Soweit ich weiß, haben einige Controller das schon in Hardware gegossen. 
Ob das auch für den ESP32 gilt, weiß ich nicht.

von Tobias (code_red)


Lesenswert?

Auf dem ESP32 läuft grundsätzlich immer FreeRTOS. Auch wenn man "nur" 
mit Arduino darauf arbeitet.

Im Prinzip kann man mit der ESP IDF und VS Code wie gewohnt alle 
FreeRTOS Funktionalitäten Nutzen.

von Alexander (alxc)


Lesenswert?

Es ist wie bereits von dir vermutet. Aufgrund der Caches musst du 
zwangsläufig synchronisieren, sonst sehen die Cores ihre gegenseitigen 
Änderungen nicht sofort bzw. nie. Das hat sich erst mal nichts mit 
Interrupts zu tun. Wenn es um “einfache” Variablen geht, reicht std 
atomics wie von dir angesprochen. Da kannst du auch die Art der 
Synchronisation genau einstellen und optimieren falls nötig. Mutex hat 
auch eine Speicherbarriere drin, funktioniert also auch in jedem Fall. 
Mutex brauchst du aber nur bei größeren Strukturen bzw. falls du 
wirklich einen Programmabschnitt schützen willst. Bei einem Mutexlock 
passiert noch eine ganze Menge im Kernel.

von Obelix X. (obelix)


Lesenswert?

Tobias schrieb:
> Auf dem ESP32 läuft grundsätzlich immer FreeRTOS.

Fast immer ;-)

https://www.espressif.com/en/sdks/esp-zephyr

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Alexander schrieb:
> Wenn es um “einfache” Variablen geht, reicht std
> atomics wie von dir angesprochen.

Aber auch nur wenn die gewünschte Operation sich "atomic" abbilden lässt 
und man keine Abhängigkeiten zwischen mehreren Atomic-Variablen hat.

Fritz G. schrieb:
> Die
> Strukturelemente sind 8, 16- oder 32-bit Variablen und sollten
> eigentlich "in einem Rutsch" geschrieben und gelesen werden

Schreibt jeder Thread wirklich nur eine 8-32bit Variable, oder sind es 
mehrere? Wenn es mehrere sind, jeweils Atomic, kann natürlich ein 
anderer Thread zwischendurch einen Zwischenzustand auslesen, wenn noch 
nicht alle Variablen gesetzt worden sind - aber jede Variable ist für 
sich intakt.

Je nachdem, welche Memory-Order man bei Atomics angibt, kann es sogar 
sein, dass ein Thread die Änderungen der einzelnen Atomics in der 
falschen Reihenfolgie sieht. Bei den ESP32 kann das aber nicht 
passieren, da sie ein Cache-Coherency-Protokoll haben, genau wie x86 und 
ARM (teilweise je nach Konfiguration).

Alexander schrieb:
> Mutex hat
> auch eine Speicherbarriere drin

Atomics auch, je nach Einstellung der Memory-Order

Alexander schrieb:
> Bei einem Mutexlock
> passiert noch eine ganze Menge im Kernel.

Nicht unbedingt, zunächst versuchen typische Mutex-Implementationen die 
Sperre mittels Atomics zu setzen; nur wenn ein Thread wirklich warten 
muss, muss der Scheduler das umsetzen.

FreeRTOS für Multicore ist übrigens etwas buggy, gerade auch auf ESP32. 
Anscheinend haben die ESP32 auch Hardware-Bugs bei simultanem Zugriff 
auf Daten im PSRAM.

von Rolf M. (rmagnus)


Lesenswert?

Fritz G. schrieb:
> Jedes Strukturelement wird nur von genau einem Task geschrieben,
> allerdings können mehrere Tasks darauf lesend zugreifen. Im Moment habe
> ich jeden Zugriff auf die Struktur, egal ob Lesen oder Schreiben, mit
> einem Mutex abgesichert. Meine Frage: Ist das in diesem Fall wirklich
> notwendig? Die Strukturelemente sind 8, 16- oder 32-bit Variablen und
> sollten eigentlich "in einem Rutsch" geschrieben und gelesen werden, so
> dass es bei einem Einkernprozessor nicht möglich sein sollte, dass
> teilweise geschriebene Werte gelesen werden.

"sollte" ist das richtige Wort. Du weißt es eben nicht 100% sicher, wenn 
du keine Atomic-Typen verwendest.
Aber Synchronisation brauchst du ja meist nicht nur für Zugriffe auf 
Einzelwerte, sondern auch auf Gruppen von zusammegehörenden Werten. 
Sagen wir mal, du hast einen 3-Achs-Beschleunigungssensor. Dann möchte 
man in der Regel ja immer 3 zusammengehörende Werte auswerten und nicht 
2 neue und einen alten, weil der noch nicht in die Variable geschrieben 
wurde. Oder du liest irgendwo vom LAN ein Datagramm ein. Dann gehören 
die Bytes (und der Wert für die Größe) ja auch zusammen, und es sollte 
nicht auf irgendwas halb fertiges zugegriffen werden.

: Bearbeitet durch User
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.