Hallo !
Ich möchte in einem Interrupt Handler (UART; ist aber letztlich egal)
das empfangene Zeichen in einen Ring schreiben - RingWrite(). Der
Schreibvorgang ist übrigens höchst einfach.
Ein Task holt sich die Zeichen vom Ring ab, und verarbeitet diese -
RingRead().
Nun muss gewährleistet sein, dass RingWrite() nicht durch RingRead()
unterbrochen wird, und umgekehrt.
Angenommen der Task liest den Ring; hier wird der Lesezeiger
weitergestellt. Wenn zufällig an einer ungünstigen Stelle unterbrochen
wird (innerhalb von zwei drei Anweisungen), kann es zu Inkonsistenzen
kommen.
Wie löst man solche Zugriffsprobleme richtig?
Ich machte es bisher immer so, dass ich im neuralgischen Teil der
Zugriffe Interrupts kurzfristig deaktivierte, sodass die Code-Sequenz
garantiert unterbrechungsfrei wird.
Ich könnte auch über binäre Semaphore arbeiten: Die ISR muss sich, bevor
sie in den Ring schreibt einen Semaphor holen (mein OS unterstützt das
auch für ISRs), und gibt diesen zurück, wenn sie fertig ist. Umgekehrt
muss sich auch der Task einen Semaphor holen und zurückgeben. Dann wird
es aber bald passieren, dass die ISR keinen Semaphor bekommt, da der
gerade dem Task gehört. Müsste ich hier "blockierend" warten, bis der
Semaphor frei wird? Irgendwie gefällt mir der Gedanke nicht, in einer
ISR zu blockieren...
Die Frage: Gibt es hier noch eine andere Möglichkeit, die ich übersehen
habe und besser ist, als meine Vorgehensweise?
Danke !
Michael W. schrieb:> Müsste ich hier "blockierend" warten, bis der> Semaphor frei wird? Irgendwie gefällt mir der Gedanke nicht, in einer> ISR zu blockieren...
Das wird so schonmal gar nicht funktionieren, außer du hast einen µC mit
mehreren Kernen und kannst tatsächlich parallel Code ausführen. Wenn du
im Interrupt auf den Semaphor wartest hast du einen schönen Deadlock
weil die ISR nie fertig wird und auch der restliche Code nicht weiter
läuft und so der Semaphor niemals frei wird. Falls du bei Semaphors
bleiben willst darf die ISR nicht blockieren und falls du in der ISR den
Semaphor nicht kriegst musst du die ISR abbrechen. Dann musst du aber
später dafür sorgen, dass die ISR erneut aufgerufen wird wenn der
Semaphor wieder frei ist.
Im Endeffekt läuft das aber darauf hinaus, dass du genauso gut
Interrupts (oder speziell nur den UART Interrupt) deaktivierst, solange
du Daten aus dem Ring ließt. So habe ich das bisher immer gemacht.
Ja, blockierend in einer ISR, das ist natürlich Unsinn. Ich habe bisher
Interrupts immer einfach deaktiviert.
Nun verwende ich ein sehr einfaches Betriebssystem (freeRTOS) und ich
frage mich, ob ich nicht irgendwelche Features des OS verwenden kann, um
solche Dinge eleganter zu machen.
Mir ist im Manual die Funktion xQueueReceiveFromISR aufgefallen, diese
kann in einer ISR verwendet werden um aus einer Queue zu lesen. Diese
Funktion ist aber nicht blockierend...und für mein Anliegen
wahrscheinlich nicht zu gebrauchen.
Michael W. schrieb:> Ich machte es bisher immer so, dass ich im neuralgischen Teil der> Zugriffe Interrupts kurzfristig deaktivierte, sodass die Code-Sequenz> garantiert unterbrechungsfrei wird.
Genau so ist es auch richtig und das ist auch die einzige Möglichkeit,
ISRs "wechselseitig" mit normalem Code zu synchronisieren.
> Ich könnte auch über binäre Semaphore arbeiten: Die ISR muss sich, bevor> sie in den Ring schreibt einen Semaphor holen
Das geht nicht. ISRs können zwar freie Semaphoren belegen, aber sie
können nicht darauf warten, daß ein belegter Semaphor irgendwann mal
freigegeben wird, denn das wird niemals passieren, weil die ISR exklusiv
läuft. Der Code, der den Semaphor belegt hat, kommt also nie zu der
Stelle, wo er ihn wieder freigeben würde. Ein klassischer deadlock ist
die Folge dieser Situation.
> (mein OS unterstützt das> auch für ISRs)
In einer ISR auf einen belegte Semaphor warten, das kann es sicher
nicht. Oder das OS isoliert dich von den wirklichen ISRs und es sind nur
queued handler, mit denen du da tatsächlich zu tun hast. Die laufen als
normaler Task (wenn auch typisch mit Realtime-Priorität) und können
dadurch dann unterbrochen werden, wenn sie implizit oder explizit eine
Wartefunktion aufrufen.
Vielleicht kannst du den Ring mithilfe von atomaren Instruktionen so
implementieren, dass du gar kein Blockieren brauchst. Ansonsten darfst
du halt nicht von der ISR aus schreiben, sondern musst von dieser aus
einen normalen Task aufwecken, der normal unterbrechbar ist und über
Mutex in den Ring schreibt.
c-hater schrieb:> In einer ISR auf einen belegte Semaphor warten, das kann es sicher> nicht.
Wie gesagt: es kann "nicht blockierend" ein Item auf einer Queue
empfangen. Aber das hilft mir hier nicht weiter...
Mit welchem Controller arbeitest du denn.
Mit einem avr sollte man einen Buffet mit 8-bittigem Index konfliktfrei
updaten können.
Zuerst Daten in den BUfer schreiben
Dann neuen Index ausrechnen und mit einer Operation updaten.
Unter der Annahme, du machst Kein read-modify-Write sollten alle 8bit
zugriffe atomar sein.
Außerdem schreibt di Isr nur den in-Index und das Main schreibt nur den
out-Index. Da gibt es also keine schreib- Konflikte.
Bei Arm controllern geht das dann auch mit 32 bit.
Grüße, adib.
Wenn in einer ISR ein Zeichen reinkommt, aber der Puffer voll ist, dann
ist das Pech. Warten geht nicht. Da bleibt nur wegwerfen und "overflow"
signalisieren.
Um Konflikte zwischen der dazu gehörende Main-Routine und ISRs zu
verhindern schaltet man Interrupts ab, oder arbeitet mit speziellen
konfliktfreien Befehlssequenzen, wenn das im betreffenden Fall geht.
Letzteres ist etwas Denksport.
Interrupts kann man mit der Brechstange abschalten, indem man alle
abschaltet. Man kann die Prioritäts-Ebenen explizit kontrollieren, was
insbesondere bei Cortex-M gut funktioniert. Und man kann gezielt nur den
Interrupt des betreffenden Device abschalten.
xQueueReceiveFromISR macht genau das selbe wie xQueueReceiveFrom,
nämlich ein Element (hier wohl Byre) in eine Queue einstellen. Das
Semaphor-Problem wird dabei durch taskENTER_CRITICAL gelöst, was in der
ISR-Variante nicht notwendig ist, denn taskENTER_CRITICAL ist in der
Regel "CLI", um's mal in AVR zu sagen. Das macht doch ein OS aus, daß es
für solche Probleme Lösungen bereit hält. Warum bei FreeRTOS beiden
Varianten das ganze Queue-Geturne machen selbst machen müssen, who
knows?
A. K. schrieb:> Um Konflikte zwischen der dazu gehörende Main-Routine und ISRs zu> verhindern schaltet man Interrupts ab, oder arbeitet mit speziellen> konfliktfreien Befehlssequenzen, wenn das im betreffenden Fall geht.> Letzteres ist etwas Denksport.
Würde ich auch so machen (den Denksport). Mit einem modernen GCC muß man
da aber aufpassen; der spielt gelegentlich an der Reihenfolge der
Speicherzugriffe herum. Herauszufinden, welche Optimierungen man
abschalten muß, damit er das bleibenläßt, artet schon fast in ein
Compilerbau-Studium aus.
Nosnibor schrieb:> Mit einem modernen GCC muß man> da aber aufpassen
Eine gute libc bringt dir atomic Definitionen mit. Die überreden dann
den GCC zu passender Arbeit.