在微处理器8085指令中,有一个机器控制操作“ nop”(无操作)。我的问题是为什么我们需要不操作?我的意思是,如果必须结束程序,我们将使用HLT或RST3。或者,如果我们要移至下一条指令,则将给出下一条指令。但是为什么不操作呢?有什么需要
在微处理器8085指令中,有一个机器控制操作“ nop”(无操作)。我的问题是为什么我们需要不操作?我的意思是,如果必须结束程序,我们将使用HLT或RST3。或者,如果我们要移至下一条指令,则将给出下一条指令。但是为什么不操作呢?有什么需要
Answers:
CPU和MCU中NOP(或NOOP,无操作)指令的一种用法是在代码中插入一些可预测的延迟。尽管NOP不执行任何操作,但是要花费一些时间来处理它们(CPU必须获取并解码操作码,因此需要一些时间来完成)。只需浪费1个CPU周期即可执行一条NOP指令(通常可以从CPU / MCU数据表中推断出确切的数字),因此按顺序排列N个NOP是插入可预测延迟的一种简便方法:
其中K是处理NOP指令所需的周期数(通常为1),而是时钟周期。
为什么要这么做?强制CPU等待一点外部设备(可能速度较慢)以完成其工作并向CPU报告数据可能是有用的,即NOP对于同步目的很有用。
另请参阅NOP上相关的Wikipedia页面。
另一个用途是将代码对准内存中的某些地址和其他“汇编技巧”,如Programmers.SE上的该线程和StackOverflow上的该另一个线程所解释的。
指向Google图书页面的此链接特别是指8085 CPU。摘抄:
每个NOP指令都使用四个时钟来进行获取,解码和执行。
编辑 (以解决评论中表达的问题)
如果您担心速度,请记住(时间)效率只是要考虑的一个参数。这完全取决于应用程序:如果您想计算的十亿分之一的数字,那么也许您唯一关心的就是速度。另一方面,如果要记录通过ADC连接到MCU的温度传感器的数据,速度通常并不那么重要,但是等待适当的时间以使ADC正确完成每个读数至关重要。在这种情况下,如果MCU等待的时间不够长,则有可能获取完全不可靠的数据(我承认,虽然:o 会更快地获取数据)。
其他答案仅考虑实际上在某个时刻执行的NOP-这是很普遍的用法,但这不是唯一的NOP用法。
在编写可以打补丁的代码时,非执行NOP也是非常有用的-基本上,您将在(或类似指令)之后用几个NOP填充该函数RET
。当您必须修补可执行文件时,可以轻松地从原始函数开始添加更多代码,RET
并根据需要使用任意数量的NOP(例如,用于跳远甚至是内联代码),然后再添加另一个RET
。
在这种用例中,noöne永远不会期望NOP
执行。唯一的一点是允许修补可执行文件-在理论上未填充的可执行文件中,您实际上必须更改函数本身的代码(有时它可能符合原始边界,但是无论如何,您经常还是需要跳转)-这要复杂得多,尤其是考虑到手动编写的汇编程序或优化的编译器;您必须尊重跳跃和可能指向某些重要代码的类似构造。总而言之,非常棘手。
当然,在过去,当制作这样小的补丁和在线。今天,您只需要分发重新编译的二进制文件即可。仍然有一些人使用补丁NOP(是否执行,而不总是执行文字NOP
s-例如,Windows MOV EDI, EDI
用于在线补丁-在这种情况下,您可以在系统实际运行时更新系统库,而无需重新启动)。
所以最后一个问题是,为什么要专门针对实际上没有做任何事情的指令?
MOV AX, AX
将执行完全相同的操作,但不会如此清晰地表明意图。NOP
很多。实际的汇编代码已经没有了。应该注意的是,这些不是x86- NOP
。NOP
很容易,使分散的读取变得更容易)和热修补(尽管到目前为止我更喜欢在Visual Studio:P中选择Edit and Continue)方面都更加容易。对于执行NOP,当然还有几点:
MOV EDI, EDI
,还有其他有效的NOP而不是文字NOP
。MOV EDI, EDI
在x86上具有2字节NOP的性能最佳。如果使用两个NOP
s,则将执行两个指令。编辑:
实际上,与@DmitryGrigoryev的讨论使我不得不多考虑这一点,并且我认为这是对该问题/答案的宝贵补充,因此让我添加一些额外的内容:
首先,很明显-为什么会有一条类似指令的指令mov ax, ax
?例如,让我们看一下8086机器代码(比386机器代码还要早)的情况:
0x90
。请注意,这仍然是许多人在集会上写作的时候。因此,即使没有专门的NOP
说明,NOP
关键字(别名/助记符)仍然会很有用,并且会映射到该关键字。MOV
实际上映射到许多不同的操作码,因为这样可以节省时间和空间-例如,mov al, 42
将“立即字节移动到al
寄存器”,可以转换为0xB02A
(0xB0
是操作码,0x2A
是“立即”参数)。因此,这需要两个字节。mov al, al
(因为这基本上是一件愚蠢的事情),因此您必须使用mov al, rmb
(rmb是“寄存器或内存”)重载。实际上需要三个字节。(尽管它可能会使用不太特定的名称mov rb, rmb
,但只需要使用两个字节mov al, al
-参数字节用于指定源寄存器和目标寄存器;现在您知道8086为什么只有8个寄存器了:D)。与相比NOP
,这是一个单字节指令!这节省了内存和时间,因为在8086中读取内存仍然非常昂贵-更不用说从磁带或软盘或其他东西加载该程序了。那xchg ax, ax
从哪里来呢?您只需要查看其他xhcg
指令的操作码即可。你会看到0x86
,0x87
最后,0x91
- 0x97
。因此nop
,它0x90
似乎非常适合xchg ax, ax
(这又不是xchg
“过载”-您必须使用xchg rb, rmb
,两个字节)。实际上,我非常确定这是当时微体系结构的一个很好的副作用-如果我没记错的话,很容易将整个范围映射0x90-0x97
到“ xchg,作用于寄存器ax
和ax
- di
”(操作数是对称的,这给了您完整的范围,包括 nop xchg ax, ax
;请注意,顺序是ax, cx, dx, bx, sp, bp, si, di
- bx
在之后dx
,ax
; 请记住,寄存器名称是助记符,而不是有序名称-累加器,计数器,数据,基址,堆栈指针,基址指针,源索引,目标索引)。其他操作数(例如mov someRegister, immediate
集合)也使用了相同的方法。从某种意义上说,您可以认为操作码实际上不是一个完整的字节-后几位是“真实”操作数的“一个参数”。
在x86上说的所有这些,都nop
可能被认为是真实的指令。xchg
如果我没记错的话,原始的微体系结构确实将其视为变体,但实际上它是nop
在规范中命名的。而且,由于xchg ax, ax
作为指令实际上并没有意义,因此您可以利用0x90
自然映射到完全“噪声” 的事实,来了解8086的设计人员如何在指令解码中节省晶体管和路径。
在另一方面,i8051具有一个完全的设计,在操作码nop
- 0x00
。有点实用。该指令的设计基本上是使用用于操作的高半字节和低半字节用于选择的操作数-例如,add a
是0x2Y
和0xX8
手段“寄存器0直接”,所以0x28
是add a, r0
。在硅上节省很多:)
我仍然可以继续,因为CPU设计(更不用说编译器设计和语言设计)是一个广泛的话题,但是我认为我已经展示了许多可以很好地融入设计的观点。
NOP
通常是一个别名MOV ax, ax
,ADD ax, 0
或类似的说明。您为什么要设计一条专门的指令,当有很多指令时什么也不做。
MOV ax, ax
距离。NOP
将始终具有固定数量的执行周期。但是我仍然看不出这与我在答案中写的内容有何关系。
MOV ax, ax
距离,因为当他们知道这是一条MOV
指令时,它们已经在管道中。
NOP
和可能会相同MOV ax, ax
)。现代的CPU比老式的C编译器要复杂得多:))
LD r, r
条指令,其中r
任何一个寄存器都与您的MOV ax, ax
指令相似。之所以是7而不是8是因为其中一条指令被重载变为HALT
。因此8080和Z80至少还有7条其他指令与NOP
!有趣的是,即使这些指令与位模式在逻辑上不相关,它们都需要4个T状态才能执行,因此没有理由使用这些LD
指令!
早在70年代后期,我们(那时我还是一个年轻的研究生)就有一个小的开发系统(如果有内存,则为8080),它运行1024个字节的代码(即单个UVEPROM)-它只有四个要加载的命令(L ),保存(S),打印(P)以及其他我不记得的内容。它由真正的电传打字机和打孔带驱动。它被严格编码!
使用NOOP的一个示例是在中断服务程序(ISR)中,该程序以8个字节的间隔隔开。该例程最终以9个字节长结束,并以(长)跳转到略微超出地址空间的地址。这意味着,按照小尾数字节顺序,高地址字节为00h,并放入下一个ISR的第一个字节,这意味着它(下一个ISR)以NOOP开头,所以“我们”可以容纳代码在有限的空间内!
因此,NOOP非常有用。另外,我怀疑intel以这种方式进行编码最容易-他们可能有一个他们想要实现的指令列表,并且像所有列表一样,它以“ 1”开头(这是FORTRAN的日子),所以零NOOP代码变成了失败。(我从未见过有文章认为NOOP是计算科学理论的重要组成部分(与Q:数学家是否有nul op区别于零组理论)是相同的问号。)
0x00
对nop
我的回答。简而言之,它节省了指令解码的费用- xchg ax, ax
从指令解码的工作方式自然而然地进行,并且执行了“ noppy”操作,那么为什么不使用它并调用它nop
,对吗?:)过去在芯片上节省了很多时间用于指令解码...
在某些架构上,NOP
用于占用未使用的延迟时隙。例如,如果分支指令没有清除管道,那么无论如何,执行后的几条指令:
JMP .label
MOV R2, 1 ; these instructions start execution before the jump
MOV R2, 2 ; takes place so they still get executed
但是,如果您在之后没有合适的说明JMP
怎么办?在这种情况下,您必须使用NOP
。
延迟时隙不限于跳转。在某些体系结构上,不会自动解决CPU管道中的数据危险。这意味着在每条修改寄存器的指令之后,都有一个插槽,在该插槽中尚无法访问寄存器的新值。如果下一条指令需要该值,则该插槽应被占用NOP
:
ADD R1, R1
NOP ; The new value of R1 is not ready yet
ADD R1, R3
另外,一些条件执行指令(If-True-False和类似的条件)对每个条件使用插槽,并且当特定条件没有与之关联的动作时,其插槽应由占用NOP
:
CMP R0, R1 ; Compare R0 and R1, setting flags
ITF GT ; If-True-False on GT flag
MOV R3, R2 ; If 'greater than', move R2 to R3
NOP ; Else, do nothing
使用两字节 NOP的另一个示例:http : //blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx
MOV EDI,EDI指令是一个两字节的NOP,它刚好足以插入跳转指令中的空间,因此可以动态更新该功能。目的是将MOV EDI,EDI指令替换为两字节的JMP $ -5指令,以将控制重定向到紧接函数启动之前的五个字节的补丁程序空间。完整跳转指令的五个字节就足够了,它可以将控制权发送到地址空间中其他位置安装的替换功能。