Зачем нам нужно «нет», т. Е. Нет инструкции по эксплуатации в микропроцессоре 8085?

19

В инструкции микропроцессора 8085 есть операция управления машиной "nop" (без операции). Мой вопрос: зачем нам операция? Я имею в виду, что если нам нужно завершить программу, мы будем использовать HLT или RST 3. Или, если мы хотим перейти к следующей инструкции, мы дадим следующие инструкции. Но почему нет операции? Какая в этом необходимость?

Demietra95
источник
5
NOP часто используется при отладке и обновлении вашей программы. Если позднее вы захотите добавить несколько строк в вашу программу, вы можете просто перезаписать NOP. В противном случае вам придется вставить строки, а вставка означает сдвиг всей программы. Также ошибочные инструкции (неправильные) могут быть заменены (просто перезаписаны) на NOP, следуя тем же аргументам.
Контрабандист плутония
Ohkay. Но использование NOP также увеличивает пространство. Принимая во внимание, что наша главная цель состоит в том, чтобы заставить это занять мало места.
Demietra95
* Я имею в виду, что наша цель - уменьшить проблему. Так не станет ли это проблемой?
Demietra95
2
Вот почему это следует использовать с умом. Иначе вся ваша программа будет просто кучкой NOP.
Контрабандист плутония

Ответы:

34

Одно из применений инструкций NOP (или NOOP без операций) в процессорах и микроконтроллерах заключается в вставке небольшой, предсказуемой задержки в ваш код. Несмотря на то, что NOP не выполняют никаких операций, их обработка занимает некоторое время (ЦП должен извлечь и декодировать код операции, поэтому для этого требуется немного времени). Всего 1 цикл ЦП «теряется» для выполнения инструкции NOP (точное число может быть выведено из таблицы данных CPU / MCU, как правило), поэтому размещение N NOP в последовательности - это простой способ вставить предсказуемую задержку:

tdelay=NTclockK

где K - количество циклов (чаще всего 1), необходимое для обработки инструкции NOP, а - тактовый период.Tclock

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

Смотрите также соответствующую страницу Википедии на NOP .

Другое использование - выравнивание кода по определенным адресам в памяти и другим «приемам сборки», как объяснено также в этом потоке на Programmers.SE и в этом другом потоке на StackOverflow .

Еще одна интересная статья на эту тему .

Эта ссылка на страницу книги Google особенно относится к 8085 CPU. Выдержка:

Каждая инструкция NOP использует четыре такта для выборки, декодирования и выполнения.

РЕДАКТИРОВАТЬ (для решения проблемы, выраженной в комментарии)

Если вы беспокоитесь о скорости, помните, что (время) эффективность - это только один параметр, который следует учитывать. Все зависит от приложения: если вы хотите вычислить 10-миллиардную цифру , возможно, вашей единственной заботой может быть скорость. С другой стороны, если вы хотите регистрировать данные от датчиков температуры , подключенных к MCU через АЦП, скорость обычно не столь важна, но в ожидании нужного количества времени , чтобы позволить АЦП правильно выполнить каждое чтение имеет важное значение . В этом случае, если MCU не ждет достаточно, он рискует получить совершенно ненадежные данные (я допускаю, что он получит эти данные быстрее , однако: o).π

Лоренцо Донати поддерживает Монику
источник
3
Многие вещи (особенно выходы, управляющие микросхемами, внешними по отношению к uC) подвержены временным ограничениям, таким как «минимальное время между стабильностью D и фронтом тактовой частоты составляет 100 мкс», или «светодиод IR должен мигать с частотой 1 МГц». Следовательно (точные) задержки часто требуются.
Wouter van Ooijen
6
NOP могут быть полезны для правильной синхронизации при последовательной передаче битов по протоколу. Они также могут быть полезны для заполнения неиспользуемого пространства кода с последующим переходом к вектору холодного запуска в редких ситуациях, если счетчик программ повреждается (например, сбой блока питания, воздействие редким событием гамма-излучения и т. Д.) И начинает выполнение кода в в противном случае пустая часть пространства кода.
Техидуде
6
В видеокомпьютерной системе Atari 2600 (вторая игровая консоль для запуска программ, хранящихся на картриджах) процессор будет выполнять ровно 76 циклов в каждой строке сканирования, и многие операции должны будут выполняться с некоторым точным числом циклов после запуска линия сканирования. На этом процессоре документированная инструкция «NOP» занимает два цикла, но для кода также характерно использование бесполезной трехтактной инструкции, чтобы увеличить задержку на точное количество циклов. Выполнение кода быстрее привело бы к полностью искаженному отображению.
суперкат
1
Использование NOP для задержек может иметь смысл даже в системах не в реальном времени в тех случаях, когда устройство ввода-вывода накладывает минимальное время между последовательными операциями, но не максимально. Например, на многих контроллерах смещение байта из порта SPI займет восемь циклов ЦП. Код, который ничего не делает, кроме выборки байтов из памяти и вывода их на порт SPI, может выполняться немного слишком быстро, но добавление логики для проверки готовности порта SPI для каждого байта сделает его излишне медленным. Добавление NOP или двух может позволить коду достичь максимальной доступной скорости ...
суперкат
1
... в случае, когда нет прерываний. В случае попадания прерывания NOP будут излишне тратить время, но время, потраченное одной или двумя NOP, будет меньше, чем время, необходимое для определения того, не делает ли прерывание ненужным.
суперкат
8

Другие ответы касаются только NOP, который на самом деле выполняется в какой-то момент - он используется довольно часто, но это не единственное использование NOP.

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

В этом случае использования noöne никогда не ожидает NOPвыполнения. Единственное, что нужно, это разрешить исправление исполняемого файла - в теоретическом незаполненном исполняемом файле вам фактически нужно изменить код самой функции (иногда она может соответствовать исходным границам, но довольно часто вам все равно потребуется переход ) - это намного сложнее, особенно если учесть написанную вручную сборку или оптимизирующий компилятор; Вы должны уважать переходы и подобные конструкции, которые могли указывать на какой-то важный фрагмент кода. В общем, довольно сложно.

Конечно, это было гораздо интенсивнее использовать в старые времена, когда было полезно делать такие патчи, как эти маленькие и онлайн . Сегодня вы просто раздадите перекомпилированный бинарный файл и покончите с этим. Есть еще некоторые, кто использует исправления NOP (выполняются или нет, и не всегда буквальные NOPs - например, Windows использует MOV EDI, EDIдля оперативного исправления - это тот тип, где вы можете обновлять системную библиотеку, пока система фактически работает, без необходимости перезапусков).

Итак, последний вопрос: почему есть специальная инструкция для чего-то, что на самом деле ничего не делает?

  • Это актуальная инструкция, важная при отладке или сборке кода вручную. Инструкции вроде MOV AX, AXбудут делать то же самое, но не так ясно обозначают намерение.
  • Заполнение - это «код», предназначенный только для улучшения общей производительности кода, который зависит от выравнивания. Это никогда не предназначалось для выполнения. Некоторые отладчики просто скрывают дополнительные NOP при их разборке.
  • Это дает больше места для оптимизации компиляторов - по-прежнему используется шаблон, состоящий в том, что у вас есть два этапа компиляции, первый из которых довольно прост и производит много ненужного ассемблерного кода, а второй очищает, перезаписывает адресные ссылки и удаляет посторонние инструкции. Это часто встречается и в JIT-скомпилированных языках - как в .NET IL, так и в JVM-байт-коде NOPдостаточно много; у фактического скомпилированного ассемблерного кода таких больше нет. Следует отметить, что это не x86- NOPе.
  • Это облегчает онлайн-отладку как для чтения (предварительно обнуленная память будет всем NOP, что облегчает чтение разборки), так и для оперативного исправления (хотя я на самом деле предпочитаю Edit и Continue в Visual Studio: P).

Для выполнения NOP, конечно, есть еще несколько моментов:

  • Конечно, производительность - это не то, почему это было в 8085 году, но даже у 80486 уже было конвейерное выполнение команд, что делает «бездействие» немного хитрее.
  • Как видно MOV EDI, EDI, есть и другие эффективные NOP, чем буквальные 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» ( операнд был симметричным, это дало вам полный диапазон, включая 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действительности это не имеет смысла в качестве инструкции, вы можете увидеть, как разработчики 8086 сэкономили на транзисторах и путях при декодировании команд, используя тот факт, что он 0x90естественным образом отображается на что-то, что является полностью «неопрятным».

С другой стороны, i8051 имеет полностью встроенный код операции для nop- 0x00. Вроде практично. Конструкция команды в основном использует высокий полубайт для операции и низкий полубайт для выбора операндов - например, add aесть 0x2Y, и 0xX8означает «прямой регистр 0», то 0x28есть add a, r0. Экономит много на силиконе :)

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

Luaan
источник
На самом деле, NOPкак правило , это псевдоним MOV ax, ax, ADD ax, 0или аналогичные инструкции. Зачем вам разрабатывать специальную инструкцию, которая ничего не делает, когда их много?
Дмитрий Григорьев
@DmitryGrigoryev Это действительно входит в дизайн самого языка CPU (ну, микроархитектуры). Большинство процессоров (и компиляторов) будут стремиться оптимизировать MOV ax, axвыезд; NOPвсегда будет иметь фиксированное количество циклов для выполнения. Но я все равно не понимаю, как это относится к тому, что я написал в своем ответе.
Luaan
Процессоры не могут по-настоящему оптимизировать MOV ax, axвыезд, потому что к тому времени, когда они узнают, что это MOVинструкция уже в процессе разработки.
Дмитрий Григорьев
@DmitryGrigoryev Это действительно зависит от типа процессора, о котором вы говорите. Современные настольные процессоры делают много вещей, а не только конвейерную обработку команд. Например, процессор знает, что ему не нужно делать недействительными строки кэша и т. Д., Он знает, что на самом деле ничего не должен делать (очень важно для HyperThreading и даже для нескольких каналов, участвующих в общем). Я не удивлюсь, если это также повлияет на предсказание ветвления (хотя, вероятно, это будет то же самое для NOPи MOV ax, ax). Современные процессоры намного сложнее, чем компиляторы
oldschool
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 Деннис
5

Еще в конце 70-х у нас (тогда я был молодым студентом-исследователем) была небольшая система разработки (8080, если память служит), которая выполнялась в 1024 байтах кода (т. Е. В одном UVEPROM) - она ​​имела только четыре команды для загрузки (L ), сохранить (S), распечатать (P) и что-то еще, что я не могу вспомнить. Это было приведено в действие с настоящим телетайпом и лентой удара. Это было жестко закодировано!

Одним из примеров использования NOOP была процедура обработки прерываний (ISR), которые были разделены интервалами в 8 байт. Эта процедура оказалась длиной 9 байтов и закончилась (длинным) переходом к адресу чуть дальше по адресному пространству. Это означало, что с учетом порядка байтов с прямым порядком байтов старший байт адреса был 00h и вставлен в первый байт следующего ISR, что означало, что он (следующий ISR) начинался с NOOP, так что «мы» могли соответствовать код в ограниченном пространстве!

Так что NOOP полезен. Кроме того, я подозреваю, что для Intel было проще всего так его кодировать - у них, вероятно, был список инструкций, которые они хотели реализовать, и он начинался с «1», как и все списки (это были дни FORTRAN), поэтому ноль Код NOOP стал выпадать. (Я никогда не видел статью, в которой утверждается, что NOOP является неотъемлемой частью теории вычислительной науки (тот же вопрос, что и: есть ли у математиков ноль операций, в отличие от нуля теории групп?)

Филип Окли
источник
Не все процессоры имеют NOP, закодированный в 0x00 (хотя 8080, 8080 и процессор, с которым я больше всего знаком, Z80, все делают). Тем не менее, если бы я проектировал процессор, я бы его поставил! Что еще удобно, так это то, что память обычно инициализируется со всеми 0x00, поэтому выполнение этого кода не будет ничего делать, пока процессор не достигнет ненулевой памяти.
CJ Деннис
@CJDennis Я объяснил, почему процессоры x86 не используют 0x00для nopв моем ответе. Короче говоря, это экономит на декодировании команд - xchg ax, axестественным образом вытекает из того, как работает декодирование команд, и делает что-то «неопрятное», так почему бы не использовать это и не вызывать его nop, верно? :) Используется, чтобы сэкономить немного на кремнии для декодирования инструкций ...
Luaan
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, чтобы перенаправить управление на пять байтов пространства исправлений, которое происходит непосредственно перед запуском функции. Для полной инструкции перехода достаточно пяти байтов, которые могут отправить управление функции замены, установленной где-то еще в адресном пространстве.

pjc50
источник