предоставляют довольно широкие возможности по
Использование системных функций
Системные функции MS-DOS и BIOS (Basic Input/Output System — базовая система ввода/вывода) предоставляют довольно широкие возможности по управлению внешними устройствами — дисками, видеосистемой, таймерами, манипулятором мыши и т. п. Интерфейс их вызова из программ, написанных на языке ассемблера, построен на использовании программного прерывания — машинной команды INT — по следующей схеме:
В принятой схеме аргумент п в команде INT задает номер группы системных функций, код регистра АН — номер подфункции, а содержимое других регистров, если это необходимо, рассматривается как исходные данные для работы выбранной подфункции. Результат работы системной функции также заносится в машинные регистры, содержимое которых должно быть сохранено для последующей обработки.
Перечень машинных регистров, задействованных в обработке программных прерываний, приведен в табл. 10.1. Вас не должно смущать, что некоторые из терминов в этой таблице использованы без детального объяснения.
Таблица 10.1. Машинные регистры обработки программных прерываний
Регистр |
Назначение |
||
АХ |
Основной сумматор. Используется и в качестве регистра данных при обмене с портами |
||
вх |
Дополнительный сумматор. Чаще используется как начальный адрес (база) в командах с индексной адресацией |
||
сх |
Счетчик циклов. Может использоваться в качестве операнда в арифметических операциях |
||
DX |
Основной регистр в операциях ввода/вывода. В сочетании с АХ используется как часть 32-разрядного сумматора в "длинной арифметике" |
||
Регистр |
Назначение |
||
ВР | Указатель базы. Используется для организации локальных стеков в процедурах и функциях | ||
SI | В сочетании с базой сегмента данных (регистр DS) задает адрес источника информации (индекс источника) | ||
DI | В сочетании с базой сегмента данных (регистр DS) задает адрес приемника информации (индекс приемника) | ||
DS | Начальный адрес (база) основного сегмента данных | ||
ES | Регистр базы дополнительного сегмента данных | ||
Flags |
Регистр с битами признаков состояния центрального процессора |
||
Содержимое регистров АХ, вх, сх и DX довольно часто рассматривается как пара 8-разрядных компонент, для обозначения которых используются сочетания АН (старшие 8 битов регистра АХ), AL (младшие 8 битов регистра АХ), ВН, BL, CH, CL, DH, DL.
Если в программу на Си или Паскале не включены непосредственные команды ассемблера, то прямого доступа к машинным регистрам у пользователя нет. Для вызова системной функции по описанной выше схеме приходится обращаться к специальным процедурам-посредникам, которые пересылают значения из данных программы в машинные регистры, моделируют соответствующее программное прерывание и возвращают на поля программы содержимое машинных регистров.
Мы остановимся более детально на двух таких посредниках, считая, что остальными вы научитесь пользоваться самостоятельно.
ТС: int86(n,&in_regs,&out_regs) ;
intdos(&in_regs,&out_regs); //частный случай при n=0х21
ТР: Intr(n,regs);
MSDOS(regs); {частный случай при n=$21}
Часть названия указанных процедур произошла от английского слова interrupt — прерывание. В функциях Си прослеживаются фрагменты обозначений старых процессоров фирмы Intel — 8086, 80186, 80286, 80386, 80486.
Основная часть системных функций поддерживается встроенным программным обеспечением материнской платы, которое раньше было жестко "зашито" в микросхемы BIOS, а теперь находится в более современной перепрограммируемой флэш-памяти. Значительная группа системных функций с общим номером 33 (Ox2i=$2i) составляет часть операционной системы MS-DOS. В этой группе насчитывается 85 подфункций.
В заголовочном файле dos. h описано следующее объединение двух структур:
Struct WORDREGS
{unsigned int ax,t>x, cx,dx, si,di,cflags, flags;} ;
struct BYTEREGS {unsigned char al,ah,bl,bh,cl,ch,dl,dh;};
union REGS {struct WORDREGS x; struct BYTEREGS h;};
К типу REGS относятся аргументы in_regs и out_regs, адреса которых задаются при вызове функций intse и intdos. Если мы включаем в свою программу, например, объединение с именем reg (union REGS reg;), то можем манипулировать c именами полей reg.x. ax, reg.x.bx, reg.h.al, reg.h. ah.
При работе с программными прерываниями значения этих полей можно считать идентичными с содержимым соответствующих машинных регистров — АХ, вх, AL, АН. На практике в программах на Си редко используют два разных объединения in_regs и out_regs, первое из которых выполняет роль полей с входной информацией, а второе — роль полей, на которые заносятся результаты работы системной функции. Обычно входные и выходные данные располагают на одном и том же поле.
В этом смысле авторы системы Turbo Pascal поступили более рационально, использовав в аналогичных процедурах на один аргумент меньше. Данные процедур intr и MSDOS располагаются на полях записи с вариантами типа Registers, описанного в модуле DOS следующим образом:
type
Registers=record case integer of
0: (AX,BX,CX,DX,BP,SI,DI,DS,ES,flags:word);
1: (AL,AH,BL,BH,CL,CH,DL,DH:byte) ; end;
Если в программе на Паскале объявлена запись reg (reg:Registers;), то мы можем использовать переменные с именами reg.АХ, RеG.вх, reg.AL, reg.АН, имея в виду, что их значения идентичны содержимому машинных регистров
АХ, ВХ, AL, АН.
Приводимые ниже фрагменты программ заменяют процедуру gotoxy, перемещающую курсор дисплея в позицию с заданными координатами на текстовой странице с указанным номером. Новая функция (процедура) movetoxy обходится только входными данными.
Программа 10_1.с
#include <dos.h>
void movetoxy(int x, int y, int page)
{
union REGS r;
r.h.ah=2; //номер подфункции позиционирования курсора
г.h.bh=page; //номер текстовой страницы, от 0 до 7
r.h.dl=x; //номер столбца, от 1 до 80
r.h.dh=y; //номер строки, от 1 до 25
int86(0xl0,&r,&r); //обращение к группе функций BIOS с номером 16
}
Программа 10_1.pas
program cursor; uses Dos;
procedure movetoxy(x,y,page:byte);
var
Registers r; begin
r.AH:=2;
r.BH:=page;
r.DL:=x;
r.DH:=y;
Intr ($10, r) ;
end;
Примером системной функции, возвращающей несколько результатов, является процедура вывода текущей даты, использующая подфункцию с номером 0х2А ($2A) в прерывании 0x33 ($33). После ее выполнения в участках памяти, соответствующих машинным регистрам, находится следующая информация:
AL — день недели (0 — воскресенье, 1 — понедельник, ..., 6 — суббота); DL — день месяца; он — номер месяца; сх — год.
Программа 10_2.с
/* 0прос текущей даты через прерывание, функция 33:42*/
#include <dos.h>
#include <stdio.h>
#include <conio.h>
void DatePrint(void);
void main() (
DatePrint(); getch();
}
/ *----------------------------------* /
void DatePrint(void)
{
char *WeekDays[7]={"воскресенье","понедельник",
"вторник","среда","четверг","пятница","суббота"};
union REGS r;
r.h.ah=0x2A;
/*Номер подфункции опроса текущей даты*/
intdos(&r,&r);
/*DL-день, DH-месяц, СХ-год, AL-день недели*/
printf("\n Сегодня %d/%d/%d - %s",r.h.dl,r.h.dh,r.x.ex,
WeekDays[r.h.al]); }
Программа 10_2.pas
program intl; uses Dos;
procedure DatePrint; const
WeekDays:array [0..6] of string=('воскресенье','понедельник1,
'среда','четверг','пятница','суббота'); var
r:Registers;
begin
with r do begin
AH:=$2A; {Номер подфункции опроса текущей даты}
MSDOS(r); {DL-день, DH-месяц, СХ-год, AL-день недели}
write('Сегодня ',DL,':',DH:2,':',СХ,' - ');
writeln(WeekDays[AL]);
end;
end;
begin
DatePrint;
readln;
end.
К сожалению, система программирования QBasic не содержит в своем составе функции, аналогичной вышеописанным. Однако реализовать нечто подобное можно с помощью подпрограммы в машинных кодах, встраиваемой в текст на Бейсике операторами DATA, пересылаемой в оперативную память на место с известным адресом и вызываемой с помощью оператора CALL ABSOLUTE. Одна из таких подпрофамм описана в книге [7] и содержит всего 38 байт. Мы приводим полный текст аналогичного, но более экономного (32 байта), варианта на языке ассемблера для того, чтобы желающие могли расширить область сохраняемых данных и, при необходимости, подменить байт с номером прерывания.
Приведенный ниже текст представляет собой так называемый билистинг, получающийся в результате трансляции исходной программы в язык машинных команд. В левой колонке находятся адреса команд, справа от которых размещаются числовые коды операций и адреса операндов (некоторые машинные команды в явном виде не содержат адреса операндов). В средней колонке команды записаны на языке ассемблера. Каждая строка соответствует отдельной машинной команде или управляющему оператору языка ассемблера. Перед мнемоническим кодом команды может находиться метка, на которую могут ссылаться другие команды (в нашем примере такими метками являются символьные обозначения двухбайтовых слов RegAX, RegBx, RegCx, RegDx, предназначенных для хранения,содержимого машинных регистров). Команды пересылки (код операции mov) перемещают значение второго операнда по первому адресу. Команды записи в стек (коды операции push, pusha) производят пересылку содержимого одного или нескольких машинных регистров в стек. Команды выборки из стека (коды операций pop, рора) осуществляют обратную пересылку из стека значения одного или нескольких регистров. Команда ret возвращает управление вызывающей программе.
Первая и последняя строки билистинга окаймляют подпрограмму. В них содержится название подпрограммы (intoos) и указание о том, что она может находиться в памяти достаточно далеко от точки обращения (far -признак дальнего вызова).
В каждой строке может находиться комментарий, размещаемый справа после точки с запятой. В нашем примере прокомментированы все строки для тех, кто впервые сталкивается с текстом программы на языке ассемблера.
0000 |
IntDos PROC far |
/начало подпрограммы |
||
0000 60 |
pusha |
; сохранение всех регистров |
||
0001 1E |
push ds |
; сохранение ds |
||
0002 ОЕ |
push cs |
; сохранение cs |
||
0003 1F |
pop ds |
; пересылка cs n ds |
||
0004 А1 0018 |
mov ax, RegAX |
; пересылка поля RegAX в ах |
||
0007 |
CD 33 |
int |
33h |
; прерывание с номером 33h |
|||
0009 |
89 IE |
001A |
mov |
RcgBX,bx |
; запоминание регистра bк |
||
000D |
89 0E |
001C |
mov |
RegCX, cx |
; запоминание регистра сх |
||
00Н |
89 16 |
001E |
mov |
RegDX, dx |
;запоминание регистра dx |
||
0015 |
IF |
pop |
ds |
;восстановление ds |
|||
0016 |
61 |
popa |
; восстановление регистров |
||||
0017 |
CB |
ret |
; возврат из подпрограммы |
||||
0018 |
0000 |
RegAX |
DW |
0 |
;поле для содержимого ах |
||
001A |
0000 |
RegBX |
DW |
0 |
;поле для содержимого Ьх |
||
001C |
0000 |
RegCX |
DW |
0 |
;поле для содержимого сх |
||
001E |
0000 |
RegDX |
DW |
0 |
;поле для содержимого dx |
||
IntDos |
ENDP |
конец подпрограммы |
|||||
Текст приведенной выше подпрограммы в шестнадцатеричном формате запоминается в блоке данных:
DATA &Н60,&Н1Е,&НОЕ,&H1F,&НА1,&Н18,SH00,&HCD,&НЗЗ,&Н89
DATA &Н1Е,&Н1А,&Н00,&Н89,&Н0Е,&Н1С,SH00,&H89,&H16,&H1E
DATA &H00,&H1F,&H61,&HCB
Для размещения этой подпрограммы и расположенных в ее хвосте четырех двухбайтовых полей в памяти резервируется массив длиной в 32 байта -intProgd то 16) AS" INTEGER. Переписи в этот массив подлежат только первые 24 байта блока данных — собственно текст программы без полей
RegAX, RegBX, RegCX И RegDX:
DIM IntProgd T0 16) AS INTEGER
' установка сегмента для работы подпрограммы DEF SEG=VARSEG(IntProg(1))
' установка смещения для работы подпрограммы Int33&=VARPTR(IntProgd) )
' Перепись подпрограммы из блока данных в массив F0R J%=0 T0 23
READ K% : P0KE Int33&+J%,K% NEXT J%
Обратите внимание на команды, изменяющие базовый (начальный) адрес сегмента. В качестве новой базы устанавливается адрес начала массива intProg, что позволяет не производить перенастройку адресов машинной программы в зависимости от ее местоположения в оперативной памяти.
В тексте программы, как и в ее прототипе [7], имеется небольшой дефект -после обработки прерывания не запоминается содержимое регистра АХ. А некоторые прерывания заносят в этот регистр результаты своей работы. Например, при опросе текущей даты в младшие разряды регистра АХ зано- сится день недели. Исправить эту оплошность довольно просто -необходимо после команды int добавить команду mov RegAX,ax, которая занимает три байта и в числовом представлении имеет вид АЗ ххуу (здесь ххуу — относительный адрес поля RegDX). При этом адреса переменных RegAX, RegBX, RegCX и RegDX увеличатся на три байта. Но их желательно сохранить на границе полуслова, т. к. с ними оперируют как с элементами целочисленного массива. Поэтому в текст приводимой ниже модификации вставлен еще один байт — пустая команда NOP (числовой код — 90h):
0000 |
IntDos PROC far |
начало подпрограммы |
||
0000 60 |
pus ha |
сохранение всех регистров |
||
0001 IE |
push ds |
сохранение ds |
||
0002 ОЕ |
push cs |
сохранение cs |
||
0003 IF |
pop ds |
пересылка cs в ds |
||
0004 Al 001C |
mov ax, RegAX |
пересылка поля RegAX в ах |
||
0007 90 |
пор |
для выравнивания границы |
||
0008 CD 33 |
int 33h |
прерывание с номером 33h |
||
ОООА A3 001С |
mov RegAX, ax |
запоминание регистра ах |
||
OOOD 89 IE 001E |
mov RegBX,bx |
запоминание регистра Ьх |
||
ООН 89 ОЕ 0020 |
mov RegCX, ex |
запоминание регистра сх |
||
0015 89 16 0022 |
mov RegDX,dx |
запоминание регистра dx |
||
0019 IF |
pop ds |
восстановление ds |
||
001А 61 |
popa |
восстановление регистров |
||
001В СВ |
ret |
возврат из подпрограммы |
||
001С 0000 |
RegAX DW 0 |
поле для содержимого ах |
||
001Е 0000 |
RegBX DW 0 |
поле для содержимого Ьх |
||
0020 0000 |
RegCX DW 0 |
поле для содержимого сх |
||
0022 0000 |
RegDX DW 0 |
поле для содержимого dx |
||
IntDos ENDP |
конец подпрограммы |
|||
Если вам понадобится вставить в текст подобной программы другие машинные команды, то для определения их цифровых кодов советуем зайти в программу td.exe (Turbo Debugger — Турбо Отладчик). Эта программа входит в комплект поставки любой Borland-системы. Набирая в окне ввода команды на языке ассемблера, вы сразу же увидите их аналог в шестнадцатеричной кодировке.
Замену команды INT 33h на команду INT 21h в новом варианте можно выполнить следующим образом:
IntProg(5)=SH21CD
Из-за того, что код операции команды INT занимает младший байт полуслова, т. е. байт с меньшим адресом, а номер прерывания — старший байт, в целочисленной константе &H21CD соответствующие значения переставлены местами. Наверное, вы уже обратили внимание на аналогичные перестановки байтов в двух байтовых адресах команд mov.
Задание номера подфункции, который должен попасть в регистр АХ, следует выполнить путем присвоения нужного значения элементу массива intProg, соответствующего полю RegAX. Так как нулевой адрес ссылается на начало этого массива, т. е. на элемент intProg(1), то адрес &Н1С=28 определяет местоположение элемента intProg(15). Поэтому адреса полей "регистров" АХ,
ВХ, СХ и DX
указывают на элементы
IntProg(15), IntProg(16), IntProg(17)
И
IntProg(18).
После выполнения подпрограммы обработки прерывания необходимо извлечь полученные данные из элементов массива intProg, в которых были сохранены значения соответствующих регистров.
Ниже приведен текст программы опроса текущей даты на Бейсике, использующий в качестве подпрограммы обработки прерывания расширенную версию машинных кодов.
Программа 10_2.bas
DATA &Н60,&Н1Е,SH0E,&H1F,&НА1,&Н1С,&Н00, &Н90, &HCD, &НЗЗ
DATA &HA3,&H1C,&H00,&H89,&H1E,&H1E,SH00,SH89,&H0E,&H20
DATA &H00,&H89,&Д16,&H22,&H00,&H1F,&H61,&HCB
DIM IntProg(1 T0 18) AS INTEGER
'установка сегмента для работы подпрограммы
DEF SEG=VARSEG(IntProg(1))
'установка смещения для работы подпрограммы
Int33& = VARPTR{IntProg(1))
'Перепись подпрограммы из блока данных в массив
F0R J%=0 T0 27
READ K%: P0KE Int33&+J%,К% NEXT J%
IntProg(5)=&H21CD IntProg(15)=&H2A00 CALL ABS0LUTE(Int33&)
F0R K=15 T0 18
PRINT IntProg(K) NEXT К WeekDay=IntProg(15) M0D 256: ' День недели = AL
Day=IntProg(18) MOD 256: ' День месяца = DL
Month=IntProg(18) 256: ' Номер месяца = DH
Year=IntProg(17): ' Год = CX
PRINT USING "Сегодня - ##/##/#### - #";Day;Month;Year;WeekDay
DEF SEG
END