§ Упрощенная схема компьютера

Любой компьютер состоит из нескольких важных блоков. Это системный блок, монитор, клавиатура, мышь, принтер и прочая периферия. Все эти вещи сложны сами по себе, но изучать будем только то, что касается именно основного блока, сердца компьютера — это процессор. Но процессор не живет сам по себе, для него, как минимум, нужна связь с внешним миром, он должен получать информацию, преобразовывать его в другую информацию и выдавать ее обратно, поэтому процессор и называют процессором, так как он выполняет какой-то процесс над данными.
Процессор должен быть подключен к памяти, а также к внешнему миру, например, к клавиатуре, мыши, жесткому диску, видеоадаптеру. Конечно, он не подключается напрямик к этим устройствам, взаимодействие с ними происходит через контроллеры — это тоже процессоры, которые позволяют взаимодействовать с внешними устройствами.
Теперь я приведу одну очень упрощенную схему, которую увидел в одной книжке, взятой из библиотеки, когда мне было 10 лет.

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

§ Состав процессора

В целом, процессоры почти что все имеют следующие обязательные компоненты, которые позволяют ему нормально функционировать.
  • Устройство управления, модуль, без которого невозможно выполнение ни одной команды. Он, на основе кода операции, выполняет отсылку сигналов в правильном порядке по другим компонентам процессора;
  • Регистровый файл (набор регистров). Это сверхоперативная память, временные данные для работы процессора, разновидность оперативной памяти. Регистры крайне важны для того чтобы хранить временные результаты, быстро оперируя ими и не обращаясь к гораздо более медленной памяти. На самом деле, регистры можно убрать из процессора, но тогда необходимо обеспечить быстрый доступ к общей памяти. В процессоре AVR примерно так и сделано, где 32 первых байт памяти зарезервированы под регистры (то есть это и память, и регистры);
  • Арифметическое-логическое устройство. Я бы назвал это именно ядром компьютера, потому что при его помощи как раз и производятся все возможные вычисления и расчеты. Процессор без этого модуля возможен, но работать он будет плохо, медленно, используя только лишь оперативную память для вычислений;
  • Счетчик PC, программный счетчик, еще один важный регистр, который умеет делать 2 вещи — либо прибавлять +1, либо обновлять на какое-то заданное значение. Этот регистр указывает на адрес в памяти, откуда в данный момент происходит считывание данных или кода. Без этого регистра невозможно вообще выполнение никакого кода, потому что он является указателем на участок программного кода, который в данный момент исполняется.

§ Адресация памяти

Стоит отдельно рассказать о методах адресации памяти в процессоре. Дело в том, что сам по себе процессор является 16-битным, что значит то, что он может адресовать только 2^16 = 65536 байт. В ранних, 8 битных процессорах наподобие Z80, проблема расширения памяти решалась тем, что где-то в памяти подменялся "банк", то есть, мы все еще оставались в пределах 64Кб, но при этом приходилось писать в порт каждый раз номер банка памяти, чтобы заменить отображение участка памяти на другое.
В 8086 такого нет. Здесь проблему решили введением сегментных регистров. Это специальные регистры, их в процессоре всего лишь 4 штуки, и называются они как ES, CS, DS и SS. Каждый сегментный регистр имеет свое назначение.
  • ES — Extended. Сегментный регистр общего назначения, но, в то же время, используется в строковых инструкциях.
  • DS — Data. Общего назначения, используется для адресации данных.
  • CS — Code. Только для указания сегмента кода.
  • SS — Stack. Для указания сегмента, где находится стек.
Каждый сегментный регистр имеет разрядность, аналогично другим регистрам, 16 бит. Но, тут есть одна важная деталь. Она заключается в том, что если мы указываем в сегментном регистре значение 1, то по итогу мы получим смещение +16.
Приведу пример. Допустим, нам надо указать на ячейку памяти, которая равна 9FA1. Как можно заметить, эта ячейка памяти находится в пределах 16 битного значения, так что, чтобы вычислить адрес, можно указать сегмент, равный нулю: 0000:9FA1. Однако, есть и другой способ. Зная то, что 1 сегмент = 10h смещения в адресе, тот то же самый адрес можно указать, например, так:
0900:0FA1
Что будет тоже равно 9FA1h, но — почему? Все просто! Мы 0900h умножим на 10h, получаем 9000h и добавляем 0FA1, получая итоговый адрес 9FA1h!
Таким способом можно формировать указатель памяти совершенно любой комбинации, используя формулу вычисления сегментного регистра и смещения:
ADDRESS = 10h x SEGMENT + OFFSET

Если взять максимальные значения сегмента и смещения, то получим максимальный адрес FFFFh x 10h + FFFFh = 10FFEFh, что равняется 1Мб плюс 64кб (минус 16 байт). Для 8086 больше чем 1 Мб адресовать было нельзя, и потому как только там делался "проворот", то указатель памяти начинался сначала. Для 286 процессора можно было записать что-то сверх 1 Мб памяти, и эта область памяти назвали HMA (High Memory Area). Туда очень удачно влезала резидентная часть DOS, освобождая место для программ.

§ Регистры

Одной из важнейшей составляющей процессора являются регистры. В процессоре 8086/8088 они разделяются на несколько категории: 1) регистры общего назначения, 2) сегментные регистры, 3) и специальные регистры.

Регистров общего назначения всего 8, и они все являются 16-битными. Но существует некоторая особенность. Первые четыре 16-разрядных регистров можно разделить на две части. Например, если взять регистр AX, то он делится на два 8-битных регистра AH и AL.
Очень важно и нужно понять, что и регистр AH и регистр AL — это просто две части регистра AX, что значит, что если мы поменяем регистр AH или AL, то AX поменяется тоже. Математически, такие регистры связаны следующим образом:
AX = 256 * AH + AL
Как мы можем заметить, если мы поменяем регистр AH, например, добавим +1, то в регистру AX добавится 256. Между прочим, не факт, что если мы поменяем регистр AX, то может поменяться AH или AL. Это смотря где будет изменение. Регистр 16 бит просто состоит из двух частей по 8 бит каждая.
Видно что первые регистры имеют букву, от A до D. 16-битный регистр начинается с AX, а потом сразу переходит на CX, DX, BX. Я не случайно расположил в этом порядке, потому что в процессоре они почему-то располагаются именно в таком порядке и это важно учитывать, потому что номер регистра AX — 0, CX — 1, DX — 2, BX — 3. Они определенным образом пронумерованы и это пригодится в будущем при разработке кода, который будет выполнять доступ к регистрам по их номерам.
Еще стоит обратить внимание на то как названы регистры 8 бит, например AH означает A-High (старший), AL означает A-Low (младший), что показывает на то, в какой именно части регистра AX находится 8-битная его компонента.
Теперь стоит рассказать о назначении каждого регистра.
  • AX — Accumulator, регистр, использующийся в различных операциях, выступает как основной временный регистр процессора
  • BX — Base, обычно при его помощи указывают смещение в памяти
  • CX — Counter, очень широко используется в счетчиках и инструкциях, обрабатывающих строки
  • DX — Data, сюда обычно пишется результат умножения, деления, расширения разрядности с 16 до 32, если это нужно
Несмотря на то что регистры имеют определенное назначение, они могут использоваться как угодно. В том числе и те, которые описываются далее.
  • SP — Stack Pointer, этот регистр нежелательно использовать как регистр общего назначения, потому что именно здесь, в совокупности с сегментным регистром SS, находится машинный стек, который позволяет записывать вызовы процедур, временные данные в память. Конечно, его можно использовать в операциях, но стоит хотя бы сохранять значение стека на время этих операции и выключать прерывания. Да и вообще это опасно. Я бы не рекомендовал бы его использование в вычислительных целях. Если не хватает места в регистрах для временных операции над данными, то лучше тогда писать операнды в память;
  • BP — Base Pointer, вспомогательный регистр, относящийся к стеку. Его особенность в том, что при указании его в качестве адреса, он автоматически по умолчанию обращается к сегменту стека SS, а не в сегменту данных DS, как большинство инструкции. Он используется обычно для работы со стеком и с теми данными, что там находятся, но вообще, ввиду того что он используется не так часто, его спокойно можно использовать к качестве регистра общего назначения;
  • SI - Source Index, DI - Destination Index — эта пара индексных регистров используется, как ни странно, для указания адреса в памяти, а еще для строковых инструкции процессора. Обычно SI — это адрес памяти, откуда мы берем данные, а DI — адрес, куда данные складываем.
Сегментные регистры тоже имеют своё назначение, причем, они разделены более специализированно, хоть и могут применяться как угодно.
  • ES — Extended Segment, обычно он используется в строковых инструкциях в качестве сегмента к регистру DI;
  • CS — Code Segment, используется в паре с IP — Instruction Pointer, составляя комбинацию CS:IP, которая однозначно указывает на байт программы, который в данный момент считывается из памяти;
  • SS — Stack Segment, используется в паре с SP, указывая на адрес в памяти, где сейчас находится стек. А также он идет по умолчанию с регистром BP, если его указать. Конечно, регистр BP необязательно идет вместе с SS:BP, в процессоре существует метод, как указать другой сегментный регистр принудительно.
  • DS — Data Segment, обычно идет вообще при указании к любым данным из памяти по умолчанию. То есть если мы обратимся к памяти, то в качестве сегмента будет выступать именно DS, опять же, если явно не укажем другой.
Регистр флагов представляет из себя особый регистр, доступ к которому осуществляет сам процессор, изменять его состояние можно только косвенно, с помощью определенных инструкции. Флаги ставятся после выполнения инструкции и каждый имеет свое собственное назначения и смысл.
  • OF — Overflow, устанавливается в 1, если при выполнении арифметической операции знаковый результат "не влез" в регистр. Например, если сложить 127+1 то в 8-битном регистре получается не 128, а -128 (минус), и чтобы об этом дать знать, процессор выставляет OF=1;
  • DF — Direction, это управляющий флаг, который ставится программно, и он указывает, в каком направлении будут инкрементироваться (или декрементироваться) регистры SI и/или DI. В случае DF=1, то эти регистры будут уменьшаться (декремент);
  • IF — Interrupt, разрешение прерываний. Указывает на то, должен ли процессор как-то реагировать на входящий сигнал IORQ или нет. Если IF=0, то процессор не вызывает прерывание, если оно пришло.
  • TF — Trap/Trace, этот флаг, если он установлен, после каждой выполненной инструкции вызывает прерывание #1. Очень было удобно раньше для того чтобы делать отладчики кода. Позже необходимость в этом флаге исчезла абсолютно, но в качестве совместимости он все еще остается в процессорах.
  • SF — Sign, ставится всякий раз, если в старшем бите арифметической или логической операции тоже есть 1, и сбрасывается, если есть 0. То есть, проще говоря, старший бит результата просто копируется в этот флаг после исполнения инструкции.
  • ZF — Zero, показывает, пришел ли нулевой результат. Если например, после выполнения любой арифметико-логической инструкции результат стал 0, то тогда ZF=1.
  • AF — Aux, полуперенос. Вообще, это устаревший флаг, который используется в уже никому не нужных инструкции двоично-десятичного счета, и он ставится если произошел либо перенос из бита 3 в 4 при сложениях, либо же наоборот, заем из бита 4 при вычитании. Он был нужен раньше для того чтобы корректировать десятичное значение после вычислений, но это настолько неудачное решение оказалось, что от него в новых режимах адресации (64 битном режиме работы процессора) вообще отказались.
  • PF — Parity, еще один устаревший флаг, который ставится тогда, когда в младших 8 битах результата четное количество единиц. Конечно, его можно и по сей день использовать, но в этом нет уже необходимости. Он вообще, использовался для того чтобы проверять четность результата, приходящего из внешнего мира, например, из UART. Считать раньше количество битов было сложно, и потому флаг использовался чтобы тестировать правильность приходящих данных. С развитием процессоров, эта необходимость отпала.
  • CF — Carry, один из реально самых важных и нужных флагов, наравне с ZF, SF и OF. Эти 4 флага составляют главную тетраду флагов, обязательных и необходимых к внедрению на любом процессоре для полноценной работы. Этот флаг сигнализирует о переносе из старшего разряда или в из младшего, необходим в инструкциях сдвига. Его задача — указывать на то, был ли перенос.

§ Стек

§ Опкод и операнды