Итак, начнем этот урок с очень важной штуки. Введем основные определения,
которых будет немного, они будут просты... но это будут самые важные понятия
во всем этом деле !
Итак, чего вообще мы хотим ? Мы хотим научиться разговаривать с Машиной на ее
языке. Поэтому дадим понятие КОМПЬЮТЕРА(Машины), именно так, как она будет
выглядеть для Вас при программировании на ассемблере:
МАШИНА - нечто, состоящие из ПРОЦЕССОРА, ОПЕРАТИВНОЙ ПАМЯТИ и ПОРТОВ
ВВОДА-ВЫВОДА. ПРОЦЕССОР умеет выполнять ПРОГРАММЫ. Раскроем детальнее эти
понятия:
- ОПЕРАТИВНАЯ ПАМЯТЬ: набор ячеек, в котрые можно записывать числа и из которых
можно читать числа.
- ПОРТЫ ВВОДА-ВЫВОДА: набор ячеек, в котрые можно записывать числа и из которых
можно читать числа.
- ПРОЦЕССОР: функция, понимающая и реализующая числа, записанные
в ОПЕРАТИВНОЙ ПАМЯТИ как действия (КОМАНДЫ).
Выполнение команд назовем ПРОГРАММОЙ.
Итак, это важно !!! - нет никаких жестких дисков, джойстиков, мышей ... Есть только
ПРОЦЕССОР, ОПЕРАТИВНАЯ ПАМЯТЬ и ПОРТЫ ВВОДА-ВЫВОДА. Любое наше действие
будет направлено ТОЛЬКО на работу с этими тремя объектами.
Итак, мы определились: есть ОПЕРАТИВНАЯ ПАМЯТЬ, в ней храняться числа, ПРОЦЕССОР
обрабатывает эти числа и выполняет действия, заданные этими числами. Очевидно из
данных нами определений, что эти действия(КОМАНДЫ) в любом случае могут быть
направлены ТОЛЬКО на измение(чтение/запись) ОПЕРАТИВНОЙ ПАМЯТИ и
(или) ПОРТОВ ВВОДА-ВЫВОДА, либо на изменение состояния самого ПРОЦЕССОРА.
В общем виде КОМАНДУ можно представить в таком виде:
Что_Делать:<Как Делать>.
Как_Делать может быть пропущенно(<...>), ибо Что_Делать: ничего не делать
не требует дополнительных сведений. Да, есть такая команда - Ничего_Не_Делать.
Пример из жизни: команда "Лежать" также не требует дополнительных данных.
Таким образом, процессор читает ОПЕРАТИНВУЮ ПАМЯТЬ и трактует прочитанные
числа как команды. Поняв команду, он выполняет заданные в ней действия.
Команды, как уже было сказано, могут сводится к одному или нескольким
из следующих действий:
- Ничего не делать;
- Изменить что-то в ОПЕРАТИВНОЙ ПАМЯТИ;
- Изменить что-то ПОРТАХ;
- Изменить состояние процессора.
Что значит "Изменить что-то в оперативной памяти" ? Это означает изменить те числа,
которые в ней находятся: прочесть их или записать новое значение числа. То же самое
и с портами ввода-вывода.
Что означает "Изменить состояние процессора" ? Это означает повлиять на то, как он будет
трактовать(исполнять, выполнять) КОМАНДЫ.
Вернемся к второй части КОМНАДЫ - "Как Делать". Очевидно, что в ней должно содержаться
указание на область ОПЕРАТИВНОЙ ПАМЯТИ, если мы хотим ее читать/записывать, либо указание
на область ПОРТОВ, если мы хотим читать/записывать в них. Поэтому введем понятие
ЭФФЕКТИВНОГО АДРЕСА - указатель на конкретный элемент памяти, портов или процессора.
Теперь хватит теории, перейдем к практике. Мы работали в Уроке 1 и будем работать в ближайшее
время с конкретным процессором - 8086 фирмы Intel, выпущенным еще в конце 70-х годов.
Процессор этот выполняет команды, записанные в опеределенного вида памяти, и может,
выполняя команды, изменять определенного вида порты ввода-вывода.
Память эта также есть набор ячеек, в которые можно записывать числа. Каждая ячейка
может хранить числа от 0 до 255. Порты также являются ячейками, в которые можно записывать
/из которых можно читать числа от 0 до 255.
Как храняться числа в этих ячейках ? Каждая из них состоит из 8 так называемых бит. Бит - это нечто,
могущее быть только в двух состояниях - 0/1(Да/Нет,Триггер ... - как Вам нравиться).
Вспомним, что любое целое и неотрицательное число (0,1,2..10000...) можно разложить по степеням
двойки:
25 = 16+8+1 = 1*2^4 + 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 = 1*16+1*8+0*4+0*2+1*1.
Итак, если мы будем трактовать 8 бит каждой такой ячейки, называемой байтом, как
коэффициенты при степени двойки, то получим возможность раскладывать числа
по 8-ми различным степеням:
2^7,2^6... 2^0.
Иначе говоря, каждое число в каждой ячейке оперативной памяти 8086 реализовано
определенным состоянием всех восьми бит этой ячейки:
Число:
25 65 128 255
Бит0 1 1 0 1
Бит1 0 0 0 1
Бит2 0 0 0 1
Бит3 1 0 0 1
Бит4 1 0 0 1
Бит5 0 0 0 1
Бит6 0 1 0 1
Бит7 0 0 1 1.
Каждая такая ячейка называется байтом.
Итак, в 8086 записываются числа в оперативную память. Процессор может читать
эти числа, трактовать их как команды, понимать из команды, какую ячейку памяти ему
нужно изменять. Как же он это делает ? Очевидно, что ему надо знать
ЭФФЕКТИВНЫЙ АДРЕС команды, ЭФФЕКТИВНЫЙ АДРЕС ячейки, которую указано
поменять в ПАМЯТИ или ПОРТАХ. По большому счету, ЭФФЕКТИВНЫЙ АДРЕС - это номер
ячейки(номер байта) в памяти или портах. Нумерация от нуля. Например, байт(ячейка)
номер 20 содержит значение 22.
Как же процессор формирует этот ЭФФЕКТИВНЫЙ АДРЕС ? (Иными словами, где
хранится и как вычисляется номер байта) ?
Как ни странно, но он делает это весьма замысловатым образом: в процессоре имеются
так называемые РЕГИСТРЫ - это тоже ячейки "памяти" самого процессора,
но размером в СЛОВО(два байта). Можно сказать, что у Процессора есть своя небольшая
память, состоящая из набора слов. Если байт - это ячейка из восьми бит, то слово содержит
целых 16 бит, и в него можно записывать числа от 0 до 65535(посмотрите, как хранятся
числа в ячейках-байтах и поймите, какие числа можно хранить в ячейках из 16 бит). Имея
около десятка таких ячеек-слов, процессор использует некоторые из них для вычисления
ЭФФЕКТИВНОГО АДРЕСА. Вы можете подумать, что номер ячейки памяти, содержащую
текущую выполняемую команду, просто храниться в одном из РЕГИСТРОВ, а после
выполнения текущей команды в него заносится номер следующей команды:
Команда1 Байт1, Байт2,Байт3 команды 1. Номер ячейки - 230 (Номер байта1).
Регистр_Текущей_Команды равен 230.
После выполнения команды1 процессор начнет выполнять команду2:
Команда2 Байт1, Байт2 команды 2. Номер ячейки - 233 (Номер байта1).
Регистр_Текущей_Команды равен 233 ?
В какой-то мере у процессора 8086 это так, но в точности - не так !
Скажем вот что: процессор 8086 может работать с памятью размером
в один Мегабайт(примерно миллон ячеек по одному байту). Команды или
какие-то вспомогательные числа могут быть размещены в любой ячейке
с номером от 0 до ~1 миллиона. Т.е. номер текущей выполняемой команды
может быть в этом диапазоне. Процессор может использовать для хранения
этого номера один или несколько из своих регистров размером в слово. Вот
эти регистры, которые есть у него(все они размером в слово):
Сокращенное имя/Полное имя
IP - Instruction Pointer;
CS - Register of Code Segment;
SS - Register of Stack Segment;
SP - Stack Pointer;
ES - Register of Extra Segment;
DS - Register of Data Segment;
AX - Accumulator;
BX - Base Register;
CX - Counter
DX - Data Register;
SI - Source Register;
DI - Destination Register;
BP - Base Pointer;
FLAGS - Flags Register.
Итак, имеем этот набор регистров. Каждый размером в слово. Не обращайте
пока внимание на их названия. Прикинте, как процессор хранит номера ячеек памяти
в них.
Вы могли догадаться, что для хранения номера ячейки недостаточно любого
одного регистра из-за того, что размер памяти 8086 = 1 миллион > максимальное
число, которое можно хранит в одном слове - 65535. Два будет абсолютно достаточно,
ибо число ~один миллион легко раскладывается по степеням двойки вплоть до 20 степени,
а, значит, нам достаточно иметь всего 20 бит для его хранения ... А два регистра
обладают уже 32 битами. Например, можно придумать такую схему размещения
номера байта(ЭФФЕКТИВНОГО АДРЕСА) в двух регистрах:
1-ый регистр - 16 младших(первых) битов числа ЭФФЕКТИВНОГО АДРЕСА;
2-ой регистр - 4 старших(вторая часть) битов.
Но это не так !!! Вот что делает этот хитрый процессор: он использует действительно
два регистра, но значение одного из них он умножает на 16 и к полученному значению
прибавляет значение из другого регистра или самой команды непосредственно:
ЭФФЕКТИВНЫЙ АДРЕС = Регистр1 x 16 + Регистр 2(или непосредственное значение).
Возьмем команду из Урока 1:
mov byte ptr es:[320*10+10],12
Эта команда предназначена для того, чтобы записать в ячейку памяти номер
658570 числа 12. Смотрите, что в этой команде задано:
Что_Делать: Записать число в память.
Как Делать:
Число взять из самой команды - 12;
Адрес памяти вычислить, используя регистр es и непосредственное значение,
указанное в команде - 320*10+10;
Размер применика(числа в памяти для записи) - один байт.
Сейчас обратим внимание ТОЛЬКО на то, как 8086 ухитрился вычислить адрес
ячейки памяти из этой команды. Смотрите:
Регистр1 = Регистр ES(в команде так и задано)
Непосредственное значение для прибавки = 320*10+10 (тоже задано в команде)
ЭФФЕКТИВНЫЙ АДРЕС = ES x 16 + 320*10+10 = 658570.
Кто же задал значение регистра ES Ясно дело, что мы ! Мы контролируем
весь процесс выполнения программы процессором ! А как мы это сделали ?
А вот как:
mov ax,0A000h
mov es,ax
Мы тут задали значение регистра ES - 0A000h = 40960.
Теперь Вы понимаете, что меняя "Что-то" в командах Урока1:
mov byte ptr es:[Что-то],Еще что-то
вы меняли Адрес Ячейки Памяти !
А вот решение задания:
mov byte ptr es:[320*160+110],9
mov byte ptr es:[320*160+111],9
mov byte ptr es:[320*160+112],9
mov byte ptr es:[320*161+110],9
mov byte ptr es:[320*161+111],9
mov byte ptr es:[320*161+112],9
mov byte ptr es:[320*162+110],9
mov byte ptr es:[320*162+111],9
mov byte ptr es:[320*162+112],9
- синий квадратик.
Сейчас у Вас должно возникнуть еще больше вопросов, нежели после
первого урока !!! Но и должно появиться смутное ощущение ПАМЯТИ,
которой Вы можете управлять такими вот командами.
А теперь - на закуску - Косвенная адресация !!!
Уже было сказано, как 8086 может вычислять адрес в памяти:
ЭФФЕКТИВНЫЙ АДРЕС = Регистр1 x 16 + Регистр 2(или непосредственное значение).
Только что мы видели, как он использует непосредственное значение, взятое
из команды. А теперь поглядите, как можно заставить процессор использовать
второй вариант - через использование двух регистров:
mov bx,320*180+100 {Заносим значение(число) 320*180+100 в регистр BX}
mov byte ptr es:[bx],2 {Изменим память, используя ДВА регистра для адресации}
В последней команде эффективный адрес будет вычислен так:
ЭФФЕКТИВНЫЙ АДРЕС = ES x 16 + BX.
Теперь попробуйте использовать для рисования комнады, использующие
такой способ адресации, например:
{Рисуем - три !!!}
asm
mov bx,320*180+100 {Заносим значение(число) 320*180+100 в регистр BX}
mov byte ptr es:[bx],2 {Изменим память, используя ДВА регистра для адресации}
mov bx,320*180+101 {Заносим значение(число) 320*180+101 в регистр BX}
mov byte ptr es:[bx],2 {Изменим память, используя ДВА регистра для адресации}
mov bx,320*180+102 {Заносим значение(число) 320*180+102 в регистр BX}
mov byte ptr es:[bx],2 {Изменим память, используя ДВА регистра для адресации}
end;
Если Вы выполните этот код, то должны увидеть маленькую зеленую полоску
внизу и посередине экрана.
Задание: попробуйте сделать эту полоску потолще и подлиннее, но используя
именно такие команды - mov byte ptr es:[bx]. Попытайтесь вместо таких команд
использовать уже известные Вам - типа mov byte ptr es:[320*180+100],2.
Урок 2 закончен. |