2012-08-10 19 views
12

Ich habe an einem Embedded-Betriebssystem für ARM gearbeitet, aber es gibt ein paar Dinge, die ich nicht über die Architektur verstanden habe, auch nicht nach ARMARM und Linux-Quelle.Atomare Operationen in ARM

Atomare Operationen.

ARM ARM besagt, dass Lade- und Speicherbefehle atomar sind und dass die Ausführung garantiert abgeschlossen ist, bevor der Interrupt-Handler ausgeführt wird. Verified by

arch/arm/include/asm/atomic.h : 
    #define atomic_read(v) (*(volatile int *)&(v)->counter) 
    #define atomic_set(v,i) (((v)->counter) = (i)) 

jedoch bei der Suche, kommt das Problem in, wenn ich will diesen Wert atomar manipulieren, indem die CPU-Instruktionen (atomic_inc, atomic_dec, atomic_cmpxchg etc ..), die LDREX und STREX für ARMv7 verwenden (mein Ziel) .

ARMARM sagt nichts über das Blockieren von Interrupts in diesem Abschnitt, daher nehme ich an, dass ein Interrupt zwischen LDREX und STREX auftreten kann. Die Sache, die es erwähnt, ist das Sperren des Speicherbusses, von dem ich denke, dass es nur für MP-Systeme hilfreich ist, wo es mehr CPUs geben kann, die versuchen, gleichzeitig auf denselben Ort zuzugreifen. Aber für UP (und möglicherweise MP), wenn ein Timer-Interrupt (oder IPI für SMP) in diesem kleinen Fenster von LDREX und STREX ausgelöst wird, führt Exception-Handler möglicherweise Änderungen CPU-Kontext und kehrt zu der neuen Aufgabe, aber der schockierende Teil kommt jetzt , es führt 'CLREX' aus und entfernt somit alle exklusiven Sperren des vorherigen Threads. Also, wie besser ist die Verwendung von LDREX und STREX als LDR und STR für die Atomarität auf einem UP-System?

Ich habe etwas über einen Exclusive lock monitor gelesen, also habe ich die Theorie, dass wenn der Thread den STREX wieder aufnimmt und ausführt, der os Monitor diesen Aufruf zum Scheitern bringt, der erkannt werden kann. ausgeführt mit dem neuen Wert im Prozess (zurück zu LDREX), Bin ich hier?

Antwort

9

Okay, habe die Antwort von ihrem website.

Wenn ein Kontextwechsel einen Prozess plant, nachdem der Prozess eine Ladeexklusivität ausgeführt hat, aber bevor er den Exklusivspeicher ausführt, gibt StoreExclusive ein falsches negatives Ergebnis zurück, wenn der Prozess fortgesetzt wird und der Speicher nicht aktualisiert wird. Dies wirkt sich nicht auf die Programmfunktionalität aus, da der Prozess den Vorgang sofort erneut versuchen kann.

8

Die Idee hinter dem Load-Linked/Speicher-exklusives Paradigma ist, dass, wenn wenn der Speicher sehr bald nach der Last folgt, ohne Speicheroperationen dazwischen, und wenn nichts anderes den Ort berührt hat, ist der Laden wahrscheinlich um erfolgreich zu sein, aber wenn etwas anderes den Standort berührt hat, ist der Laden bestimmte zu scheitern. Es gibt keine Garantie dafür, dass Geschäfte nicht ohne ersichtlichen Grund ausfallen. wenn die Zeit zwischen Lade- und Speicher wird auf ein Minimum beschränkt, aber, und es gibt keine Speicherzugriffe zwischen ihnen, einer Schleife wie:

do 
{ 
    new_value = __LDREXW(dest) + 1; 
} while (__STREXW(new_value, dest)); 

kann in der Regel darauf verlassen, dass innerhalb von wenigen Versuchen, um erfolgreich zu sein.Wenn die Berechnung des neuen Wertes basierend auf dem alten Wert einige bedeutende Berechnung erforderlich, sollte man die Schleife umschreiben als:

do 
{ 
    old_value = *dest; 

    new_value = complicated_function(old_value); 
} while (CompareAndStore(dest, new_value, old_value) != 0); 

... Assuming CompareAndStore is something like: 

uint32_t CompareAndStore(uint32_t *dest, uint32_t new_value, uint_32 old_value) 
{ 
    do 
    { 
    if (__LDREXW(dest) != old_value) return 1; // Failure 
    } while(__STREXW(new_value, dest); 
    return 0; 
} 

Dieser Code wird haben seine Hauptschleife erneut auszuführen, wenn sich etwas ändert * dest während der neue Wert berechnet wird , sondern nur die kleine Schleife muß erneut ausgeführt werden, wenn die __STREXW aus einem anderen Grunde nicht [die, dass es hoffentlich nicht allzu wahrscheinlich, da ist nur etwa zwei Befehle zwischen der __LDREXW und __STREXW sein]

Nachtrag Ein Beispiel für eine Situation, in der "neuen Wert basierend auf alt berechnen" kompliziert sein könnte, wäre einer, bei dem die "Werte" ar sind e effektiv ein Verweis auf eine komplexe Datenstruktur. Code kann die alte Referenz abrufen, eine neue Datenstruktur von der alten ableiten und dann die Referenz aktualisieren. Dieses Muster kommt viel häufiger in von Müll gesammelten Frameworks vor als in "Bare-Metal" -Programmierung, aber es gibt eine Vielzahl von Möglichkeiten, wie es selbst beim Programmieren von Bare-Metal-Systemen auftreten kann. Normale malloc-/calloc-Zuordner sind im Allgemeinen nicht Thread-sicher/Interrupt-sicher, aber Zuweisungen für Strukturen fester Größe sind oft. Wenn man einen „Pool“ von einiger Power-of-zwei Anzahl von Datenstrukturen (etwa 255) hat, ein so etwas wie verwenden:

#define FOO_POOL_SIZE_SHIFT 8 
#define FOO_POOL_SIZE (1 << FOO_POOL_SIZE_SHIFT) 
#define FOO_POOL_SIZE_MASK (FOO_POOL_SIZE-1) 

void do_update(void) 
{ 
    // The foo_pool_alloc() method should return a slot number in the lower bits and 
    // some sort of counter value in the upper bits so that once some particular 
    // uint32_t value is returned, that same value will not be returned again unless 
    // there are at least (UINT_MAX)/(FOO_POOL_SIZE) intervening allocations (to avoid 
    // the possibility that while one task is performing its update, a second task 
    // changes the thing to a new one and releases the old one, and a third task gets 
    // given the newly-freed item and changes the thing to that, such that from the 
    // point of view of the first task, the thing never changed.) 

    uint32_t new_thing = foo_pool_alloc(); 
    uint32_t old_thing; 
    do 
    { 
    // Capture old reference 
    old_thing = foo_current_thing; 

    // Compute new thing based on old one 
    update_thing(&foo_pool[new_thing & FOO_POOL_SIZE_MASK], 
     &foo_pool[old_thing & FOO_POOL_SIZE_MASK); 
    } while(CompareAndSwap(&foo_current_thing, new_thing, old_thing) != 0); 
    foo_pool_free(old_thing); 
} 

Wenn es nicht oft mehrere Threads sein/Unterbrechungen/was auch immer versucht, Wenn Sie dasselbe zur gleichen Zeit aktualisieren, sollte dieser Ansatz die sichere Durchführung von Aktualisierungen ermöglichen. Wenn eine Prioritätsbeziehung zwischen den Dingen besteht, die versuchen, den gleichen Gegenstand zu aktualisieren, ist die höchste Priorität beim ersten Versuch garantiert erfolgreich, die nächsthöhere Priorität wird bei jedem Versuch erfolgreich sein, der nicht vorweggenommen wird die mit der höchsten Priorität, usw. Wenn man Sperren verwendet, müsste die Aufgabe mit der höchsten Priorität, die die Aktualisierung durchführen möchte, warten, bis die Aktualisierung mit der niedrigeren Priorität beendet ist; Bei Verwendung des VergleichAndSwap-Paradigmas wird die Aufgabe mit der höchsten Priorität nicht von der niedrigeren beeinflusst (aber die niedrigere Aufgabe muss verschwendete Arbeit verrichten).

+0

Ich habe genau die gleiche Sache gemacht, aber der Teil, wo für den neuen Wert erhebliche Rechenleistung erforderlich ist, verwirrt mich immer noch. Die Verwendung von cmxchg-Schleife ist sinnvoll, weil dann der exklusive Monitor nicht durch einen Kontextwechsel gelöscht wird, aber die signifikante Berechnung erfordert viel Overhead, da ich beobachtet habe, dass die Straße ohne offensichtliche Gründe fehlschlägt (UP mit in PSR maskierten IRQs)) wie in Ihrem Beitrag erwähnt. – sgupta

+0

@ user1075375: Siehe Anhang – supercat

+0

Diese (__LDREXW & __STREXW) sind in den Keil-Compilern für Cortex-M-Mikrocontroller-Prozessoren enthalten und nicht allgemein für Mainstream-ARM-Targets (z. B. AArch64) und Compiler (z. B. gcc, llvm) Recht? http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABDEEJC.html – ahcox

Verwandte Themen