BAULA1 (1110624), страница 8
Текст из файла (страница 8)
A dw ?
X dw
Old dw ?
Dw ?
Diagn db ′Ошибка – большое значение!$′
Data ends
st segment stack
dw 64 dup (?)
st ends
code segment
assume cs:code, ds:data, ss:st
start:mov ax,data
mov ds,ax
; инициализация обработчика into
mov ax,0
mov es,ax; es - на вектор прерываний
My_into equ word ptr es:[4*4]
; сохранения адреса старой процедуры into
mov ax,My_into
mov Old,ax
mov ax,My_into+2
mov Old+2,ax;
; занесение нового адреса процедуры into
cli ; Закрыть прерывания
mov My_into,offset Error; Начало
mov My_into+2,code; процедуры-обработчика
sti Открыть прерывания
; собственно начало программы
mov ax,data
mov ds,ax
inint A
inint X
mov ax,A
add ax,X; Возможно переполнение
into
imul X; Возможны значащие биты в DX
into
mov X,ax; X:=X*(A+X)
outint X
; восстановление старого адреса into
Voz: cli ; Закрыть прерывания
mov ax,Old
mov My_into,ax
mov ax,Old+2
mov My_into+2,ax
sti Открыть прерывания
finish
; Начало нашей процедуры-обработчика
Error:
; Минимальная программная реакция
push ds; Сохранение регистров
push dx
push ax
sti Открыть прерывания
; Полная программная реакция
mov ax,data
mov ds,ax
mov dx,offset Diagn
outstr
newline
pop ax; Восстановление регистров
pop dx
pop ds
iret ; Возврат из прерывания
code ends
end start
Обратите внимание, что при выполнении некоторых групп команд работу нашей программы нельзя прерывать, иначе может возникнуть ситуация, когда в вектор прерывания не занесётся полностью вся необходимая информация, а уже произойдёт переключение на другую программу по пришедшему сигналу прерывания. В программировании такие группы команд называются критическими секциями программы. В нашей программе критические сеции заключены в рамки, в начале каждой критической секции стоит команда запрета прерывания от внешних устройств cli , а в конце секции – команда открытия прерываний sti .
После выдачи диагностики об ошибке наша процедура-обработчик может не продолжать выполнение программы, а, например, завершать выполнение программы. Для этого вместо предложения
iret ; Возврат из прерывания
надо поставить два предложения
add SP,3*2; Очистка стека от IP, CS и FLAGS
jmp Voz
И, наконец, рассмотрим команду, которая всегда вызывает прерывание с номером N, заданным в качестве её операнда:
int op1
Здесь op1 имеет формат i8. Заметим, что с помощью этой команды можно вызвать прерывание с любым номером, например прерывание, соответствующее делению на ноль или плохому коду операции. Более того, прерывания с номерами большими 31, в нашей архитектуре можно вызвать, только выполняя команду int с соответствующим параметром-номером прерывания. Используя эти команды, легко отлаживать процедуры-обработчики прерываний, но основное назначение таких команд состоит в другом.
Дело в том, что в большинстве программ необходимо выполнять некоторые широко распространённые действия (обмен данными с внешними устройствами, выполнение стандартных процедур и многое другое). Обычно процедуры, реализующие эти действия, оформляются в виде библиотеки стандартных процедур и всегда находятся в оперативной памяти компьютера. Так как адреса этих процедур часто меняются, то лучше всего присвоить каждой такой процедуре свой номер N и оформлять такие процедуры в виде обработчиков прерываний с этим номером. В этом случае вызов конкретной процедуры с номером N следует производить командой int N .
Исходя из описанного выше, такие команды прерывания (а часто и соответствующие им процедуры) обычно называют системными вызовами (системными функциями операционной системы), а библиотека стандартных процедур – Базовой системой процедур ввода/вывода (английское сокращение – BIOS). Параметры для таких процедур обычно передаются на регистрах, т.е. для системных вызовов не выполняются стандартные соглашения о связях.
В качестве примера рассмотрим системный вызов int 21h , который реализует многие операции ввода/вывода. Так, для вывода строки текста на экран в качестве параметров следует передать номер конкретного действия на регистре ah (для вывода строки ah=9) и адрес начала выводимой строки на регистрах ds:dx (строка должна кончаться символом '$'). Исходя из этого на место нашей макрокоманды вывода строки текста
outstr
можно подставить команды
mov ah,9
int 21h
В качестве примера опишем на Ассемблере процедуру, использующую системный вызов. Эта процедура при её вызове выдаёт звуковой сигнал:
Beep proc
push ax
push dx
mov al,7; символ-звуковой сигнал
mov ah,02h; номер функции вывода символа
int 21h; системный вызов
pop dx
pop ax
ret
Beep endp
Можно заметить, что наша процедура Beep при своём вызове выполняет те же действия, что и макрокоманда outch 7 .
В дальнейшем мы познакомимся ещё с одним важным назначением системных вызовов при изучении мультипрограммного режима работы ЭВМ.
Процедуры обработки прерываний реализуют особый стиль программирования, их иногда называют процедурами обратного вызова (call back procedure) или процедурами-демонами. Такая процедура при своей инициализации (размещении в памяти) оставляет в определённом месте адрес своего начала. Далее вызов этой процедуры производится при возникновении соответствующих условий путём (дальнего) косвенного перехода на эту процедуру.
В качестве примера рассмотрим рассчёт платы за междугородний телефонный разговор, при которым за каждую новую минуту разговора к общей сумме прибавляется некоторая величина – тариф за минуту разговора с данным городом. При наступлении льготного времени (обычно ночью и в выходные дни) срабатывает будильник (специальная системная программа-обработчик прерываний от встроенного в ЭВМ таймера), который вызывает процедуру-демона пересчёта всех тарифов. Заметим, что в некоторые языки высокого уровня включены аналогичные возможности, например, в языке С можно писать так называемые функции-реакции на сигналы, о чём Вы узнаете в следующем семестре в курсе "Системное программное обеспечение".
В заключении нашего по необходимости краткого рассмотрения прерываний заметим, что появление в компьютерах системы прерываний было, несомненно, одним из важнейших событий в развитии архитектуры вычислительных машин. Недаром появившиеся компьютеры с системой прерываний стали относить к следующему, третьему поколению ЭВМ. Подробнее об этом можно прочитать в книге [3].
9. Дополнительные возможности Ассемблера.
9.1. Строковые команды.
Сейчас мы рассмотрим последний полезный для понимания архитектуры нашего компьютера формат команд память-память (формат SS). До сих пор для работы с переменными в оперативной памяти мы использовали формат команд регистр-память (или память-регистр), при этом один из аргументов находился на регистре центрального процессора. Это не всегда удобно, если мы не предполагаем больше никаких операций с выбранным на регистр операндом, тогда его пересылка из памяти на регистр оказывается лишним действием.1 Именно для таких случаев и предназначены команды формата память-память. В архитектуре нашего компьютера такие команды относятся к так называемым строковым командам (мы скоро поймём, почему они так называются).
Строковые команды хорошо использовать для обработки элементов массивов. Массивы коротких беззнаковых целых чисел могут трактоваться как строки (цепочки) символов, отсюда и название – строковые или цепочечные команды. При выполнении строковых команда в цикле получается удобный способ обработки массивов, элементами которых являются короткие или длинные целые числа.
Знакомство со строковыми командами начнём с команд пересылки байта (movsb) или слова (movsw) с одного места памяти в другое. Эти команды различаются только битом размера операнда w в коде операции. С битом w мы познакомились при изучении форматов команд регистр-регистр и регистр-память, w=0 для операндов-байтов и w=1 для операндов-слов. Команды movsb и movsw не имеют явных операндов, их оба операнда op1 и op2 формата m8 (для w=0) и m16 (для w=1) заданы неявно (по умолчанию).
Выполнение команд movsb и movsw существенно зависит от так называемого флага направления DF из регистра флагов FLAGS. Для смены значения этого флага можно использовать команды cld (для операции DF:=0), и std (для операции DF:=1). Для более компактного описания правил выполнения команд movsb и movsw введём следующие условные обозначения:
δ = (w+1)*(-1)DF; φ(r16)={ r16 := (r16 + δ)mod 216 }
Как видим, δ может принимать значения ±1 и ±2, а оператор φ меняет величину регистра на значение δ. В этих обозначениях команды movsb и movsw выполняются по правилу:
<es,di> := <ds,si>; φ(di); φ(si)
Таким образом, неявный операнд op1 находится в памяти по адресу, заданному регистровой парой <es,di>, а операнд op2 – по адресу <ds,si>, т.е. операнды находятся в разных сегментах памяти.
Для того чтобы лучше понять логику работы описанных выше команд, рассмотрим задачу пересылки массива целых чисел с одного места оперативной памяти в другое. На языке Паскаль такая задача решается просто, например:
Var A,B: array[1..10000] of integer;
. . .
B := A;
Для языка Турбо-Паскаль, правда, это только частный случай задачи пересылки массива, так как массивы A и B будут располагаться в одном сегменте данных. Более общий случай пересылки массива на Паскале можно реализовать, например для массивов символов, в таком виде:
Const N = 50000;
Type Mas = Array[1..N] of char;
Var A,B: Mas;
. . .
New(A); New(B);
. . .
B := A;
В этом примере динамические переменные (массивы символов, для Ассемблера, как мы знаем, это массивы коротких беззнаковых целых чисел) обязательно будут располагаться в разных сегментах памяти (понять это!). А теперь рассмотрим реализацию похожей задачи пересылки массива из одного места памяти в другое на языке Ассемблер (наши два массива будут не динамическими, а статическими, но так же располагаться в разных сегментах памяти). Сначала опишем два сегмента данных, в которых будут располагаться наши массивы A и B:
N equ 50000
D1 segment
. . .
A db N dup (?)
. . .
D1 ends
D2 segment
. . .
B db N dup (?)
. . .
D2 ends
На начало сегмента D1 установим сегментный регистр ds, а на начало сегмента D2 – сегментный регистр es, тогда фрагмент программы для реализации оператора присваивания B:=A может, например, иметь такой вид:
Code segment
assume cs:Code,ds:D1,es:D2,ss:Stack
Start:mov ax,D1
mov ds,ax
mov ax,D2
mov es,ax
. . .
mov si,offset A
mov di,offset B
mov cx,N
jcxz L1
L: mov al,[si]
mov es:[di],al
inc si
inc di
loop L
L1: . . .
Оценим сложность нашего алгоритма пересылки массива. За единицу измерения примем обмен данными или командами между центральным процессором и оперативной памятью. В нашем случае сложность алгоритма пересылки массива равна 7*N, где N – это длина массива (N чтений элементов массива, N записей в память, 5*N раз считать в цикле команды из памяти в устройство управления).
Как видим, для пересылки целого числа из одного места памяти в другое нам понадобились две команды
mov al,[si]
mov es:[di],al
так как написать одну команду
mov byte ptr[si],es:[di]