2016-04-21 6 views
0

Um einige Sicherheitseigenschaften zu erfüllen, möchte ich sicherstellen, dass wichtige Daten bereits im Cache sind, wenn eine Anweisung darauf zugreift (damit es keinen Cache-Fehler gibt). Zum Beispiel für diesen CodePrefetch Instruktionsverhalten

... 
a += 2; 
... 

Ich möchte sicherstellen, dass a im Cache befindet sich direkt vor a += 2 ausgeführt wird.

ich erwäge die PREFETCHh Anweisung von x86 zu verwenden, um dies zu erreichen:

... 
__prefetch(&a);  /* pseudocode */ 
a += 2; 
... 

ich jedoch, dass die Vorholbefehl Recht vor a += 2 Einsetzen gelesen haben könnte zu spät sein a im Cache zu gewährleisten ist, wenn a += 2 wird ausgeführt. Ist diese Behauptung wahr? Wenn es wahr ist, kann ich es beheben, indem ich einen CPUID Befehl nach dem Prefetch einfüge, um sicherzustellen, dass der Präfektchbefehl ausgeführt wurde (weil das Intel-Handbuch PREFETCHh mit Bezug auf CPUID bestellt)?

+1

Ich bin nicht sicher, was das tatsächlich erreichen würde. Sie ändern nur die Reihenfolge von fetch_into_cache (a), read_from_cache (a), write_to_cache (a) in prefetch_into_cache (a), read_from_cache (a), write_to_cache (a). Außer vielleicht jetzt mit einer großen Verzögerung zwischen dem Prefetch und dem Lesen. –

+1

was genau wollen Sie erreichen? Timing-Attacken vermeiden? –

Antwort

5

Ja, Sie müssen mit einer Vorlaufzeit von ungefähr der Speicherlatenz vorausholen, damit es optimal ist. Ulrich Dreppers What Every Programmer Should Know About Memory spricht viel über Prefetching.

Dies zu erreichen wird für einen einzelnen Zugriff höchst nicht trivial sein. Zu früh und Ihre Daten werden möglicherweise vor den Insn, die Ihnen wichtig sind, geräumt. Zu spät und es könnte die Zugriffszeit etwas reduzieren. Das Tuning hängt von der Compiler-Version/-Optionen und von der Hardware ab, auf der Sie arbeiten. (Höhere Instruktionen pro Zyklus bedeutet, dass Sie früher im Voraus abrufen müssen. Höhere Speicherwartezeit bedeutet auch, dass Sie früher abrufen müssen).

Da Sie eine read-modify-write zu a schreiben möchten, sollten Sie PREFETCHW verwenden, wenn verfügbar. Die anderen Vorabrufbefehle werden nur zum Lesen vorgelesen, so dass der gelesene Teil eines RMW treffen könnte, aber ich denke, der Speicherabschnitt könnte durch die MOSI-Cache-Kohärenz verzögert werden, wodurch der Schreibbesitz der Cachezeile erhalten wird.

Wenn a nicht atomar ist, können Sie auch einfach a weit vor der Zeit laden und die Kopie in einem Register verwenden. Der Speicher zurück zum Global könnte in diesem Fall leicht verfehlen, was jedoch die Ausführung behindern könnte.

Sie werden es wahrscheinlich schwer haben, das mit einem Compiler zuverlässig zu tun, anstatt selbst zu schreiben. Jede der anderen Ideen erfordert auch die Überprüfung der Compiler-Ausgabe, um sicherzustellen, dass der Compiler das getan hat, was Sie hoffen.

Prefetch-Anweisungen müssen nicht unbedingt vorab eingelesen werden. Sie sind "Hinweise", die vermutlich ignoriert werden, wenn die Anzahl der ausstehenden Lasten in der Nähe von max ist (d. H. Fast unbelastete Puffer).


Eine weitere Option ist es zu laden (und nicht nur Prefetch) und dann mit einem CPUID serialisiert. (Eine Last, die das Ergebnis wegwirft, ist wie ein Prefetch). Die Last müsste vor der Serialisierungsanweisung abschließen, und Anweisungen nach der Serialisierung insn können die Decodierung erst dann beginnen. Ich denke, dass ein Prefetch in den Ruhestand gehen kann, bevor die Daten ankommen, was normalerweise ein Vorteil ist, aber nicht in diesem Fall, in dem wir uns um eine Operation kümmern, die auf Kosten der Gesamtleistung geht.

Von insn ref Handbuch des Intel (siehe Tag wiki) Eintrag für CPUID:

Serialisierung Befehlsausführung garantiert, dass alle Änderungen auf Merker, Register und Speicher für frühere Anweisungen vor nächsten abgeschlossen sind Anweisung wird abgerufen und ausgeführt.

Ich denke, eine Sequenz wie dies ziemlich gut ist (aber immer noch garantiert nicht, alles in einem Präventiv Multi-Tasking-System):

add [mem], 0  # can't retire until the store completes, requiring that our core owns the cache line for writing 
CPUID    # later insns can't start until the prev add retires 
add [mem], 2  # a += 2 Can't miss in cache unless an interrupt or the other hyper-thread evicts the cache line before this insn can execute 

Hier verwenden wir add [mem], 0 als Schreib -prefetch, die sonst ein naher no-op ist. (Es ist ein nicht-atomare lesen-ändern-umschreiben). Ich bin mir nicht sicher, ob PREFETCHW wirklich sicherstellen wird, dass die Cache-Zeile bereit ist, wenn Sie PREFETCHW/CPUID/add [mem], 2 tun. Das Insn wird mit bestellt. CPUID, aber das Handbuch sagt nicht, dass der Prefetch-Effekt bestellt wird.


Wenn avolatile ist, dann (void)a; wird gcc oder Klirren erhalten, um eine Last insn zu emittieren. Ich nehme an, die meisten anderen Compiler (MSVC?) Sind gleich. Sie können wahrscheinlich (void) *(volatile something*)&a verwenden, um einen Zeiger auf volatile zu dereferenzieren und eine Last von der Adresse a zu erzwingen.


Um Garantie, dass ein Speicherzugriff im Cache getroffen wird, dann würden Sie müssen in Echtzeit Priorität einen Kern festgesteckt zu laufen, die keine Interrupts empfangen. Abhängig vom Betriebssystem ist der Timer-Interrupt-Handler wahrscheinlich so leicht, dass die Wahrscheinlichkeit, dass Daten aus dem Cache entfernt werden, gering genug ist.

Wenn Ihr Prozess zwischen dem Ausführen eines Prefetch-Befehls und dem Ausführen des tatsächlichen Zugriffs verschoben wird, werden die Daten wahrscheinlich aus mindestens L1-Cache entfernt.

Es ist also unwahrscheinlich, dass Sie einen Angreifer besiegen können, der einen Timing-Angriff auf Ihren Code durchführt, es sei denn, es ist realistisch, in Echtzeit zu laufen. Ein Angreifer könnte viele Threads von speicherintensivem Code ausführen ...

+0

Vielen Dank für Ihre großartige Antwort! Ich habe allerdings ein paar Fragen. Lassen Sie mich zunächst bestätigen: Unter Last verstehen Sie nur eine regelmäßige Ladeanweisung, oder? Wenn ich es ohne serialisieren mit CPUID oder etwas lade, wird möglicherweise ein Cache-Fehler auftreten? – Seves

+0

Eigentlich hat x86 keine Ladeanweisung ... meinst du mov? – Seves

+0

@Seves. Ja, nur eine normale Ladung, wie ein 'mov eax, [mem]'. Oder 'add eax, [mem]' beinhaltet auch einen Load und dekodiert zu einem add-up und einem load-up. Wenn Sie nicht serialisieren, dann kann der Cache-Miss auf dem ersten insn noch ausstehen, wenn der 2. insn läuft und auch verfehlt. Siehe meine Bearbeitung. BTW, wenn Sie sich um die Benennung von Anweisungen kümmern wollen, dann hat x86 * einen Laden insn: 'lodsb' lädt' [esi] 'in' al' und inkrementiert 'esi'. : P Aber ja, es ist einfacher "load" als "' mov "mit einer Speicherquelle zu sagen, wenn du" load "meinst. –

Verwandte Themen