Навіщо нам потрібна “nop”, тобто немає інструкції з експлуатації в мікропроцесорі 8085?


19

В інструкції з мікропроцесора 8085 є операція управління машиною "nop" (без роботи). Моє запитання: для чого нам потрібна операція без роботи? Я маю на увазі, якщо нам доведеться закінчити програму, ми будемо використовувати HLT або RST 3. Або якщо ми хочемо перейти до наступної інструкції, ми дамо наступні вказівки. Але чому немає операції? У чому потреба?


5
NOP часто використовується для налагодження та оновлення вашої програми. Якщо пізніше ви хочете додати кілька рядків у свою програму, ви можете просто перезаписати NOP. Інакше вам доведеться вставляти рядки, а вставка означає зміщення всієї програми. Також несправні вказівки (неправильні) можуть бути замінені (просто перезаписані) NOP за такими ж аргументами.
контрабандист плутонію

Добре. Але використання nop також збільшує простір. Тоді як наша основна мета - зробити так, щоб воно мало займало місце.
Demietra95

* Я маю на увазі, наша мета - зменшити проблему. Так чи не стає це також проблемою?
Demietra95

2
Ось чому це слід використовувати розумно. Інакше вся ваша програма буде просто купою нопонів.
контрабандист Плутонію

Відповіді:


34

Одне використання інструкцій NOP (або NOOP, без роботи) в процесорах та MCU полягає в тому, щоб вставити у свій код невелику, передбачувану затримку. Хоча NOP не виконують жодної операції, їх обробка потребує певного часу (центральний процесор повинен отримати та розшифрувати код коду, тому для цього потрібно трохи часу). Лише 1 цикл процесора "витрачається" на виконання інструкції NOP (точне число може бути виведено з таблиці даних CPU / MCU), тому введення N NOP в послідовність - це простий спосіб вставити передбачувану затримку:

тгелау=NТcлоcкК

де K - кількість циклів (найчастіше 1), необхідних для обробки інструкції NOP, а - тактовий період.Тcлоcк

Навіщо ти це робив? Може бути корисним змусити процесор трохи зачекати, щоб зовнішні (можливо повільніші) пристрої завершили свою роботу та повідомили дані в процесор, тобто NOP корисний для цілей синхронізації.

Дивіться також пов’язану сторінку Вікіпедії на NOP .

Іншим використанням є вирівнювання коду за певними адресами в пам'яті та інші "трюки складання", як це пояснено також у цій темі на Programmers.SE та в цій іншій потоці на StackOverflow .

Ще одна цікава стаття на цю тему .

Це посилання на сторінку книги Google особливо стосується 8085 ЦП. Витяг:

Кожна інструкція NOP використовує чотири годинники для отримання, декодування та виконання.

EDIT (для вирішення проблеми, висловленої в коментарі)

Якщо ви турбуєтесь про швидкість, майте на увазі, що ефективність (час) - це лише один параметр. Все залежить від програми: якщо ви хочете обчислити 10-мільярдну цифру , то, можливо, вашою єдиною турботою може стати швидкість. З іншого боку, якщо ви хочете реєструвати дані з датчиків температури, підключених до MCU через АЦП, швидкість зазвичай не настільки важлива, але очікування потрібної кількості часу, щоб дозволити АЦП правильно виконати кожне зчитування , важливо . У цьому випадку, якщо MCU не чекає достатньо, це ризикує отримати цілком недостовірні дані (я визнаю, вони отримають ці дані швидше , хоча: o).π


3
Для багатьох речей (особливо виходів, що керують ІС, що перебувають поза межами ЦК), обмежуються тимчасові обмеження, як "мінімальний час, коли D стабільний, а межа годинника - 100 нас", або "ІЧ-світлодіод повинен блимати на 1 МГц". Тому часто потрібні (точні) затримки.
Wouter van Ooijen

6
NOP можуть бути корисними для правильного встановлення часу під час бітового удару послідовного протоколу. Вони також можуть бути корисними для заповнення невикористаного кодового простору з подальшим переходом до вектора холодного запуску для рідкісних ситуацій, якщо лічильник програм пошкоджується (наприклад, глюк PSU, вплив рідкісної події гамма-променів тощо) та починає виконувати код у в іншому випадку порожня частина кодового простору.
Технідуд

6
На відеокомп'ютерній системі Atari 2600 (другої консолі відеоігор для запуску програм, що зберігаються на картриджах), процесор виконуватиме рівно 76 циклів у кожній лінії сканування, і для багатьох операцій потрібно буде виконати деяку кількість циклів після початку роботи лінія сканування. На цьому процесорі задокументована інструкція "NOP" займає два цикли, але також звичайно для коду використовувати інакше непридатну інструкцію три циклу, щоб зменшити затримку до точної кількості циклів. Більше швидке виконання коду призвело б до абсолютно поважного дисплея.
supercat

1
Використання NOP для затримок може мати сенс навіть у системах не в реальному часі у випадках, коли пристрій вводу / виводу призначає мінімальний час між послідовними операціями, але не максимум. Наприклад, на багатьох контролерах виведення байта на порт SPI займе вісім циклів процесора. Код, який не має нічого, крім отримання байтів із пам'яті та виведення їх на порт SPI, може працювати трохи надто швидко, але додавання логіки для перевірки готовності SPI-порту для кожного байту зробить його непотрібним. Додавання NOP або двох може дозволити коду досягти максимальної доступної швидкості ...
supercat

1
... у випадку, коли немає перерв. Якщо перерва потрапляє, НОП марно витрачає час, але витрачений час одним або двома НОП буде менше часу, необхідного для визначення необхідності переривання.
суперкар

8

Інші відповіді розглядають лише NOP, який фактично виконується в якийсь момент - це використовується досить часто, але це не єдине використання NOP.

Невиконання NOP також дуже корисно при написанні коду , який може бути виправлений - в основному, ви будете подушечка функція з кілька НОПОМ після на RET(або аналогічної інструкції). Коли вам доведеться виправити виконувану програму, ви можете легко додати більше коду до функції, починаючи з оригіналу RETта використовуючи стільки тих NOP, скільки вам потрібно (наприклад, для стрибків у довгі або навіть вбудований код) і закінчуючи іншим RET.

У цьому випадку використання ніколи не очікує NOPвиконання. Єдиний момент - дозволити виправлення виконуваного файлу - у теоретичному непідставному виконуваному файлі вам доведеться фактично змінити код самої функції (іноді це може відповідати початковим межам, але досить часто вам знадобиться стрибок у будь-якому випадку ) - це набагато складніше, особливо враховуючи складену вручну збірку або оптимізуючий компілятор; ви повинні поважати стрибки та подібні конструкції, які, можливо, вказували на якийсь важливий фрагмент коду. Загалом, досить хитро.

Звичайно, це було набагато більше застосовувалося в старі часи, коли корисно було робити патчі на зразок цих маленьких і Інтернеті . Сьогодні ви просто розповсюдите перекомпільований двійковий файл і будете робити з ним. Є ще деякі, хто використовує патчі NOP-файлів (виконуючи чи ні, і не завжди буквений NOPs - наприклад, Windows використовує MOV EDI, EDIдля інтернет-патчі - саме такий спосіб можна оновити системну бібліотеку, поки система насправді працює, не потребуючи перезапуску).

Отже, останнє запитання: чому мати спеціальну інструкцію для чогось, що насправді нічого не робить?

  • Це фактична інструкція - важлива під час налагодження або складання ручного кодування. Інструкції, якMOV AX, AX будуть робити точно так само, але не сигналізують про наміри настільки чітко.
  • Padding - "код", який існує тільки для покращення загальної продуктивності коду, що залежить від вирівнювання. Це ніколи не призначено стратити. Деякі налагоджувачі просто приховують прокладки NOP під час їх розбирання.
  • Це дає більше місця для оптимізації компіляторів - все ще застосовується схема полягає в тому, що у вас є два етапи компіляції, перший досить простий і створює безліч непотрібних кодів збірки, а другий очищає, перенаправляє посилання на адреси та видаляє сторонні інструкції. Це часто спостерігається і в мовах, що складаються з JIT, - і IL, і байт-код JVM використовують NOPдосить багато; фактично складений код складання більше не має цих. Слід зазначити, що це не x86- NOPі.
  • Це робить налагодження в Інтернеті простішим як для читання (попередньо нульова пам'ять буде загальною NOP, що робить розбирання набагато простішим для читання), так і для гарячого виправлення (хоча я на сьогоднішній день віддаю перевагу "Редагувати та продовжувати" у Visual Studio: P).

Для виконання НОП, звичайно, є ще кілька пунктів:

  • Продуктивність, звичайно - це не тому, що це було в 8085 році, але навіть 80486 вже мав конвеєрне виконання інструкцій, що робить "нічого не робити" трохи складніше.
  • Як бачимо з MOV EDI, EDI, існують інші ефективні НОП, крім буквальних NOP. MOV EDI, EDIмає найкращу продуктивність як 2-байтний NOP на x86. Якщо ви використовували два NOPs, це були б дві інструкції для виконання.

Редагувати:

Власне, обговорення з @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 ax, ax? Ви просто повинні подивитися на коди інших xhcgінструкцій. Ви побачите 0x86, 0x87і, нарешті, 0x91- 0x97. Так nopщо, 0x90здається, це дуже добре підходить xchg ax, ax(що, знову ж таки, не є xchg«перевантаженням» - вам доведеться використовувати xchg rb, rmb, у два байти). Насправді я впевнений, що це була приємна побічна дія мікро-архітектури того часу - якщо я пригадую правильно, було легко відобразити весь діапазон 0x90-0x97на "xchg, діючи над регістрами axта ax- di" ( операнд симетричний, це дало вам весь діапазон, включаючи ноп xchg ax, ax; зауважте, що порядок ax, cx, dx, bx, sp, bp, si, di- bxце після dx,ax; пам'ятайте, імена регістрів - це мнемоніка, а не упорядковані імена - акумулятор, лічильник, дані, база, покажчик стека, базовий покажчик, індекс джерела, індекс призначення). Такий же підхід застосовувався і для інших операндів, наприклад mov someRegister, immediateнабору. Певним чином, ви могли подумати про це так, як ніби опкод насправді не був повноцінним байтом - останні кілька біт є "аргументом" "справжньому" операнду.

Все це, сказане на x86, nopможе вважатися справжньою інструкцією, чи ні. Оригінальна мікро-архітектура вважала це варіантом, xchgякщо я пам'ятаю правильно, але вона була фактично названа nopв специфікації. А оскільки xchg ax, axнасправді це не має сенсу як інструкція, ви можете побачити, як дизайнери 8086 економили на транзисторах і шляхах при розшифровці інструкцій, використовуючи той факт, що 0x90природно відображає щось, що повністю "безглуздо".

З іншого боку, i8051 має повністю розроблений опкод для nop- 0x00. Якийсь практичний. Конструкція інструкцій в основному використовує високий нібл для роботи і низький нибл для вибору операндів - наприклад, add aє 0x2Yі 0xX8означає "зареєструвати 0 прямих", так 0x28є add a, r0. Економлять багато на кремнію :)

Я все ще можу продовжувати, оскільки дизайн процесора (не кажучи вже про дизайн компілятора та мовний дизайн) - досить широка тема, але я думаю, що я показав багато різних точок зору, які досить добре вписалися в дизайн.


На насправді, NOPяк правило , це псевдонім MOV ax, ax, ADD ax, 0або аналогічні інструкції. Чому б ви створили спеціальну інструкцію, яка нічого не робить, коли там багато.
Дмитро Григор’єв

@DmitryGrigoryev Це дійсно входить в розробку самої мови процесора (ну, мікро-архітектури). Більшість процесорів (і компіляторів) прагнуть оптимізувати їх MOV ax, ax; NOPзавжди матиме фіксовану кількість циклів для виконання. Але я не бачу, наскільки це стосується того, що я написав у своїй відповіді.
Луань

Процесори реально не можуть оптимізувати роботу MOV ax, ax, оскільки до того часу, коли вони знають, це MOVінструкція вже в стадії розробки .
Дмитро Григор’єв

@DmitryGrigoryev Це дійсно залежить від типу процесора, про який ви говорите. Сучасні настільні процесори роблять багато чого, а не просто інструкцію з конвеєрного набору. Наприклад, процесор знає, що не потрібно виводити з ладу кеш-рядки тощо. Він знає, що насправді нічого не потрібно робити (дуже важливо для HyperThreading і навіть для багатьох труб, що беруть участь загалом). Я б не здивувався, якби це також вплинуло на прогнозування галузей (хоча це, мабуть, те саме NOPі для MOV ax, ax). Сучасні процесори набагато складніші за компілятори oldschool C :))
Luaan

1
+1 для оптимізації нікого до noöne! Я думаю, що ми повинні співпрацювати і повернутися до цього стилю написання! Z80 (а в свою чергу 8080) має 7 LD r, rінструкцій, де rє будь-який єдиний реєстр, подібний до вашої MOV ax, axінструкції. Причина, що це 7, а не 8, полягає в тому, що одна з інструкцій перевантажена HALT. Тож у 8080 та Z80 є щонайменше 7 інших інструкцій, які виконують те саме, що NOP! Цікаво, що навіть якщо ці вказівки не є логічно пов'язаними з бітовою схемою, всі вони потребують 4 T станів для виконання, тому немає ніяких причин використовувати LDінструкції!
CJ Dennis

5

Ще в кінці 70-х у нас (я тоді був молодим студентом-дослідником) була невелика система розробок (8080, якщо пам'ять слугує), яка працювала в 1024 байтах коду (тобто один УВЕПРОМ) - вона мала лише чотири команди для завантаження (L ), збереження (S), друк (P) та щось інше, що я не можу згадати. Її везуть із справжнім телетайпом та перфомантом. Це було щільно закодовано!

Одним із прикладів використання NOOP був режим обслуговування переривань (ISR), який був розміщений у 8 байтових інтервалах. Цей звичайний результат закінчився 9 байтами, що закінчується (довгим) стрибком на адресу трохи далі в адресному просторі. Це означало, зважаючи на маленький порядок ендіанських байтів, що байт високої адреси був 00h, і прорізався в перший байт наступного ISR, що означало, що він (наступний ISR) починався з NOOP, просто так, щоб ми могли відповідати код у обмеженому просторі!

Тож NOOP корисний. Плюс, я підозрюю, що Intel було найпростіше кодувати це таким чином - у них, мабуть, був список інструкцій, які вони хотіли реалізувати, і він почався на "1", як це роблять усі списки (це були дні FORTRAN), тому нуль Код NOOP став випаданням. (Я ніколи не бачив статті, яка стверджує, що NOOP є важливою частиною теорії обчислювальної науки (те саме, що і Q: чи мають математики нульову функцію, відмінну від нуля теорії груп?)


Не всі процесори кодують NOP до 0x00 (хоча 8085, 8080 та процесор, з якими я найбільше знайомий, Z80, все це робиться). Однак, якби я розробляв процесор, саме там я би поставив це! Що ще корисно - це те, що пам'ять зазвичай ініціалізується на всі 0x00, тому виконання цього коду нічого не буде робити, поки процесор не досягне нульової пам'яті.
CJ Dennis

@CJDennis Я пояснив , чому x86 процесори не використовують 0x00дляnop в моїй обороні. Коротше кажучи, це економить на розшифровці інструкцій - xchg ax, axприродним чином витікає з того, як працює розшифровка інструкції, і вона робить щось "безглузде", так чому б не використати це і назвати це nop, правда? :) Використовується для заощадження на кремнії для розшифровки інструкцій ...
Луань

5

У деяких архітектурах NOPвикористовується для зайняття невикористаних слотів затримки . Наприклад, якщо інструкція з гілки не очищає конвеєр, кілька інструкцій після його виконання все одно:

 JMP     .label
 MOV     R2, 1    ; these instructions start execution before the jump
 MOV     R2, 2    ; takes place so they still get executed

Але що робити, якщо у вас немає ніяких корисних інструкцій, які б відповідати після JMP? У такому випадку вам доведеться використовувати NOPs.

Затримки слотів не обмежуються стрибками. У деяких архітектурах небезпеки даних у конвеєрі процесора не вирішуються автоматично. Це означає, що після кожної інструкції, що модифікує регістр, є слот, де нове значення регістра ще не доступне. Якщо наступна інструкція потребує цього значення, слот повинен бути зайнятий символом 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

+1. Звичайно, вони схильні з'являтись лише в архітектурах, яким не байдужа зворотна сумісність - якщо x86 спробував щось подібне при впровадженні конвеєрних інструкцій, майже кожен просто назвав би це неправильним (адже вони просто оновили свій процесор і програми перестали працювати!). Тож x86 має переконатися, що програма не помічає, коли такі покращення додаються до процесора - поки ми все одно не отримаємо багатоядерні процесори ...: D
Luaan

2

Ще один приклад використання для двобайтового NOP: http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

Інструкція MOV EDI, EDI - це двобайтний NOP, що достатньо місця для виправлення в інструкції зі стрибку, щоб функцію можна було оновити на льоту. Намір полягає в тому, що інструкція MOV EDI, EDI буде замінена на двобайтову інструкцію JMP $ -5 для перенаправлення контролю на п'ять байтів простору патча, що надходить безпосередньо перед початком функції. П'яти байтів достатньо для повного розпорядження про стрибок, який може надіслати управління функцією заміни, встановленої десь в адресному просторі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.