Установив вектор, программа завершается с оставлением в памяти ее ^резидентной части с помощью функции 31h.
Резидентная часть программы является классическим обработчиком; программного прерывания. В первых же предложениях сохраняются регис-АХ и ВХ, используемые далее в программе, а затем содержимое сег->местного регистра CS переносится в регистр ВХ. С таким же успехом можно было скопировать содержимое любого из регистров DS, ES или SS, "так как в программе типа.СОМ все регистры настроены на один и тот же.сегментный адрес (см. рис. 3.2). Копирование из сегментного регистра в •'регистр общего назначения понадобился потому, что в дальнейшем нам [Придется работать с отдельными половинками сегментного адреса, а у 'сегментных регистров половинок нет.
'Команды и алгоритмы |
Глава 3
Далее старшая половина сегментного адреса заносится в регистр AL, и вызовом уже.знакомой нам функции BIOS OEh этот код выводится на экран. Затем таким же образом выводится младшая половина сегментного адреса. Наконец, после восстановления регистров ВХ и АХ (в обратном порядке по отношению к их сохранению) командой iret упраштение возвращается в прерванную программу, которой в данном случае является COMMAND.COM.
Вывод программы (ей для наглядности было дано имя TSR.COM) для конкретного прогона показан на рис, 3.8.
FACURRENT>tsr.con
F:4CURREMT>*=
3.2. Циклы и условные переходы
Циклы, позволяющие выполнить некоторый участок программы многократно, в любом языке являются одной из наиболее употребительных конструкций. В системе команд МП 86 циклы реализуются, главным образом, с помощью команды loop (петля), хотя имеются и другие способы организации циклов. Во всех случаях число шагов в цикле определяется содержимым регистра СХ, поэтому максимальное число шагов составляет 64 К.
Рассмотрим простой пример организации цикла. Пусть в программе зарезервировано место для массива размером 10000 слов, и этот массив надо заполнить натуральным рядом чисел от 0 до 9999. Эти числа, заполняющие последовательные элементы массива, иногда называют числами-заполнителями. Соответствующий фрагмент программы будет выглядеть следующим образом:
Рис. 3.8. Вывод программы 3-4.
Полученный результат далек от наглядности. Действительно, разделив сегментный адрес на две половины длиной в байт каждая, мы просто записали в видеобуфер эти числа. Каждое число размером в байт можно трактовать, как код ASCII какого-то символа При выводе числа на экран эти символы и отображаются. Изображение пикового туза соответствует коду 06, а знак равенства имеет код 3Dh (см. таблицу кодов ACSII на рис. 3.1). Таким образом, сегментный адрес находящейся в памяти резидентной программы оказался равен 063DU, что соответствует приблизительно 25 Кбайт. Так и должно быть, так как конфигурация компьютера, использованного для подготовки примеров, предусматривала хранение большей части DOS в расширенной памяти, в области НМА. В основной памяти в этом случае располагается кусочек DOS вместе с драйверами обслуживания расширенной памяти и частью программы COMMAND.COM общим объемом около 25 Кбайт.
Для того, чтобы получить на экране сегментный адрес в привычной нам форме, его двоичное машинное представление необходимо преобразовать в коды ASCII, отображающие шестнадцатеричное (или, если угодно, десятичное) представление этого числа. В нашем примере, чтобы получить на экране изображение числа 063DU, надо было сформировать такую цепочку кодов ASCII (в шестнадцатеричном представлении):
30 36 33 44 68
Рассмотренный метод вывода на экран чисел в виде изображений символов, конечно, дачек от совершенства, однако подкупает свой исключительной простотой и вполне может быть использован в процессе отладки резидентных программ и обработчиков прерываний, включение в которые довольно громоздких программ перекодировки может оказаться нежелательным или даже невозможным.
Читатель может, подготовив рассмотренный пример, загрузить несколько экземпляров программы и посмотреть, как изменяются в этом случае их начальные адреса.
;В сегменте данных array dw 10000 dup(O);В программном сегменте mov BX,offset array mov SI,0 mov AX,0 |
;Адрес массива
; И ндекс
;Начальное значение
;заполнителя
mov mov inc add loop |
CX, 10000 [BX][SI],AX AX SI,2 fill |
fill: |
; Счетчик цикла
;3аполнитель пошлем в массив
;Инкремент заполнителя
;Модификация индекса
;Команда цикла
На этапе подготовки мы заносим в регистр ВХ относительный адрес начала массива, отождествляемый с его именем array, устанавливаем на-чатьное значение индекса элемента массива в регистре SI (с таким же успехом можно было взять DI) и начальное значение числа-заполнителя. Сам цикл состоит из трех команд — единственной содержательной команды засылки числа-заполнителя в очередной элемент массива (по адресу, который вычисляется, как сумма содержимого регистров ВХ и SI), а также модификации числа-заполнителя и индекса очередного элемента массива. Завершающей командой loop управление передастся на метку fill, и цикл повторяется столько раз, каково содержимое СХ, в данном случае 10000 шагов.
Следует обратить внимание на команду модификации индекса — в каждом шаге к содержимому SI добавляется 2, так как массив состоит из Двухбайтовых слов. Если бы нужно было заполнить байтовый массив, то в каждом шаге содержимое регистра цикла SI следовало увеличивать на 1.
Стоит отметить некоторые детали, связанные с механизмом выполнения команды loop. При реачизации этой команды процессор сначата уменьшает содержимое регистра СХ на 1, а затем сравнивает полученное число с нулем. Если СХ > 0, переход на указанную метку выполняется. Если СХ = 0, цикл разрывается и процессор переходит на команду, еле-
Глава 3
Команды и алгоритмы
дующую за командой loop. Поэтому после нормального выхода из цикла содержимое СХ всегда равно 0.
Другое обстоятельство связано с кодированием команды loop. В ее коде под смещение к точке перехода отводится всего 1 байт. Поскольку смещение должно являться величиной со знаком, максимальное расстояние, на которое можно передать управление командой loop, составляет от -128 до +127 байт (хотя довольно трудно представить себе цикл, в котором переход осуществляется вперед). Другими словами, тело цикла ограничивается всего 128 байтами. Если циклически повторяемый, фрагмент программы имеет большую длину, цикл придется организовать другим, более сложным способом;
Организация длинного цикла
mov СХ, 10000;Счетчик цикла
fill:
; Метка начала цикла
;Тсло длинного цикла
dec cmp |
СХ сх,о finish |
;Дскремент счетчика цикла
;Отработано заданное число
; шагов'
;Да, на метку продолжения
; про граммы
jmp fill |
;Нет, на начало цикла
;Продолжение программы
finish:
В этом, весьма типичном фрагменте мы «вручную» уменьшаем содержимое счетчика цикла и сравниваем полученное значение с 0. Если СХ = О, это значит, что в цикле выполнено заданное число шагов, и командой условного перехода je осущсстшмется переход на продолжение программы (метка finish). Если СХ еще не равно нулю, командой безусловного перехода jmp осуществляется возврат в начало цикла. Как было показано в гл. 2, команда jmp позволяет перейти в любую точку сегмента, и ограничение на размер тела цикла снимается.
При необходимости организовать вложенные циклы, для сохранения счетчика внешнего цикла на время выполнения внутреннего удобно воспользоваться стеком. В следующем фрагменте организуется временная задержка длительностью несколько секунд (конкретная величина задержки зависит от скорости работы процессора).
mov СХ.2000;Счетчик внешнего цикла
outer: push СХ;Сохраним его в стеке
mov CX,0;Счетчик внутреннего цикла
inner: loop inner;loop внутреннего цикла
pop СХ;Восстановим внешний счетчик
loop outer;loop внешнего цикла
Программные задержки удобно использовать при отладке программ, чтобы замедлить их работу и успеть рассмотреть их частичные результаты; иногда программные задержки позволяют синхронизовать работу аппаратуры, подключенной к компьютеру, если скорость отработки аппаратурой посыпаемых в нее из компьютера команд меньше скорости процессора. ~
В приведенном выше фрагменте внешний цикл выполняется 2000 раз; внутренний — 65536 раз. При счете числа шагов внутреннего цикла используется явление оборачивания, которое уже упоминалось ранее. Начальное значение в регистре СХ равно нулю; после выполнения тела цикла 1 раз команда loop уменьшает содержимое СХ на 1, что дает число FFFFh (которое можно рассматривать, как -1). В результате цикл повторяется еще 65535 раз, а в сумме — точно 64 К шагов.
Команда loop внутреннего цикла передает управление на саму себя, т.е. тело внутреннего цикла состоит из единственной команды loop. В этом нет ничего незаконного. Любая команда, в том числе и loop, требует какого-то времени для своего выполнения, и повторение 64 К раз команды loop дает некоторую временную задержку (на современных процессорах порядка тысячной доли секунды).
Перейдем теперь к рассмотрению команд условных переходов.
В приведенном выше фрагменте для реализации длинного цикла использовалась команда условного перехода по равенству je. В системе команд МП 86 имеется свыше трех десятков команд условных переходов, позволяющих осуществлять переходы при наличии разнообразных условий: равенства, неравенства, положительности или отрицательности результата и проч. При выполнении всех этих команд процессор анализирует содержимое регистра флагов и осуществляет (или не осуществляет) переход на указанную метку в зависимости от состояния отдельных флагов или их комбинаций. Поскольку на состояние регистра флагов влияют многие команды процессора, командами условных переходов можно пользоваться не только после команд сравнения или анализа, но и после многих других команд, если внимательно изучить влияние этих команд на флаги процессора. Приведем несколько абстрактных примеров.
cmp | AX,BX |
je | equal |
cmp | SI, mem |
jne | notequ |
hit | 21h |
jc | syserr |
or | BX,BX |
jz | zero |
in | AL,DX |
test | AL,80h |
je | inpt |
test | AX, 7 |
jne | found |
;Сравнение двух регистров;Переход, если АХ=ВХ;Сравнение регистра и ячейки памяти;Переход, если SlOmem;Вызов DOS
;Переход, если была ошибка;и флаг CF = 1;Анализ ВХ; Переход, если ВХ=0
inpt: in AL,DX;Ввод данного из устройства
;Анализ бита 7 в данном;Ввод до тех пор, пока бит;бит 7=0(ожидание установки бита 7);Анализ битов О, I, 2 в АХ;Переход, если хотя бы 1 бит;из них установлен
test DI,OFh;Анализ битов 0...3 в DI
jz reset;Переход, если все они сброшены
Глава j
В гл. 2 отмечаюсь, что двоичные числа, записываемые в регистры процессора или ячейки памяти, можно рассматривать, либо как числа суще-ственно положительные, т.е. числа без знака, либо как числа со знаком. Например, адреса ячеек, разумеется, не могут быть отрицательными. Поэтому число FFFFh, если по смыслу программы оно является адресом, обозначает 65535. Если, однако, то же число FFFFU получилось в арифметической операции вычитания 2 из 1, то его надо рассматривать, как — 1. Точно так же понятие знака бессмысленно по отношению к кодам символов, которые с равным успехом могут принимать любое значение из диапазона 0...255. С другой стороны, мы можем условно считать, что коды символов первой половины таблицы ASCII положительны, а коды второй половины таблицы (у них установлен старший бит) отрицательны, и использовать для обработки символов команды, чувствительные к знаку.
В составе команд условных переходов имеются две группы команд для сравнения чисел без знака (это команды ja, jae, jb, jbe, jna, jnae, jnb и jnbe) и чисел со знаком (jg> Jgc, jl, jle, jug, jiige, jnl и jnle). В аббревиатурах этих команд для сравнения чисел без знака используются слова above (выше) и below (ниже), а для чисел со знаком — слова greater (больше) и Less (меньше).
Разница между теми и другими командами условных переходов заключается в том, что команды для чисел со знаком рассматривают понятия «больше-меньше* применительно к числовой оси -32К...О...+32К, а команды для чисел без знака — применительно к числовой оси 0...64К. Поэтому для первых команд число 7FFFh (+32767) больше числа SOOOh (-32768), а для вторых число 7FFFU (32767) меньше числа 8000U (32768). Аналогично, команды для чисел со знаком считают, что 0 больше, чем FFFFh (-1), а команды для чисел без знака — меньше.
Рассмотрим пример использования команд условных переходов для обработки символов. Пусть мы вводим с клавиатуры некоторую строку символов (например, имя файла), и хотим, чтобы в программе эта строка была записана прописными буквами, независимо от того, какие буквы использовались при ее вводе. Между прочим, при вводе с клавиатуры команд DOS система всегда выполняет эту операцию, поэтом}' и команды, и ключи, и имена файлов можно вводить как прописными, так и строчными буквами — DOS во всех случаях преобразует все буквы в прописные.
Пример 3-5. Сравнение чисел без знака
code segment ;
assume cs:code,ds:data \'\
main proc i--\
mov AX,data Инициализируем:
mov DS,AX;регистр DS •)•-
;Выведем служебное сообщение |v
mov AH,09h;Функция вывода:;,
mov DX,oflset msg;Адрес сообщения, ,h
int 2 Hi \*
;Поставим запрос к DOS на ввод строки
г
Команды а алгоритмы
mov mov mov mov int mov | AH,3Fh BX,0 CX,80 DX,offset buf 21h action, AX | ;Функция ввода Дескриптор клавиатуры;Ввод максимум 80 байт;Адрес буфера ввода ;Фактически введено |
;Превратим строчные русские буквы в прописные
mov | CX,actlen |
mov | SI,0 |
filter: mov | AL,buf[SI] |
cmp | AL,'a' |
jb | noletter |
cmp | AL/я1 |
ja | noletter |
cmp | AL, 'n' |
ja | more |
sub | AL,20h |
jmp | store |
more: cmp | AL, 'p' |
jb | noletter |
sub | AL,50h |
store: mov | buf[SI],AL |
noletter: | inc SI |
loop filter |
;Длина введенной строки
;Указатель в буфере
;Возьмем символ
;Меньше 'а'?
;Да, не преобразовывать
;Болыпе 'я'?
;Да, не преобразовывать
; Больше 'п1?
;Да, на дальнейшую проверку
;'а'...'п'. Преобразуем в прописную
;На сохранение в буфере
;Меньше 'р1 (псевдографика)?
;>'п', <'р'. Не изменять
;'р'...'я'. Преобразуем в прописную
;Отправим назад в buf
;Сместим указатель
;Цикл по всем символам
;Функция вывода;Дескриптор экрана;Длина сообщения;Адрес сообщения ;Остановим программу ;в ожидании нажатия клавиши |
AH,40h BX,1 CX,actlen DX,oflset buf 21h AH,01 2 111 |
;Выведем результат преобразования на экран для контроля
mov
mov
mov
mov
int
mov
int;3авершим программу
mov AX,4COOh
; Буфер ввода |
int 2 In main endp code ends data segment msg db 'Вводите! S' buf db 80 dup (' ') actlendw 0 data ends stk segment stack dw 128 dup (') stk ends end main
Глава j
Цдчанды и алгоритмы
В начале программы на экран выводится служебное сообщение «Вводите! «, которое служит запросом программы, адресованным пользователю. Далее с помощью функции DOS 3Fh выполняется ввод строки текста с клавиатуры. Функция 3Fh может вводить данные из разных устройств — файлов, последовательного порта, клавиатуры. Различные устройства идентифицируются их дескрипторами. При работе с файлами дескриптор каждого файла создается системой в процессе операции открытия или создания этого файла, а для стандартных устройств — клавиатуры, экрана, принтера и последовательного порта действуют дескрипторы, закрепляемые за этими устройствами при загрузке системы. Для ввода с клавиатуры используется дескриптор 0, для вывода на экран дескриптор 1.
При вызове функции 3Fh в регистр ВХ следует занести требуемый дескриптор, в регистр DX — адрес области в программе, выделенной для приема вводимых с клавиатуры символов, а в регистр СХ — максимальное число вводимых символов. Мы считаем, что пользователь не будет вводить более 80 символов. Можно ввести и меньше; в любом случае ввод строки следует завершить нажатием клавиши <Enter>. Функция 3Fh, отработав, вернет в регистре АХ решхьное число введенных символов (включая коды 13 и 10, образуемые при нажатии клавиши <Enter>). В примере 3.5 число введенных символов сохраняется в ячейке actlen с целью использования далее по ходу программы.
Далее в цикле из actlen шагов выполняется анализ каждого введенного символа путем сравнения с границами диапазонов строчных русских букв. Русские строчные буквы размещаются в двух диапазонах кодов ASCII (а...п и р...с), причем для преобразования в прописные букв первого диапазона их код следует уменьшать на 20h, а для преобразования букв второго диапазона — на 50h. Поэтому анализ проводится с помощью четырех команд сравнения стр и соответствующих команд условных переходов. Модифицированный символ записывается на то же место в буфере buf.
После завершения анализа и преобразования введенных символов, выполняется контрольный вывод содержимого buf на экран. Поскольку мы заранее не знаем, сколько символов будет введено, вывод на экран осуществляется функцией 40h, среди параметров которой указывается число выводимых символов. Так же, как и в случае функции ввода 3Fh, для функции вывода 40h в регистре ВХ необходимо указать дескриптор устройства ввода, в данном случае экрана, а в регистре DX — адрес выводимой строки.
Коды символов являются числами без знака, и использование в данном случае команд условных переходов для чисел без знака представляется логичным и даже единственно возможным. Если, однако, внимательно рассмотреть понятия больше-меньше для чисел со знаком и без знака, то легко увидеть, что пока мы сравниваем друг с другом только «положительные» или только «отрицательные» числа, команда ja эквивалентна команде jg, а команда jb эквивалентна команде J1. Однако при сравнении, например, кодов цифр с кодами русских букв, правильный результат можно получить лишь при использовании команд переходов для чисел
без знака. Впрочем, всегда нагляднее и надежнее использовать те команды, которые соответствуют существу рассматриваемых данных, даже если такой же правильный результат получится и при использовании «неправильных» команд.
Более отчетливо разница между числами со знаком и без знака проявляется при использовании арифметических операций, например, операций умножения или деления. Здесь для чисел со знаком и чисел без знака предусмотрены отдельные команды:
inul — команда умножения чисел без знака; imul — команда умножения чисел со знаком; div — команда деления чисел без знака; idiv — команда деления чисел со знаком.
Поясним различия этих команд на формальных примерах.
;Умножение положительных чисел со знаком
mov AL,5;Первый сомножитель=5
mov BL.7;Второй сомножитель=7
mul BL;AX=0023h=35
mov AL,5;Первый сомножитель=5
mov BL,7;Второй сомножитель=7
imul BL;AX=0023h=35
Обе команды, mul и imul, дают в данном случае одинаковый результат, так как положительные числа со знаком совпадают с числами без знака. Не так обстоит дело при умножении отрицательных чисел.
;Умножение отрицательных чисел со знаком
mov AL,OFCh;Первый сомножитель=252
mov BL,4;Второй сомножитель=4
mul BL;AX=03FOh= 1008
mov AL,OFCh;Первый сомножитель=-4
mov BL,4;Второй сомножитель=4
imul BL;AX=FFFO=-16
Здесь действие команд mul и imul над одними и теми же операндами дает разные результаты. В первом примере число без знака FCh, которое интерпретируется, как 252, умножается на 4, давая в результате число без знака 3FO, т.е. 1008. Во втором примере то же число FCh рассматривается, как число со знаком. В этом случае оно составляет -4. Умножение на 4 дает FFFOh.T.e.-16.
Глава 3
г
алгоритмы
3.3. Обработка строк
Для работы со строками, или цепочками символов или чисел (т.е. попросту говоря, с массивами произвольных данных) в МП предусмотрен ряд специальных команд:
movs — пересылка строки;
cmps — сравнение двух строк;
seas — поиск в строке заданного элемента;
lods — загрузка аккумулятора (регистров AL или АХ) из строки;
stos — запись элемента строки из аккумулятора (регистров АХ или AL).
Эти команды очень удобны, однако их использование сопряжено с некоторыми трудностями, так как процессор, выполняя эти команды, неявным образом использует ряд своих регистров. Только если все эти регистры настроены должным образом, команды будут выполняться правильно. В результате включение в программу предложения с командой, например, movs, требует иной раз 6—7 дополнительных предложений, в которых осуществляется подготовка условий для правильного выполнения этой команды.
Хотя команды обработки строк, как правило, включаются в программу без явного указания операндов, однако каждая команда, в действительности, использует два операнда. Для команд seas и stos операндом-источником служит аккумулятор, а операнд-приемник находится в памяти. Для команды lods, наоборот, операнд-источник находится в памяти, а приемником служит аккумулятор. Наконец, для команд movs и cmps оба операнда, и источник, и приемник, находятся в памяти.
Вес рассматриваемые команды, выполняя различные действия, подчиняются одинаковым правилам, перечисленным ниже. Операнды, находящиеся в памяти, всегда адресуются единообразно: one ранд-источник через регистры DS:SI, а операнд-приемник через регистры ES:DI. При однократном выполнении команды обрабатывают только один элемент, а для обработки строки команды должны предваряться одним из префиксов повторения. В процессе обработки строки регистры SI и DI автоматически смещаются по строке вперед (если флаг DF = 0) или назад (если флаг DF = 1), обеспечивая адресацию последующих элементов. Каждая команда имеет модификации для работы с байтами или словами (например, movsb и movsw).
Таким образом, для правильного выполнения команд обработки строк необходимо (в общем случае) предварительно настроить регистры DS:SI и ES:DI, установить или сбросить флаг DF, занести в СХ длину обрабатываемой строки, а для команд seas и stos еще поместить операнд-источник в регистр АХ (или AL при работе с байтами).
Однако сама операция, после всей этой настройки, осуществляется одной командой, которая обычно даже не содержит операндов, хотя может иметь префикс повторения.
Стоит подчеркнуть, что строки, обрабатываемые рассматриваемыми командами, могут находиться в любом месте памяти: в полях данных программы, в системных областях данных, в ПЗУ, в видеобуфере. Например,
с помощью команды movs можно скопировать массив данных из одной массивной переменной в другую, а можно переслать страницу текста на экран терминала. Рассмотрим несколько примеров использования команд обработки строк, ограничившись лишь теми фрагментами программ, которые имеют отношение к рассматриваемому вопросу.
;Пример 3-6. Чтение из ПЗУ BIOS даты его выпуска
;В программном сегменте
main proc
mov AX,OFOOOh;3анесем в DS/
mov DS,AX;сегментный адрес ПЗУ BIOS
mov SI,OFFF5h;Смещение к интересующему нас полю
mov AX,data;Настроим ES
mov ES,AX;на сегмент данных программы
mov DI,ofTset Ью8;Смещение к полю для хранения даты
mov СХ,8;Перенести 8 байт
eld Движение по строке вперед
rep movsb;Перенос байтов
;Выведем полученную информацию на экран
mov AX,data;Теперь настроим DS
mov DS,AX;на сегмент данных программы
mov AH,40h;Функция вывода
mov BX,1;Дескриптор экрана
mov CX,8;Вывести 8 байт
mov DX,offset bios;Смещение к строке
int 21h;Вызов DOS
;В сегменте данных
bios db 8 dup(');Поле для хранения даты
Известно, что в ПЗУ BIOS, сегментный адрес которого составляет FOOOh (см. рис. 1.5), наряду с программами управления аппаратурой компьютера, хранятся еще и некоторые идентификаторы. Так, в восьми байтах ПЗУ, начиная с адреса FOOOh:FFF5h, записана в кодах ASCII дата разработки ПЗУ. В примере 3.6 выполняется чтение этой даты, сохранение ее в памяти и вывод на экран для контроля. Поскольку интересующая нас дата хранится в ПЗУ BIOS в кодах ASCII, никаких преобразований содержимого этого участка ПЗУ перед выводом на экран не требуется.
В программе осуществляется настройка всех необходимых для выполнения команды movs регистров (DS:SI, ES:DI, СХ и флага DF) и одной командой movsb с префиксом гср содержимое требуемого участка ПЗУ переносится в поле bios. Перенос строки байтами подчеркивает се формат (в строке записаны байтовые коды ASCII), однако в нашем примере, при четном числе переносимых байтов, более эффективно осуществить перенос по словам. В этом варианте команда movs будет фактически повторяться не 8 раз, а только 4. Для этого достаточно занести в СХ число 4 (вместо 8) и использовать вариант команды movsw.
Глава 3
Команды и алгоритмы
Для выполнения команды niovs нам пришлось настроить сегментный регистр DS на сегмент BIOS. Если в дальнейшем предполагается обращение к полям данных программы, как это имеет место в примере 3-6, в регистр DS следует занести сегментный адрес сегмента данных. После этого, настроив остальные регистры для вызова функции 40h, прочитанную из BIOS строку можно вывести на экран.
В рассмотренном примере неявно предполагалось, что программа будет в дальнейшем как-то использовать полученную из BIOS информацию, Если задача программы заключается просто в выводе на экран даты выпуска BIOS, то нет необходимости сначала копировать эту дату из BIOS в поля данных программы, а потом выводить ее на экран. Можно было поступить гораздо проще: настроив регистр DS на сегмент BIOS, а регистр DX на адрес строки с датой, вызвать функцию 40h и вывести на экран текст непосредственно из сегмента BIOS. Тогда содержательная часть программы сократится в два раза и примет такой вид:
mov mov mov mov mov mov hit |
AX.OFOOOh DS,AX AH,40h BX,1 CX,8 DX,OFFF5h 21h |
;Настроим DS;на сегмент BIOS;Функция вывода;Дескриптор экрана;Вывести 8 байт;Смещение к дате;Вызов DOS
Приведенный фрагмент не имеет отношения к данному разделу, так как в нем уже нет команд обработки строк. В то же время он подчеркивает важность сегментных регистров и гибкость сегментной адресации. Функция 40h ожидает найти адрес выводимой на экран строки в регистрах DS:DX, и никакие другие регистры в этом случае использовать нельзя. С другой стороны, эти регистры можно настроить на любой участок памяти и вывести на экран (а также и на принтер, в файл или в последовательный порт) данные откуда угодно.
Рассмотрим теперь пример работы с командами lods и stos, которые можно использовать как по отдельности, так и в паре друг с другом. Эти команды очень удобны, в частности, для прямого обращения к видеопамяти.
К экрану, как и к любому другому устройству, входящему в состав компьютера, можно обращаться тремя способами: с помощью функций DOS (прерывание 21h), с использованием прерывания BIOS (для управления экраном используется прерывание 10h) и, наконец, путем прямого программирования аппаратуры, в данном случае видеобуфера (видеопамяти). Функции DOS позволяют выводить только черно-белый текст и имеют ряд других ограничений (нельзя очистить экран, нет средств позиционирования курсора); при использовании прерывания BIOS все эти ограничения снимаются, однако программирование с помощью средств BIOS весьма трудоемко; наконец, прямая запись в видеопамять, предоставляя возможность вывода цветного текста в любую точку экрана, является процедурой очень простой и, к тому же, повышает скорость вывода (по сравнением с использованием системных средств) в десятки и сотни