Эти уроки предназначены для тех, кто вообще не знаком с Ассемблером, или имеет весьма отдаленное представление о нем. Конечно, если вы имеете опыт программирования на других языках (Basic, C/C++, Pascal...), то это вам очень поможет.
Но даже если вы хорошо знаете Ассемблер, то просмотр этого документа вам не помешает, так как здесь вы можете изучить синтаксис Emu8086.
Подразумевается, что вы имеете некоторое представление о системах счисления (HEX/BIN). Ну а если нет, то рекомендуем вам изучить раздел Системы счисления перед тем, как вы продолжите ознакомление с этим документом.
Что такое язык Ассемблера?
Язык Ассемблера - это язык программирования низкого уровня. Для начала вы должны ознакомиться с общей структурой компьютера, чтобы в дальнейшем понимать, о чем идет речь. Упрощенная модель компьютера:
System bus - системная шина (окрашена желтым цветом) соединяет различные компоненты компьютера.
CPU - центральный процессор - это сердце компьютера. Большинство вычислений происходит в CPU.
RAM - оперативная память (ОЗУ). В оперативную память загружаются программы для выполнения.
Строение процессора
РЕГИСТРЫ ОБЩЕГО НАЗНАЧЕНИЯ
Процессор 8086 имеет 8 регистров общего назначения, каждый регистр имеет имя:
• AX - регистр-аккумулятор (разделен на два регистра: AH и AL). • BX - регистр базового адреса (разделяется на BH / BL). • CX - регистр-счетчик (разделяется на CH / CL). • DX - регистр данных (разделяется на DH / DL). • SI - регистр - индекс источника. • DI - регистр - индекс назначения. • BP - указатель базы. • SP - указатель стека.
Несмотря на имя регистра, программист сам определяет, для каких целей использовать регистры общего назначения. Основное назначение регистра - хранение числа (переменной). Разрядность вышеописанных регистров 16 бит, т.е., например,0011000000111001b (в двоичной системе) или 12345 в десятичной (человеческой) системе.
4 регистра общего назначения (AX, BX, CX, DX) разделены на две части. К каждой части можно обращаться как к отдельному регистру. Например, если AX=0011000000111001b, то AH=00110000b, а AL=00111001b. Старший байт обозначается буквой "H", а младший байт - буквой "L".
Поскольку регистры расположены внутри процессора, то работают они значительно быстрее, чем память. Обращение к памяти требует использования системной шины, а на это уходит больше времени. Обращение к регистрам вообще не отнимает время. Поэтому вы должны стараться хранить переменные в регистрах. Количество регистров очень небольшое и многие регистры имеют специальное назначение, которое не позволяет использовать их для хранения переменных, но все же они являются наилучшим местом для записи временных данных и вычислений.
СЕГМЕНТНЫЕ РЕГИСТРЫ
• CS - указывает на сегмент, содержащий начальный адрес текущей программы. • DS - обычно указывает на начальный адрес сегмента данных (переменных). • ES - дополнительный сегментный регистр. • SS - содержит начальный адрес сегмента стека.
Хотя в сегментных регистрах можно хранить любые данные, делать это неразумно. Сегментные регистры имеют строго определенное назначение - обеспечение доступа к блокам памяти.
Сегментные регистры работают совместно с регистрами общего назначения для доступа к памяти. Например, если мы хотим получить доступ к памяти с физическим адресом 12345h (в шестнадцатиричном исчислении), мы должны установить DS = 1230h и SI = 0045h. И это правильно, потому что таким образом мы можем получить доступ к памяти, физический адрес которой больше, чем значение, которое может поместиться в одиночном регистре.
Процессор вычисляет физический адрес, умножая значение сегментного регистра на 10h и прибавляя к полученному результату значение регистра общего назначения(1230h * 10h + 45h = 12345h):
Адрес, сформированный с помощью двух регистров, называетсяреальным адресом.
По умолчанию регистры BX, SI и DI работают с сегментным регистром DS; регистры BP и SP работают с SS.
Другие регистры общего назначения не могут формировать реальный адрес!
Также, хотя BX может формировать реальный адрес, BH и BL не могут!
РЕГИСТРЫ СПЕЦИАЛЬНОГО НАЗНАЧЕНИЯ
• IP - командный указатель. • Флаговый регистр - определяет текущее состояние процессора.
Регистр IP всегда работает совместно с сегментным регистром CS и указывает на выполняемую в данный момент команду.
Флаговый регистр автоматически изменяется процессором после математических операций. Он позволяет определять тип результата и передавать управление другим участкам программы.
Вообще вы не можете напрямую обращаться к этим регистрам.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Для доступа к памяти можно использовать следующие четыре регистра: BX, SI, DI, BP.
Комбинируя эти регистры внутри квадратных скобок [ ], мы можем получить доступ к различным местоположениям в памяти. Возможны следующие комбинации (режимы адресации):
Подстановка может быть непосредственным значением или смещением переменной, или даже и тем и другим. Она преобразуется компилятором в одиночное непосредственное значение.
Подстановка может быть как внутри так и вне квадратных скобок ([ ]), компилятор генерирует одинаковый машинный код в обоих случаях.
Подстановка - это величина со знаком, поэтому она может быть как положительной, так и отрицательной.
Обычно компилятор различает d8 и d16 и генерирует требуемый машинный код.
Например, представим что DS = 100, BX = 30, SI = 70. Следующий способ адресации: [BX + SI] + 25 вычисляется процессором для этого физического адреса: 100 * 16 + 30 + 70 + 25 = 1725.
По умолчанию сегментный регистр DS используется для всех способов адресации, кроме способа, который используется с регистром BP. В последнем случае используют сегментный регистр SS.
Легко запомнить все возможные комбинации с помощью таблицы:
Вы можете формировать все имеющие силу комбинации, взяв по одному пункту из каждого столбца, либо пропустить какой-либо столбец и ничего из него не взять. Как вы можете видеть, BX и BP никогда не идут вместе. SI и DI также не могут быть вместе. Здесь приведен пример имеющего силу способа адресации: [BX+5].
Значение в сегментном регистре (CS, DS, SS, ES) называется "segment (сегмент)", а значение в регистре общего назначения (BX, SI, DI, BP) называется "offset (смещение)". Если DS содержит значение 1234h, а SI содержит значение 7890h, то это можно записать как 1234:7890. Физический адрес будет таким: 1234h * 10h + 7890h = 19BD0h.
Чтобы указать компилятору тип данных, должны использоваться эти префиксы:
BYTE PTR - для байта. WORD PTR - для слова (два байта).
Например: BYTE PTR [BX] ; доступ к байту. или WORD PTR [BX] ; доступ к слову. Emu8086 поддерживает короткие префиксы:
b. - для BYTE PTR w. - для WORD PTR
иногда компилятор может вычислить тип данных автоматически, но вы не можете и не должны полагаться на это, если один из операндов является непосредственным значением.
Команда MOV
• Копирует второй операнд (источник) в первый операнд (приемник). • Операнд-источник может быть непосредственным значением, регистром общего назначения или местоположением памяти. • Регистр-приемник может быть регистром общего назначения или местоположением памяти. • Оба операнда должны иметь одинаковый размер байта или слова.
Команда MOVне может использоваться для установки значений регистров CS и IP.
Здесь короткая программа, которая демонстрирует использование команды MOV:
#MAKE_COM# ; команда компилятору для создания СОМ-файла. ORG 100h ; директива, необходимая для COM-программы. MOV AX, 0B800h ; установить AX в шестнадцатиричное значение B800h. MOV DS, AX ; копировать значение из AX в DS. MOV CL, 'A' ; установить в CL ASCII-код символа 'A', т.е. 41h. MOV CH, 01011111b ; установить CH в двоичное значение. MOV BX, 15Eh ; установить BX в 15Eh. MOV [BX], CX ; копировать содержимое из CX в память с адресом B800:015E RET ; вернуться в операционную систему.
Вы можете скопировать и вставить вышеописанную программу в редактор кода Emu8086, и нажать кнопку [Compile and Emulate] (или нажать клавишу F5 на клавиатуре).
Окно эмулятора должно открыться с загруженной программой. Щелкните кнопку [Single Step] (пошаговый режим) и наблюдайте за содержимым регистров.
Как скопировать и вставить:
1. Выделите техт программы с помощью мыши: щелкните перед текстом и, не отпуская кнопки, продвигайте мышь, пока не будет выделен нужный текст. 2. Нажмите комбинацию клавиш Ctrl + C для копирования. 3. Перейдите в редактор кода Emu8086 и нажмите комбинацию клавиш Ctrl + V, чтобы вставить текст.
Как вы могли догадаться, точка с запятой (";") используется для комментариев. Все символы, которые следуют за ";", игнорируются компилятором.
Вы должны увидеть нечто подобное, когда программа закончит свою работу:
Фактически, вышеописанная программа записывает данные непосредственно в видеопамять, так что MOV - это очень мощная инструкция.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Дата: Воскресенье, 28.06.2015, 21:45 | Сообщение # 4
Агро-Разработчик
[ Легенда Зоны ]
Часть 3: Переменные
Переменные хранятся в памяти по определенным адресам. Программисту проще иметь дело именами переменных, чем с адресами в памяти. Например, переменная с именем "var1" будет более понятна в коде программы, чем адрес 5A73:235B, особенно когда количество переменных велико.
Наш компилятор поддерживает два типа переменных: BYTE и WORD.
Синтаксис для объявления переменных:
имяDBзначение
имяDWзначение
DB - Define Byte - определяет байт. DW - Define Word - определяет слово.
имя - может быть любой комбинацией букв или цифр, но должно начинаться с буквы. Можно объявлять безымянные переменные, которые имеют адрес, но не имеют имени.
значение - может быть любой числовой величиной, которая представлена в какой-либо из поддерживаемых систем счисления (шестнадцатиричной, двоичной или десятичной). Значение может также быть симолом "?" для не инициализированных переменных.
Как вы уже знаете из части 2 этих уроков, команда MOV используется для копирования значения из источника в приемник.
Давайте посмотрим другой пример с командой MOV:
#MAKE_COM# ORG 100h
MOV AL, var1 MOV BX, var2
RET ; завершение программы.
VAR1 DB 7 var2 DW 1234h
Скопируйте вышеприведенный код в редактор кода Emu8086 и нажмите клавишу F5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы увидите примерно такую картину:
На рисунке вы можете заметить команды, похожие на те, что используются в нашем примере. Только переменные заменены фактическими местоположениями в памяти. Когда компилятор создает машинный код, он автоматически заменяет имена всех переменных их смещениями. По умолчанию сегмент загружен в регистр DS (в COM-файлах значение регистра DS устанавливается таким же, что и значение в регистре CS - сегменте кода).
В таблице памяти (memory) первый столбец - это смещение, второй столбец - это шестнадцатиричное значение, третий столбец - десятичное значение, а последний столбец - это символ ASCII, соответствующий данному числу.
Компилятор не чувствителен к регистру, поэтому "VAR1" и "var1" - это одно и то же.
Смещение переменной VAR1 - это 0108h, а полный адрес -0B56:0108.
Смещение переменной var2 - это 0109h, а полный адрес -0B56:0109. Эта переменная имеет тип WORD, поэтому занимает 2 БАЙТА. Принято младший байт записывать по меньшему адресу, поэтому 34h размещается перед 12h.
Вы можете увидеть некоторые другие инструкции после команды RET. Это случается потому, что дизассемблер не знает, где начинаются данные. Он только обрабатывает значения в памяти и понимает их как имеющие силу инструкции процессора 8086 (мы изучим их позже).
Вы можете даже написать программу, используя только директиву DB:
#MAKE_COM# ORG 100h
DB 0A0h DB 08h DB 01h
DB 8Bh DB 1Eh DB 09h DB 01h
DB 0C3h
DB 7
DB 34h DB 12h
Скопируйте вышеприведенный код в редактор кода Emu8086 и нажмите клавишу F5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы получите тот же самый дизассемблированный код и тот же самый результат работы программы!
Как вы можете догадаться, компилятор только преобразует исходный код программы в набор байтов. Этот набор байтов называется машинным кодом. Процессор обрабатывает машинный код и выполняет его.
ORG 100h - это директива компилятора (она указывает компилятору как обрабатывать исходный код). Эта директива очень важна при работе с переменными. Она указывает компилятору, какой исполняемый файл будет загружаться в смещение (offset) 100h (256 байтов), так что компилятор должен вычислить правильный адрес для всех переменных, когда он размещает имена переменных с их смещениями. Директивы никогда не преобразуются в какой-либо реальный машинный код. Почему исполняемый файл загружается по смещению 100h? Операционная система хранит некоторые данные о программе в первых 256 байтах, начиная от CS(сегмента кода), такие как параметры командной строки и т.д. Все это справедливо только для COM-файлов, файлы EXE загружаются по смещению 0000, и обычно используют специальный сегмент для переменных. Может быть, мы поговорим об EXE-файлах позже.
Массивы
Массив можно рассматривать как цепочку переменных. Текстовая строка - это пример массива байтов, в котором каждый символ представлен значением ASCII-кода (0..255).
Вот некоторые примеры определения массивов:
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h b DB 'Hello', 0
b - это точная копия массива a - когда компилятор видит строку, заключенную в кавычки, он автоматически преобразует ее в набор байтов. Эта таблица показывает участок памяти, где эти массивы объявлены:
Вы можете получить значение любого элемента массива, используя квадратные скобки, например:
MOV AL, a[3]
Вы можете также использовать какой-либо из регистров BX, SI, DI, BP, например:
MOV SI, 3 MOV AL, a[SI]
Если необходимо объявить большой массив, вы можете использовать оператор DUP.
Синтаксис для DUP:
количество DUP ( значение(я) )
количество - количество дубликатов (любая константа). значение - выражение, которое будет дублироваться оператором DUP.
например:
c DB 5 DUP(9)
а это альтернативный способ объявления:
c DB 9, 9, 9, 9, 9
еще один пример:
d DB 5 DUP(1, 2)
а это альтернативный способ объявления:
d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2
Конечно, вы можете использовать DW вместо DB, если требуется хранить числа более 255, или менее -128. DW не может быть использован для объявления строк!
Оператор DUP не может содержать более 1020 знаков в качестве операнда! (в последнем примере 13 знаков). Если вам необходимо объявить очень большой массив, разделите его на две строки (вы получите один большой массив в памяти).
Получение адреса переменной
Есть такая команда LEA (Load Effective Address) и альтернативный оператор OFFSET. Как OFFSET так и LEA могут быть использованы для получения смещения адреса переменной.
LEA более мощная, т.к. она также позволяет вам получить адрес индексированных переменных. Получение адреса переменной может быть очень полезно в различных ситуациях, например, если вам необходимо поместить параметр в процедуру.
Напоминание: Чтобы указать компилятору тип данных, вы должны использовать следующие префиксы:
BYTE PTR - для байта. WORD PTR - для слова (два байта).
Например: BYTE PTR [BX] ; доступ к байту. или WORD PTR [BX] ; доступ к слову. Emu8086 поддерживает короткие префиксы:
b. - для BYTE PTR w. - для WORD PTR
иногда компилятор может вычислить тип данных автоматически, но вы не можете и не должны полагаться на это, если один из операндов является непосредственным значением.
Здесь первый пример:
ORG 100h
MOV AL, VAR1 ; проверить значение VAR1, поместив ее в AL.
LEA BX, VAR1 ; записать адрес переменной VAR1 в BX.
MOV BYTE PTR [BX], 44h ; изменить содержимое переменной VAR1.
MOV AL, VAR1 ; проверить значение VAR1, поместив ее в AL.
RET
VAR1 DB 22h
END
Здесь другой пример, который использует OFFSET вместо LEA:
ORG 100h
MOV AL, VAR1 ; проверить значение VAR1, поместив ее в AL.
MOV BX, OFFSET VAR1 ; записать адрес переменной VAR1 в BX.
MOV BYTE PTR [BX], 44h ; изменить содержимое переменной VAR1.
MOV AL, VAR1 ; проверить значение VAR1, поместив ее в AL.
RET
VAR1 DB 22h
END
Оба примера функционально идентичны.
Эти строки:
LEA BX, VAR1 MOV BX, OFFSET VAR1
даже компилируются в одинаковый машинный код: MOV BX, num num - это 16-битовое значение смещения переменной.
Пожалуйста учтите, что только эти регистры могут использоваться внутри квадратных скобок (как указатели памяти):
BX, SI, DI, BP!
(См. предыдущую часть уроков).
Константы
Константы подобны переменным, но они существуют до того, как ваша программа откомпилирована (ассемблирована). После определения константы ее значение не может быть изменено. Для определения константы используется директива EQU:
имя EQU < любое выражение > Например:
k EQU 5
MOV AX, k
Этот пример функционально идентичен коду:
MOV AX, 5
Вы можете наблюдать переменные во время выполнения программы, если выберите пункт "Variables" в меню "View" эмулятора.
Чтобы наблюдать массивы, вы должны щелкнуть по переменной и установить свойство Elements - размер массива. В Ассемблере нет строгих типов данных, поэтому любые переменные могут быть представлены как массив.
Переменная может быть просмотрена в любой числовой системе:
HEX - шестнадцатиричная (основа 16).
BIN - двоичная (основа 2).
OCT - восмеричная (основа 8).
SIGNED - десятичная со знаком (основа 10).
UNSIGNED - десятичная без знака (основа 10).
CHAR - коды ASCII-символов (всего 256 символов, некоторые символы невидимы).
Вы можете редактировать переменные, когда ваша программа выполняется, просто щелкнув дважды по переменной или выбрать ее и щелкнуть кнопку Edit.
Можно вводить числа в любой системе, шестнадцатиричные цифры должны иметь суффикс "h", двоичные - суффикс "b", восмеричные - суффикс "o", десятичные цифры не требуют суффикса. Строка может быть введена следующим способом:
'hello world', 0
(эта строка заканчивается нулем).
Массив может быть введен следующим способом:
1, 2, 3, 4, 5
(массив может быть массивом байтов или слов, это зависит от того, выбран ли BYTE или WORD для введенной переменной).
Выражения преобразуются автоматически, например:
если введено это выражение:
5 + 2
оно будет преобразовано в 7 и т.п...
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Дата: Воскресенье, 28.06.2015, 21:46 | Сообщение # 5
Агро-Разработчик
[ Легенда Зоны ]
Часть 4: Прерывания
Прерывания можно рассматривать как номер функции. Эти функции делают программирование более легким - вместо написания кода путем печатания символов вы можете просто вызвать прерывание и оно все сделает за вас. Существуют также функции прерываний, которые работают с дисками и другим "железом". Мы называем такие функции программными прерываниями.
Прерывания могут быть также вызваны различными устройствами. Такие прерывания называются аппаратными прерываниями. Но сейчас нас интересуют только программные прерывания.
Чтобы выполнить программное прерывание, используют команду INT, которая имеет очень простой синтаксис: INT значение Где значение может быть числом в диапазоне от 0 до 255 (или от 0 до 0FFh), обычно мы будем использовать шестнадцатиричные числа. Вы можете подумать, что имеются только 256 функций, но это не так. Каждое прерывание может иметь подфункции. Чтобы определить подфункцию, в регистр AH нужно записать ее номер перед вызовом прерывания. Каждое прерывание может иметь до 256 подфункций (таким образом мы получаем 256 * 256 = 65536функций). В основном используется регистр AH, но иногда могут использоваться и другие регистры. Обычно другие регистры используются для записи параметров и данных подфункции.
Следующий пример использует прерывание INT 10h и подфункцию 0Eh, чтобы напечатать сообщение "Hello!". Эта функция выводит символ на экран, перемещая курсор и прокручивая экран по необходимости.
#MAKE_COM# ; инструкция компилятора для создания СОМ-файла.
ORG 100h
; Подфункция, которую мы используем
; не изменяет регистр АН после завершения,
; так что мы можем использовать его только один раз
MOV AH, 0Eh ; выбор подфункции.
; Подфункция INT 10h / 0Eh принимает
; в качестве параметра ASCII-код символа,
; который нужно записывать в регистр AL.
MOV AL, 'H' ; ASCII-код: 72
INT 10h ; напечатать его!
MOV AL, 'e' ; ASCII-код: 101
INT 10h ; напечатать его!
MOV AL, 'l' ; ASCII-код: 108
INT 10h ; напечатать его!
MOV AL, 'l' ; ASCII-код: 108
INT 10h ; напечатать его!
MOV AL, 'o' ; ASCII-код: 111
INT 10h ; напечатать его!
MOV AL, '!' ; ASCII-код: 33
INT 10h ; напечатать его!
RET ; вернуться в операционную систему.
Скопируйте и вставьте эту программу в редактор кода Emu8086 и нажмите кнопку [Compile and Emulate]. Запустите ее!
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Дата: Воскресенье, 28.06.2015, 21:46 | Сообщение # 6
Агро-Разработчик
[ Легенда Зоны ]
Часть 5: Библиотека общих функций - emu8086.inc
Чтобы облегчить программирование, имеются несколько общих функций, которые можно включать в вашу программу. Чтобы использовать в вашей программе функции, определенные в другом файле, вы должны применить директиву INCLUDE, за которой следует имя файла. Компилятор автоматически найдет файл в той же папке, где размещен файл с исходным кодом программы, а если там этого файла не окажется, то поиск будет продолжен в папке Inc.
Возможно, что на сегодняшний момент вы не сможете полностью понять содержимое emu8086.inc (расположенной в каталоге Inc), но это не страшно, т.к. вам достаточно понять, для чего все это нужно.
Чтобы использовать какие-либо функции в emu8086.inc, вы должны вписать следующую строку в вашем исходном файле:
include 'emu8086.inc'
emu8086.inc определяет следующие макросы:
PUTC char - макрос с одним параметром, печатает ASCII-символ в текущей позиции курсора.
GOTOXY col, row - макрос с двумя параметрами, устанавливает позицию курсора.
PRINT string - макрос с одним параметром, печатает строку.
PRINTN string - макрос с одним параметром, печатает строку. Это тоже самое, что и PRINT, но автоматически добавляется "перевод каретки" в конце строки (аналогично процедуре Writeln в Паскале).
CURSOROFF - скрывает текстовый курсор.
CURSORON - показывает текстовый курсор. Чтобы использовать какой-либо из вышеописанных макросов, просто напечатайте его имя в нужном месте вашего кода и, если необходимо, параметры. Например:
include emu8086.inc
ORG 100h
PRINT 'Hello World!'
GOTOXY 10, 5
PUTC 65 ; 65 - это ASCII-код для буквы 'A' PUTC 'B'
RET ; вернуться в операционную систему. END ; директива для окончания компиляции.
Когда компилятор обрабатывает ваш исходный код, он ищет файлemu8086.inc для объявленных макросов и заменяет имя макроса реальным кодом. Вообще макросы - это относительно небольшие участки кода, частое использование макросов может сделать вашу программу (исполнимый файл) слишком большой (для оптимизации размера лучше использовать процедуры).
emu8086.inc также определяет следующие процедуры:
PRINT_STRING - процедура для печати строки с нулевым окончанием с текущей позиции курсора. Получает адрес строки в регистре DS:SI. Чтобы использовать эту процедуру, следует объявить: DEFINE_PRINT_STRING перед директивой END.
PTHIS - процедура для печати строки с нулевым окончанием с текущей позиции курсора (как и PRINT_STRING), но получает адрес из стека. Строка с нулевым окончанием должна быть определена только после команды CALL. Например:
CALL PTHIS db 'Hello World!', 0Чтобы использовать эту процедуру, следует объявить: DEFINE_PTHIS перед директивой END.
GET_STRING - процедура для получения строки с нулевым окончанием от пользователя. Принятая строка записана в буфер, адрес которого указан в DS:DI, размер буфера должен быть в DX. Процедура завершает ввод, если нажата клавиша 'Enter'. Чтобы использовать эту процедуру, следует объявить: DEFINE_GET_STRING перед директивой END.
CLEAR_SCREEN - процедура для очистки экрана (выполняет полную прокрутку экрана и устанавливает курсор в его верхней части) Чтобы использовать эту процедуру, следует объявить: DEFINE_CLEAR_SCREEN перед директивой END.
SCAN_NUM - процедура, которая получает многозначное число СО ЗНАКОМ с клавиатуры, и записывает результат в регистр CX. Чтобы использовать эту процедуру, следует объявить: DEFINE_SCAN_NUM перед директивой END.
PRINT_NUM - процедура, которая печатает число со знаком, которое находится в регистре AX. Чтобы использовать эту процедуру, следует объявить: DEFINE_PRINT_NUM перед директивой END.
PRINT_NUM_UNS - процедура, которая печатает число без знака из регистра AX. Чтобы использовать эту процедуру, следует объявить: DEFINE_PRINT_NUM_UNS перед директивой END.
Чтобы использовать какую-либо из вышеописанных процедур, вы должны сначала объявить функцию в нижней части вашего файла (но перед END!!), а затем использовать команду CALL, за которой следует имя процедуры. Пример:
include 'emu8086.inc'
ORG 100h
LEA SI, msg1 ; попросить ввести число CALL print_string ; CALL scan_num ; получить число в CX.
MOV AX, CX ; копировать число в AX.
; напечатать следующие строки: CALL pthis DB 13, 10, 'You have entered: ', 0
CALL print_num ; напечатать число из AX.
RET ; вернуться в операционную систему.
msg1 DB 'Enter the number: ', 0
DEFINE_SCAN_NUM DEFINE_PRINT_STRING DEFINE_PRINT_NUM DEFINE_PRINT_NUM_UNS ; требуется для print_num. DEFINE_PTHIS
END ; директива окончания компиляции.
Сначала компилятор обрабатывает объявления (это просто обычные макросы, которые рассматриваются до процедур). Когда компилятор получает команду CALL, он заменяет имя процедуры адресом кода, где объявлена процедура. Когда выполнится команда CALL, то управление будет передано процедуре. Это очень удобно: даже если вы вызовите в вашем коде одну и ту же процедуру 100 раз, то вы все-равно будете иметь относительно небольшой размер исполнимого файла. Кажется сложным, не так ли? Но это не страшно. Со временем вы все это изучите, а пока достаточно понимания только основных принципов.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Дата: Воскресенье, 28.06.2015, 22:07 | Сообщение # 7
Агро-Разработчик
[ Легенда Зоны ]
Часть 6: Арифметические и логические команды
Большинство арифметических и логических команд влияют на регистр состояния процессора (или Флаги)
Как вы можете видеть, в этом регистре 16 бит. Каждый бит называется флагом и может принимать значение 1 или 0.
Carry Flag (CF) - перенос - этот флаг устанавливается в 1, когдаслучается беззнаковое переполнение. Например, если вы увеличили байт 255 + 1 (результат не помещается в диапазоне 0...255). Если переполнение не происходит, этот флаг установлен в 0.
Zero Flag (ZF) - ноль - устанавливается в 1, если результат равен нулю. Если результат не нулевой, то этот флаг устанавливается в 0.
Sign Flag (SF) - знак - установлен в 1, если результат -отрицательное число. Если результат положительный, то этот флаг устанавливается в 0. Обычно этот флаг принимает значение старшего значащего бита.
Overflow Flag (OF) - переполнение - устанавливается в 1, если случается переполнение при арифметических операциях со знаком. Например, если вы увеличили байт 100 + 50(результат не помещается в диапазоне -128...127).
Parity Flag (PF) - контроль четности - этот флаг устанавливается в 1, если в младших 8-битовых данных четное число. Если число нечетное, то этот бит установлен в 0. Даже если результат - это слово, то анализируются только 8 младших бит!
Auxiliary Flag (AF) - внешний перенос - установлен в 1, если случилось переполнение без знака младших 4-х битов (т.е. перенос из 3-го бита).
Interrupt enable Flag (IF) - прерывание - если этот флаг установлен в 1, то процессор реагирует на прерывание от внешних устройств.
Direction Flag (DF) - направление - этот флаг используется некоторыми командами для обработки цепочки данных. Если флаг установлен в 0 - обработка происходит в прямом направлении, если 1 - в обратном.
После операции между операндами результат всегда записывается в первый операнд. Команды CMP и TEST воздействуют только на флаги и не записывают результат (эта команда используется для принятия решения во время выполнения программы).
Эти команды влияют только на флаги: CF, ZF, SF, OF, PF, AF.
ADD - Прибавить второй операнд к первому.
SUB - Вычесть второй операнд из первого.
CMP - Вычесть второй операнд из первого только для флагов.
AND - Логическое И между всеми битами двух операндов. При этом соблюдаются правила: 1 AND 1 = 1 1 AND 0 = 0 0 AND 1 = 0 0 AND 0 = 0Как видите, мы получаем 1 только в том случае, если оба бита равны 1.
TEST - То же самое, что AND, но только для флагов.
OR - Логическое ИЛИ между всеми битами двух операндов. При этом соблюдаются правила: 1 OR 1 = 1 1 OR 0 = 1 0 OR 1 = 1 0 OR 0 = 0Как видите, мы получаем 1 каждый раз, когда хотя бы один бит равен 1.
XOR - Логическое XOR (исключающее ИЛИ) между всеми битами двух операндов. При этом соблюдаются правила: 1 XOR 1 = 0 1 XOR 0 = 1 0 XOR 1 = 1 0 XOR 0 = 0Как видите, мы получаем 1 каждый раз, когда биты имеют различное значение.
Команды MUL и IMUL влияют только на эти флаги: CF, OF Если результат превышает размер операнда, то эти флаги установлены в 1, если результат умещается в размер операнда, то эти флаги установлены в 0.
Для команд DIV и IDIV флаги не определены.
MUL - беззнаковое умножение:если операнд - это байт: AX = AL * операнд.если операнд - это слово: (DX AX) = AX * операнд.
IMUL - умножение со знаком: если операнд - это байт: AX = AL * операнд. если операнд - это слово: (DX AX) = AX * операнд.
DIV - беззнаковое деление: если операнд - это байт: AL = AX / операнд AH = остаток (модуль). . если операнд - это слово: AX = (DX AX) / операнд DX = остаток (модуль). .
IDIV - деление со знаком: если операнд - это байт: AL = AX / операнд AH = остаток (модуль). . если операнд - это слово: AX = (DX AX) / операнд DX = остаток (модуль). .
Команды INC и DEC влияют только на эти флаги: ZF, SF, OF, PF, AF.
Команда NOT не влияет ни на какие флаги!
Команда NEG влияет только на эти флаги: CF, ZF, SF, OF, PF, AF.
NOT - инвертирование каждого бита операнда.
NEG - Меняет знак операнда (дополнение до двух). Обычно она инвертирует каждый бит операнда, а затем прибавляет к нему единицу. Например, 5 преобразуется в -5, а -2 преобразуется в 2.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Управление ходом программы - очень важная вещь. Это то, что заставляет программу принимать решения, в зависимости от некоторых условий.
• Безусловные переходы
Основная команда, которая передает управление в другую точку программы - это JMP.
Основной синтаксис команды JMP:JMP метка Чтобы объявить метку в вашей программе, просто напечатайте ее имя и в конце добавьте двоеточие ":". Метка может быть любой комбинацией символов, но не должна начинаться с цифры. Например, ниже представлены три правильных объявления меток:label1: label2: a:Метка может быть объявлена на отдельной строке или перед любой другой командой, например: x1: MOV AX, 1
x2: MOV AX, 2Здесь представлен пример команды JMP:
ORG 100h
MOV AX, 5 ; записать в AX число 5. MOV BX, 2 ; записать в ВX число 5.
JMP calc ; перейти к 'calc'.
back: JMP stop ; перейти к 'stop'.
calc: ADD AX, BX ; прибавить BX к AX. JMP back ; перейти к 'back'.
stop:
RET ; вернуться в операционную систему.
END ; директива для прекращения компиляции.
Конечно, есть более простой путь для вычисления результата с двумя числами, но это хороший пример применения команды JMP. Как вы можете видеть из этого примера, команда JMP может передавать управление другому участку программы, который может находиться как после этой команды, так и перед ней. Этот переход может быть осуществлен в пределах текущего сегмента кода (65,535 байтов). • Короткие условные переходы
Подобно команде JMP, которая выполняет безусловный переход, существуют команды, которые осуществляют условный переход (переход, который осуществляется только в том случае, если выполняется определенное условие). Эти команды разделяются на три группы. Первая группа только проверяет отдельный флаг, вторая - сравнивает числа со знаком, третья - сравнивает числа без знака.
Команды перехода, проверяющие одиночный флаг
КомандаОписаниеУсловиеОбратная команда JZ , JEПереход, если "равно" ("нуль"). Т.е. если сравниваемые значения равны, то ZF = 1 и переход выполняется ZF = 1JNZ, JNE JC , JB, JNAEПереход, если есть перенос ("ниже", "не выше или равно"). CF = 1JNC, JNB, JAE JSПереход по знаку. SF = 1JNS JOПереход по переполнению. OF = 1JNO JPE, JPПереход, если есть паритет или паритет четный. PF = 1JPO
JNZ , JNEПереход по "не равно" или по "не нуль" ZF = 0JZ, JE JNC , JNB, JAEПереход, если нет переноса ("выше или равно" или "не ниже"). CF = 0JC, JB, JNAE JNSПереход, если нет знака. SF = 0JS JNOПереход, если нет переполнения. OF = 0JO JPO, JNPПереход, если нет паритета или паритет нечетный. PF = 0JPE, JP
Как вы можете видеть, существуют команды, которые выполняют одинаковые действия. Это нормально. Они даже ассемблируются в одинаковый машинный код, поэтому будет неплохо, если вы запомните, что при компиляции команды JE, после дизассемблирования вы получите ее как: JZ. Различные имена используются для того, чтобы делать программы более легкими для понимания и кодирования.
Команды перехода для чисел со знаками
КомандаОписаниеУсловиеОбратная команда JE , JZПереход, если "равно" (=). переход, если "ноль".ZF = 1JNE, JNZ JNE , JNZПереход, если "не равно" (<>). Переход, если "не ноль".ZF = 0JE, JZ JG , JNLEПереход, если "больше" (>). Переход, если "не меньше или равно"(not <=).ZF = 0 and SF = OFJNG, JLE JL , JNGEПереход, если "меньше" (<). Переход, если "не больше или равно"(not >=).SF <> OFJNL, JGE JGE , JNLПереход, если "больше или равно" (>=). Переход, если "не меньше" (not <).SF = OFJNGE, JL JLE , JNGПереход, если "меньше или равно" (<=). Переход, если "не больше" (not >).ZF = 1 or SF <> OFJNLE, JG
<> - этот знак означает "не равно".
Команды перехода для чисел без знаков
КомандаОписаниеУсловиеОбратнаякоманда JE , JZПереход, если "равно" (=). Переход, если "ноль".ZF = 1JNE, JNZ JNE , JNZПереход, если "не равно" (<>). Переход, если "не ноль".ZF = 0JE, JZ JA , JNBEПереход, если "выше" (>). Переход, если "не ниже или равно" (not <=).CF = 0 and ZF = 0JNA, JBE JB , JNAE, JCПереход, если "ниже" (<). Переход, если "не выше или равно" (not >=). Переход по переносу.CF = 1JNB, JAE, JNC JAE , JNB, JNCПереход, если "выше или равно" (>=). Переход, если "не ниже" (not <). Переход, если "нет переноса".CF = 0JNAE, JB JBE , JNAПереход, если "ниже или равно" (<=). Переход, если "не выше" (not >).CF = 1 or ZF = 1JNBE, JA
Обычно, если требуется сравнить два числовых значения, то используют команду CMP (она делает то же самое, что и команда SUB (вычитание), но не сохраняет результат, а влияет только на флаги.
Логика очень простая, например:
требуется сравнить числа 5 и 2,
5 - 2 = 3
результат - НЕ НОЛЬ (Флаг Нуля - Zero Flag (ZF) установлен в0).
Другой пример:
требуется сравнить 7 и 7,
7 - 7 = 0
результат - НОЛЬ! (Флаг Нуля - Zero Flag (ZF) установлен в 1 и команды JZ или JE выполнят переход).
Ниже приведен пример команды CMP и условного перехода:
include emu8086.inc
ORG 100h
MOV AL, 25 ; записать в AL число 25. MOV BL, 10 ; записать в BL число 10.
CMP AL, BL ; сравнить AL с BL.
JE equal ; если AL = BL (ZF = 1), то перейти к метке equal.
PUTC 'N' ; иначе, если AL <> BL, то продолжить выполнение JMP stop ; программы - напечатать 'N' и перейти к метке stop.
equal: ; если программа н этой метке, PUTC 'Y' ; то AL = BL, поэтому выводим на экран 'Y'.
stop:
RET ; сюда приходим в любом случае
END
Попробуйте вышеописанный пример с различными числами в AL и BL, откройте флаги, щелкнув по кнопке [FLAGS]. Используйте [Single Step - пошаговый режим] и наблюдайте за происходящим. Не забывайте перекомпилировать и перезагружать вашу программу после кажого сделанного вами в ней изменения (используйте клавишу F/b]).
Все условные переходы имеют одно серьезное ограничение - в отличие от канды [b]JMP, они могут выполнять переход только на 127 байтов вперед или на 128 байтов назад (учтите, что большие команды ассемблируются в 3 и более байтов).
Мы можем легко преодолеть это ограничение, используя следующий метод: • Взять обратную команду из приведенной выше таблицы и выполнить переход к метке label_x. • Использовать команду JMP для перехода к нужному участку программы. • Определить метку label_x: только после команды JMP. label_x: - может быть любым именем.
Пример:
include emu8086.inc
ORG 100h
MOV AL, 25 ; записать в AL число 25. MOV BL, 10 ; записать в BL число 10.
CMP AL, BL ; сравнить AL с BL.
JNE not_equal ; переход, если AL <> BL (ZF = 0). JMP equal not_equal:
; представим, что здесь у нас ; размещается код, который ассмеблируется ; более чем в 127 байтов...
PUTC 'N' ; если мы оказались здесь, то AL <> BL, JMP stop ; тогда печатаем 'N', и переходим к метке stop.
equal: ; если мы оказались здесь, PUTC 'Y' ; то AL = BL, тогда печатаем 'Y'.
stop:
RET ; сюда приходим в любом случае.
END
Другой, реже используемый метод, представляет собой применение непосредственного значения (адреса) вместо метки. Если непосредственное значение начинается с символа '$', то выполняется относительный переход - переход относительно текущего адреса. Компилятор вычисляет команду, которая находится по заданному смещению, и выполняет переход непосредственно к ней. Например:
ORG 100h
; безусловный переход вперед: ; пропускаем следующие два байта, JMP $2 a DB 3 ; 1 байт. b DB 4 ; 1 байт.
; переход назад на 7 байтов, если BL <> 0: ; (Команда JMP занимает 2 байта) MOV BL,9 DEC BL ; 2 байта. CMP BL, 0 ; 3 байта. JNE $-7
RET
END
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Процедура - это часть кода, которая может быть вызвана из вашей программы для выполнения какой-либо определенной задачи. Процедуры делают программу более структурной и доступной для понимания. В общем случае процедура возвращает программу к той же самой точке, откуда она была вызвана.
Синтаксис для объявления процедуры:имя PROC
; здесь находится ; код процедуры ...
RET имя ENDP имя - это имя процедуры. Одно и то же имя должно быть в верхней и нижней части, это используется для проверки правильности закрытия процедур.
Возможно, вы уже знаете, что команда RET используется для возвращения в операционную систему. Эта же команда используется для возвращения из процедуры (фактически операционная система воспринимает вашу программу, как специальную процедуру).
PROC и ENDP - это директивы компилятора, поэтому они не ассемблируются в какой-либо реальный машинный код. Компилятор только запоминает адрес процедуры.
Команда CALL используется для вызова процедуры.
Пример:
ORG 100h
CALL m1
MOV AX, 2
RET ; вернуться в операционную систему.
m1 PROC MOV BX, 5 RET ; вернуться в программу, из которой была вызвана. m1 ENDP
END
Вышеописанный пример вызывает процедуру m1, которая выполняет команду MOV BX, 5. После окончания процедуры, программа выполняет команду, следующую после команды CALL, т.е. команду: MOV AX, 2.
Есть несколько способов передачи параметров процедуре. Самый простой из них - использование регистров. Здесь представлен пример процедуры, которая принимает два параметра в регистрах AL и BL, умножает их и возвращает результат в регистр AX:
ORG 100h
MOV AL, 1 MOV BL, 2
CALL m2 CALL m2 CALL m2 CALL m2
RET ; вернуться в операционную систему.
m2 PROC MUL BL ; AX = AL * BL. RET ; вернуться в вызвавшую программу. m2 ENDP
END
В этом примере значение регистра AL изменяется каждый раз при вызове процедуры, а состояние регистра BL не изменяется. Таким образом мы получили алгоритм вычисления числа 2 в 4-ой степени. В результате в регистре AX будет число 16 (или 10h).
Здесь дан другой пример, в котором используется процедура для вывода на экран сообщения Hello World!:
ORG 100h
LEA SI, msg ; загрузить адрес msg в регистр SI.
CALL print_me
RET ; вернуться в ОС.
; ========================================================== ; эта процедура печатает строку, строка должна оканчиваться ; нулем (иметь ноль в конце), адрес строки дожен быть в ; регистреhe SI: print_me PROC
next_char: CMP b.[SI], 0 ; проверить регистр SI, если он JE stop ; равен 0, перейти к метке stop
MOV AL, [SI] ; иначе получить ASCII-символ.
MOV AH, 0Eh ; номер функции для печати символа. INT 10h ; используем прерывание для печати ; символа из AL.
ADD SI, 1 ; увеличить индекс строкового массива
JMP next_char ; вернуться и напечатать другой символ
stop: RET ; вернуться в программу. print_me ENDP ; ==========================================================
msg DB 'Hello World!', 0 ; строка с нулевым окончанием
END
"b." - префикс перед [SI] означает, что нам необходимо сравнивать байты, а не слова. Если вы хотите сравнить слова, добавьте префикс "w." вместо "b.". Если один из сравниваемых операторов - регистр, то вставлять префиксы не требуется, так как компилятор знает размер каждого регистра.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Стек - это область памяти для хранения временных данных. Стек используется командой CALL для хранения адреса, чтобы программа могла вернуться к тому месту, откуда была вызвана процедура. Команда RET получает этот адрес из стека и возвращает управление по этому смещению. то же самое происходит, когда команда INT вызывает прерывание, она записывает в стек регистр флагов, сегмент и смещение кода. Команда IRET используется для возвращения после вызова прерывания.
Вы можете использовать стек для хранения любых данных. Для работы со стеком имеются две команды:
memory: [BX], [BX+SI+7], 16-ти битовая переменная и т.п...
immediate: 5, -24, 3Fh, 10001101b, и т.п...
Синтаксис для команды POP:
POP REG POP SREG POP memory REG: AX, BX, CX, DX, DI, SI, BP, SP.
SREG: DS, ES, SS, (кроме CS).
memory: [BX], [BX+SI+7], 16-ти битовая переменная и т.п...
Примечания: • Команды PUSH и POP работают только с 16-ти битовыми значениями! • Примечание: PUSH immediate работает только на процессорах 80186 и выше!
Стек использует алгоритм LIFO (Last In First Out - Последним пришел - первым ушел), это значит, что если мы поместим эти значения одно за другим в стек:
1, 2, 3, 4, 5
то первым значением, которое мы можем получить из стека, будет 5, затем 4, 3, 2 и только потом 1.
Очень важно применять равное количество команд PUSH и POP, иначе стек может быть нарушен и невозможно будет вернуться в операционную систему. Как вы уже знаете, мы используем команду RET для возвращения в операционную систему. Когда программа запускается, ее адрес записывается в стек (обычно это 0000h).
Команды PUSH и POP чрезвычайно полезны, т.к. для хранения данных обычно недостаточно только регистров. Вот выход из ситуации: • Записать значение регистра в стек (используя PUSH). • Использовать этот регистр в своих целях. • Восстановить предыдущее значение регистра из стека (используя POP).
Пример:
ORG 100h
MOV AX, 1234h PUSH AX ; записать значение из AX в стек.
MOV AX, 5678h ; изменить значение регистра AX.
POP AX ; восстановить первоначальное значение AX.
RET
END
Стек можно также использовать для того, чтобы поменять местами значения в регистрах:
ORG 100h
MOV AX, 1212h ; записать в АХ число 1212h. MOV BX, 3434h ; записать в ВХ число 3434h.
PUSH AX ; записать значение AX в стек. PUSH BX ; записать значение ВX в стек.
POP AX ; установить в AX значение BX. POP BX ; установить в ВX значение АX.
RET
END
Обмен данными происходит потому, что стек использует алгоритм LIFO (Последним пришел - первым вышел), поэтому когда мы помещаем в стек число 1212h, а затем - 3434h, то при обращении к стеку мы сначала получим число 3434h, и только потом - 1212h.
Область памяти стека устанавливается при помощи регистров SS(Stack Segment - сегмент стека) и SP (Stack Pointer - указатель стека). Обычно операционная система устанавливает значения этих регистров на начало программы.
Команда "PUSH источник" делает следующее: • Вычитает 2 из регистра SP. • Записывает значение источника по адресу SS:SP.
Команда "POP приемник" делает следующее: • Записывает данные, размещенные по адресу SS:SP вприемник. • Увеличивает на 2 значение регистра SP.
Текущий адрес указателя в SS:SP называется вершиной стека.
Для COM-файлов сегмент стека - это обычно и сегмент кода, а указатель стека установлен в значение 0FFFEh. По адресу SS:0FFFEh записывается адрес возврата для команды RET, которая выполняется в конце программы.
Вы можете наблюдать за работой стека, щелкнув по кнопке [Stack] в окне эмулятора. Вершина стека отмечена знаком "<".
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Макросы - это те же процедуры, только виртуальные. Макросы подобны процедурам, но они выполняются только во время компиляции. После компиляции все макросы заменяются реальными командами. Если вы объявите макрос и никогда не будете использовать его в вашем коде, компилятор просто проигнорирует его. emu8086.inc содержит хорошие примеры использования макросов. Этот файл содержит несколько макросов, которые делают создание кода более легким.
Определение макроса: имя MACRO [параметры,...]
<команды>
ENDM
В отличие от процедур, макрос должен быть определен перед участком кода, где он будет использоваться, например:
MyMacro MACRO p1, p2, p3
MOV AX, p1 MOV BX, p2 MOV CX, p3
ENDM
ORG 100h
MyMacro 1, 2, 3
MyMacro 4, 5, DX
RET
Вышеописанный код эквивалентен следующему набору команд:
Некоторые важные замечания о макросах и процедурах: • Если вы хотите использовать процедуру, вы должны применить команду CALL, например: CALL MyProc • Если вы хотите использовать макрос, вы можете просто напечатать его имя. Пример: MyMacro • Процедура располагается по определенному адресу в памяти. Если вы воспользуетесь процедурой 100 раз, процессор будет передавать управление в эту часть памяти. Возвращение из процедуры выполняется командой RET. Для хранения адреса возврата используется стек. Команда CALL занимает 3 байта, так что размер исполнимого файла увеличивается очень незначительно, независимо от того, сколько раз вызывается процедура. • Макрос выполняется непосредственно в коде программы. Поэтому если вы используете один и тот же макрос 100 раз, то компилятор будет распаковывать этот макрос также 100 раз, делая исполнимый файл все больше и больше, каждый раз вставляя в программу команды макроса. • Вы должны использовать стек или регистры общего назначения для передачи параметров процедуре. • Для передачи параметров макросу, вам достаточно просто напечатать их после имени макроса. Например: MyMacro 1, 2, 3 • Чтобы обозначить конец макроса, достаточно директивы ENDM. • Чтобы обозначить конец процедуры, вы должны напечатать имя процедуры перед директивой ENDP.
Макрос распаковывается непосредственно в коде, поэтому, если имеются метки внутри макроопределения, вы можете получить ошибку "Duplicate declaration" (Двойное объявление), если макрос используется два или более раз. Чтобы избежать такой проблемы, используйте директиву LOCAL, сопровождающую имена переменных, меток или имен процедур. Например:
MyMacro2 MACRO LOCAL label1, label2
CMP AX, 2 JE label1 CMP AX, 3 JE label2 label1: INC AX label2: ADD AX, 2 ENDM
ORG 100h
MyMacro2
MyMacro2
RET
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Часть 11: Создание вашей собственной операционной системы
Обычно, когда компьютер стартует, он пытается загрузиться с первого 512-байтового сектора (это Цилиндр 0, Головка 0, Сектор 1) дискеты в дисководе A: в память по адресу 0000h:7C00h и передать ей управление. Если это не удается, то BIOS пытается использовать MBR первого жесткого диска.
Этот урок посвящен загрузке с дискеты. Те же принципы используются при загрузке с жесткого диска. Но использование дискеты имеет несколько преимуществ: • Вы можете сохранить существующую операционную систему неповрежденной (Windows, DOS...). • Загрузочную запись дискеты легко модифицировать.
Пример простой загрузочной программы для дискеты:
; директива для создания BOOT-файла: #MAKE_BOOT#
; Загрузочная запись помещается в 0000:7C00. ; Эту информацию нужно сообщить компилятору: ORG 7C00h
; загружаем адрес сообщения в регистр SI: LEA SI, msg
; функция телетайпа: MOV AH, 0Eh
print: MOV AL, [SI] CMP AL, 0 JZ done INT 10h ; печать в режиме телетайпа. INC SI JMP print
; ожидание нажатия любой клавиши: done: MOV AH, 0 INT 16h
msg DB 'Hello This is My First Boot Program!' DB new_line, 'Press any key to reboot', 0
Скопируйте описанный выше пример в редактор исходного кода Emu8086 и нажмите кнопку [Compile and Emulate]. Эмулятор автоматически загрузит ".boot" файл по адресу 0000h:7C00h.
Вы можете управлять им как обычной программой или использовать меню Virtual Drive -> Write 512 bytes at 7C00h to -> Boot Sector виртуального дисковода (файл FLOPPY_0 в каталоге, где установлен эмулятор). После записи вашей программы в Виртуальный Дисковод, вы можете выбрать Boot from Floppy из меню Virtual Drive.
Из любопытства вы можете записать виртуальную дискету (FLOPPY_0) или ".boot"-файл на реальную дискету и загрузить с нее ваш компьютер. Я рекомендую использовать "RawWrite for Windows" из: http://uranus.it.swin.edu.au/~jn/linux/rawwrite.htm (с недавнего времени работает под всеми версиями Windows!)
Примечание: Этот .boot-файл не совместим с загрузочным сектором MS-DOS (будет невозможно прочитать или записать данные на вашу дискету, пока вы ее снова не отформатируете). Фактичеки, если вы используете какие-либо "сырые" программы, такие как описанная выше, они так или иначе будут стирать все данные. Так что убедитесь, что ваша дискета не содержит важных данных.
".boot"-файлы имеют ограничение 512 байтов (размер сектора). Если ваша операционная система имеет размер, превышающий это ограничение, то вам придется использовать программу для загрузки из других секторов. Хороший пример маленькой операционной системы можно найти в каталоге "Samples": micro-os_loader.asm micro-os_kernel.asm
Для создания расширенной операционной системы (более 512 байтов), вы можете использовать файлы ".bin" (выберите "BIN Template" из меню "File" -> "New").
Чтобы записать файл ".bin" на виртуальный дисковод, выберите"Write .bin file to floppy..." из меню эмулятора "Virtual Drive":
Вы можете также использовать для записи ".boot"-файлов.
• Дисковод имеет 2 стороны и 2 головки - по одной на каждую сторону (0..1). Головки дисковода перемещаются над поверхностью диска на каждой стороне. • Каждая сторона имеет 80 цилиндров (нумерация 0..79). • Каждый цилиндр имеет 18 секторов (1..18). • В каждом секторе 512 байтов. • Общий размер дискеты: 2 x 80 x 18 x 512 = 1474560 байтов.
Чтобы прочитать секторы с дискеты, используйте INT 13h / AH = 02h.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014
Часть 12: Управление внешними устройствами [Конец Урока]
Эмулятор имеет три встроенных виртуальных устройства: светофор, шаговый двигатель и робот. Вы можете посмотреть эти устройства, используя меню эмулятора "Virtual Devices".
Техническую информацию ищите в разделе I/O ports.
Как правило, можно использовать любые процессоры семейства х86 для управления всеми видами устройств, различающихся в основном номерами портов ввода-вывода, которые могут быть изменены с помощью хитрого электронного оборудования. Обычно ".bin"-файл записывается микросхему ПЗУ (Read Only Memory (ROM) - постоянное запоминающее устройство - ПЗУ), система читает программу из этой микросхемы, загружает программу в ОЗУ и выполняет программу. Этот принцип используется для многих современных устройств, таких как микроволновые печи и т.п.
Светофор
Обычно для управления светофором используется массив (таблица) значений. В определенный период времени значение читается из массива и отправляется в порт. Например:
; директива для создания BIN-файла: #MAKE_BIN# #CS=500# #DS=500# #SS=500# #SP=FFFF# #IP=0#
; установить интервал (1 миллион микросекунд - 1 секунда): MOV CX, 0Fh MOV DX, 4240h MOV AH, 86h INT 15h
; восстановить регистры: POP AX POP DX POP CX RET PAUSE ENDP ; ==========================
Шаговый двигатель
Двигатель может выполнить "полушаг" при помощи пары магнитов, которые поворачивают ротор двигателя на определенный угол. Затем включается следующая пара магнитов и поворачивает ротор двигателя на следующий "шаг" и т.д.
Двигатель может выполниь полный шаг, если его повернуть парой магнитов, затем другой парой магнитов и в конце - одиночным магнитом и т.п. Лучший способ осуществить полный шаг - это выполнить его как два полушага.
Полушаг - это 11.25 градусов. Полный шаг - это 22.5 градуса.
Двигатель может вращаться как по часовой стрелке, так и против часовой стрелки.
См. stepper_motor.asm в каталоге Samples.
См. также раздел ссылок Emu8086 I/O ports.
Робот
Для управления роботом должен быть использован набор алгоритмов для достижения максимальной эффективности. Самым простым, но очень неэффективным является алгоритм случайных перемещений (см. robot.asm в каталоге Samples).
Можно также использовать таблицу данных (как и в светофоре). Это может быть хорошо, если робот всегда работает в одной и той же среде.
IP-адрес: Страна: Российская Федерация Город: Москва Дата регистрации: 25.10.2014