Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsia_12-14_2011_protsedury_SRC_slaydy.doc
Скачиваний:
9
Добавлен:
21.12.2018
Размер:
1 Mб
Скачать
  1. К имени процедуры в объектном модуле автоматически добавляется @8 (а не @0).

  2. Ассемблер автоматически управляется со стеком.

Трансляция.

  1. В проект Visual C++ включается уже объектный модуль, откомпилированный первоначально с помощью ml.exe.

  2. Если необходимо включить в проект ассемблерный текст, то нужно для него указать способ трансляции, т.е. командную строку для программы ML.EXE.

3) Сложение двух целых чисел.

; файл insert.asm

.386

.model flat,stdcall

.CODE

PUBLIC _power2

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'

_power2 PROC

push ebp ; Входная последовательность-сохранение старого BP

mov ebp,esp ;Установка указателя на запись активации

mov eax,[ebp+8] ;Загрузка Arg 1 в AX

mov ecx,[ebp+0Ch] ;Загрузка Arg 2 в CX

add eax,ecx

;Возвращаемое значение остается в AX

pop ebp ;Выходная последовательность-востановление старого BP

ret ;Выход

_power2 ENDP

_TEXT ENDS

END

Программа на С++:

#include <windows.h>

#include <stdio.h>

#include "stdafx.h"

extern "C" _stdcall power2(int, int);

int main()

{

int a = 3;

int b = 5;

printf("a + b = %d + %d = %d\n", a, b, power2(a,b));

return 0;

}

3) Delphi 5.0

Так как строки Паскаль понимает иначе, чем Си, в качестве строк возьмём символьный массив. Директива stdcall используется и здесь.

{ программа COPYD.PAS, использующая ассемблер

путем подключения объектного модуля }

program Project2;

uses

SysUtils;

{$APPTYPE CONSOLE}

{$L 'copy.OBJ'}

procedure COPYSTR(s1,s2 : PChar); stdcall; EXTERNAL;

var

s1,s2 : array[1..30] of char;

begin

s2[1] ='P';

s2[2] ='r';

s2[3] ='i';

s2[4] ='v';

s2[5] ='e';

s2[6] ='t';

s2[7] = char(0);

COPYSTR(addr(s1[1]),addr(s2[1])) ;

Writeln(s1);

end.

Пример подключения объектного модуля к программе на Delphi.

; файл proc.asm

.386P

.MODEL FLAT, stdcall

PUBLIC COPYSTR

; плоская модель

CODE SEGMENT DWORD PUBLIC USE32 'CODE'

; процедура копирования одной строки в другую

; строка, куда копировать [EBP+08Н]

; строка, что копировать [EBP+0CH]

; не учитывает длину строки, куда производится копирование

COPYSTR PROC

PUSH EBP

MOV EBP,ESP

MOV ESI,DWORD PTR [EBP+0CH]

MOV EDI,DWORD PTR [EBP+08H]

L1:

MOV AL,BYTE PTR [ESI]

MOV BYTE PTR [EDI],AL

CMP AL,0

JE L2

INC ESI

INC EDI

JMP L1

L2:

MOV EAX,DWORD PTR [EBP+08H]

POP EBP

RET 8

COPYSTR ENDP

CODE ENDS

END

Трансляция.

Необходимо подготовить объектный модуль (с помощью транслятора TASM32) и указать его в директиве {$L copy.obj} - далее транслятор сделает все сам.

Использование регистрового (быстрого) типа вызова

В соответствии с таблицей в начале главы, регистровый тип вызова предполагает, что три первых параметра будут передаваться в регистрах (EAX,EDX,ECX), а остальные в стеке, как и в случае соглашения stdcall. При этом если стек был задействован, освобождение его возлагается на вызываемую процедуру.

В случае быстрого вызова транслятор Си добавляет к имени значок @ спереди, что необходимо учитывать в ассемблерном модуле.

#include <windows.h>

#include <stdio.h>

// файл ADDC.cpp

// объявляется внешняя функция сложения четырех целых чисел

extern "C" _fastcall ADDD(DWORD, DWORD, DWORD, DWORD);

void main()

{

DWORD a,b,c,d;

a=1; b=2; c=3; d=4;

printf("%lu\n",(DWORD *)ADDD(a,b,c,d));

ExitProcess(0);

}

Пример регистрового соглашения вызова процедуры.

; файл add.asm

.386P

.MODEL FLAT, stdcall

PUBLIC @ADDD

; плоская модель

_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'

; процедура возвращает сумму четырех параметров

; передача параметров регистровая

; первые три параметра в регистрах EAX,EDX,ECX

; четвертый параметр в стеке, т.е. [ЕРВ+08Н]

@ADDD PROC

PUSH EBP

MOV EBP,ESP

ADD EAX,ECX

ADD EAX,EDX

ADD EAX,DWORD PTR [EBP+08H]

POP EBP

RET 4

@ADDD ENDP

_TEXT ENDS

END

Трансляция модулей:

tasm32 /ml add.asm

включаем add.obj в проект addc (Borland C++ 5.0)

Вызываемая ассемблерная процедура может содержать не только какие-то вспомогательные процедуры, но и работу с функциями API и ресурсами. Причем, ресурсы, разумеется, являются для всех модулей проекта общими. Можно иметь несколько файлов, задающих ресурсы, но главное, чтобы не совпадали имена и идентификаторы.

Лекция №14. Резидентные программы.

Резидентная программа (резидент) - программа, которая постоянно находится в памяти. Примером резидента является драйвер мыши, всевозможные антивирусы, которые следят за тем, что делает та или иная программа и сообщают о ее действиях пользователю и пр.

Как правило, резидентная программа должна перехватывать то или иное прерывание, с тем чтобы программист или другие программы могли обратиться к ней.

Прерывание - это своего рода процедура (подпрограмма), которая имеет не название (например, print_string), а номер. В компьютере есть 256 различных номеров прерываний. Некоторые номера зарезервированы BIOS (ПЗУ) компьютера (например, 16h);

mov ah,10h

int 16h

или операционной системой (MS-DOS) например, 21h:

mov ah,9

mov dx,offset String

int 21h

Есть возможность перехватить, скажем, прерывание 21h, и тем самым контролировать, кто и что делает с ним.

Например, вызовем функцию 09 прерывание 21h:

mov ah,9

mov dx,offset Our_string

int 21h

...

Our_string db 'Привет!$'

В результате ОС выведет на экран сообщение "Привет!".

Можно перехватить прерывание 21h и, если какая-то программа попытается вывести на экран некую строку (не важно, какую), то мы можем в регистры ds:dx занести адрес нашей строки. В таком случае, все, что бы не выводилось на экран путем вызова функции 09 прерывания 21h, на экране будет появляться наша строка. Это можно сделать так:

Часть обработчика прерывания 21h:

...

cmp ah,9

je Out_str

Go_21h:

; здесь передаем управление "настоящему" 21h.

Out_str:

push cs

pop ds

mov dx,offset My_string

jmp Go_21h

...

My_string db “My string!$”

В примере проверяем, вызывается ли функция вывода строки на экран (09h) прерывания 21h или какая-либо другая. Если вызывается другая функция (например, 3Dh), то мы просто передаем управление оригинальному обработчику. В противном случае загружаем в ds:dx адрес некоторой нашей строки и опять-таки передаём управление оригинальному обработчику прерывания 21h.

Обработчик прерывания – это процедура, постоянно (или временно) находящаяся в памяти. Обработчик прерывания первым получает управление, выполняет некоторые действия, а затем передаёт управление оригинальному обработчику ( т. е. процедуре, которая уже находилась в памяти до загрузки нашего обработчика. Оригинальный обработчик может также выполнить некоторые действия, а затем передать управление другому обработчику и т.д.

Оригинальные (скажем так, первичные) обработчики MS-DOS прерываний 20h – 2Fh находятся в файле IO.SYS, MSDOS.SYS. До того момента, пока они не загрузились в память, эти прерывания «пустые». То есть при попытке вызвать одно из прерываний DOS (начиная с 20h) до загрузки указанных выше файлов либо ничего не произойдёт, либо компьютер «зависнет».

Прерывания делятся на программные и аппаратные по способу их вызова.

  1. Программные прерывания вызывает непосредственно программа при помощи команды int (отсюда и название - программные). Например, для того чтобы получить код клавиши, которую нажмёт пользователь (mov ah,0 int 16h), или вывести некоторую строку на экран (mov ah,9 int 21h).

  2. Аппаратные прерывания вызываются самостоятельно процессором (аппаратурой компьютера) при возникновении каких-либо событий. При этом процессор прекращает выполнение текущей программы, сохраняет в стеке регистры ss, sp и флаги, вызывает соответствующее прерывание, а затем восстанавливает сохранённые регистры и продолжает выполнение текущей программы. Например: при нажатии и отпускании какой-либо клавиши пользователем вызывается прерывание ПЗУ 09h. Или прерывание таймера (также ПЗУ) 1Сh, вызываемое автоматически примерно 18,2 раза в секунду.

*******************************************************

Программы могут ненадолго заблокировать вызов аппаратных прерываний (кроме немаскируемых, типа «деления на ноль») с помощью команды cli в момент выполнения критических участков кода. К таким участкам можно отнести смену векторов прерываний напрямую в таблице векторов прерываний или смену стековых регистров. После выполнения необходимых действий программа должна снова разрешать вызов аппаратных прерываний при помощи команды sti. Если аппаратные прерывания не будут разблокированы командой sti после их блокировки командой cli, то это приведёт к «зависанию» компьютера, так как аппаратные прерывания будут заблокированы. Но при этом программные прерывания можно вызывать всегда.

Как правило, все резидентные программы пишутся в .com формате. Программы DOS могут быть записаны в двух форматах:

- формат .EXE;

- формат .COM.

Формат EXE позволяет использовать наиболее общую в DOS сегментацию программы. Программы могут:

– иметь насколько сегментов;

– могут ссылаться на сегмент или группу сегментов по имени.

– могут превышать по размеру 64К.

Программы в формате .COM:

– не могут содержать символьных ссылок на имена групп и сегментов;

– обычно пишутся с использованием модели TINY;

– ограничены по размеру данных или кода 64 килобайтами.

При загрузке программы EXE операционная система устанавливает регистры следующим образом:

Регистр

Значение

CS, DS, ES, SS

Содержит адрес параграфа для префикса программного сегмента программы (PSP). PSP содержит передаваемые программе в командной строке аргументы и указатель на строку операционной среды для программы.

CS:IP

Содержит начальный адрес, заданный в операторе END в одном из модулей программы, или адрес директивы STARTUPCODE

SS:SP

Содержит адрес последнего слова, которое задает в программе сегмент стека.

В программах EXE можно задавать любую модель памяти. Следует использовать более простую модель, поскольку это обеспечивает быстрое выполнение и упрощает программирование. Например, если в программе никогда не предполагается использовать более 64К данных и области стека, то вполне можно использовать модель TINY.

Программы COM представляют собой ограниченные версии программ EXE. Каждую программу формата COM можно представить как программу EXE, но не каждую программу EXE можно представить как программу формата COM. Здесь действуют следующие ограничения:

- Программы COM следует писать с использованием модели TINY.

- В программах COM нельзя использовать предопределенный сегмент стека.

- Программа COM не может содержать прямых адресных ссылок на адрес сегмента или группы. Это означает, что программа не может содержать непосредственных дальних вызовов или ссылаться на сегменты по имени.

- Выполнение должно начинаться со смещения 100h в сегменте кода. Чтобы это произошло, необходимо указать в качестве первой инструкции сегмента кода директиву org 100h.

При загрузке программы COM устанавливаются следующие регистры:

Регистр

Значение

CS, DS, ES, SS

Содержит адрес параграфа в PSP программы.

IP

Устанавливается в значение 100h.

SP

Устанавливается в 0FFFEh (последнее слово в сегменте программы).

Если нет необходимости размещать стек в конце сегмента программы, то нужно установить новый стек. Для такого стека можно использовать неинициализированный сегмент данных (UDATASEG).

Хотя программы COM должны определяться с моделью памяти TINY, с помощью директив CODESEG, DATASEG и UDATASEG можно разделить код, данные и неинициализированные данные.

Как и в случае программ EXE, когда загружается программа COM, Турбо Ассемблер выделяет для ее завершения всю оставшуюся память.

Пример: Создать простейшую программу, которая выводит строку «Hello World!» на экран в формате .com.

.model tiny ; модель памяти, используемая для СОМ

.code ; начало сегмента кода

org 100h ; начальное значение счетчика - 100h

start: mov ah,9 ; номер функции DOS - в АН

mov dx,offset message ; адрес строки - в DX

int 21h ; вызов системной функции DOS

ret ; завершение СОМ-программы

message db "Hello World!",0Dh,0Ah,'$' ; строка для вывода

end start ; конец программы

Обработчики прерываний

Когда в реальном режиме выполняется команда INT, управление передается по адресу, который считывается из специального массива, таблицы векторов прерываний, начинающегося в памяти по адресу 0000h:0000h. Каждый элемент этого массива представляет собой дальний адрес обработчика прерывания в формате сегмент:смещение или 4 нулевых байта, если обработчик не установлен. Команда INT помещает в стек регистр флагов и дальний адрес возврата, поэтому, чтобы завершить обработчик, надо выполнить команды popf и retf или одну команду iret, которая в реальном режиме полностью им аналогична.

; Пример обработчика программного прерывания

int_handler proc far

mov ax,0

iret

int_handler endp

После того как обработчик написан, следующий шаг — привязка его к выбранному номеру прерывания. Это можно сделать, прямо записав его адрес в таблицу векторов прерываний, например так:

push 0 ; сегментный адрес таблицы векторов прерываний

pop es ; в ES

pushf ; поместить регистр флагов в стек

cli ; запретить прерывания

; (чтобы не произошло аппаратного прерывания между следующими

; командами, обработчик которого теоретически может вызвать INT 87h

; в тот момент, когда смещение уже будет записано, а сегментный

; адрес еще нет, что приведет к передаче управления в неопределенную область памяти)

; поместить дальний адрес обработчика int_handler в таблицу

; векторов прерываний, в элемент номер 87h (одно из неиспользуемых прерываний)

mov word ptr es:[87h*4], offset int_handler

mov word ptr es:[87h*4+2], seg int_handler

popf ; восстановить исходное значение флага IF

Теперь команда INT 87h будет вызывать наш обработчик, то есть приводить к записи 0 в регистр АХ.

Перед завершением работы программа должна восстанавливать все старые обработчики прерываний, даже если это были неиспользуемые прерывания типа 87h. Для этого надо перед предыдущим фрагментом кода сохранить адрес старого обработчика. Полный набор действий для программы, перехватывающей прерывание 87h, будет выглядеть следующим образом:

push 0

pop es

; скопировать адрес предыдущего обработчика в переменную old_handler

mov eax,dword ptr es:[87h*4]

mov dword ptr old_handler,eax

; установить новый наш обработчик

pushf

cli

mov word ptr es:[87h*4], offset int_handler

mov word ptr es:[87h*4+2], seg int_handler

popf

; тело программы

[...]

; восстановить предыдущий обработчик

push 0

pop es

pushf

cli

mov eax,word ptr old_handler

mov word ptr es:[87h*4],eax

popf

Для изменения таблицы векторов прерываний в обычных программах, DOS предоставляет две системные функции: 25h и 35h — установить и считать адрес обработчика прерывания, которые и рекомендуются к использованию в обычных условиях.

; скопировать адрес предыдущего обработчика в переменную old_handler

mov ax,3587h ; АН = 35h, AL = номер прерывания

int 21h ; функция DOS: считать адрес обработчика прерывания

mov word ptr old_handler,bx ; возвратить смещение в ВХ

mov word ptr old_handler+2,es ; и сегментный адрес в ES,

; установить наш обработчик

mov ax,2587h ; АН = 25h, AL = номер прерывания

mov dx,seg int_handler ; сегментный адрес

mov ds,dx ; в DS

mov dx,offset int_handler ; смещение в DX

int 21h ; функция DOS: установить обработчик

[...]

; восстановить предыдущий обработчик

lds dx,old_handler ; сегментный адрес в DS и смещение в DX

mov ax,2587h ; АН = 25h, AL = номер прерывания

int 21h ; установить обработчик

Обычно обработчики прерываний используют для того, чтобы обрабатывать прерывания от внешних устройств или чтобы обслуживать запросы других программ.

Резидентные программы

Программы, остающиеся в памяти, после того как управление возвращается в DOS, называются резидентными. Превратить программу в резидентную просто — достаточно вызвать специальную системную функцию DOS.

Функция DOS 31h: Оставить программу резидентной

Ввод:

АН = 31h

AL = код возврата

DX = размер резидента в 16-байтных параграфах (больше 06h), считая от начала PSP

Кроме того, существует и иногда используется предыдущая версия этой функции — прерывание 27h:

INT 27h: Оставить программу резидентной

Ввод:

АН = 27h

DX = адрес последнего байта программы (считая от начала PSP) + 1

Эта функция не может оставлять резидентными программы размером больше 64 Кб, но многие программы, написанные на ассемблере, соответствуют этому условию. Так как резидентные программы уменьшают объем основной памяти, их всегда пишут на ассемблере и оптимизируют для достижения минимального размера.

Принято разделять резидентные программы на активные и пассивные, в зависимости от того, перехватывают ли они прерывания от внешних устройств или получают управление, только если программа специально вызовет команду INT с нужным номером прерывания и параметрами.

_______________

Программа N 01 (первый обработчик прерывания). Записывает в оперативную память пассивный резидент, который для любой программы реагирует на прерывание 21h так, что выводит свою строку 'My string!$'.

CSEG segment

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG

org 100h

Start:

;Переходим на метку инициализации. Нам нужно будет перехватить прерывание 21h, а также оставить программу резидентной в памяти

jmp Init

; Ниже идет, код обработчика прерывания 21h прерывания (он будет резидентный). После того, как программа выйдет, процедура Int_21h_proc останется в памяти и будет контролировать функцию 09 прерывания 21h.

Int_21h_proc proc

pushf ;сохраним в стеке регистр флагов

cmp ah,9 ;проверим: это функция 09h?

je Ok_09

popf ;восстановим регистр флагов

;Если нет, прейдем на оригинальный обработчик прерывания 21h. Все, на метку Ok_09 программа уже не вернется

jmp dword ptr cs:[Int_21h_vect]

Ok_09:

push ds ;Сохраним регистры

push dx

push cs ;Адрес троки должен быть DS:DX

pop ds

; Выводим нашу строку (My_string) вместо той, которую должна была вывести программа, вызывающая 21h-е прерывание

mov dx,offset My_string

pushf

call dword ptr cs:[Int_21h_vect] ;Вывели нашу строку вместо той, которую надо было

pop dx ;восстановим использованные регистры

pop ds

popf

iret ;продолжим работу (выйдем из прерывания)

; Программа, выводящая строку, считает, что на экран было выведено её сообщение. Но на самом деле это не так!

Int_21h_vect dd ? ;переменная для хранения оригинального адреса обработчика 21h

My_string db 'My string!$'

int_21h_proc endp

;Со следующей метки нашей программы уже не будет в памяти (это нерезидентная часть). Она затрется сразу после выхода (после вызова прерывания 27h).

Init:

;Установим наш обработчик (Int_21h_proc) (адрес нашего обработчика) на прерывание 21h. Это позволяет сделать функция 25h прерывания 21h. Но прежде нужно запомнить оригинальный адрес этого прерывания. Для этого используется функция 35h прерывания 21h:

mov ah,35h ;AH содержит номер функции

mov al,21h ;AL указывает номер прерывания, адрес (или вектор) которого нужно получить

int 21h ;тепрь в ES:BX адрес (вектор) прерывания 21h (ES - сегмент, BX - смещение)

mov word ptr Int_21h_vect,bx

mov word ptr Int_21h_vect+2,es

;итак, адрес сохранили. Теперь перехватываем прерывание:

mov ax,2521h

mov dx,offset Int_21h_proc ;DS:DX должны указывать на наш обработчик (т.е. Int_21h_proc)

int 21h

;Теперь, если какая-либо программа вызовет 21h, то вначале компьютер попадет на наш обработчик (Int_21h_proc). Далее следует корректно завершить программу, оставив ее резидентной в памяти (чтобы никто не затер наш обработчик. Иначе компьютер просто зависнет!)

mov dx,offset Init

int 27h

; Прерывание 27h выходит в DOS (как 20h), при этом оставив нашу программу резидентной. DX должен указывать на последний байт, оставшийся в памяти (это как раз метка Init). То есть в памяти остаётся от 0000h до адреса, по которому находится метка Init.

CSEG ends

end Start

_______________

Как проверить работу программы? В данном случае нам понадобится отладчик, который способен заходить внутрь прерываний. Например, AFD и пр., но не как не CodeView. То, что нам сейчас нужно! Если нет AFD, то его можно взять на сайте: http://www.Kalashnikoff.ru

Затем создайте простейшую программу, которая будет выводить на экран некоторую строку путем вызова 09 функции 21h прерывания. Например, так:

Программа N 02

CSEG segment

assume CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

org 100h

Begin:

mov ah,9

mov dx,offset String

int 21h

int 20h

String db 'My string.$'

end Begin

CSEG ends

Запускаем сперва Программу N 01. После того, как она вернется в DOS (в Norton Commander, DOS Navigator, Far и пр. оболочки), запускайте Программу N 02. Что вы увидите?

Теперь запустите Программу N 02 в отладчике. Заходите смело "внутрь" 21h-ого прерывание. Что вы видите теперь?

Управление работой в реальном времени

Когда программа постоянно занята какой-либо работой, но она должна в определенные моменты времени прерывать свои операции для выполнения определенной задачи, то в этом случае можно воспользоваться прерыванием таймера, которое выполняется 18.2 раза в секунду. Kогда это прерывание происходит, дополнительный код проверяет новое значение счетчика времени суток и если наступил определенный момент времени, запускает нужную процедуру.

Hизкий уровень.

BIOS содержит специальное пустое прерывание (1CH), которое ничего не делает, пока для него не будет написана процедура. При старте вектор этого прерывания указывает на инструкцию IRET (возврат из прерывания); при его вызове происходит моментальный возврат. Hо прерывание 1CH вызывается прерыванием таймера BIOS после того, как это прерывание обновило значение счетчика времени суток. Можно сказать, что это аппаратное прерывание, происходящее автоматически 18.2 раза в секунду. Можно изменить вектор этого прерывания так, чтобы он указывал на процедуру в Вашей программе. После этого Ваша процедура будет вызываться 18.2 раза в секунду.

Доступ к часам реального времени возможен либо через ячейки КМОП-памяти, либо через специальные функции BIOS (что более предпочтительно с точки зрения независимости работы программы от особенностей аппаратуры).

Использование регистров КМОП-памяти часами реального времени приведено в таблице:

Регистр

Назначение

0

счетчик секунд

1

регистр секунд будильника

2

счетчик минут

3

регистр минут будильника

4

счетчик часов

5

регистр часов будильника

6

счетчик дней недели (1 - воскресенье)

7

счетчик дней месяца

8

счетчик месяцев

9

счетчик лет (последние две цифры текущего года)

Часы реального времени вырабатывают аппаратное прерывание IRQ8, которому соответствует прерывание с номером 70h. Это прерывание может вырабатываться по трем причинам:

  • Прерывание по окончанию изменения данных. Вырабатывается при установленном в 1 бите 4 регистра состояния B после каждого обновления регистров часов.

  • Прерывание будильника вырабатывается при совпадении регистров часов и регистров будильника и при установленном в 1 бите 5 регистра состояний B.

  • Периодическое прерывание вырабатывается с интервалом примерно 1 миллисекунда при установленном в 1 бите 6 регистра состояний B.

При срабатывании будильника BIOS вырабатывает прерывание INT 4Ah. Программа может подготовить собственный обработчик для этого прерывания.

Для работы с часами реального времени можно обращаться непосредственно к перечисленным выше ячейкам КМОП-памяти, используя порты 70h и 71h. Однако лучше всего воспользоваться функциями 2 - 7 прерывания 1Ah BIOS.

Прочитать показания часов реального времени

На входе: AH = 02h.

На выходе: CH = часы в BCD-формате (например, 13h означает 13 часов);

CL = минуты в BCD-формате;

DH = секунды в BCD-формате;

CF = CY = 1, если часы реального времени не установлены.

Установить часы реального времени

На входе: AH = 03h;

CH = часы в BCD-формате (например, 13h означает 13 часов);

CL = минуты в BCD-формате;

DH = секунды в BCD-формате;

DL = 1, если необходимо использовать летнее время (daylight savings time option).

На выходе: не используются.

Прочитать дату из часов реального времени

На входе: AH = 04h.

На выходе: CH = столетие в BCD-формате ;

CL = год в BCD-формате (например, CX=1991h означает 1991 год);

DH = месяц в BCD-формате;

DL = число в BCD-формате;

CF = CY = 1, если часы реального времени не установлены.

Установить дату в часах реального времени

На входе: AH = 05h;

CH = столетие в BCD-формате ;

CL = год в BCD-формате (например, CX=1991h означает 1991 год);

DH = месяц в BCD-формате;

DL = число в BCD-формате;

На выходе: не используются.

Установить будильник

На входе: AH = 06h;

CH = часы в BCD-формате;

CL = минуты в BCD-формате;

DH = секунды в BCD-формате.

На выходе: CF = CY = 1, если часы реального времени не установлены.

Эта функция позволяет установить будильник на заданное время. Когда будильник "зазвенит", будет вызвано прерывание INT 4Ah (это прерывание вызывают модули BIOS после прихода аппаратного прерывания от часов реального времени IRQ8, т.е. прерывания с номером 70h). Программа, использующая функцию будильника, должна подготовить обработчик прерывания INT 4Ah, завершающий свою работу выполнением команды IRET.

Программа может установить только один будильник.

Сброс будильника

На входе: AH = 07h.

На выходе: не используются.

Основные функции MS-DOS для работы с таймером

MS-DOS использует четыре функции прерывания INT 21h для работы с системным таймером. Эти функции позволяют узнать и установить текущие дату и время. MS-DOS версии 3.30 и более поздних версий при установке времени и даты изменяет также показания часов реального времени.

Определение текущей даты

Для определения текущей даты используется функция 2Ah:

Регистры на входе:

AH = 2Ah

Регистры на выходе:

DL = день (0...31);

DH = месяц (1...12);

CX = год (1980...2099);

AL = номер дня недели:

0 - воскресенье;

1 - понедельник;

2 - вторник;

. . .

6 - суббота

Обратите внимание на то, что функция возвращает вам номер дня недели, который она вычисляет на основе даты.

Установка даты

Для установки даты используйте функцию 2Bh:

Регистры на входе:

AH = 2Bh

DL = день (0...31);

DH = месяц (1...12);

CX = год (1980...2099)

Регистры на выходе:

AL = 0, если установка выполнена правильно;

AL = FFh, если при установке были заданы неправильные параметры

Определение текущего времени

Для того, чтобы определить текущее время, можно воспользоваться функцией 2Ch:

Регистры на входе:

AH = 2Ch

Регистры на выходе:

CH = часы (0-24);

CL = минуты (0-59);

DH = секунды(0...59);

DL = сотые доли секунды (0-99)

Точность времени, полученного при помощи этой функции, определяется таймером (его счетчик обновляется 18,2 раза в секунду).

Установка времени

Для установки времени можно использовать функцию 2Dh:

Регистры на входе:

AH = 2Dh

CH = часы (0-24);

CL = минуты (0-59);

DH = секунды(0-59);

DL = сотые доли секунды (0-99)

Регистры на выходе:

AL = 0, если установка выполнена правильно;

AL = FFh, если при установке были заданы неправильные параметры

Пример 2: Вывести текущее время на экран, использую функцию 02h прерывания 1Ah. В программе нет резидентной части.

.186 ; для pusha/popa и сдвигов

CSEG segment

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG

org 100h

Start:

mov ah,02h ; Функция 02h прерывания 1Ah:

int 1Ah ; чтение времени из RTC,

jc exit_handler ; если часы заняты - в другой раз

mov al,ch ; CH = час в BCD-формате

call bcd2asc ; преобразовать в ASCII,

mov byte ptr output_line[2],ah ; поместить их в

mov byte ptr output_line[3],al ; строку output_line

mov byte ptr output_line[4],':'

mov al,cl ; CL = минута в BCD-формате

call bcd2asc

mov byte ptr output_line[5],ah

mov byte ptr output_line[6],al

mov byte ptr output_line[7],':'

mov al,dh ; DH = секунда в BCD-формате

call bcd2asc

mov byte ptr output_line[8],ah

mov byte ptr output_line[9],al

mov byte ptr output_line[10],'$'

mov ah,9

mov dx,offset output_line

int 21h

int 20h

exit_handler:

mov ah,9

mov dx,offset string1

int 21h

int 20h

; процедура bcd2asc

; преобразует старшую цифру упакованного BCD-числа из AL в ASCII-символ,

; который будет помещен в АН, а младшую цифру - в ASCII-символ в AL

bcd2asc proc near

mov ah,al

and al,0Fh ; оставить младшие 4 бита в AL

shr ah,4 ; сдвинуть старшие 4 бита в АН

or ax,3030h ; преобразовать в ASCII-символы

ret

bcd2asc endp

string1 db 'V drygoy raz.$'

output_line db ?

CSEG ends

Пример 3: Вывести текущее время на экран, использую функцию 02h прерывания 1Ah. Программа должна содержать резидентную часть.

.model tiny

.code

.186 ; для pusha/popa и сдвигов

org 100h

Start:

mov ax, 3 ;

int 10h ;

startproc proc near

; сохранить адрес предыдущего обработчика прерывания 1Ch

mov ax,351Ch ; АН = 35h, AL = номер прерывания

int 21h ; функция DOS: определить адрес обработчика

mov word ptr old_int1Ch,bx ; прерывания

mov word ptr old_int1Ch+2,es ; (возвращается в ES:BX)

; установить наш обработчик

mov ax,251Ch ; АН = 25h, AL = номер прерывания

mov dx,offset int1Ch_handler ; DS:DX - адрес обработчика

int 21h ; установить обработчик прерывания 1Ch

; здесь размещается собственно программа, например вызов command.com

mov ah,1

int 21h ; ожидание нажатия на любую клавишу

; конец программы

; восстановить предыдущий обработчик прерывания 1Ch

mov ax,251Ch ; АН = 25h, AL = номер прерывания

mov dx,word ptr old_int1Ch+2

mov ds,dx

mov dx,word ptr cs:old_int1Ch ; DS:DX - адрес обработчика

int 21h

ret

old_int1Ch dd ? ; здесь хранится адрес предыдущего обработчика

start_position dw 0 ; позиция на экране, в которую выводится текущее время

startproc endp

; обработчик для прерывания 1Ch

; выводит текущее время в позицию start_position на экране

; (только в текстовом режиме)

int1Ch_handler proc far

pusha ; обработчик аппаратного прерывания

push es ; должен сохранять ВСЕ регистры

push ds

push cs ; на входе в обработчик известно только

pop ds ; значение регистра CS

mov ah,02h ; Функция 02h прерывания 1Ah:

int 1Ah ; чтение времени из RTC,

jc exit_handler ; если часы заняты - в другой раз

mov al,ch ; CH = час в BCD-формате

call bcd2asc ; преобразовать в ASCII,

mov byte ptr output_line[2],ah ; поместить их в

mov byte ptr output_line[4],al ; строку output_line

mov al,cl ; CL = минута в BCD-формате

call bcd2asc

mov byte ptr output_line[10],ah

mov byte ptr output_line[12],al

mov al,dh ; DH = секунда в BCD-формате

call bcd2asc

mov byte ptr output_line[16],ah

mov byte ptr output_line[18],al

mov cx,output_line_l ; число байт в строке - в СХ

push 0B800h

pop es ; адрес в видеопамяти

mov di,word ptr start_position ; в ES:DI

mov si,offset output_line ; адрес строки в DS:SI

cld

rep movsb ; скопировать строку

exit_handler:

pop ds ; восстановить все регистры

pop es

popa

jmp cs:old_int1Ch ; передать управление предыдущему обработчику

; процедура bcd2asc

; преобразует старшую цифру упакованного BCD-числа из AL в ASCII-символ,

; который будет помещен в АН, а младшую цифру - в ASCII-символ в AL

bcd2asc proc near

mov ah,al

and al,0Fh ; оставить младшие 4 бита в AL

shr ah,4 ; сдвинуть старшие 4 бита в АН

or ax,3030h ; преобразовать в ASCII-символы

ret

bcd2asc endp

; строка " 00: 00:00 " с атрибутом 1Fh (белый на синем) после каждого символа

output_line db ' ',1Fh,'0',1Fh,'0',1Fh,':',1Fh

db ' ',1Fh,'0',1Fh,'0',1Fh,':',1Fh

db '0',1Fh,'0',1Fh,' ',1Fh

output_line_l equ $ - output_line

int1Ch_handler endp

end start

82

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]