題:
為什麼我們需要“小睡”,即微處理器8085中沒有操作指令?
Demietra95
2015-05-31 17:16:07 UTC
view on stackexchange narkive permalink

在微處理器8085指令中,有一個機器控制操作“ nop”(無操作)。我的問題是為什麼我們需要不操作?我的意思是,如果必須結束程序,我們將使用HLT或RST3。或者,如果我們要移至下一條指令,則將給出下一條指令。但是為什麼不操作呢?有什麼需要嗎?

NOP常用於調試和更新程序。如果以後要在程序中添加一些行,則可以簡單地覆蓋NOP。否則,您將不得不插入行,並且插入意味著轉移整個程序。同樣,錯誤的指令(錯誤的)也可以由NOP替換(僅覆蓋)相同的參數。
哦但是使用nop也會增加空間。而我們的主要目標是使其占據很少的位置。
*我的意思是我們的目標是使問題更小。難道這也不成問題嗎?
這就是為什麼應該明智地使用它。否則,您的整個程序將只是一堆NOP。
五 答案:
Lorenzo Donati -- Codidact.com
2015-05-31 17:28:15 UTC
view on stackexchange narkive permalink

在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),它會更快地獲得該數據。

好。邏輯被接受。但是,為什麼程序需要延遲?我的意思是不是相反,對於高效的程序,執行時間應該更快些?是從外設正確或其他獲得輸入所需的延遲嗎?
許多事情(尤其是uC外部的輸出驅動IC)受制於時序約束,例如“ D穩定到時鐘沿的最小時間是100 us”,或“ IR LED必須以1 MHz閃爍”。因此,經常需要(準確的)延遲。
@Lorenzo Donati已經指出:當您想要同步某些內容時,需要delay(NOP)。例如,在[流水線指令執行](http://postimg.org/image/5s9xgcmyr/)中(對於非英語的首字母縮寫詞表示抱歉,但找不到更合適的圖像。)。簡而言之:在這裡,指令3將同時從需要讀取指令6的位置寫入操作數存儲器。
延遲和延遲-有助於每週進行一些調試,我們在一些ARM C代碼中插入了內聯彙編nop以設置斷點。她嘗試了類似x = x的方法;但是編譯器太聰明了,會優化語句。
當對串行協議進行位碰撞時,NOP對於正確計時很有用。如果程序計數器損壞(例如,PSU故障,罕見的伽瑪射線事件影響等)並開始在程序計數器中執行代碼,則在極少數情況下,它們也可以用於填充未使用的代碼空間,然後跳轉到冷啟動矢量。否則,清空部分代碼空間。
非常感謝。檢查答案並理解要點。再次感謝您的幫助。
我知道90年代製造的產品有兩種配置-一種具有比另一種“更快”的處理器。為了使基準測試能顯示出較快系統的優越性,較慢的系統特意插入了一些NOP。這就是所謂的“產品差異化”,它使速度更快的產品看起來更具吸引力(因此價格更高)。
在Atari 2600視頻計算機系統(運行存儲在盒帶上的程序的第二個視頻遊戲控制台)上,處理器將在每條掃描行上精確運行76個週期,並且在開始掃描後需要執行一些確切的周期數掃描線。在該處理器上,記錄的“ NOP”指令需要兩個週期,但是代碼通常會利用本來沒有用的三週期指令來將延遲延遲到精確的周期數。更快地運行代碼將產生完全亂碼的顯示。
當然,使用NOP進行延遲僅在實時系統上才有意義。在復雜的搶占式OS上,依賴任何指令的執行時間是一個壞主意。但是即使那樣,運行Windows的現代x86 CPU仍然具有NOP-實際上,它們被用於不確定性的延遲(例如,用於旋轉等待的“ rep; nop”)。這些通常是與CPU工程師協調設計的,以確保性能一流(例如,在一個HT內核上進行一次旋轉等待可以使另一個HT內核運行幾乎100%)。
在I / O設備在連續操作之間施加最小時間而不是最大時間的情況下,即使在非實時系統上,也可以使用NOP進行延遲。例如,在許多控制器上,將一個字節移出SPI端口將需要八個CPU週期。除了從內存中獲取字節並將其輸出到SPI端口外,什麼也不做的代碼可能會運行得有點快,但是添加邏輯以測試SPI端口是否已為每個字節準備好了,將使它不必要地變慢。添加一兩個NOP可能會使代碼達到最大可用速度...
...在沒有中斷的情況下。如果遇到中斷,則NOP會不必要地浪費時間,但是被一兩個NOP浪費的時間將少於確定中斷是否使它們不必要所需要的時間。
Luaan
2015-06-01 14:42:09 UTC
view on stackexchange narkive permalink

其他答案僅考慮實際上在某個時候執行的NOP-這已經很普遍了,但這不是唯一的NOP用法。

不執行的NOP在編寫時也非常有用可以修補的代碼-基本上,您將在 RET (或類似說明)之後的 之後加上一些NOP。當您必須修補可執行文件時,可以從原始的 RET 開始輕鬆地向函數中添加更多代碼,並根據需要使用任意數量的NOP(例如,用於跳遠甚至是內聯代碼),並且用另一個 RET 完成。

在此用例中,noöne從未希望執行 NOP 。唯一的一點是允許修補可執行文件-在理論上未填充的可執行文件中,您實際上必須更改函數本身的代碼(有時它可能符合原始邊界,但是無論如何,您經常還是需要跳轉)-這要復雜得多,尤其是考慮到手動編寫的彙編程序或優化的編譯器;您必須尊重可能指向某些重要代碼段的跳轉和類似結構。總而言之,這很棘手。

當然,在過去,當製作諸如 small 之類的補丁很有用時,這種方法的使用量大大增加在線。今天,您只需要分發重新編譯的二進製文件即可。仍然有一些人使用補丁NOP(是否執行補丁,並且不總是按字面意義 NOP s-例如,Windows使用 MOV EDI,EDI 進行在線補丁補丁-在這種情況下您可以在系統實際運行時更新系統庫,而無需重新啟動。)

所以最後一個問題是,為什麼要為不執行任何操作的專用指令?

  • 這是一條實際的指令-在調試或手工編碼彙編時很重要。諸如 MOV AX,AX 之類的指令將執行完全相同的操作,但是並沒有非常清楚地表明其意圖。
  • 填充-此處的“代碼”只是為了提高代碼的整體性能這取決於對齊方式。它決不是要執行的。一些調試器只是在其反彙編中隱藏了填充NOP。
  • 它為優化編譯器提供了更多空間-仍然使用的模式是您有兩個編譯步驟,第一個很簡單,並且會產生很多不需要的彙編代碼,而第二個則清理了,重新連接了地址引用並刪除了多餘的指令。在JIT編譯語言中也經常看到這種情況-.NET的IL和JVM的字節碼都使用 NOP 相當多。實際的彙編代碼已經沒有了。應該注意的是,這些不是x86- NOP
  • 它使在線調試更容易讀取(預先置零的內存將全部為- NOP s,使它更易於閱讀)和熱修補(儘管到目前為止,我還是更喜歡在Visual Studio中使用Edit and Continue:P)。

用於執行NOP ,當然還有幾點:

  • 性能,當然-這不是8085的原因,但即使80486也已經具有流水線的指令執行,這使得“什麼也不做”有點棘手。
  • MOV EDI,EDI 可以看出,除了字面的 NOP 之外,還有其他有效的NOP。 MOV EDI,EDI 在x86上具有2字節NOP的性能最佳。如果您使用了兩個 NOP ,則將執行兩條指令。

編輯:

實際上,與@DmitryGrigoryev的討論使我不得不多考慮這一點,並且我認為這是對該問題/答案的寶貴補充,所以讓我添加一些額外的內容:

首先,很明顯-為什麼會有一條指令執行類似 mov ax,ax 的操作?例如,讓我們看一下8086機器代碼(甚至比386機器代碼還早)的情況:

  • 有一個專用的NOP指令,其操作碼為 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設計(更不用說編譯器設計和語言設計)是一個廣泛的話題,但是我認為我已經展示了許多不同的觀點,照原樣很好地進入了設計。

實際上,“ NOP”通常是“ MOV斧頭,斧頭”,“ ADD斧頭,0”或類似指令的別名。您為什麼要設計一條專門的指令,當那裡有很多指令時什麼也不做。
@DmitryGrigoryev這實際上涉及到CPU語言(很好,微體系結構)本身的設計。大多數CPU(和編譯器)會趨向於優化“ MOV ax,ax”。“ NOP”將始終具有固定數量的執行週期。但是我仍然看不出這與我在答案中寫的內容有什麼關係。
CPU並不能真正優化MOV斧頭,因為當他們知道它是MOV頭時,指令已經在流水線中。
@DmitryGrigoryev確實取決於您所談論的CPU類型。現代台式機CPU做很多事情,而不僅僅是指令流水線。例如,CPU知道它不必使高速緩存行無效,它知道它實際上不必執行任何操作(對於HyperThreading甚至對於一般涉及的多個管道而言,這都非常重要)。如果它也影響分支預測,我不會感到驚訝(儘管對於“ NOP”和“ MOV ax,ax”來說可能是相同的)。現代的CPU比老式的C編譯器要復雜得多:))
如果您正在考慮使用x86 CPU,那麼“ NOP”指令實際上就是“ xchg ax,ax”,並且不會得到“優化”。
@DmitryGrigoryev不是全部*。NOP可以映射為1字節的“ 0x90”(在32位CPU上為“ xchg eax,eax”;實際上,這很複雜)或2字節的“ 0x6690”(`xchg ax,ax)。它具有更多的可移植性(隨著程序集可移植性的發展:)。但這並沒有真正說明CPU本身,而只是說明編譯器。當然,HT優化適用於`rep;nop`指令,絕對不是* xchg ax,ax`。無論如何,我們在談論的是“專用”指令,而不是x86指令。但是我仍然不明白你的意思。您不同意我的回答的哪一點?
讓我們[繼續聊天中的討論](http://chat.stackexchange.com/rooms/24348/discussion-between-dmitry-grigoryev-and-luaan)。
+1可優化從無到無!我認為我們應該合作並回到這種拼寫方式!Z80(以及8080)具有7條LD r,r指令,其中r是任何單個寄存器,類似於您的MOV ax,ax指令。之所以是7而不是8是因為其中一條指令被重載變成了“ HALT”。因此8080和Z80至少還有7條與“ NOP”相同的指令!有趣的是,即使這些指令與位模式在邏輯上不相關,它們都需要4個T狀態來執行,因此沒有理由使用`LD`指令!
轉換為0xB02A的命令是錯誤的:實際上它轉換為B0 2A,即0x2AB0,因為x86是低位字節序。
Philip Oakley
2015-06-01 01:52:36 UTC
view on stackexchange narkive permalink

早在70年代末,我們(那時我還是一個年輕的研究生)就有一個小的開發系統(如果有內存,則為8080),它運行1024個字節的代碼(即單個UVEPROM)-它只有四個命令可以加載(L),保存(S),打印(P)以及其他我不記得的內容。它由真正的電傳打字機和打孔帶驅動。

使用NOOP的一個例子是在中斷服務程序(ISR)中,該程序以8個字節的間隔隔開。該例程最終以9個字節長結束,並以(長)跳轉到略微超出地址空間的地址。這意味著,按照小尾數字節順序,高地址字節為00h,並放入下一個ISR的第一個字節,這意味著它(下一個ISR)以NOOP開頭,所以“我們”可以容納代碼在有限的空間內!

因此,NOOP非常有用。另外,我懷疑intel最容易以這種方式進行編碼-他們可能有一個他們想要實現的指令列表,並且像所有列表一樣,它以“ 1”開頭(這是FORTRAN的日子),所以零NOOP代碼變成了失敗。 (我從未見過有文章認為NOOP是計算科學理論的重要組成部分(與Q:數學家是否有nul op區別於零組理論)是相同的問題。

並非所有的CPU都將NOP編碼為0x00(儘管8080、8080和我最熟悉的CPU Z80都可以)。但是,如果我正在設計處理器,那將是我的想法!方便的是,通常將內存初始化為全0x00,因此將其作為代碼執行將不會執行任何操作,直到CPU到達未歸零的內存為止。
-1
Dmitry Grigoryev
2015-06-01 16:28:48 UTC
view on stackexchange narkive permalink

在某些體系結構上, 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;否則,什麼也不做 
+1。當然,那些僅傾向於出現在不關心向後兼容性的體系結構上-如果x86在引入指令流水線時嘗試了類似的操作,幾乎每個人都會簡單地將其稱為錯誤(畢竟,他們只是升級了CPU,應用程序停止工作!)。所以x86 *必須*確保應用程序不會注意到何時將類似這樣的改進添加到CPU上-直到我們仍然使用多核CPU為止...:D
pjc50
2015-06-01 13:44:06 UTC
view on stackexchange narkive permalink

使用另一個兩字節 NOP的示例: http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

MOV EDI,EDI指令是一個兩字節的NOP,它的空間足以修補跳轉指令,因此可以動態更新該功能。目的是將MOV EDI,EDI指令替換為2字節的JMP $ -5指令,以將控制重定向到緊接函數啟動之前的5個字節的補丁程序空間。完整跳轉指令的五個字節就足夠了,它可以將控制權發送到地址空間中其他位置安裝的替換功能。



該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 3.0許可。
Loading...