awesome-everything EN
↑ Обратно к восхождению

Базовый CS с нуля

Машинный код

Суть Инструкции — это биты, хранящиеся в памяти, неотличимые от данных. Счётчик команд проходит по ним. Программа — это данные, которые CPU исполняет: так гласит идея хранимой программы (архитектура фон Неймана).
◷ 20 min

Ты знаешь, что программы состоят из инструкций. Знаешь, что у инструкций есть опкоды и операнды. Но где инструкции хранятся физически? Ты знаешь, что память хранит данные, — но хранит ли она саму программу?

Ответ — да, и это одна из важнейших идей в вычислительной технике: инструкции — это просто байты, хранящиеся в памяти. Они находятся по обычным адресам памяти. Счётчик команд проходит по ним один за другим. Физически ничем не отличаются байты, представляющие число, от байтов, представляющих инструкцию — CPU обрабатывает их по-разному только потому, что счётчик команд указывает на них.

Эта идея — что программа и её данные разделяют одну и ту же память — называется концепцией хранимой программы или архитектурой фон Неймана. Это фундамент каждого универсального компьютера, созданного с 1940-х годов.

Цель

После этого урока ты сможешь объяснить, что такое машинный код, описать, как инструкции кодируются байтами в памяти, сформулировать принцип хранимой программы (фон Неймана) и объяснить, почему программа — это просто данные, которые CPU исполняет.

1

Машинный код: инструкции как битовые паттерны. CPU не читает человекочитаемый текст вроде «ADD R0, R1». Он читает необработанный двоичный код — конкретные битовые паттерны, кодирующие инструкцию. Эти битовые паттерны называются машинным кодом (иногда машинным языком). Машинный код — единственный язык, который аппаратура CPU действительно понимает.

Каждой инструкции в системе команд присваивается уникальный числовой код — её опкод (код операции). Помимо опкода, кодировка инструкции содержит поля для операндов: какие регистры использовать, какое непосредственное значение (константа, непосредственно встроенная в инструкцию) использовать в операции или по какому адресу выполнить переход.

Пример (упрощённый): предположим, 16-битный CPU кодирует инструкции в 2 байта. Первые 4 бита — опкод, следующие 4 бита — регистр-назначение, последние 8 бит — непосредственное значение:

Биты:    [0001] [0000] [00100101]
Смысл:    LOAD   R0    адрес 37

На реальном CPU кодировка сложнее (инструкции x86-64 имеют переменную длину от 1 до 15 байтов), но принцип тот же: каждая инструкция — конкретный битовый паттерн, который CPU декодирует на шаге декодирования цикла выборки–декодирования–исполнения.

2

Инструкции хранятся в памяти по обычным адресам. В предыдущем разделе ты узнал, что память — один длинный ряд байтовых ячеек. Инструкции программы живут в том же ряду. Операционная система загружает программу в память — копирует байты машинного кода в ячейки, начиная с некоторого адреса, — а затем устанавливает счётчик команд на адрес первой инструкции. С этого момента шаг выборки читает ячейки памяти, как любое другое чтение памяти.

Это означает:

  • Инструкции по адресу 100 находятся в ячейке памяти 100.
  • Инструкции по адресу 104 — в ячейке памяти 104.
  • Между байтами инструкций могут находиться байты данных (записанные инструкцией LOAD по соседнему адресу) — сами ячейки памяти не «знают», хранят ли они инструкции или данные.

Различие между инструкцией и данными — исключительно вопрос контекста: если счётчик команд указывает на ячейку, CPU интерпретирует эти байты как инструкцию. Если на ячейку указывает инструкция LOAD, CPU интерпретирует эти байты как данные.

3

Концепция хранимой программы (архитектура фон Неймана). Идея о том, что программа и её данные разделяют одну память, называется концепцией хранимой программы. Её сформулировал математик Джон фон Нейман в докладе 1945 года, описывающем компьютер EDVAC, и она стала основой конструкции каждого крупного универсального компьютера с тех пор.

До компьютеров с хранимой программой программы часто кодировались в физической разводке или перфокартах — смена программы означала физическую перекоммутацию машины или загрузку новых карт. Идея хранимой программы состоит в следующем: помести программу в память как данные, и ты сможешь сменить её, просто записав в память другие байты. Одна машина может выполнять любую программу, потому что программа — просто данные, которые сначала загружают.

Именно поэтому:

  • Можно устанавливать новое программное обеспечение на ноутбук, не открывая его.
  • CPU в смартфоне может запустить браузер, музыкальный плеер и игру — это просто разные последовательности байтов, загружаемых в память.
  • Самомодифицирующиеся программы принципиально возможны (и используются в специальных контекстах, например в JIT-компиляторах, которые генерируют машинный код в памяти во время выполнения).
Почему это работает

Почему это кажется рискованным. Если данные и инструкции разделяют память, что мешает ошибочной программе случайно записать случайные данные в область, где хранятся инструкции, а затем CPU выполнит этот мусор? На практике операционная система использует аппаратную защиту памяти, помечая некоторые области памяти как исполняемые (инструкции), а другие — как доступные только для чтения или неисполняемые (данные). Попытка записать в исполняемую область или исполнить область данных вызывает аппаратную ошибку. Эта защита отсутствовала на ранних компьютерах, из-за чего ошибки в ранних программах могли приводить к по-настоящему непредсказуемому поведению. Современные CPU обеспечивают эту защиту на уровне кремния.

4

Программа — это данные. Принцип хранимой программы ведёт к поразительному выводу: программа — просто данные, лежащие в памяти. CPU не различает «байты программы» и «байты данных» на аппаратном уровне. Единственное, что превращает байты в программу, — счётчик команд, проходящий по ним.

Это имеет глубокие последствия:

  • Компиляторы — программы, которые читают исходный код (данные в одном формате) и записывают байты машинного кода (данные в другом формате) в файл. Запуск скомпилированной программы означает загрузку этих байтов в память и установку счётчика команд на них.
  • Интерпретаторы (например, среда выполнения Python) — программы, которые читают исходный код Python (данные) и выполняют его инструкции, исполняя собственный машинный код — без прямого преобразования текста Python в нативный машинный код.
  • Вирусы работают, внедряя собственные байты в память и заставляя счётчик команд посещать их.

Во всех случаях инструкции — байты, байты — данные, а данные — просто битовый паттерн в ячейке памяти.

Граничные случаи

Гарвардская архитектура: отдельная память для инструкций и данных. Архитектура фон Неймана использует одну память и для инструкций, и для данных. Некоторые микроконтроллеры и сигнальные процессоры используют гарвардскую архитектуру, где память инструкций и память данных — физически раздельные адресные пространства. Это предотвращает случайное исполнение данных и позволяет выборкам инструкций и чтениям данных происходить одновременно по отдельным шинам. Большинство универсальных CPU (x86-64, ARM) используют модифицированную гарвардскую конструкцию на уровне кэша (отдельный кэш инструкций и кэш данных), но единую архитектуру фон Неймана на уровне основной памяти.

0001 0000
100
0010 0101
101
0010 0001
102
0010 0110
103
0011 0010
104
0000 0000
105
00011010
200
00000111
201
Ячейки памяти 100–105 хранят байты инструкций (выделены: первая инструкция занимает адреса 100–101). Ячейки 200–201 хранят байты данных. Байты выглядят одинаково — контекст (счётчик команд) определяет, что является инструкцией.
Разбор примера

Декодирование упрощённой инструкции машинного кода.

Предположим, CPU использует 16-битные (2-байтовые) инструкции со следующей фиксированной кодировкой:

  • Биты 15–12 (4 бита): опкод
  • Биты 11–8 (4 бита): номер регистра-назначения
  • Биты 7–0 (8 бит): непосредственное значение (константа, встроенная в инструкцию)

Опкоды: 0001 = LOAD-IMMEDIATE (загрузить константу в регистр), 0010 = ADD-IMMEDIATE (прибавить константу к регистру), 0011 = STORE (сохранить регистр по адресу из битов 7–0).

Память по адресу 100 содержит два байта: 0001 0010 и 0000 0101.

Объединяем в одно 16-битное слово: 0001 0010 0000 0101.

Декодирование:

  • Биты 15–12: 0001 → опкод = LOAD-IMMEDIATE
  • Биты 11–8: 0010 → назначение = регистр 2 (R2)
  • Биты 7–0: 0000 0101 → непосредственное значение = 5 (двоичное 0000 0101 = десятичное 5)

Инструкция: «Загрузить константу 5 в регистр R2».

Это и есть машинный код: два «сырых» байта в двух ячейках памяти, кодирующих полную инструкцию, которую схема декодирования CPU превращает в конкретные управляющие сигналы.

Практика 0 / 5

Инструкции машинного кода хранятся в памяти в каком виде? Введи 1 (байты/битовые паттерны) или 2 (текст на языке программирования).

Концепция хранимой программы означает, что программа и её данные разделяют одну и ту же ___. Введи 1 (память) или 2 (видеокарту).

16-битная инструкция имеет опкод 0001 в старших 4 битах, регистр 0011 в битах 11–8, значение 25 в битах 7–0. Каков десятичный номер регистра, указанного в битах 11–8? (Двоичное 0011 = ?)

Две ячейки памяти содержат байты инструкции. На них указывает счётчик команд. Что CPU делает с этими байтами? Введи 1 (интерпретирует их как инструкцию) или 2 (игнорирует).

Установка нового ПО на ноутбук возможна без вскрытия железа, потому что программы хранятся как ___. Введи 1 (байты в памяти, которые можно перезаписать) или 2 (жёстко зашитые схемы).

Проверь себя
Викторина

Что такое принцип хранимой программы (архитектура фон Неймана) и почему он значим?

Итог

Машинный код — набор битовых паттернов, непосредственно кодирующих инструкции для конкретного CPU. Каждая инструкция — определённая последовательность байтов в памяти: первые биты — опкод (идентифицирующий операцию), остальные биты кодируют операнды (регистры, непосредственные значения или адреса памяти). Инструкции живут в той же байтовой памяти, что и данные — это принцип хранимой программы (архитектура фон Неймана) — и на уровне памяти нет аппаратного различия между байтами инструкций и байтами данных. CPU интерпретирует байты как инструкции только тогда, когда счётчик команд указывает на них во время выборки. Поскольку программы — просто данные в памяти, установка нового ПО требует только записи новых байтов в память, а не изменения железа. Это единственное понимание — программа есть данные — является фундаментом универсальных вычислений.

Продолжить восхождение ↑Игрушечный CPU
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.