1.7. Машинный код
Машинный кодилимашинный язык– это язык, на котором записываются программы для данной машины или данного семейства машин. Этот код определяется набором кодов операций и форматом команд процессора, типом адресации ячеек, форматом представления данных в памяти и пр.
Для примера рассмотрим программу, которая выводит на консоль текст «Hello,world!», для процессора архитектуры x86 фирмыIntelc16-разрядной адресацией. В этой архитектуре адресуемой ячейкой памяти является каждый 8‑разрядный байт. Поле КОП команды занимает первый байт (т.е. допускается не более 2 8 = 256 различных команд), поле адреса – 2 байта (т.е. можно адресовать 2 16 байтов = 65536 байтов = 64 КБ – длинный адрес) или 1 байт (можно адресовать только 256 байт – короткий адрес).
Первая команда этой программы занимает 3 байта и выглядит в двоичном коде следующим образом:
Для удобства чтения программ программисты записывают их в шестнадцатеричном(16-ричном) коде, заменяя каждую четвёрку битов одной 16-ричной цифрой (всего этих цифр 16), как показано в следующей таблице:
16-ричная цифра
Двоичное число
Десятичное число
В любой системе счисления число, следующее за последней цифрой, равно 10, т.е. изображается двумя цифрами (см. в таблице 102, 1010и 1016).
Таким образом, эта команда в 16-ричной системе имеет вид ВВ 11 01. Байты 11 01 образуют здесь двухбайтовое поле адреса. Адрес, указанный в этом поле, – 011116, а не 110116. Чтобы записать правильный адрес, нужно поменять местами байты адресного поля! В десятичной системе этот адрес равен 273.
Вся программа в 16-ричном коде выглядит следующим образом (за программой последует её комментарий):
Адрес первого байта
Команды и данные 25
8A 07
CD 10
E2 F9
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
Программа размещена, начиная с ячейки с адресом 010016, и состоит из 8 команд, занимающих первые 17 байтов. После команд в неё включена выдаваемая строка текста «Hello,world!» – 13 символов (включая пробел). Каждый символ представлен своим 8‑разряднымASCII-кодом (см. любую таблицу символов). Вся строка занимает 1310=D16байтов:
Таким образом, вся программа с данными занимает 30 байтов.
Команды с номерами 4 – 7 (выделены жирным шрифтом) повторяются столько раз, сколько символов в выводимой строке (13 раз). Это – цикл.
Комментарии к программе
В программе для передачи данных используются регистры: 8-разрядные – AL,AH(это байты 16-разрядного регистра АХ), 16‑разрядные –BX,CX. РегистрыCXиIP(адрес следующей команды) используются для организации цикла. Команды «знают», какие регистры они используют.
Вывод символа на консоль осуществляется операционной системой (BIOS) с помощью прерывания с номером 1016. Останов программы (возврат управления операционной системеMSDOSпосле завершения программы) осуществляется с помощью прерывания 2016. Команда останова необходима, иначе процессор начнёт следующий байт (4816– символ «Н») рассматривать как код операции, и это может привести к тяжёлым последствиям.
Команды и данные
Комментарии
Поместить в BX адрес строки (байт с символом «Н» становится текущим)
Поместить в CX длину строки (000D)
Поместить в AH номер функции 0E16 прерывания 1016
8A 07
Поместить в AL значение ячейки памяти, адрес которой находится в BX (т.е. текущий символ)
CD 10
Вызов прерывания 1016, которое с помощью регистра АХ выведет текущий символ
Увеличить значение ВХ на 1 (сделать текущим следующий байт)
E2 F9
Если CX≠0, то уменьшить CX на 1 и увеличить IP (равный 010F) на F9 (равносильно уменьшению на 7, т.к. F9 – дополнительный код числа -7), т.е. перейти по адресу 010816 – на начало цикла
Вызвать прерывание 2016: завершить программу
48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
Этот пример показывает, насколько сложно писать, читать (понимать) и отлаживать программу в машинном коде. Производительность программиста, программирующего в цифровом машинном коде, – всего несколько команд в день. В первых ЭВМ архитектура процессоров была гораздо проще, но пользоваться самим компьютером было значительно сложнее. Но именно такие языки позволяют максимально использовать возможности конкретной машинной архитектуры при создании максимально эффективных программ.
Поэтому уже в начале 1950-х годов стали появляться языки программирования, позволявшие записывать машинные команды и данные не в цифровом виде, в символическом, используя мнемонические имена команд, регистров, ячеек памяти. Эти языки называют языками символического кодирования.Для них создавались программы-трансляторы, преобразующие команды из символической записи в цифровую. Эти трансляторы, а вместе с ними и языки символического кодирования, назывались сначалаавтокодами, а позже ассемблерами(по-русски – «сборщиками»). Появление таких языков и трансляторов стало возможным лишь на машинах с архитектурой фон Неймана благодаря принципу хранимой программы (они и были созданы для первых таких машин 26 ).
Приведём код той же программы на языке ассемблера:
mov bx, HW ; move — переместить
L: mov al, [bx]
int 10h ; interruption — прерывание
inc bx ; increment – увеличить на 1
loop L ; loop — цикл
HW: db ‘Hello, World!’
Кроме расшифровки смысла команд, здесь происходит автоматическое определение адресов команд и данных с помощью меток (LиHW). В целом, использование языков символического кодирования увеличило производительность программистов на порядок, сохраняя возможность использования всех особенностей архитектуры вычислительной системы. Таким образом, языки символического кодирования и программы-трансляторы автокоды и ассемблеры стали первым шагомавтоматизации программирования– одной из важнейших составляющих технологии программирования.
Однако, языки ассемблера и написанные на них программы привязаны к конкретной архитектуре вычислительной системы и не годятся для других систем. Кроме того, производительность программистов на ассемблере слишком низкая для написания того объёма программного кода, который требуется в современных условиях. Для удовлетворения этой потребности разрабатываются языки программирования высокого и сверхвысокого уровня – следующие шаги автоматизации программирования. На таких языках предыдущая программа превращается в один простой оператор:
Машинный код
![]()
Маши́нный код (платфо́рменно-ориенти́рованный код), маши́нный язы́к — система команд (набор кодов операций) конкретной вычислительной машины, которая интерпретируется непосредственно процессором или микропрограммами этой вычислительной машины. [1]
Компьютерная программа, записанная на машинном языке, состоит из машинных инструкций, каждая из которых представлена в машинном коде в виде т. н. опкода — двоичного кода отдельной операции из системы команд машины. Для удобства программирования вместо числовых опкодов, которые только и понимает процессор, обычно используют их условные буквенные мнемоники. Набор таких мнемоник, вместе с некоторыми дополнительными возможностями (например, некоторыми макрокомандами, директивами), называется языком ассемблера.
Каждая модель процессора имеет собственный набор команд, хотя во многих моделях эти наборы команд сильно перекрываются. Говорят, что процессор A совместим с процессором B, если процессор A полностью «понимает» машинный код процессора B. Если процессоры A и B имеют некоторое подмножество инструкций, по которым они взаимно совместимы, то говорят, что они одной «архитектуры» (имеют одинаковую архитектуру набора команд).
Содержание
Машинная инструкция
Каждая машинная инструкция выполняет определённое действие, такое как операция с данными (например, сложение или копирование машинного слова в регистре или в памяти) или переход к другому участку кода (изменение порядка исполнения; при этом переход может быть безусловным или условным, зависящим от результатов предыдущих инструкций). Любая исполнимая программа состоит из последовательности таких атомарных машинных операций.
Операции, записываемые в виде одной машинной инструкции, можно разделить на «простые» (элементарные операции) и «сложные». Кроме того, большинство современных процессоров состоит из отдельных «исполнительных устройств» — вычислительных блоков, которые умеют исполнять лишь ограниченный набор простейших операций. При исполнении очередной инструкции специальный блок процессора — декодер — транслирует (декодирует) её в последовательность элементарных операций, понимаемых конкретными исполнительными устройствами.
Архитектура набора команд процессора определяет, какие операции он способен выполнять, и какой машинной инструкции какие числовые коды операций (опкоды) соответствуют. Опкоды бывают постоянной длины (у RISC-, MISC-архитектур) и диапазонной (у CISC-архитектур; например: для архитектуры x86 команда имеет длину от 8 до 120 битов).
Современные суперскалярные процессоры способны выполнять несколько машинных инструкций за один такт.
Машинный код как язык программирования
Машинный код можно рассматривать как примитивный язык программирования или как самый низкий уровень представления скомпилированных или ассемблированных компьютерных программ. Хотя вполне возможно создавать программы прямо в машинном коде, сейчас это делается редко в силу громоздкости кода и трудоёмкости ручного управления ресурсами процессора, за исключением ситуаций, когда требуется экстремальная оптимизация. Поэтому подавляющее большинство программ пишется на языках более высокого уровня и транслируется в машинный код компиляторами. Машинный код иногда называют нативным кодом (также собственным или родным кодом — от англ. native code ), когда говорят о платформенно-зависимых частях языка или библиотек. [2]
Программы на интерпретируемых языках (таких как Basic или Python) не транслируются в машинный код; вместо этого они либо исполняются непосредственно интерпретатором языка, либо транслируются в псевдокод (байт-код). Однако интерпретаторы этих языков (которые сами можно рассматривать как процессоры), как правило, представлены в машинном коде.
Микрокод
В некоторых компьютерных архитектурах поддержка машинного кода реализуется ещё более низкоуровневым слоем программ, называемых микропрограммами. Это позволяет обеспечить единый интерфейс машинного языка у всей линейки или семейства компьютеров, которые могут иметь значительные структурные отличия между собой, и облегчает перенос программ в машинном коде между разными моделями компьютеров. Примером такого подхода является семейство компьютеров IBM System/360 и их преемников: несмотря на разные шины шириной от 8 до 64 бит и выше, тем не менее, у них общая архитектура на уровне машинного языка.
Использование слоя микрокода для реализации эмулятора позволяет компьютеру представлять архитектуру совершенно другого компьютера. В линейке System/360 это использовалось для переноса программ с более ранних машин IBM на новое семейство — например, эмулятор IBM 1401/1440/1460 на IBM S/360 model 40.
Абсолютный и позиционно-независимый код
Абсолютный код (англ. absolute code ) — программный код, пригодный для прямого выполнения процессором [1] , то есть код, не требующий дополнительной обработки (например, разрешения ссылок между различными частями кода или привязки к адресам в памяти, обычно выполняемой загрузчиком программ). Примерами абсолютного кода являются исполнимые файлы в формате .COM и загрузчик ОС, располагаемый в MBR. Часто абсолютный код понимается в более узком смысле как позиционно-зависимый код (то есть код, привязанный к определённым адресам памяти).
Позиционно-независимый код (англ. position-independent code ) — программа, которая может быть размещена в любой области памяти, так как все ссылки на ячейки памяти в ней относительные (например, относительно счётчика команд). Такую программу можно переместить в другую область памяти в любой момент, в отличие от перемещаемой программы, которая хотя и может быть загружена в любую область памяти, но после загрузки должна оставаться на том же месте. [1]
Возможность создания позиционно-независимого кода зависит от архитектуры и системы команд целевой платформы. Например, если во всех инструкциях перехода в системе команд должны указываться абсолютные адреса, то код, требующий переходов, практически невозможно сделать позиционно-независимым. В архитектуре x86 непосредственная адресация в инструкциях работы с данными представлена только абсолютными адресами, но поскольку адреса данных считаются относительно сегментного регистра, который можно поменять в любой момент, это позволяет создавать позиционно-независимый код со своими ячейками памяти для данных. Кроме того, некоторые ограничения набора команд могут сниматься с помощью самомодифицирующегося кода или нетривиальных последовательностей инструкций.
Программа «Hello, world!»
Программа «Hello, world!» для процессора архитектуры x86 (ОС MS DOS, вывод при помощи BIOS прерывания int 10h) выглядит следующим образом (в шестнадцатеричном представлении):
BB 11 01 B9 0D 00 B4 0E 8A 07 43 CD 10 E2 F9 CD 20 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
Данная программа работает при её размещении по смещению 10016. Отдельные инструкции выделены цветом:
Секреты машинного кода

В одной из прошлых статей мы поговорили о языках программирования высокого уровня. Сегодня мы опустимся гораздо ниже — в самые недра программных систем и попробуем разобраться в том, на каком «языке» говорит с пользователями и программистами сама ЭВМ.
Мы часто вкладываем в это понятие самые различные смыслы. «Двоичный код», «машинный код», «программный код». Между тем, значение этих терминов различно. А вот использование в машинной математике двоичного кода очевидно и вполне логично.
Цифровая электроника работает с двумя типами электрических сигналов, или, если точнее, с двумя «состояниями» — высоким уровнем сигнала в его источнике либо передатчике и низким его уровнем. Высокий уровень сигнала — это «ноль», низкий — «единица». Это наиболее надежная схема передачи сигналов, которая дает стабильный результат. Какие бы помехи не возникали на пути сигнала, мы всегда поймем, где «единица», а где — «ноль».
На такую схему великолепно ложится двоичная система счисления. Справедливости ради стоит отметить, что впервые такая система была описана в древнекитайской «книге Перемен». В дальнейшем двоичная система счисления только обретала теоретическую основу. Математик Лейбниц, например, доказал, что основой системы счисления может быть любое число, кроме нуля. Были, а, возможно и будут и другие теоретические работы в этом направлении. Сейчас используются, как минимум, три системы счисления — двоичная, восьмеричная, шестнадцатиричная. Но нас интересует именно двоичная.
Как работает «двоичный код»
С помощью этого кода можно легко перевести десятичное число в понятное компьютеру двоичное. Возьмем число (например, 11001). Пронумеруем его с конца, причем, нумерацию начнем с нуля. Порядковый номер каждой цифры станет степенью, в которую мы возведем основание системы счисления (2), затем перемножим результат с цифрой. Нужно провести эту операцию с каждой цифрой двоичного числа, затем суммировать результат:
Теперь попробуем перевести число 25 обратно, в двоичный код. Используем для этого простейшую арифметическую операцию — получение остатка от деления.
В результатах, снизу вверх, читаем наше число.
Таким образом считать довольно легко, причем, даже на «пальцах». У нас ведь на руке пять пальцев? Если каждому присвоить единицу или ноль (ноль – палец согнут, единица – выпрямлен) и прикинуть значение основания системы, возведенной в нужную степень, то можно считать от нуля и до 1023. Правда, в случае с числом 25 жест руки будет немного неприличным.
Для любителей посчитать есть сегодня любые инструменты — начиная от калькулятора и заканчивая таблицами разрядности.
С дробными числами расчет ведется по иному. Всем цифрам до «запятой», степень присваивается в обратном порядке, от нуля, как и обычно, а вот числа после «запятой» сопровождаются отрицательной степенью, причем, она уменьшается от запятой. Вот как это выглядит:
В остальном, операция выполняется также.
Как считает компьютер?
Компьютер не может, допустим, приписать «минус» к двоичному числу и считать его отрицательным. Этот минус нужно где-то поместить. Простейший способ — снабдить число дополнительным знаком (в восьмиразрядной ячейке, в этом случае мы сможем поместить семиразрядное двоичное число, где дополнительный разряд будет означать знак — единица отрицательный, ноль — положительный). Это называется «прямым кодом».
Но отрицательные числа лучше представлять по-другому — для того, чтобы компьютер мог с ними работать. «Единица» в крайнем разряде по-прежнему будет выполнять роль «маркера» отрицательного числа. Но остальные разряды требуется инвертировать (то есть, разряды с нулями заменить единицей и наоборот). На месте остается только маркер.
Теперь к числу прибавляем единицу:
Для чего же вся эта малопонятная морока? – спросит читатель. Для того, чтобы ЭВМ смогла выполнять операции с числами.
Знаковые разряды в этом случае просто не учитываются.
Но если число отрицательное — все меняется. Переводим его в дополнительный или обратный код (инвертируем все разряды кроме знакового, затем прибавляем единицу) и складываем снова, затем из результата вычитаем единицу и инвертируем его:
Вычитание работает по тому же принципу, умножение — путем последовательного сложения (я очень сильно упрощаю но принцип именно таков). Что касается операции деления, то она реализуется путем неоднократного сложения делимого с дополнительным кодом делителя до получения необходимого результата.
Подробно эти действия описывать не буду. Расскажу лишь про арифметический сдвиг. Это ещё одна «хитрость», которая позволяет упростить вычисления. Если сдвинуть двоичное число вправо или влево, то оно меняется, согласно определенным правилам.
Сдвиг вправо (слева добавляем единицу, последний разряд убираем):
Ещё один сдвиг вправо:
С дополнительным кодом — ещё интереснее. Сдвиг числа влево дает умножение на 2, вправо – деление на 2. Это очень удобно для умножения и деления целых чисел на числа, равные степени 2 (2, 4, 8, 16, 32, 64). Существует множество вариантов сдвига — арифметический (мы его выполнили), циклический, логический и т.д. Каждый выполняет свою задачу.
Добавлю, что всё, о чем я говорил — это «азы» компьютерных вычислений. Когда-то они выполнялись компьютером именно так. Сегодня они ведутся уже на ином уровне. Персональный компьютер выполняет расчеты при помощи встроенного в ядро основного ЦПУ математического сопроцессора, где каждая операция выполняется одной командой, что значительно ускоряет работу. Математический сопроцессор оптимизирован именно для вычислений, эти функции заложены в него на аппаратном уровне. Но описание работы сопроцессора — это тема для отдельной статьи или даже целой книги.
Кодируем… двоичный код
Сейчас мы подошли к самому главному моменту: мы уже знаем, что компьютер выполняет операции с числами в двоичном коде. Причем, это единственный пока вариант для ЭВМ и не такой уж сложный, если вдуматься, для нашего с вами понимания. Остается выяснить, как же объяснить компьютеру, что он должен делать с числом (числами), чтобы получить результат.
Для этого, очевидно, нужны команды, которые будут точно поняты и интерпретированы центральным процессором. А поскольку ничего, кроме двоичных чисел ЭВМ не понимает, то команды должны состоять из определенного набора цифр — машинной инструкции. Она представляет собой запись, состоящую из нескольких элементов — начального и конечного маркеров и самого её «тела».
Машинные инструкции разрабатываются для каждого семейства процессоров и некоторых отдельных их разновидностей. Поддержка инструкций заложена в самом процессоре.
Конечно, программу из таких инструкций написать очень и очень трудно, так как ручное управление процессором — задача нетривиальная и трудоемкая, для этого существуют специальные языки нижнего уровня, которые работают напрямую с процессором — ассемблеры. Специальная программа компилятор, превращает команды ассемблеров в машинные инструкции, которые располагаются в специальных бинарных (двоичных) файлах.
В принципе, кодирование информации происходит в компьютере неоднократно. Это циклический процесс, реализованный и на программном и на аппаратном уровнях. Начинается он со скан-кода (в IBM-совместимых компьютерах), с помощью которого драйвер клавиатуры распознает нажатия и отпускания клавиш.
Далее в электронных документах, с которыми мы работаем, символы кодируются при помощи кодовых таблиц (кодировок) — (КОИ — 8, СР1251, СР866, Мас, ISO, Unicode), в которых на один символ отводится от одного и более байт. Коды у одинаковых символов в разных кодировках различны. Допустим, двоичное число 11000010 в кодировке KOI-8 будет означать строчную букву «б», а в кодировке CP1251 — прописную «В».
С помощью стандартных кодировок записываются и команды абсолютно всех языков программирования. А затем программа проходит через транслятор, который снова кодирует её, только уже в понятные процессору машинные инструкции либо в ассемблер. В принципе, транслятор решает массу задач но это — тема для другого разговора. Наша цель — составить более-менее стройную картину преобразования информации внутри ЭВМ.
Собственно, мы уже несколько раз упоминали это «семейство» низкоуровневых языков программирования. Сейчас их используют реже, чем это было раньше, так как появилось немало более удобных, высокоуровневых языков. Тем не менее, ассемблеры всё же применяются — когда нужен небольшой но высокоэффективный фрагмент программного кода. Ассемблерные вставки поддерживаются и в ряде высокоуровневых языков программирования, с целью ускорения работы кода.
Ассемблеры — аппаратно-ориентированные языки, они привязаны к архитектуре процессора, поэтому, их синтаксис для разных процессоров будет различен. Но их объединяет один принцип. Команды процессора, которые представляют собой просто числа, в ассемблерах представлены как «мнемонические» символы. Чтобы не расшифровывать это понятие, я просто продемонстрирую несколько небольших программ.
Вот как будет выглядеть программа на ассемблере 8086 (x86-совместимая), которая выводит на консоль Hello World:
Понять, что здесь написано не так уж и трудно. Use16 – генерация 16-битного кода, регистр DX (mov dx) содержит адрес строки (строка заканчивается значком $ и объявляется директивой db), регистр AH (mov ah) содержит 9 — номер функции DOS, int 21h — обращение к функциям DOS. 4C00h — завершение программы, где 0 — успешное её завершение.
Может даже показаться, что программирование на ассемблере — легкая задача. Но это отнюдь не так. Если вы начнете делать что-то более или менее сложное, писанины будет куда больше. Вот, взгляните на вывод латинских букв по алфавиту в цикле:
Кроме уже знакомых нам команд здесь есть и другие. 02h — вывод символа, dl — первый символ, Inc dl – следующий символ, 08h — ввод без отображения (чтобы не закрылась программа), 13,10 — возврат каретки, перевод строки, сх — счетчик повторений. Loop — метка, которая «выбрасывает» цикл к началу.Собственно, далеко не каждый, даже опытный программист, сможет работать с ассемблерами. Для этого нужно очень хорошо знать архитектуру процессора. Для промышленного программирования гораздо удобнее пользоваться языками высокого уровня, о которых мы говорили в прошлый раз.
Команды ассемблера — это, практически, прямые инструкции для процессора, которые выполняются и трактуются однозначно. Машинный код программ, написанных на современных языках программирования высокого уровня, генерируется и оптимизируется автоматически, порой проходя несколько уровней кодирования. Это сделано для того, чтобы ускорить и упростить работу программиста. Но «упрощение», в данном случае — весьма относительное понятие.
Современные языки программирования представляют собой сложнейшие системы, которые позволяют относительно быстро создавать и тестировать программные комплексы любого масштаба и сложности.
Заходите на наш сайт ITквариат в Беларуси за новой порцией интересных новостей!
А также подписывайтесь и читайте новости от ITквариат раньше остальных в нашем Telegram-канале !
Машинный код
Машинный код (платформенно-ориентированный код), машинный язык — система команд (набор кодов операций) конкретной вычислительной машины, которая интерпретируется непосредственно процессором или микропрограммами этой вычислительной машины. [1]
Каждая инструкция выполняет определённое (обычное элементарное) действие, такое как операция с данными (например, сложение или копирование; в регистре или в памяти) или переход к другому участку кода (изменение порядка исполнения; при этом переход может быть безусловным или условным, зависящим от результатов предыдущих инструкций). Каждая исполнимая программа состоит из последовательности таких атомарных инструкций.
Машинный код можно рассматривать как примитивный язык программирования или как самый низкий уровень представления скомпилированных или ассемблированных компьютерных программ. Хотя вполне возможно создавать программы прямо в машинном коде, сейчас это делается редко в силу громоздкости кода и трудоёмкости управления ресурсами процессора, за исключением ситуаций, когда требуется экстремальная оптимизация. Поэтому подавляющее большинство программ пишется на языках более высокого уровня и транслируется в машинный код компиляторами. Машинный код иногда называют нативным кодом (также собственным или родным кодом — от англ. native code ), когда говорят о платформенно-зависимых частях языка или библиотек. [2]
Программы на интерпретируемых языках (таких как Бейсик или Python) не транслируются в машинный код, вместо этого они либо исполняются непосредственно интерпретатором, либо транслируются в псевдокод (байт-код). Однако интерпретаторы этих языков (которые сами можно рассматривать как процессоры) как правило представлены в машинном коде.
Каждая модель процессора имеет свой собственный набор команд, хотя во многих моделях эти наборы команд сильно перекрываются. Говорят, что процессор A совместим с процессором B, если процессор A полностью «понимает» машинный код процессора B. Если процессор A знает несколько команд, которых не понимает процессор B, то B несовместим с A.
Раньше процессоры просто выполняли инструкции одну за другой, но новые суперскалярные процессоры способны выполнять несколько инструкций за раз.
Также инструкции бывают постоянной длины (у RISC-, MISC-архитектур) и диапазонной (у CISC-архитектур; например, для архитектуры x86 команда имеет длину от 8 до 120 битов).
Содержание
Микрокод
В некоторых компьютерных архитектурах поддержка машинного кода реализуется ещё более низкоуровневым слоем программ, называемых микропрограммами, что позволяет обеспечить единый интерфейс машинного языка у всей линейки или семейства компьютеров, которые могут иметь значительные структурные отличие между собой. Это делается для облегчения переноса программ в машинном коде между разными моделями компьютеров. Примером этого является семейство компьютеров IBM System/360 и их преемников: несмотря на разные шины шириной от 8 до 64 бит и выше, тем не менее у них общая архитектура на уровне машинного языка.
Использование слоя микрокода для реализации эмулятора позволяет компьютеру представлять архитектуру совершенно другого компьютера. В линейке System/360 это использовалось для переноса программ с более ранних машин IBM на новое семейство — например, эмулятор IBM 1401/1440/1460 на IBM S/360 model 40.
Абсолютный и позиционно-независимый код
Абсолютный код (англ. absolute code ) — программный код, пригодный для прямого выполнения процессором [1] , то есть код, не требующий дополнительной обработки (например, разрешения ссылок между различными частями кода или привязки к адресам в памяти, обычно выполняемой загрузчиком программ). Примерами абсолютного кода являются исполнимые файлы в формате .COM и загрузчик ОС, располагаемый в MBR. Часто абсолютный код понимается в более узком смысле как позиционно-зависимый код (то есть код, привязанный к определённым адресам памяти).
Позиционно-независимый код (англ. position-independent code ) — программа, которая может быть размещена в любой области памяти, так как все ссылки на ячейки памяти в ней относительные (например, относительно счётчика команд). Такую программу можно переместить в другую область памяти в любой момент, в отличие от перемещаемой программы, которая хотя и может быть загружена в любую область памяти, но после загрузки должна оставаться на том же месте. [1]
Возможность создания позиционно-независимого кода зависит от архитектуры и системы команд целевой платформы. Например, если во всех инструкциях перехода в системе команд должны указываться абсолютные адреса, то код, требующий переходов, практически невозможно сделать позиционно-независимым. В архитектуре x86 непосредственная адресация в инструкциях работы с данными представлена только абсолютными адресами, но поскольку адреса данных считаются относительно сегментного регистра, который можно поменять в любой момент, это позволяет создавать позиционно-независимый код со своими ячейками памяти для данных. Кроме того, некоторые ограничения набора команд могут сниматься с помощью самомодифицирующегося кода или нетривиальных последовательностей инструкций.
Программа «Hello, world!»
Программа «Hello, world!» для процессора архитектуры x86 (ОС DOS, вывод при помощи BIOS Int 10h (англ.) выглядит следующим образом (в шестнадцатеричном представлении побайтно):
BB 11 01 B9 0D 00 B4 0E 8A 07 43 CD 10 E2 F9 CD 20 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21
Данная программа работает при её размещении по смещению 10016. Отдельные инструкции выделены цветом: