Hi, ich brauche für ein Projekt ein paar Funktionen die mir Flags setzen und löschen können sollen und das auch bei globalen Variablen (INT-Vars). Bis jetzt hatte ich einfach die INTs zwischen drin deaktiviert und die Sachen bearbeitet. Aber auf dauer ist das sehr umständlich. So, jetzt habe ich mir ein paar Funktionen geschrieben die ohne deaktivierung der INTs auskommen sollen. Könntet Ihre euch die mal ansehen ob das so in Ordnung sind oder ob ich da noch Fehler drin habe. Optimierungsvorschläge sind auch willkommen. ;-) PS: ist mein erstes mal mit dem PIC ASM also bitte etwas nachsicht. Stephan
Stephan schrieb: > So, jetzt habe ich mir ein paar Funktionen geschrieben die ohne > deaktivierung der INTs auskommen sollen. Da ich mich in PIC32 Assembler nicht auskenne: Wodurch erreichst Du die Atomarität bei den Read-Modify-Write-Zugriffen ohne die Interrupts zu sperren?
durch die Befehle 'LL' und 'SC'. LL - merkt sich beim lesen die Speicherstelle und wenn der Wert wieder geschrieben wird, durch SC, funktioniert das nur, wenn die Speicherstelle nicht verändert worden ist.
Ohne besondere Maßnahmen (Interruptsperre) ist alles, was länger als ein Assembler-Befehl ist, nicht atomar. Ob man es in C oder Assembler schreibt, ist egal. Bei RISC-CPUs würde ich mir deshalb eine Art Präfixbefehl wünschen: "DI 5" (Führe die folgenden 5 Befehle unter Interruptsperre aus).
Peter Dannegger schrieb: > Ohne besondere Maßnahmen (Interruptsperre) ist alles, was länger als ein > Assembler-Befehl ist, nicht atomar. Die für einige RISCs typische LL/SC Methodik ist zwar nicht atomar, muss es aber auch nicht sein. Der Trick besteht darin, dass die Kollision mit Interrupts oder anderen Busmastern erkannt wird und zur Neuversuch führt.
> Bei RISC-CPUs würde ich mir deshalb eine Art Präfixbefehl wünschen: > "DI 5" (Führe die folgenden 5 Befehle unter Interruptsperre aus). PIC24 und dsPIC33 haben sowas: DISI Disable Interrupts Temporarily Syntax: {label:} DISI #lit14 Operands: lit14 [0 ... 16383] Operation: lit14 → DISICNT 1 → DISI Disable interrupts for (lit14 + 1) cycles Status Affected: None PIC32 kenn ich (noch) nicht.
Die Vorteil von LL/SC gegenüber Interrupt-Abschaltung: - Die Reaktionszeit von Interrupts wird nicht verschlechtert. - Es funktioniert auch zwischen verschiedenen Cores. Der Nachteil: Man muss es verstanden haben. ;-)
Stephan schrieb: > Könntet Ihre euch die mal ansehen ob das so in Ordnung sind oder ob ich > da noch Fehler drin habe. Bin mit MIPs nicht vertraut. Üblicherweise gibts aber Beispielcode, nicht zuletzt vom Hersteller (Microchip oder MIPS). Da Microchips Compiler aber GCC ist scheint mir der Rückgriff auf Assembler unnötig. Oft gibts für solche Spezialbefehle irgendwo inline Pseudo-Funktionen, so dass man in C bleiben kann.
Peter Dannegger schrieb: > Ohne besondere Maßnahmen (Interruptsperre) ist alles, was länger als ein > Assembler-Befehl ist, nicht atomar. Aber bei manchen Prozessoren gibt es geeignete Befehle in der Art Test and Set, die ein Bit atomar lesen und dann setzen, damit lassen sich einige Aufgaben lösen. Ob es sowas bei PIC32 gibt weiss ich nicht. Beim guten alten Z80 konnte man mit einem Befehl ein Byte im Speicher decrementieren und dabei die Flags setzen. Kann man für Semaphore usw. brauchen. Gruss Reinhard
Zum Code: Der Abstand zwischen LL und SC sollte kurz gehalten werden. Ich könnte mir vorstellen, dass fast alle Befehle zwischendrin auch vor LL platziert werden knnen.
A. K. schrieb: > Der Trick besteht darin, dass die Kollision mit > Interrupts oder anderen Busmastern erkannt wird und zur Neuversuch > führt. Klingt sehr kompliziert. Heißt das, die CPU merkt sich die LL-Zeile und wenn vor SC ein Interrupt erfolgt, führt sie allen Code ab LL nochmal aus? Heißt das auch, LL/SC müssen genau paarweise erfolgen? Und sind mehrere LL/SC geschachtelt möglich?
Reinhard Kern schrieb: > Aber bei manchen Prozessoren gibt es geeignete Befehle in der Art Test > and Set, die ein Bit atomar lesen und dann setzen, damit lassen sich > einige Aufgaben lösen. Ob es sowas bei PIC32 gibt weiss ich nicht. Da es LL/SC gibt offensichtlich nicht. TAS oder LOCK XCHG (x86) sind aufgrund ihrer Ineffizienz etwas aus der Mode gekommen. Solche LL/SC sind wesentlich effizienter.
Peter Dannegger schrieb: > Heißt das, die CPU merkt sich die LL-Zeile und wenn vor SC ein Interrupt > erfolgt, führt sie allen Code ab LL nochmal aus? Ich beschreibe es mal ungefähr so, wie ich es von PowerPC und neueren ARMs her kenne: Die CPU läd in LL ein Wort aus dem Speicher und merkt sich die Adresse des Wortes in einem speziellen Register. Wenn dieser Wert bei SC noch übereinstimmt, dann wird ein Wort dort abgespeichert, sonst nicht. Zusätzlich wird dieser Status als Ergebnis von SC irgendwo abgelegt. In Interrupts kann nun dieses Register gelöscht werden, wenn sinnvoll. Bei externen Buszyklen (anderer Core, anderer Busmaster), die auf ebendiese Adresse zugreifen, geschieht es automatisch. Wenn also der Code beim SC Befehl merkt, dass etwas dazwischen kam, dann probiert der Code den Kram einfach nochmal. Also nicht der Processor, sondern das Programm. Siehe oben, im direkt auf SC folgenden bedingten Sprung. Vorteil: Diese Befehle sind sehr einfache Befehle, die im Normalfall schnell durchlaufen und keine besonderen und teuren Buszyklen erfordern. > Heißt das auch, LL/SC müssen genau paarweise erfolgen? So ist es gedacht. Man sollte sich das ggf. im Handbuch genau nachlesen, oft gibt es bestimmte Voraussetzungen und Randbedingungen. > Und sind mehrere LL/SC geschachtelt möglich? Nein. Wüsste auch nicht, was das hier brächte. Wenn dir das zu einfach ist und du es gerne etwas komplexer hast, dann schau dir mal Intels Befehlserweiterung in Haswell an, der Support für Transactional Memory. David Kanter hat das auf http://www.realworldtech.com/ gut beschrieben.
Danke für die Erklärung. Ein DISI x fände ich allerdings einfacher. Und da Interrupts eh immer ein Gedöns an Prolog/Epilog haben, sind mal 5 Zyklen Sperre nicht von Bedeutung.
Peter Dannegger schrieb: > Ein DISI x fände ich allerdings einfacher. Ist in Assembler recht nett. Für die heute dominierende Umsetzung in Hochsprache ist es allerdings ziemlich unpraktisch, setzt es doch eine entsprechende Compilermimik voraus. Sowas wie __run_with_irqs_disabled { code ... } Zilog hat in den eZ8 auch sowas in Art drin - aber dank geradezu klassischer Konstrukteur/Entwickler-Schere keinen entsprechenden Support im eigenen Compiler.
G++ schrieb: > Oder ist das Bestandteil der AVRlibc oder so? So ist es. Sieht dort nicht genauso aus, aber die Sequenz
1 | a; |
2 | { ... } |
3 | b; |
lässt sich beispielsweise ungefähr als Makro
1 | for (a; irgendwas; b) { ... } |
verstecken. Aber mach das mal, wenn du in "a" die Anzahl Maschinenbefehle im Codeblock angeben musst.
Peter Dannegger schrieb: > Ein DISI x fände ich allerdings einfacher. > Und da Interrupts eh immer ein Gedöns an Prolog/Epilog haben, sind mal 5 > Zyklen Sperre nicht von Bedeutu > Ein DISI x fände ich allerdings einfacher. > Und da Interrupts eh immer ein Gedöns an Prolog/Epilog haben, sind mal 5 > Zyklen Sperre nicht von Bedeutung.ng. Code und Kommentar aus einem Manual für PIC24 kopiert, weiß nicht mehr aus welchem (benutze nur C):
1 | void EepErase(void) { |
2 | NVMCON = 0x4050; // Set up NVMCON to bulk erase the data EEPROM |
3 | asm volatile ("disi #5"); // Disable Interrupts For 5 Instructions |
4 | __builtin_write_NVM(); // Issue Unlock Sequence and Start Erase Cycle |
5 | while(_WR) |
6 | ;
|
7 | }
|
MfG Klaus
yup, dass gibt es mit gcc schon frei haus fuer mips/arm und andere, auch im gcc des xc32 scheint es enabled zu sein. also auch ohne assembler oder _ASM_ in c/c++ nutzbar. siehe gcc doc: Atomic-Builtins 37: result = __sync_add_and_fetch(&safecounter, 1); 9D001054 0000000F SYNC 9D001058 C3828014 LL V0, -32748(GP) 9D00105C 24410001 ADDIU AT, V0, 1 9D001060 E3818014 SC AT, -32748(GP) 9D001064 1020FFFC BEQ AT, ZERO, 0x9D001058 9D001068 24420001 ADDIU V0, V0, 1 9D00106C 0000000F SYNC 9D001070 AF828018 SW V0, -32744(GP)
Morgen A. K. schrieb: > Der Abstand zwischen LL und SC sollte kurz gehalten werden. Das werde ich noch ändern. Danke.
Peter Dannegger schrieb: > Ein DISI x fände ich allerdings einfacher. > Und da Interrupts eh immer ein Gedöns an Prolog/Epilog haben, sind mal 5 > Zyklen Sperre nicht von Bedeutung. Wenn ich es richtig verstehe, funktioniert LL/SC nicht nur für Interrupts, sondern auch für mehrere Busmaster. Letzteres kann DISI nicht abfangen.
>LL - merkt sich beim lesen die Speicherstelle und wenn der Wert wieder >geschrieben wird, durch SC, funktioniert das nur, wenn die >Speicherstelle nicht verändert worden ist. LL-SC ist als atomic Ersatz witzlos, da im Falle des veränderten Mem-Wertes doch wieder zusätzlicher Code (mit zus. Takten) erforderlich ist. >Aber bei manchen Prozessoren gibt es geeignete Befehle in der Art Test >and Set, die ein Bit atomar lesen und dann setzen,... sind schon CISC-Befehle (RMW), die typ. RISCs nicht haben. >PIC24 und dsPIC33 haben sowas: >DISI Disable Interrupts Temporarily PIC24 kann oft darauf verzichten, da der versch. CISC-Befehle hat. (sofern 8kB-Bereich)
MCUA schrieb: > LL-SC ist als atomic Ersatz witzlos, da im Falle des veränderten > Mem-Wertes doch wieder zusätzlicher Code (mit zus. Takten) erforderlich > ist. Warum es deshalb witzlos? Es ist übrigens kein zusätzlicher Code, sondern der gleich wie beim vorherigen Versucht, als Schleife. Voraussetzung ist natürlich, dass man mit einer nichtdeterministischen Laufzeit der Sequenz leben kann. Aber wie ich oben schon andeutete: Diese Methode adressierte ursprünglich Multiprocessing. Die Verwendbarkeit auf Singlecores als Ersatz atomarer Operationen ist eher ein Nebenffekt. Wer damit nichts anfangen kann, dem bleibt es unbenommen, in traditioneller Weise die Interrupts abzuschalten.
A. K. schrieb: > in traditioneller Weise die > Interrupts abzuschalten. Ich sehe auch nicht, wieso eigentlich ein Pärchen DI..EI so unzumutbar viel Aufwand sein soll und die Performance beeinträchtigt. Das Problem ist höchstens, dass man es vergisst, aber das gilt für alles andere auch, des Problems atomarer Operationen muss man sich bewusst sein, oder man kann eben nicht programmieren. Natürlich darf man zwischen DI und EI nur das absolut notwendige durchführen und nicht eine Differentialgleichung lösen. Gruss Reinhard
Reinhard Kern schrieb: > Ich sehe auch nicht, wieso eigentlich ein Pärchen DI..EI so unzumutbar > viel Aufwand sein soll und die Performance beeinträchtigt. Tut es auch nicht, die paar Takte abgeschalteten Interrupts sind meistens kein Problem. Es gibt eben beide Wege. Jeder Weg hat seine Vorteile. Der hier betrachtete ist freilich vielen Leuten unbekannt, was leicht zu dem "kenn ich nicht, also mag ich es nicht" Effekt führt. Ein bedeutender Unterschied entsteht freilich dann, wenn man nicht mehr mit universellen Rechten programmiert. Denn wenn es überhaupt Privilegstufen gibt (z.B. ARM7, Cortex-M), dann sind DI/EI privilegiert. LL/SC artige Operationen stehen jedoch auch nichtpriviligierten Programmen zur Verfügung. Dass die Cortex-M3/4 und PIC32 Microcontroller solche Operationen überhaupt haben liegt daran, dass diese Cores universell gebaut sind und ggf. auch in Mehrprozessorsystemen einsetzbar sein sollen.
A. K. schrieb: > Der hier betrachtete ist freilich vielen Leuten unbekannt, was > leicht zu dem "kenn ich nicht, also mag ich es nicht" Effekt führt. Ja da kenne ich genügend Leute..... ;-) > Ein bedeutender Unterschied entsteht freilich dann, wenn man nicht mehr > mit universellen Rechten programmiert. Denn wenn es überhaupt > Privilegstufen gibt (z.B. ARM7, Cortex-M), dann sind DI/EI privilegiert. > LL/SC artige Operationen stehen jedoch auch nichtpriviligierten > Programmen zur Verfügung. das ist gut. kannte ich auch noch nicht.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.