In der Anweisung des Mikroprozessors 8085 gibt es eine Maschinensteueroperation "nop" (keine Operation). Meine Frage ist, warum wir keine Operation brauchen? Ich meine, wenn wir das Programm beenden müssen, werden wir HLT oder RST 3 verwenden. Oder wenn wir zur nächsten Anweisung übergehen wollen, werden wir die nächste Anweisung geben. Aber warum keine Operation? Was ist der Bedarf?
microprocessor
Demietra95
quelle
quelle
Antworten:
Eine Verwendung des Befehls NOP (oder NOOP, no-operation) in CPUs und MCUs besteht darin, eine kleine, vorhersehbare Verzögerung in Ihren Code einzufügen. Obwohl NOPs keine Operation ausführen, dauert es einige Zeit, sie zu verarbeiten (die CPU muss den Opcode abrufen und dekodieren, so dass dies einige Zeit in Anspruch nimmt). Bereits 1 CPU-Zyklus wird für die Ausführung eines NOP-Befehls "verschwendet" (die genaue Anzahl kann normalerweise dem CPU / MCU-Datenblatt entnommen werden). Daher ist das Aneinanderreihen von NOPs eine einfache Möglichkeit, eine vorhersagbare Verzögerung einzufügen:
Dabei ist K die Anzahl der Zyklen (am häufigsten 1), die für die Verarbeitung eines NOP-Befehls benötigt werden, und ist die Taktperiode.Tclock
Warum würdest du das tun? Es kann nützlich sein, die CPU zu zwingen, ein wenig zu warten, bis externe (möglicherweise langsamere) Geräte ihre Arbeit abgeschlossen haben und Daten an die CPU melden, dh NOP ist nützlich für Synchronisationszwecke.
Siehe auch die zugehörige Wikipedia-Seite zu NOP .
Eine andere Verwendung ist das Ausrichten von Code an bestimmten Adressen im Speicher und andere "Assembly-Tricks", wie auch in diesem Thread zu Programmers.SE und in diesem anderen Thread zu StackOverflow erläutert .
Ein weiterer interessanter Artikel zum Thema .
Dieser Link zu einer Google-Buchseite bezieht sich insbesondere auf die 8085-CPU. Auszug:
EDIT (um ein in einem Kommentar geäußertes Anliegen anzusprechen)
Wenn Sie sich Gedanken über die Geschwindigkeit machen, denken Sie daran, dass (Zeit-) Effizienz nur ein Parameter ist, den Sie berücksichtigen müssen. Es hängt alles von der Anwendung ab: Wenn Sie die 10-milliardste Zahl von berechnen möchten, besteht Ihre einzige Sorge möglicherweise in der Geschwindigkeit. Auf der anderen Seite, wenn Sie von Temperatursensoren mit einer MCU über eine ADC Logdaten wollen, ist die Geschwindigkeit der Regel nicht so wichtig, aber die richtige Menge an Zeit warten , füllen Sie bitte der ADC korrekt jede Lesung zu ermöglichen , ist von wesentlicher Bedeutung . In diesem Fall besteht die Gefahr, dass die MCU, wenn sie nicht genug wartet, völlig unzuverlässige Daten erhält (ich gebe zu, dass sie diese Daten schneller erhält , obwohl: o).π
quelle
Die anderen Antworten beziehen sich nur auf eine NOP, die tatsächlich zu einem bestimmten Zeitpunkt ausgeführt wird. Dies wird häufig verwendet, ist jedoch nicht die einzige Verwendung von NOP.
Das nicht ausgeführte NOP ist auch sehr nützlich, wenn Sie Code schreiben, der gepatcht werden kann. Im Grunde genommen füllen Sie die Funktion nach dem
RET
(oder einem ähnlichen Befehl) mit ein paar NOPs auf . Wenn Sie die ausführbare Datei patchen müssen, können Sie der Funktion auf einfache Weise mehr Code hinzufügen, indem Sie vom Original ausRET
so viele dieser NOPs verwenden, wie Sie benötigen (z. B. für lange Sprünge oder sogar Inline-Code) und mit einem anderen abschließenRET
.In diesem Anwendungsfall erwartet noöne jemals die
NOP
Ausführung. Der einzige Punkt ist, das Patchen der ausführbaren Datei zuzulassen - in einer theoretischen nicht aufgefüllten ausführbaren Datei müssten Sie den Code der Funktion selbst tatsächlich ändern (manchmal passt er möglicherweise an die ursprünglichen Grenzen, aber häufig benötigen Sie trotzdem einen Sprung ) - das ist viel komplizierter, besonders wenn man die manuell geschriebene Assembly oder einen optimierenden Compiler in Betracht zieht; Sie müssen Sprünge und ähnliche Konstrukte respektieren, die auf ein wichtiges Stück Code hingewiesen haben könnten. Alles in allem ziemlich knifflig.Natürlich wurde dies in den alten Tagen viel häufiger verwendet, als es nützlich war, solche kleinen und Online- Patches zu erstellen . Heute verteilen Sie einfach eine neu kompilierte Binärdatei und sind fertig damit. Es gibt immer noch einige, die Patch-NOPs verwenden (die ausgeführt werden oder nicht, und nicht immer wörtliche
NOP
s - zum Beispiel WindowsMOV EDI, EDI
für Online-Patches - auf diese Weise können Sie eine Systembibliothek aktualisieren, während das System tatsächlich ausgeführt wird, ohne dass ein Neustart erforderlich ist).Die letzte Frage ist also, warum Sie eine spezielle Anweisung für etwas haben, das nicht wirklich etwas bewirkt.
MOV AX, AX
tun genau das Gleiche, signalisieren die Absicht jedoch nicht ganz so deutlich.NOP
ziemlich viel. Der tatsächlich kompilierte Assembler-Code enthält diese nicht mehr. Es ist jedoch zu beachten, dass es sich nicht um x86NOP
handelt.NOP
Dies erleichtert das Online-Debuggen sowohl beim Lesen (Speicher vor dem Nullpunkt ist alles , was das Zerlegen erleichtert) als auch beim Hot-Patching (obwohl ich das Bearbeiten und Fortfahren in Visual Studio: P bei weitem bevorzuge).Für die Ausführung von NOPs gibt es natürlich noch ein paar Punkte:
MOV EDI, EDI
, gibt es andere effektive NOPs als das LiteralNOP
.MOV EDI, EDI
hat die beste Leistung als 2-Byte-NOP auf x86. Wenn Sie zweiNOP
s verwenden, sind dies zwei auszuführende Anweisungen.BEARBEITEN:
Eigentlich hat mich die Diskussion mit @DmitryGrigoryev dazu gezwungen, ein bisschen mehr darüber nachzudenken, und ich denke, es ist eine wertvolle Ergänzung zu dieser Frage / Antwort, also lassen Sie mich ein paar zusätzliche Punkte hinzufügen:
Erstens, klar, warum sollte es eine Anweisung geben, die so etwas tut
mov ax, ax
? Betrachten wir zum Beispiel den Fall von 8086-Maschinencode (älter als 386-Maschinencode):0x90
. Dies ist immer noch die Zeit, in der viele Leute in der Versammlung geschrieben haben, wohlgemerkt. Selbst wenn es keine spezielleNOP
AnweisungNOP
gäbe , wäre das Schlüsselwort (Alias / Mnemonik) dennoch nützlich und würde dem entsprechen.MOV
Karte tatsächlich auf viele verschiedenen Opcodes, denn das auf Raum und Zeit spart - zum Beispiel,mov al, 42
ist „immediate Byte zum bewegenal
Register“, was übersetzt0xB02A
(0xB0
wobei der Opcode,0x2A
als "Sofortargument"). Das dauert also zwei Bytes.mov al, al
(da dies im Grunde genommen eine dumme Sache ist), daher müssen Sie diemov al, rmb
Überladung (rmb ist "register or memory") verwenden. Das dauert eigentlich drei Bytes. (obwohl es wahrscheinlich das weniger spezifischemov rb, rmb
verwenden würde, das nur zwei Bytes aufnehmen solltemov al, al
- das Argument Byte wird verwendet, um sowohl das Quell- als auch das Zielregister anzugeben; jetzt wissen Sie, warum 8086 nur 8 Register hatte: D). Vergleichen Sie mitNOP
, was ein Einzelbyte-Befehl ist! Dies spart Speicher und Zeit, da das Lesen des Speichers im 8086 noch recht teuer war - ganz zu schweigen vom Laden des Programms von einer Kassette oder Diskette oder so.Wo kommt der denn her
xchg ax, ax
? Sie müssen sich nur die Opcodes der anderenxhcg
Anweisungen ansehen . Sie werden sehen0x86
,0x87
und schließlich0x91
-0x97
. Sonop
mit ist es0x90
scheint wie eine ziemlich gute Passform fürxchg ax, ax
(die wiederum ist keinexchg
„Überlast“ - Sie verwenden würdenxchg rb, rmb
, um zwei Bytes). Und in der Tat bin ich mir ziemlich sicher, dass dies ein netter Nebeneffekt der0x90-0x97
Mikroarchitektur der Zeit war - wenn ich mich recht entsinne, war es einfach, die gesamte Bandbreite auf "xchg" abzubilden, über Register zu agierenax
undax
-di
( Da der Operand symmetrisch ist, haben Sie den gesamten Bereich einschließlich des NOP erhaltenxchg ax, ax
. Beachten Sie, dass die Reihenfolgeax, cx, dx, bx, sp, bp, si, di
-bx
danachdx
ist.ax
; Denken Sie daran, dass die Registernamen mnemonische und nicht geordnete Namen sind (Akkumulator, Zähler, Daten, Basis, Stapelzeiger, Basiszeiger, Quellindex, Zielindex). Der gleiche Ansatz wurde auch für andere Operanden verwendet, zum Beispiel diemov someRegister, immediate
Menge. In gewisser Weise könnte man sich das so vorstellen, als ob der Opcode tatsächlich kein vollständiges Byte wäre - die letzten paar Bits sind "ein Argument" für den "echten" Operanden.All dies kann auf x86
nop
als echte Anweisung angesehen werden oder nicht. Die ursprüngliche Mikroarchitektur behandelte es als eine Variante,xchg
wenn ich mich richtig erinnere, aber es wurde tatsächlichnop
in der Spezifikation benannt. Und daxchg ax, ax
dies als Anweisung nicht wirklich sinnvoll ist, können Sie sehen, wie die Designer des 8086 bei der Anweisungsdecodierung Transistoren und Pfade eingespart haben, indem sie die Tatsache ausnutzen, dass0x90
die Zuordnung auf natürliche Weise zu etwas erfolgt, das vollständig "Noppy" ist.Auf der anderen Seite verfügt der i8051 über einen vollständig integrierten Opcode für
nop
-0x00
. Ein bisschen praktisch. Der Befehl Design ist im Grunde das hohe Halbbyte für den Betrieb mit und den niedrigen Nibble für die Operanden der Auswahl - zum Beispiel,add a
ist0x2Y
, und0xX8
bedeutet „Register 0 direkte“, so0x28
istadd a, r0
. Spart viel Silizium :)Ich könnte noch weitermachen, da CPU-Design (ganz zu schweigen von Compiler-Design und Sprachdesign) ein ziemlich breites Thema ist, aber ich denke, ich habe viele verschiedene Standpunkte gezeigt, die in das Design ganz gut eingegangen sind, wie es ist.
quelle
NOP
ist in der Regel ein AliasMOV ax, ax
,ADD ax, 0
oder ähnliche Anweisungen. Warum sollten Sie eine spezielle Anweisung entwerfen, die nichts bewirkt, wenn es genügend gibt?MOV ax, ax
.NOP
wird immer eine feste Anzahl von Zyklen für die Ausführung haben. Aber ich verstehe sowieso nicht, wie wichtig das für das ist, was ich in meiner Antwort geschrieben habe.MOV ax, ax
, daMOV
der Befehl bereits in Vorbereitung ist, wenn sie wissen, dass es sich um eine handelt.NOP
und wahrscheinlich auch so wäreMOV ax, ax
). Moderne CPUs sind viel komplexer als die C-Compiler derLD r, r
Befehle, wobeir
sich ein beliebiges einzelnes Register befindet, ähnlich wie bei IhremMOV ax, ax
Befehl. Der Grund, warum es 7 und nicht 8 ist, ist, dass eine der Anweisungen überladen ist, um zu werdenHALT
. So haben der 8080 und der Z80 mindestens 7 andere Anweisungen, die dasselbe tun wieNOP
! Interessanterweise benötigen alle diese Befehle 4 T-Zustände, um ausgeführt zu werden, obwohl sie nicht logisch nach Bitmuster verknüpft sind, sodass es keinen Grund gibt, dieLD
Befehle zu verwenden!In den späten 70er Jahren hatten wir (damals war ich noch ein junger Student) ein kleines Entwicklungssystem (8080, wenn Speicher zur Verfügung steht), das 1024 Byte Code enthielt (dh ein einzelnes UVEPROM) - es mussten nur vier Befehle geladen werden (L ), speichern (S), drucken (P) und etwas anderes, an das ich mich nicht erinnern kann. Es wurde mit einem echten Fernschreiber und Lochstreifen gefahren. Es war eng codiert!
Ein Beispiel für die Verwendung von NOOP war eine Interrupt-Service-Routine (ISR), die in 8-Byte-Intervallen angeordnet war. Diese Routine hatte eine Länge von 9 Byte und endete mit einem (langen) Sprung zu einer Adresse, die etwas weiter oben im Adressraum liegt. Angesichts der Little-Endian-Bytereihenfolge bedeutete dies, dass das High-Address-Byte 00h lautete und in das erste Byte des nächsten ISR eingefügt wurde, was bedeutete, dass es (der nächste ISR) mit NOOP begann, damit „wir“ passen konnten der Code in dem begrenzten Raum!
Der NOOP ist also nützlich. Außerdem denke ich, dass es für Intel am einfachsten war, es so zu codieren. Sie hatten wahrscheinlich eine Liste von Anweisungen, die sie implementieren wollten, und es begann bei '1', wie es bei allen Listen der Fall ist (dies waren die Tage von FORTRAN), also die Null NOOP-Code wurde zu einem Problem. (Ich habe noch nie einen Artikel gesehen, in dem argumentiert wurde, dass ein NOOP ein wesentlicher Bestandteil der Theorie der Informatik ist.)
quelle
0x00
fürnop
in meiner Antwort. Kurz gesagt, es spartxchg ax, ax
Befehlsdecodierung - ergibt sich auf natürliche Weise aus der Funktionsweise der Befehlsdecodierung, und es wird etwas "Noppy" ausgeführt. Warum also nicht das verwenden und es aufrufennop
? :) Verwendet, um einiges auf dem Silizium für dieAuf einigen Architekturen
NOP
wird verwendet, um nicht verwendete Verzögerungsschlitze zu belegen . Wenn die Verzweigungsanweisung beispielsweise die Pipeline nicht löscht, werden trotzdem mehrere Anweisungen ausgeführt:Aber was ist, wenn Sie nach dem keine nützlichen Anweisungen zum Anpassen haben
JMP
? In diesem Fall müssen SieNOP
s verwenden.Verzögerungsschlitze sind nicht auf Sprünge beschränkt. In einigen Architekturen werden Datenrisiken in der CPU-Pipeline nicht automatisch behoben. Dies bedeutet, dass nach jedem Befehl, der ein Register ändert, ein Schlitz vorhanden ist, in dem der neue Wert des Registers noch nicht zugänglich ist. Wenn der nächste Befehl diesen Wert benötigt, sollte der Steckplatz mit a belegt sein
NOP
:Einige Anweisungen zur bedingten Ausführung ( If-True-False und ähnliches) verwenden Slots für jede Bedingung. Wenn mit einer bestimmten Bedingung keine Aktionen verknüpft sind, sollte ihr Slot mit einem belegt sein
NOP
:quelle
Ein weiteres Beispiel für die Verwendung eines Zwei-Byte- NOP: http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx
quelle