Практика программирования (Бейсик, Си, Паскаль)

       

предоставляют довольно широкие возможности по


Использование системных функций

Системные функции MS-DOS и BIOS (Basic Input/Output System — базовая система ввода/вывода) предоставляют довольно широкие возможности по управлению внешними устройствами — дисками, видеосистемой, таймерами, манипулятором мыши и т. п. Интерфейс их вызова из программ, написанных на языке ассемблера, построен на использовании программного прерывания — машинной команды INT — по следующей схеме:

  • запоминание в стеке содержимого регистров центрального процессора;

  • формирование в регистрах исходной информации для работы системной функции INT n;

  • извлечение из регистров результатов работы системной функции;

  • восстановление регистров по информации из стека.

    В принятой схеме аргумент п в команде 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



    Содержание раздела