Регистры - это маленькие ячейки памяти расположенные непосредственно на самом процессоре компьютера, поэтому, скорость обращения к ним очень высока и почти равна тактовой частоте процессора. То есть, если тактовая частота вашего процессора равна 3000 Гц, то скорость выполнения операций будет больше 2500 в секунду, именно поэтому ассемблер был и будет самым быстрым языком программирования.
В этой статье мы будем рассматривать 16-ти битный ассемблер архитектуры 8086 на которой работают DOS-овские программы - он проще чем, скажем, ARM архитектура на которой написаны программы для смартфонов, 8086 позволяет лучше понять, как работает память и "железо" компьютера в отличии от 32-х битного ассемблера, на котором работают ОС Windows и Linux. 16-й режим называют "реальным режимом" потому, что он напрямую обращается к аппаратуре компьютера, а 32-й называется "защищенным режимом", здесь прямой доступ пользователя к аппаратуре закрыт и осуществляется через операционную систему которая установлена на компьютере.
Пользовательские регистры.
Пользовательский 16-ти битный регистр состоит из двух частей по 8 бит, которые имеют свои "имена", для примера возьмем регистр AX, он состоит из старшего байта AH и младшего AL, то есть если представить схематично, то выглядит он примерно так:
________AX_________
___AH________AL____
00000000 00000000
Старший бит находится слева, а младший самый правый.
32-х битный регистр будет выглядеть так:
__________________EAX_________________
____________________________AX________
_______________________AH_______AL____
00000000 00000000 00000000 00000000
то есть старшие два байта не имеют своих названий. Он получает дополнительную букву "E", что означает "extension", или "расширенный" в переводе с английского.
Сейчас компьютеры работают уже на 64-х битных процессорах, здесь регистр называется уже RAX, который включает в себя четыре новых байта не имеющих имен, EAX, AX, AH и AL соответственно. Я не буду его изображать - слишком много нулей.
Значение в регистр грузится с помощью команды MOV (от англ. "move" - переместить), причем значение перемещается справа налево через запятую, например загрузим в регистр AX значение 256:
MOV AX, 256
теперь AX = 256
загрузим значение находящееся в AX в другой регистр - BX:
MOV BX, AX
AX = 256
BX = 256
нередко в ассемблере бывает необходимо загрузить битовое значение:
MOV AX, 1000100000010110b
еще чаще используются шестнадцатеричные значения:
MOV AX, 5Ah
если строковое значение впереди, например A5h, то перед ним ставится "0", чтобы компилятор понял, что это цифра:
MOV AX, 0A5h
Пользовательские регистры:
AX (AH, AL) - аккумулятор и арифметический регистр
BX (BH, BL) - регистр базы
CX (CH, CL) - счетчик
DX (DH,DL) - регистр данных
это не значит, что, например, CX должен обязательно использоваться как счетчик, вы можете загрузить в него какое-то свое значение и использовать его для арифметических операций, но этот регистр "связан" с командами LOOP и REP, поэтому если вы организуете цикл в своей программе, то этот регистр желательно оставлять для использования как счетчик цикла, то же касается и других регистров. Здесь надо отметить, что есть команды "вшитые" в процессор, они называются опкоды (opcode) и определенные регистры связаны с определенными командами на уровне архитектуры процессора. Опкоды можно скачать из интернета, запрос лучше производить на английском - получите больше информации.
Сегментные регистры.
Размер сегмента памяти может быть от 16 до 65536 байт (16 х 16 = 256, 256 х 256 = 65536), так как отсчет в компьютере начинается с нуля, то первое значение будет = 0, последнее = 65535. Для того, чтобы получить доступ к каким-то значениям в памяти, вся память разбита на сегменты, а чтобы получить доступ к самим сегментам, существуют сегментные регистры и регистры указатели. Сегментный регистр всегда указывает на начало сегмента, а регистр указатель на смещение относительно этого регистра. "offset" - смещение, запомните это слово, если вы будете изучать программирование, или просто интересуетесь компьютерами, то оно будет часто вам встречаться.
Сегментный регистр и его смещение принято записывать так:
0000:0000
т.е.:
сег.регитр : смещение в указателе
например возьмем регистр ES, он как бы "связан" с указателем DI. Рассмотрим ситуацию которая часто встречается в программировании на ассемблере - мы хотим записать в видеопамять некоторое значение. Текстовая видеопамять начинается с адреса B800h, мы хотим записать значение по адресу B800:0056h, нам надо учитывать, что к сегментным регистрам нельзя обращаться напрямую, поэтому обращаемся к нему через регистр AX (опять-таки архитектура процессора):
MOV AX, 0B800H
MOV ES, AX
XOR DI, DI
MOV DI, 56H
Обратите внимание на строку XOR DI,DI - она обнуляет регистр к которому обращается (логическое вычитание - позже мы разберемся как оно работает). Обнуление необходимо для того, чтобы затереть предыдущее значение. Например такая ситуация:
AX = 3415H ; AX содержит 3415H
мы грузим:
MOV AL, 3
значение загрузится в AL, в AH останется 34H, в итоге значение:
AX = 3403H
Ошибка!!! довольно грубая, но довольно частая в ассемблере. Я привел, может быть, не совсем удачный пример, все происходит обычно немного иначе, но этот пример наглядно позволяет понять для чего надо обнулять регистры перед загрузкой в них чисел.
Сегментные регистры:
CS - (code segment) этот регистр указывает на начало сегмента содержащего код выполняемой программы, а регистр IP - указатель инструкции (instruction pointer) указывает на адрес инструкции, которая должна быть выполнена следующей, то есть как-то так:
CS = сегмент 8561h (например)
8561:0000 -начало кода
....
8561: IP = 0100 MOV AX, 0003h
IP указывает на адрес 8561:0100, после выполнения инструкции, находящейся по этому адресу (MOV AX,0003h), значение находящееся в IP увеличится на два байта и будет указывать на следующую инструкцию находящуюся по адресу 8561:0102 (INT 10h) и т.д.
8561:0102 INT 10h
8561:0104 MOV AX,0500h
DS - (data segment) указывает на начало сегмента данных. Используется он в паре с пользовательским регистром DX и зачастую с регистром-указателем SI - индекс источника (source index).
SS - (stack segment) указывает на начало (вернее конец) сегмента стека и используется вместе с регистром-указателем SP - указатель стека (stack pointer), этот регистр работает примерно также как и регистр IP в сегменте кода, и регистром-указателем BP - указатель базы (base pointer). Здесь надо отметить, что стек растет снизу вверх, т. е., примерно так:
....
и т.д.
SS:FFFA
SS:FFFC
SS:FFFE
ES - (extra segment) указывает на начало дополнительного пользовательского сегмента, используется обычно с регистром-указателем DI - индекс приемника (destination index)
Сегментные регистры FS и GS появились в Windows, но ничто не мешает вам использовать их в своих 16-х программах, работают они также как и регистр ES.
Регистры DI, SI и BP можно использовать как обычные пользовательские регистры.
Флаговый регистр.
FLAGS - регистр флагов или как его еще называют - регистр признаков, меняет значение своих битов в зависимости от результата той или иной операции. Получить значения битов флагового регистра можно так:
PUSHF
копируем значения регистра в стек
POP AX
возвращаем эти значения в регистр AX
Опишу некоторые значения этого регистра:
Бит 0: CF (Carry flag) - принимает значение 1 если в результате какой-либо арифметической операции произошло переполнение регистра-приемника, например, в случае если к 0FFFFh прибавить 1, то значение уже не помещается в регистре.
Бит 2: PF (Parity flag) - флаг четности, принимает значение 1 если значение младшего байта регистра четное, и 0 если нет.
Бит 4: AF (Auxiliary carry flag) - флаг вспомогательного переноса, применяется при работе с BCD-числами (упакованное число), это отдельная тема для разговора - его надо рассматривать при изучении упакованных и не упакованных чисел.
Бит 6: ZF (Zero flag) - флаг нуля, устанавливается в том случае, если результат операции равен нулю, например, в результате уменьшающегося цикла (декремента) в регистре CX устанавливается значение 0.
Бит 7: SF (Sign flag) - флаг знака (плюс или минус), всегда равен старшему (самому левому) биту результата операции: 1 - минус, 0 - плюс.
Бит 8: TF (Trace flag) - флаг трассировки (ловушка), используется 16-ти битными программами-отладчиками. Установка этого флага в 1 приводит к тому, что после каждого "шага" программы (увеличения IP регистра), происходит остановка и управление передается программе-отладчику.
Бит 9: IF (Interrupt flag) - флаг прерываний. Установка этого флага в 0 (команда CLI) приводит к тому, что компьютер перестает реагировать на внешние устройства и программа выполняется до того момента, пока в программе не встретится команда STI - включение прерываний.
Бит 10: DF (Directory flag) - флаг направления, используется при работе со строками и устанавливает направление чтения: слева - направо (0) и справа - налево(1).
Бит 11: OF (Overflow flag) - флаг переполнения, устанавливается в случае, если в результате операций со знаковыми числами (положительные, отрицательные) произошло переполнение. То есть он устанавливается в 1 в случае если при сложении двух положительных чисел старший бит (самый левый) равен 1, дело в том, что в положительных числах старший бит равен нулю, в отрицательных равен 1-му.
PS. Старался описывать все наиболее доступно и полно, незнаю как это у меня получилось. Желаю всем любителям успехов в программировании!
#регистры #ассемблер #программирование