在微處理器8085指令中,有一個機器控制操作“ nop”(無操作)。我的問題是為什麼我們需要不操作?我的意思是,如果必須結束程序,我們將使用HLT或RST3。或者,如果我們要移至下一條指令,則將給出下一條指令。但是為什麼不操作呢?有什麼需要嗎?
在微處理器8085指令中,有一個機器控制操作“ nop”(無操作)。我的問題是為什麼我們需要不操作?我的意思是,如果必須結束程序,我們將使用HLT或RST3。或者,如果我們要移至下一條指令,則將給出下一條指令。但是為什麼不操作呢?有什麼需要嗎?
在CPU和MCU中使用NOP(或NOOP,無操作)指令的一種方法是在代碼中插入一些可預測的延遲。儘管NOP不執行任何操作,但是要花費一些時間來處理它們(CPU必須獲取和解碼操作碼,所以這樣做需要一些時間)。只需花費1個CPU週期即可執行一條NOP指令(通常可以從CPU / MCU數據表中推斷出確切的數字),因此按順序排列N個NOP是插入可預測延遲的一種簡便方法:
\ $ t_ {delay} = N \ cdot T_ {clock} \ cdot K \ $
其中,K是處理NOP所需的周期數(通常為1)指令,並且\ $ T_ {clock} \ $是時鐘週期。
為什麼要這麼做?強制CPU稍等片刻以等待外部(可能較慢)的設備完成其工作並向CPU報告數據,即NOP對於同步目的很有用。
另請參見相關的 NOP上的Wikipedia頁面。
另一種用法是將代碼對準內存中的某些地址和其他“彙編技巧”,這也可以在 Programmers.SE上的該線程中進行解釋和 StackOverflow上的該另一個線程中。
關於該主題的另一篇有趣的文章。
該鏈接到Google圖書頁面,尤其是指8085 CPU。摘錄:
每個NOP指令都使用四個時鐘來進行獲取,解碼和執行。
EDIT (以解決表達的關注點在評論中)
如果您擔心速度,請記住,(時間)效率只是要考慮的一個參數。一切都取決於應用程序:如果要計算\ $ \ pi \ $的十億位數,那麼您可能唯一關心的就是速度。另一方面,如果要記錄通過ADC連接到MCU的溫度傳感器的數據,速度通常並不那麼重要,但是要等待適當的時間以使ADC正確完成每個讀數 必不可少 。在這種情況下,如果MCU等待的時間不夠長,則有可能會獲得完全不可靠的數據(我承認,雖然:o),它會更快地獲得該數據。
其他答案僅考慮實際上在某個時候執行的NOP-這已經很普遍了,但這不是唯一的NOP用法。
不執行的NOP在編寫時也非常有用可以修補的代碼-基本上,您將在 RET
(或類似說明)之後的 之後加上一些NOP。當您必須修補可執行文件時,可以從原始的 RET
開始輕鬆地向函數中添加更多代碼,並根據需要使用任意數量的NOP(例如,用於跳遠甚至是內聯代碼),並且用另一個 RET
完成。
在此用例中,noöne從未希望執行 NOP
。唯一的一點是允許修補可執行文件-在理論上未填充的可執行文件中,您實際上必須更改函數本身的代碼(有時它可能符合原始邊界,但是無論如何,您經常還是需要跳轉)-這要復雜得多,尤其是考慮到手動編寫的彙編程序或優化的編譯器;您必須尊重可能指向某些重要代碼段的跳轉和類似結構。總而言之,這很棘手。
當然,在過去,當製作諸如 small 和之類的補丁很有用時,這種方法的使用量大大增加在線。今天,您只需要分發重新編譯的二進製文件即可。仍然有一些人使用補丁NOP(是否執行補丁,並且不總是按字面意義 NOP
s-例如,Windows使用 MOV EDI,EDI
進行在線補丁補丁-在這種情況下您可以在系統實際運行時更新系統庫,而無需重新啟動。)
所以最後一個問題是,為什麼要為不執行任何操作的專用指令?
MOV AX,AX
之類的指令將執行完全相同的操作,但是並沒有非常清楚地表明其意圖。 NOP
相當多。實際的彙編代碼已經沒有了。應該注意的是,這些不是x86- NOP
。 NOP
s,使它更易於閱讀)和熱修補(儘管到目前為止,我還是更喜歡在Visual Studio中使用Edit and Continue:P)。用於執行NOP ,當然還有幾點:
MOV EDI,EDI
可以看出,除了字面的 NOP
之外,還有其他有效的NOP。 MOV EDI,EDI
在x86上具有2字節NOP的性能最佳。如果您使用了兩個 NOP
,則將執行兩條指令。編輯:
實際上,與@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斧頭,斧頭
是哪裡來的?您只需要查看其他 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,Instant
。從某種意義上講,您可以認為操作碼實際上不是一個完整的字節-後幾位是“實際”操作數的“自變量”。
在x86上所有這些都表示, nop
可能是真實的指令,也可能不是。如果我沒記錯的話,原始的微體系結構確實將其視為 xchg
的變體,但在規範中實際上被命名為 nop
。而且,由於 xchg ax,ax
並不是真正意義上的指令,因此您可以利用 0x90 自然地映射到完全“不穩定”的東西。
另一方面,i8051具有完全為 nop
- 0x00
設計的操作碼。有點實用。指令設計基本上使用高半字節進行操作,而低半字節選擇操作數-例如, add a
是 0x2Y
和 0xX8
表示“直接註冊0”,因此 0x28
是添加a,r0
。在芯片上節省了很多:)
我仍然可以繼續,因為CPU設計(更不用說編譯器設計和語言設計)是一個廣泛的話題,但是我認為我已經展示了許多不同的觀點,照原樣很好地進入了設計。
早在70年代末,我們(那時我還是一個年輕的研究生)就有一個小的開發系統(如果有內存,則為8080),它運行1024個字節的代碼(即單個UVEPROM)-它只有四個命令可以加載(L),保存(S),打印(P)以及其他我不記得的內容。它由真正的電傳打字機和打孔帶驅動。
使用NOOP的一個例子是在中斷服務程序(ISR)中,該程序以8個字節的間隔隔開。該例程最終以9個字節長結束,並以(長)跳轉到略微超出地址空間的地址。這意味著,按照小尾數字節順序,高地址字節為00h,並放入下一個ISR的第一個字節,這意味著它(下一個ISR)以NOOP開頭,所以“我們”可以容納代碼在有限的空間內!
因此,NOOP非常有用。另外,我懷疑intel最容易以這種方式進行編碼-他們可能有一個他們想要實現的指令列表,並且像所有列表一樣,它以“ 1”開頭(這是FORTRAN的日子),所以零NOOP代碼變成了失敗。 (我從未見過有文章認為NOOP是計算科學理論的重要組成部分(與Q:數學家是否有nul op區別於零組理論)是相同的問題。
在某些體系結構上, NOP
用於佔用未使用的延遲插槽。例如,如果分支指令沒有清除流水線,則無論如何都要執行多條指令:
JMP .label MOV R2,1;這些指令在跳轉MOV R2,2之前開始執行;發生,這樣它們仍然可以執行
但是,如果在 JMP
之後沒有任何有用的說明適合怎麼辦?在這種情況下,您將必須使用 NOP
s。
延遲插槽不限於跳轉。在某些體系結構上,不會自動解決CPU管道中的數據危害。這意味著在每條修改寄存器的指令之後,都有一個插槽,在該插槽中尚無法訪問寄存器的新值。如果下一條指令需要該值,則該插槽應由 NOP
佔用:
ADD R1,R1 NOP; R1的新值尚未準備好,但要添加R1,R3
。此外,一些條件執行指令( If-True-False 等)對每個條件,並且當特定條件沒有與之關聯的操作時,其插槽應由 NOP
:
CMP R0,R1佔用;比較R0和R1,設置標誌ITF GT; GT標誌MOV R3,R2上的If-True-False;如果“大於”,將R2移至R3NOP;否則,什麼也不做
使用另一個兩字節 NOP的示例: http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx
MOV EDI,EDI指令是一個兩字節的NOP,它的空間足以修補跳轉指令,因此可以動態更新該功能。目的是將MOV EDI,EDI指令替換為2字節的JMP $ -5指令,以將控制重定向到緊接函數啟動之前的5個字節的補丁程序空間。完整跳轉指令的五個字節就足夠了,它可以將控制權發送到地址空間中其他位置安裝的替換功能。