En la instrucción del microprocesador 8085, hay una operación de control de máquina "nop" (sin operación). Mi pregunta es ¿por qué necesitamos una operación no? Quiero decir que si tenemos que finalizar el programa usaremos HLT o RST 3. O si queremos pasar a la siguiente instrucción, daremos las siguientes instrucciones. ¿Pero por qué no hay operación? Cual es la necesidad?
microprocessor
Demietra95
fuente
fuente
Respuestas:
Un uso de la instrucción NOP (o NOOP, sin operación) en las CPU y MCU es insertar un pequeño retraso predecible en su código. Aunque los NOP no realizan ninguna operación, lleva algún tiempo procesarlos (la CPU tiene que buscar y decodificar el código de operación, por lo que necesita algo de tiempo para hacerlo). Tan poco como 1 ciclo de CPU se "desperdicia" para ejecutar una instrucción NOP (el número exacto se puede inferir de la hoja de datos de CPU / MCU, por lo general), por lo tanto, poner N NOP en secuencia es una manera fácil de insertar un retraso predecible:
donde K es el número de ciclos (con mayor frecuencia 1) necesarios para el procesamiento de una instrucción NOP, y es el período de reloj.Tc l o c k
¿Por qué harías eso? Puede ser útil forzar a la CPU a esperar un poco para que los dispositivos externos (tal vez más lentos) completen su trabajo e informen los datos a la CPU, es decir, NOP es útil para fines de sincronización.
Vea también la página relacionada de Wikipedia sobre NOP .
Otro uso es alinear el código en ciertas direcciones en la memoria y otros "trucos de ensamblaje", como se explica también en este hilo en Programmers.SE y en este otro hilo en StackOverflow .
Otro interesante artículo sobre el tema .
Este enlace a una página del libro de Google se refiere especialmente a la CPU 8085. Extracto:
EDITAR (para abordar una preocupación expresada en un comentario)
Si le preocupa la velocidad, tenga en cuenta que la eficiencia (del tiempo) es solo un parámetro a tener en cuenta. Todo depende de la aplicación: si desea calcular la cifra 10 mil millones de , entonces quizás su única preocupación podría ser la velocidad. Por otro lado, si desea registrar datos de sensores de temperatura conectados a una MCU a través de un ADC, la velocidad no suele ser tan importante, pero es esencial esperar la cantidad de tiempo adecuada para permitir que el ADC complete correctamente cada lectura . En este caso, si la MCU no espera lo suficiente, corre el riesgo de obtener datos completamente poco confiables (aunque reconozco que obtendría esos datos más rápido : o).π
fuente
Las otras respuestas solo están considerando un NOP que realmente se ejecuta en algún momento, que se usa con bastante frecuencia, pero no es el único uso de NOP.
El NOP no de ejecución también es bastante útil cuando se escriben código que puede ser parcheado - básicamente, podrás rellenar la función con unos PON después de la
RET
(o instrucción similar). Cuando tiene que parchear el ejecutable, puede agregar fácilmente más código a la función comenzando desde el originalRET
y utilizando tantos NOP como necesite (por ejemplo, para saltos largos o incluso código en línea) y terminando con otroRET
.En este caso de uso, noöne espera
NOP
que se ejecute. El único punto es permitir parchear el ejecutable: en un ejecutable teórico no acolchado, tendría que cambiar el código de la función en sí (a veces podría ajustarse a los límites originales, pero a menudo necesitará un salto de todos modos ) - eso es mucho más complicado, especialmente considerando el ensamblaje escrito manualmente o un compilador optimizador; tienes que respetar los saltos y construcciones similares que podrían haber apuntado en alguna pieza importante de código. En general, bastante complicado.Por supuesto, esto era mucho más utilizado en los viejos tiempos, cuando era útil hacer parches como estos pequeños y en línea . Hoy, simplemente distribuirá un binario recompilado y terminará con él. Todavía hay algunos que usan parches NOP (ejecutando o no, y no siempre literales
NOP
, por ejemplo, Windows usaMOV EDI, EDI
para parches en línea), ese es el tipo en el que puedes actualizar una biblioteca del sistema mientras el sistema se está ejecutando, sin necesidad de reinicios.Entonces, la última pregunta es, ¿por qué tener una instrucción dedicada para algo que realmente no hace nada?
MOV AX, AX
harán exactamente lo mismo, pero no indican la intención con tanta claridad.NOP
bastante; el código de ensamblado compilado real ya no tiene esos SinNOP
embargo, debe tenerse en cuenta que esos no son x86- s.NOP
, lo que hace que el desmontaje sea mucho más fácil de leer) como para parchear en caliente (aunque prefiero editar y continuar en Visual Studio: P).Para ejecutar NOP, por supuesto, hay algunos puntos más:
MOV EDI, EDI
, hay otros NOP efectivos además del literalNOP
.MOV EDI, EDI
tiene el mejor rendimiento como NOP de 2 bytes en x86. Si usó dosNOP
s, serían dos instrucciones para ejecutar.EDITAR:
En realidad, la discusión con @DmitryGrigoryev me obligó a pensar un poco más sobre esto, y creo que es una valiosa adición a esta pregunta / respuesta, así que permítanme agregar algunos bits adicionales:
Primero, señale, obviamente, ¿por qué habría una instrucción que haga algo así
mov ax, ax
? Por ejemplo, veamos el caso del código de máquina 8086 (más antiguo que incluso el código de máquina 386):0x90
. Todavía es el momento en que muchas personas escribieron en asamblea, eso sí. Entonces, incluso si no hubiera unaNOP
instrucción dedicada , laNOP
palabra clave (alias / mnemonic) seguiría siendo útil y se asignaría a eso.MOV
realidad se asignan a muchos códigos de operación diferentes, porque eso ahorra tiempo y espacio, por ejemplo,mov al, 42
es "mover el byte inmediato alal
registro", que se traduce en0xB02A
(0xB0
ser el código de operación,0x2A
ser el argumento "inmediato"). Entonces eso toma dos bytes.mov al, al
(ya que es algo estúpido que hacer, básicamente), por lo que tendrá que usar lamov al, rmb
sobrecarga (rmb es "registro o memoria"). Eso realmente toma tres bytes. (aunque probablemente usaría el menos específico en sumov rb, rmb
lugar, que solo debería tomar dos bytesmov al, al
: el byte de argumento se usa para especificar tanto el registro de origen como el de destino; ahora sabe por qué 8086 solo tenía 8 registros: D). Compare conNOP
, que es una instrucción de un solo byte. Esto ahorra memoria y tiempo, ya que leer la memoria en el 8086 todavía era bastante costoso, por no mencionar cargar ese programa desde una cinta o disquete o algo así, por supuesto.Entonces, ¿de dónde
xchg ax, ax
viene el? Solo tiene que mirar los códigos de operación de las otrasxhcg
instrucciones. Verás0x86
,0x87
y finalmente,0x91
-0x97
. Pornop
lo tanto,0x90
parece que es una buena opciónxchg ax, ax
(que, de nuevo, no es unaxchg
"sobrecarga", tendría que usarxchg rb, rmb
, en dos bytes). Y, de hecho, estoy bastante seguro de que este fue un agradable efecto secundario de la microarquitectura de la época: si recuerdo correctamente, fue fácil mapear todo el rango de0x90-0x97
"xchg, actuando sobre registrosax
yax
-di
" el operando es simétrico, esto le dio el rango completo, incluido el nopxchg ax, ax
; tenga en cuenta que el orden esax, cx, dx, bx, sp, bp, si, di
-bx
es despuésdx
,ax
; recuerde, los nombres de registro son mnemotécnicos, no nombres ordenados: acumulador, contador, datos, base, puntero de pila, puntero base, índice de origen, índice de destino). El mismo enfoque también se utilizó para otros operandos, por ejemplo, elmov someRegister, immediate
conjunto. En cierto modo, se podría pensar en esto como si el código de operación en realidad no fuera un byte completo: los últimos bits son "un argumento" para el operando "real".Todo esto dicho, en x86,
nop
podría considerarse una instrucción real, o no. La microarquitectura original lo trató como una variante dexchg
si recuerdo correctamente, pero en realidad fue nombradonop
en la especificación. Y dadoxchg ax, ax
que realmente no tiene sentido como una instrucción, puede ver cómo los diseñadores del 8086 ahorraron en transistores y vías en la decodificación de instrucciones explotando el hecho de que se0x90
asigna de forma natural a algo que es completamente "noppy".Por otro lado, el i8051 tiene un código de operación completamente diseñado para
nop
-0x00
. Un poco práctico El diseño de la instrucción básicamente utiliza el mordisco alto para la operación y el mordisco bajo para seleccionar los operandos, por ejemplo,add a
es0x2Y
y0xX8
significa "registro 0 directo", así0x28
esadd a, r0
. Ahorra mucho en silicio :)Todavía podría continuar, ya que el diseño de la CPU (sin mencionar el diseño del compilador y el diseño del lenguaje) es un tema bastante amplio, pero creo que he mostrado muchos puntos de vista diferentes que entraron en el diseño bastante bien tal como están.
fuente
NOP
es por lo general un aliasMOV ax, ax
,ADD ax, 0
o instrucciones similares. ¿Por qué diseñaría una instrucción dedicada que no hace nada cuando hay muchas por ahí?MOV ax, ax
distancia;NOP
siempre tendrá una cantidad fija de ciclos para la ejecución. Pero no veo cómo eso es relevante para lo que he escrito en mi respuesta de todos modos.MOV ax, ax
ausente, porque para el momento en que lo saben,MOV
la instrucción ya está en proceso.NOP
yMOV ax, ax
). Las CPU modernas son mucho más complejas que los compiladores de C de la vieja escuela :))LD r, r
instrucciones donder
hay un único registro, similar a suMOV ax, ax
instrucción. La razón es 7 y no 8 es porque una de las instrucciones está sobrecargada para convertirseHALT
. ¡Entonces el 8080 y el Z80 tienen al menos otras 7 instrucciones que hacen lo mismo queNOP
! Curiosamente, a pesar de que estas instrucciones no están lógicamente relacionadas por el patrón de bits, todas requieren 4 estados T para ejecutarse, por lo que no hay razón para usarlasLD
.A finales de los años 70, nosotros (yo era un joven estudiante de investigación entonces) teníamos un pequeño sistema de desarrollo (8080 si la memoria funciona) que se ejecutaba en 1024 bytes de código (es decir, un solo UVEPROM): solo tenía cuatro comandos para cargar (L ), guardar (S), imprimir (P) y algo más que no recuerdo. Fue conducido con un teletipo real y cinta adhesiva. ¡Estaba fuertemente codificado!
Un ejemplo del uso de NOOP fue en una rutina de servicio de interrupción (ISR), que se espaciaron en intervalos de 8 bytes. Esta rutina terminó teniendo 9 bytes de longitud y terminando con un salto (largo) a una dirección un poco más arriba en el espacio de direcciones. Esto significaba, dado el pequeño orden de bytes endian, que el byte de alta dirección era 00h, y se ubicaba en el primer byte del siguiente ISR, lo que significaba que (el próximo ISR) comenzaba con NOOP, solo para que 'pudiéramos' encajar El código en el espacio limitado!
Entonces el NOOP es útil. Además, sospecho que fue más fácil para Intel codificarlo de esa manera: probablemente tenían una lista de instrucciones que querían implementar y comenzó en '1', como todas las listas (este fue el día de FORTRAN), por lo que el cero El código NOOP se convirtió en una falla. (Nunca he visto un artículo que argumenta que un NOOP es una parte esencial de la teoría de la Ciencia de la Computación (la misma P que: ¿los matemáticos tienen una operación nula, a diferencia del cero de la teoría de grupos?)
fuente
0x00
paranop
en mi respuesta. En resumen, ahorra en la decodificación de instrucciones:xchg ax, ax
fluye naturalmente de la forma en que funciona la decodificación de instrucciones y hace algo "noppy", así que ¿por qué no usar eso y llamarlonop
, verdad? :) Solía ahorrar bastante en el silicio para la decodificación de instrucciones ...En algunas arquitecturas,
NOP
se utiliza para ocupar ranuras de retardo no utilizadas . Por ejemplo, si la instrucción de bifurcación no borra la tubería, varias instrucciones después de que se ejecute de todos modos:Pero, ¿qué pasa si no tiene instrucciones útiles para encajar después del
JMP
? En ese caso tendrás que usarNOP
s.Los espacios de demora no se limitan a los saltos. En algunas arquitecturas, los riesgos de datos en la canalización de la CPU no se resuelven automáticamente. Esto significa que después de cada instrucción que modifica un registro hay una ranura donde el nuevo valor del registro aún no es accesible. Si la siguiente instrucción necesita ese valor, el espacio debe estar ocupado por un
NOP
:Además, algunas instrucciones de ejecución condicional ( If-True-False y similares) usan espacios para cada condición, y cuando una condición en particular no tiene acciones asociadas, su espacio debe estar ocupado por un
NOP
:fuente
Otro ejemplo de uso para un NOP de dos bytes : http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx
fuente