Ich habe die Zahlen geändert, aber ich sehe nicht warum das MESI-Protokoll sowas verhindern würde (wenn man mal von Atomizität bei cmpxchg absieht).
09. November 2026, 18:29
Es könnte sein, dass das Kapitel 7.1 in den Intel® 64 and IA-32 Architectures Software Developer’s Manual 3A dir die Lösung dazu verrät....Genau das ist die Sorte Text die mir missfällt. Da steht einfach nur "vertrau uns". Wenn dort der Weg beschreiben wäre und dieser Weg wäre plausibel dann würde ich Intel auch tatsächlich vertrauen, aber so leider nicht.
Deine Paranoia bei genau dem Befehl verstehe ich ehrlich gesagt nicht wirklich... Bei jedem anderem vertraust du Intel, aber genau hier nicht?Das liegt einfach da ran das dieser Befehl so elementar wichtig ist aber man seine Zuverlässigkeit nicht wirklich prüfen kann. Eine ALU oder FPU kann man mit vielen Beispielrechnungen prüfen ob immer das richtige Ergebnis raus kommt, bei der Atomizität geht das nicht. Man kann zwar prüfen ob das Restrisiko nahe 0 ist, aber man kann eben nicht auf genau 0 prüfen.
Außerdem wurde der Befehl exakt für Multiprozessorsysteme eingeführt. Da ist es unvorstellbar, dass es Randfälle geben soll die nicht abgedeckt sein sollen.Da gebe ich Dir zwar Recht, aber ich habe schon von verschiedenen Leuten gehört das die Cache-Kohärenz-Protokolle eine ziemlich komplizierte (und damit fehlerträchtige) Angelegenheit sind, deshalb bevorzuge ich Wege die mit wenig Aufwand und wenig Komplexität ein zuverlässiges Ergebnis bringen.
Dieses einfache Owner-Prinzip garantiert, dass so etwas wie zeitgleiches Schreiben nicht funktioniert.Intel hat keinen "Owner", dort gibt es nur MESI ohne O. Außerdem springt das Cache-Kohärenz-Protokoll erst an wenn geschrieben wird, es könnte natürlich sein dass das lock-Präfix beim cmpxchg hier noch mal was dreht aber das geht aus den Intel-Dokus nicht hervor. Dort wird doch beschrieben das der cmpxchg erst dann nach außen spürbare Auswirkungen hat wenn auch geschrieben wird.
erik hat nicht von zeitgleichem Schreiben gesprochen,Es geht mir darum das zwischen dem Lesen und dem Schreiben der einen CPU eine andere überhaupt die Lock-Variable lesen kann (ohne das eine von beiden CPUs das bemerkt). Der Lese-Zugriff der anderen CPU bringt die selben Daten und damit geht die andere CPU eben auch davon aus (so wie die erste CPU) das der Lock noch frei ist und fühlt sich hinterher als rechtmäßiger Besitzer (so wie auch die erste CPU). Daran ändert auch nichts das dann beide CPUs später einen "irgendwie gesicherten" Schreibzugriff durchführen (beide mit den selben Daten), dann ist es schließlich schon zu spät. Die Entscheidung (Lock bekommen oder nicht) wird zwischen dem Lese-Zugriff und dem Schreib-Zugriff getroffen und alles externe was zwischen diesen Zugriffen passiert beeinflusst diese Entscheidung eben nicht.
....was aber nicht sein kann, dass das Lesen+Schreiben zusammen atomar ist.So sollte es sein, aber den Intel-Dokus kann man nicht entnehmen das es wirklich so ist.
Atomar heißt ja nicht, dass das ganze in einem Takt durch zuziehen ist.Beides Richtig.
Es kann doch auch sein, dass er in den ersten paar Takten liest und danach schreibt.
Er darf in der Zeit halt nur nicht auf der IRQ-Leitung und der CPU-Kommunikations-Leitung lauschen.Hä?
Die müssen währenddessen einfach ignoriert werden, dann ist das ganze Atomar
Ich will ja nicht unhöflich sein aber Du schreibst da wirres Zeugs.Was ist wenn bei beiden CPUs die Daten nicht im Cache sind? Dann kommen 2 ganz normale Speicher-Lese-Zugriffe. Ich kann nicht erkennen wie damit die Atomizität gewährleistet werden soll.Und warum darf die eine CPU nicht während Lesen+Schreiben ein #LOCK hochziehen?
siehe oben, abgesehen davon bin ich nicht dumm, du brauchst das Problem nicht 3mal wiederholen, ich habe es auch beim ersten Mal verstanden, aber ich sehe nicht warum man das nicht in Hardware implementieren können sollte mit #LOCK zB.erik hat nicht von zeitgleichem Schreiben gesprochen,Es geht mir darum das zwischen dem Lesen und dem Schreiben der einen CPU eine andere überhaupt die Lock-Variable lesen kann (ohne das eine von beiden CPUs das bemerkt). Der Lese-Zugriff der anderen CPU bringt die selben Daten und damit geht die andere CPU eben auch davon aus (so wie die erste CPU) das der Lock noch frei ist und fühlt sich hinterher als rechtmäßiger Besitzer (so wie auch die erste CPU). Daran ändert auch nichts das dann beide CPUs später einen "irgendwie gesicherten" Schreibzugriff durchführen (beide mit den selben Daten), dann ist es schließlich schon zu spät. Die Entscheidung (Lock bekommen oder nicht) wird zwischen dem Lese-Zugriff und dem Schreib-Zugriff getroffen und alles externe was zwischen diesen Zugriffen passiert beeinflusst diese Entscheidung eben nicht.
Das es atomar ist kann man den Dokus schon entnehmen, warum es funktioniert nicht, aber das ist auch nicht Ziel der Manuals.....was aber nicht sein kann, dass das Lesen+Schreiben zusammen atomar ist.So sollte es sein, aber den Intel-Dokus kann man nicht entnehmen das es wirklich so ist.
Gerade bei sehr vielen CPUs verursacht das Cache-Kohärenz-Protokoll aber einen erheblichen Overhead so das der cmpxchg-Befehl mit Sicherheit deutlich langsamer ist.Genauso langsam wie xchg, da die selben Protokolle verwendet werden.
A LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
Und warum darf die eine CPU nicht während Lesen+Schreiben ein #LOCK hochziehen?Das darf sie schon. Aber es soll ja gerade der Vorteil von cmpxchg sein das erst beim Schreibzugriff, falls dieser überhaupt notwendig ist, entsprechende Sicherungsprotokolle benutzt werden. Das ist doch der Vorteil von cmpxchg gegenüber von xchg, oder irre ich da?
abgesehen davon bin ich nicht dumm, du brauchst das Problem nicht 3mal wiederholen, ich habe es auch beim ersten Mal verstanden, aber ich sehe nicht warum man das nicht in Hardware implementieren können sollte mit #LOCK zB.Entschuldige bitte, aber ich hatte nicht den Eindruck das Du verstehst warum ich xchg vertraue und cmpxchg nicht. Bei cmpxchg hast Du immer gesagt das der Lesezugriff noch keine (wirksamen) Sicherheitsmaßnahmen ergreift (und sogar nur im Cache arbeiten könnte) und das finde ich einfach nicht vertrauenerweckend.
Das es atomar ist kann man den Dokus schon entnehmen, warum es funktioniert nicht, aber das ist auch nicht Ziel der Manuals.Da haben wir offensichtlich verschiedene Ansichten bezüglich einer CPU-Dokumentation. Bei anderen Herstellern werden solche Mechanismen sehr ausführlich erläutert, ARM ist da nur ein Beispiel von vielen, was auch eindeutig die elementare Wichtigkeit solcher Details zeigt (SMP ist schließlich sehr im kommen). Bei Intel geht das offensichtlich nicht (jedenfalls nicht bei cmpxchg aber bei xchg schon) und das stört mich.
Ist xchg wirklich so anders als cmpxchg? Also ich nehme mal an, dass auf dem Datenbus nicht gleichzeitig Daten in beide Richtungen fließen können. Der Befehl xchg muss auch erst lesen und dann schreiben. Damit müsste xchg an den selben Problemen wie die hier für cmpxchg beschriebenen leiden, sofern diese existieren.Beim xchg-Befehl werden die Schreibdaten gleich mit in die Lese-Anforderung rein gepackt. Die Atomizität entsteht im Speichercontroller weil dieser den einen Job, mit 2 gegensätzlichen Speicherzugriffen, atomar ausführt. Einen Speichercontroller so zu entwickeln das er Jobs (auch wenn sie aus mehreren Teilen bestehen) seriell ausführt ist ungleich einfacher als die Entwicklung eines zuverlässigem Cache-Kohärenz-Protokolls. Wenn die Lock-Variable in keinem Cache ist wird für xchg auch kein Cache-Kohärenz-Protokoll benötigt und wenn doch dann müssen die betreffenden Cache-Lines nur zurückgeschrieben oder invalidiert werden, das ist deutlich simpler und wahrscheinlich auch schneller.
Das ist auch kein Wunder weil der CPU-Kern ja vom absenden der speziellen Leseanforderung (mit den Schreibdaten) bis zur Antwort (mit den Lesedaten) untätig warten muss, 100 Takte sind da in größeren Systemen bestimmt noch optimistisch gerechnet. Wenn cmpxchg keine (IMHO unzulässigen) Optimierungen machen würde wäre es kein Stück schneller, da ich davon ausgehe (ohne es genau zu wissen) das sich cmpxchg mit lock-Präfix ähnlich verhält ist es sicher nicht schneller als xchg.Zitat von: http://www.agner.org/optimize/instruction_tables.pdfA LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
Und warum darf die eine CPU nicht während Lesen+Schreiben ein #LOCK hochziehen?Weil es kein #LOCK-Signal mehr gibt. Das war früher, wo es noch einen einzigen zentralen Frondside-BUS gab an dem alle CPUs hingen, mal aktuell aber seit den Opterons mit dem HyperTransport-Netzwerk oder den Core i7 mit dem QPI-Netzwerk gibt es keine zentrale Stelle mehr über die alle Speicherzugriffe müssen und an der man ein #LOCK-Signal implementieren könnte. Diese Aufgabe muss jetzt das dezentralisierte Cache-Kohärenz-Protokoll übernehmen und das ist gerade bei vielen CPUs ein sehr komplexes System. Weder von Intel noch von AMD bekommt man da irgendwelche Infos wie das genau funktioniert. Da jedes Byte im Speicher aber einen Heimat-Speichercontroller hat würde es sich gerade hier anbieten den xchg-Mechanismus zu nutzen und die Lock-Variablen nie in einen Cache zu lassen.
Das klingt ja immer mehr nach Verschwörungstheorie. Deine Meinung ist also, dass Intel - Hersteller von Prozessoren mit Milliarden Transitoren - vorsätzlich ein fehlerhaftes Lockingprotokoll implementiert, weil der korrekte Weg zu kompliziert wäre?Nein, ich denke schon das Intel es ordentlich macht, aber wenn Intel den cmpxchg-Befehl ordentlich implementiert dann ist er definitiv langsamer (und aufwändiger) als xchg. Ich schrieb doch eindeutig das ich Intel als Lügner und Geheimniskrämer betrachte.
da ich davon ausgehe (ohne es genau zu wissen) das sich cmpxchg mit lock-Präfix ähnlich verhält ist es sicher nicht schneller als xchg.Hier hab ich mich geirrt. cmpxchg kann sich nicht so wie xchg verhalten weil es die gelesenen Daten ja erst prüft bevor eventuell ein Schreibzugriff ausgelöst wird. Die Simplizität von xchg, wegen seinem bedingungslosen schreiben, kann der cmpxchg-Befehl mit lock-Präfix gar nicht erreichen.
Edit: Außerdem lockt cmpxchg bereits beim Lesen.Logisch betrachtet muss es ja auch so sein (schreib ich doch schon die ganze Zeit), aber dann frage ich mich woher der beworbene Performancevorteil kommt.
Logisch betrachtet muss es ja auch so sein (schreib ich doch schon die ganze Zeit), aber dann frage ich mich woher der beworbene Performancevorteil kommt.Hier im Thread wurde das geringere Cache Thrashing als Vorteil genannt. Außerdem ist die Semantik von cmpxchg eine andere als alles, was du mit xchg + weitere Befehle bauen kannst. Also es lassen sich zum Beispiel Algorithmen implementieren, die mit xchg nicht machbar sind, beziehungsweise bessere "Performance" (z.B. wait-freedom) haben.
Außerdem ist die Semantik von cmpxchg eine andere als alles, was du mit xchg + weitere Befehle bauen kannst. Also es lassen sich zum Beispiel Algorithmen implementieren, die mit xchg nicht machbar sind, beziehungsweise bessere "Performance" (z.B. wait-freedom) haben.Das mit cmpxchg bestimmte Dinge "einfacher" (zumindest für den Assembler-Programmierer) gehen steht außer Frage. xchg ist nur für einfache Locks geeignet so wie eben ein klassischer Test-and-Set-Befehl, aber diese Aufgabe beherrscht xchg mit maximaler Effizienz.
Hier im Thread wurde das geringere Cache Thrashing als Vorteil genannt.Okay, dann wollen wir uns das mal an einem Beispiel ansehen:

Das Cache-Thrashing-Argument kam ursprünglich nicht von mir.Stimmt, sorry.
Ich hab dieses Argument ohne groß drüber nachzudenken für schlüssig gehalten, weil weniger Schreibzugriffe weniger Write Backs erzeugen und damit weniger Thrashing. Das ist zumindest in deinem Modell, das ich für schlüssig halte, nicht der Fall.Man könnte für den Zeitraum eines cmpxchg die Cache-Lines in den Caches der anderen CPUs "sperren" (dazu müsste es dann aber einen zusätzlichen Zustand im MESI geben) so das die CPU, welche den cmpxchg-Befehl ausführt, auf ihrer lokalen Kopie im Cache arbeiten kann. Dazu müssten vorher natürlich noch eventuelle Modifikationen von anderen CPUs (also aus dehren Caches) eingesammelt werden und davor müsste man bei mehreren gleichzeitigen Anfragen von verschiedenen CPUs, die alle einen cmpxchg (oder auch was anderes wie z.B. einen xchg) auf diese Speicherstelle machen wollen, noch einen Gewinner ermitteln (schon allein das ist nicht trivial, vor allem nicht bei mehr als 2 interessierten Teilnehmern). Nach dem Ende von cmpxchg müssten dann die anderen Cache-Lines wieder freigegeben werden (kein Schreibzugriff) oder invalidiert werden (Schreibzugriff erfolgt). Ob der ganze Aufwand in kürzerer Zeit als ein xchg durchgeführt werden kann wage ich stark zu bezweifeln, selbst die ARM-Variante zu unterbieten dürfte nahezu unmöglich sein. Nebst dessen das dieses aufwendige Cache-Kohärenz-Protokoll eine Menge Traffic, über das gesamte CPU-Netzwerk, erzeugt der die anderen CPUs am arbeiten hindert.
struct spinlock_t {
uint32t volatile lock;
uint32t flags;
};
static inline void spinAcquire(struct spinlock_t *spinlock) {
asm volatile("pushfl\n\t"
"1:\tcli\n\t"
"testl $1,(%%eax)\n\t"
"je 2f\n\t"
"popfl\n\t"
"pushfl\n\t"
"pause\n\t"
"jmp 1b\n\t"
".align 4,0x90\n\t"
"2:\tlock bts $0,(%%eax)\n\t"
"jc 1b\n\t"
"popl %[output]\n\t"
"cli"
:[output] "=q"(spinlock->flags)
:"a"(spinlock)
:"memory", "flags");
}
static inline void spinRelease(struct spinlock_t *spinlock) {
//look if ints were enabled or disabled
asm volatile(""
:
:
:"memory");
spinlock->lock= 0;
if(likely(spinlock->flags & 0x200))
asm volatile("sti");
}
Geht es nur um spinlocks im User-Code oder auch im Kernel-Code?Ich denke um beides.
Also erstmal wäre es nicht wirklich toll, wenn ihr eine Spinlock nur über xchg programiert (der lock Präfix ist wie gesagt sehr teuer, vorallem wenn man das ständig macht). Nicht umsonst gibt es die Test-And-Set Variante.Der BTS-Befehl ist ohne lock-Präfix aber nicht Multi-CPU-sicher! Warum das so ist habe ich in diesem Thread nun erschöpfend genug erklärt. In Deinem Quell-Code benutzt Du ja schließlich auch das lock-Präfix. Wenn man die Sicherheit will muss man auch den Preis für das lock-Präfix bezahlen!
Ich bin leider noch nicht so weit das ich mir schon ne Rübe darüber machen müsste wie ich im User-Code nen Lock ohne in den Kernel zu gehen hinbekomme, aber ich würde nie eine Spinlock im User-Code nutzen.Warum nicht? Wenn man den Spinlock nur braucht um 3 Speicherzugriffe durchzuführen (z.B. kleine Struktur atomar modifizieren) dann lohnen zwei teure SYSCALLs wahrlich nicht.
Ich meine stellt euch mal die Situation vor, der Thread bekommt die Lock und gleich danach wird der Scheduler aufgerufen und der Thread ist nicht mehr an der Reihe. Das würde heißen das er den Lock hat und alle anderen die Versuchen ihn zu bekommen müssen warten (und zwar mit einem busy-wait, ganz schlecht) und das kann unter Umständen schon mal ne Weile dauern.Das ist natürlich ein Problem, für das es aber ein paar hübsche Lösungen gibt. Zum einen kann der wartende Thread nach jedem fehlgeschlagenen Versuch die restliche Zeitscheibe freigeben damit der Thread der den Lock gerade hat möglichst schnell wieder an die CPU kommt und fertig wird. Bei Locks die immer nur kurz belegt werden ist das die effizienteste Lösung. Bei Locks die länger belegt werden ist es meist günstiger dafür den OS-Kernel zu bemühen der den wartenden Thread schlafen legt und sofort aufweckt sobald der Lock freigegeben wird (noch im Freigabe-SYSCALL). Dort könnte man dann auch so tolle Sachen wie Prioritätsvererbung einbauen, damit der Low-Prio-Thread der gerade den Lock besitzt auf die selbe Prio angehoben wird wie der High-Prio-Thread welcher den Lock haben will damit dieser möglichst bald dran kommt.
Ich nutze folgenden Code im Kernel für meine Spinlocks:Was mir auffällt ist das Du in der spinRelease-Funktion zwar eine Memory-Bariere (das erste Assembler-Macro) hast aber den eigentlichen Freigabe-Speicherzugriff erst danach ausführst, das bedeutet das dieser Freigabe-Speicherzugriff auch erst deutlich später kommen kann (auch nach dem zweiten Assembler-Macro). Ich finde dieser Freigabe-Speicherzugriff sollte in das erste Assembler-Macro rein. Durch die Befehle cli und sti ist Dein code auch nicht für User-Space-Programme geeignet, ein ordentliches OS wird die Nutzung dieser Befehle im User-Mode zuverlässig verhindern.Code: [Auswählen]....
Der BTS-Befehl ist ohne lock-Präfix aber nicht Multi-CPU-sicher! Warum das so ist habe ich in diesem Thread nun erschöpfend genug erklärt. In Deinem Quell-Code benutzt Du ja schließlich auch das lock-Präfix. Wenn man die Sicherheit will muss man auch den Preis für das lock-Präfix bezahlen!Hier hast du mich ein wenig missverstanden. Was ich an dem Code den ich hier gesehen habe, zu beanstanden habe ist dass er ständig eine Schleife macht und ständig den Lock durchführt!
(im Kernel ist ja nur aktives Warten möglich)Wie kommst du darauf? Also mein Kernel beherrscht Semaphores (ihm ist es auch egal ob es ein User oder ein Kernel-Thread, da eh alles im Kernel läuft). Also das im Kernel nur aktives warten möglich ist, ist erstmal Quatsch, das ist ne reine Implementations Sache!
Hier hast du mich ein wenig missverstanden. Was ich an dem Code den ich hier gesehen habe, zu beanstanden habe ist dass er ständig eine Schleife macht und ständig den Lock durchführt!Der Code versucht ständig den Lock zu bekommen und jeder Versuch wird mit einem atomaren Speicher-Befehl durchgeführt (das muss ja auch so sein).
Deswegen sollte man ja auch die Test-And-Set Variante verwenden! Das man erst den Memorybus Locked wenn der Lock wahrscheinlich frei ist!Das macht Dein Code aber auch nicht besser.
Also das im Kernel nur aktives warten möglich ist, ist erstmal Quatsch, das ist ne reine Implementations Sache!Stimmt, man kann eine Thread auch im Kernel-Mode unterbrechen.