MS-DOS для программиста© Александр Фролов, Григорий ФроловТом 18, М.: Диалог-МИФИ, 1995, 254 стр. 6.8. Примеры драйверовВ этом разделе мы приведем исходные тексты нескольких драйверов, которые вы можете использовать в своих разработках. Простейший драйвер DRVSIMP.SYSСначала приведем пример простейшего драйвера символьного устройства, который обслуживает только команду инициализации. Фактически этот драйвер не работает ни с каким физическим устройством. Он просто стирает содержимое экрана при инициализации, выводит сообщение и ожидает, когда пользователь нажмет любую клавишу. После этого драйвер завершает свою работу без установки в MS-DOS. Исходный текст драйвера приведен в листинге 6.4. Листинг 6.4. Файл drvsimp\drvsimp.asm .MODEL tiny
.CODE
drvsimp proc far
; Отмечаем конец области памяти, которая
; будет резидентной в памяти. Так как наш
; драйвер не устанавливается резидентно,
; располагаем эту метку в начале драйвера
DriverEnd:
; ------------------------------------------------
; Заголовок драйвера
; ------------------------------------------------
dd 0ffffffffh ;адрес следующего драйвера
dw 8000h ;байт атрибутов
dw dev_strategy ;адрес процедуры стратегии
dw dev_interrupt ;адрес процедуры прерывания
db 'SIMPLDRV' ;имя устройства
; ------------------------------------------------
; Программа стратегии
; ------------------------------------------------
dev_strategy:
mov cs:req_seg, es
mov cs:req_off, bx
ret
; Здесь запоминается адрес заголовка запроса
req_seg dw ?
req_off dw ?
; ------------------------------------------------
; Обработчик прерывания
; ------------------------------------------------
dev_interrupt:
; Сохраняем регистры
push es
push ds
push ax
push bx
push cx
push dx
push si
push di
push bp
; Устанавливаем ES:BX на заголовок запроса
mov ax, cs:req_seg
mov es, ax
mov bx, cs:req_off
; Получаем код команды из заголовка запроса и умножаем
; его на два, чтобы использовать в качестве индекса
; в таблице адресов обработчиков команд
mov al, es:[bx]+2
shl al, 1
sub ah, ah ; записывем 0 в регистр AH
lea di, functions ; смещение таблицы
add di, ax ; добавляем смещение в таблице
jmp word ptr [di] ; переходим на адрес из таблицы
; ------------------------------------------------
; Таблица функций
; ------------------------------------------------
functions LABEL WORD
dw initialize
dw check_media
dw make_bpb
dw ioctl_in
dw input_data
dw nondestruct_in
dw input_status
dw clear_input
dw output_data
dw output_verify
dw output_status
dw clear_output
dw ioctl_out
dw Device_open
dw Device_close
dw Removable_media
; Выходим, если функция не поддерживается
check_media:
make_bpb:
ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_verify:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:
output_data:
input_data:
or es:word ptr [bx]+3, 8103h
jmp quit
quit:
or es:word ptr [bx]+3, 100h
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop ds
pop es
ret
; ------------------------------------------------
; Процедура выводит на экран строку
; символов в формате ASCIIZ
; ------------------------------------------------
Puts proc near
push si
puts_loop:
cmp ds:byte ptr [si],0
jz end_puts
mov ah, 02h
mov dl, ds:byte ptr [si]
int 21h
inc si
jmp puts_loop
end_puts:
pop si
ret
Puts endp
hello db 13,10,'+--------------------------------+'
db 13,10,'| *DRVSIMP* (C)Frolov A., 1995 |'
db 13,10,'+--------------------------------+'
db 13,10
db 13,10,'Press any key...'
db 13,10,0
; ------------------------------------------------
; Выполнение инициализации драйвера
; ------------------------------------------------
initialize:
; Записываем адрес конца драйвера в заголовок
lea ax, DriverEnd
mov es:word ptr [bx]+14, ax
mov es:word ptr [bx]+16, cs
; Стираем экран
mov dh, 18h
mov dl, 80h
xor cx, cx
mov bh, 7
xor al, al
mov ah, 6
int 10h
; Устанавливаем курсор в левый верхний угол экрана
mov bh, 0
xor dx, dx
mov ah, 2
int 10h
; Выводим сообщение
mov ax, cs
mov ds, ax
mov si, offset hello
call Puts
; Ждем, когда пользователь нажмет любую клавишу
mov ax, 0
int 16h
jmp quit
drvsimp ENDP
END drvsimp
Текст этого драйвера транслировался при помощи ассемблера Turbo Assembler версии 3.0. Соответствующий пакетный файл представлен в листинге 6.5. Листинг 6.5. Файл drvsimp\mk.bat tasm drvsimp tlink drvsimp,drvsimp.sys /t Нетрудно заметить, что процедура получения загрузочного модуля драйвера действительно похожа на процедуру получения com-программы. Обратите внимание, что при запуске редактора связей tlink.exe мы указали имя загрузочного файла драйвера drvsimp.sys явным образом. Расширение имени .com указывать нельзя, так как в противном случае будет создан обычный com-файл, для которого стартовый адрес всегда равен 100h. Для драйвера стартовый адрес должен быть равен нулю. Для испытания этого и других драйверов запишите драйвер, например, в корневой каталог диска C: и поместите в файл config.sys такую строку : device=c:\simple.sys Драйвер DRVIOCTL.SYSПриведем исходный текст программы драйвера DRVIOCTL.SYS, который обрабатывает команды ввода и вывода (листинг 6.6). Для ввода драйвер использует клавиатуру, вывод выполняется на экран консоли. Листинг 6.6. Файл drvioctl\drvioctl.asm .MODEL tiny
.CODE
ORG 0
drvioctl proc far
;===================================================
; Заголовок драйвера
;===================================================
dd 0ffffffffh ; адрес следующего драйвера
dw 8000h ; байт атрибутов
dw dev_strategy ; адрес процедуры стратегии
dw dev_interrupt ; адрес процедуры прерывания
db 'IODRIVER' ; имя устройства
;===================================================
; Программа стратегии
;===================================================
dev_strategy:
mov cs:req_seg, es
mov cs:req_off, bx
ret
; Здесь запоминается адрес заголовка запроса
req_seg dw ?
req_off dw ?
;===================================================
; Обработчик прерывания
;===================================================
dev_interrupt:
push es
push ds
push ax
push bx
push cx
push dx
push si
push di
push bp
mov ax, cs:req_seg
mov es, ax
mov bx, cs:req_off
mov al,es:[bx]+2
shl al,1
sub ah,ah
lea di,functions
add di,ax
jmp word ptr [di]
functions LABEL WORD
dw initialize
dw check_media
dw make_bpb
dw ioctl_in
dw input_data
dw nondestruct_in
dw input_status
dw clear_input
dw output_data
dw output_verify
dw output_status
dw clear_output
dw ioctl_out
dw Device_open
dw Device_close
dw Removable_media
check_media:
make_bpb:
ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_verify:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:
or es:word ptr [bx]+3, 8103h
jmp quit
;===================================================
; Обработчик команды вывода данных
;===================================================
output_data:
; Записываем в регистр CL количество
; выводимых символов
mov cl, es:[bx]+18
push cx
; Выводим сообщение о начале вывода
mov ax, cs
mov ds, ax
mov si, offset outmsg
call Puts
pop cx
; Загружаем в DS:SI адрес буфера данных
mov ax, es:[bx]+16
mov ds, ax
mov si, es:[bx]+14
; Выводим на экран символы из буфера
out_loop:
mov ah, 02h
mov dl, ds:byte ptr [si]
int 21h
inc si
loop out_loop
jmp quit
;===================================================
; Обработчик команды ввода данных
;===================================================
input_data:
; Записываем в регистр CL количество
; вводимых символов
mov cl, es:[bx]+18
push cx
; Выводим сообщение о начале ввода
mov ax, cs
mov ds, ax
mov si, offset inpmsg
call Puts
; Загружаем в DS:SI адрес буфера данных
pop cx
mov ax, es:[bx]+16
mov ds, ax
mov di, es:[bx]+14
; Вводим символы с клавиатуры и записываем в буфер
inp_loop:
mov ax,0
int 16h
mov ds:byte ptr [di], al
mov ah, 02h
mov dl, al
int 21h
inc di
loop inp_loop
jmp quit
quit:
or es:word ptr [bx]+3, 100h
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop ds
pop es
ret
;===================================================
; Процедура выводит на экран строку
; символов в формате ASCIIZ
;===================================================
Puts proc near
push si
Puts_loop:
cmp ds:byte ptr [si], 0
jz end_Puts
mov ah, 02h
mov dl, ds:byte ptr [si]
int 21h
inc si
jmp Puts_loop
end_Puts:
pop si
ret
Puts endp
hello db 13,10,'+--------------------------------+'
db 13,10,'| *DRVIOCTL* (C)Frolov A., 1995 |'
db 13,10,'+--------------------------------+'
db 13,10,0
outmsg DB 13,10,'Выведено: ',0
inpmsg DB 13,10,'Введите символ: ',0
E_O_P:
initialize:
lea ax, E_O_P
mov es:word ptr [bx]+14, ax
mov es:word ptr [bx]+16, cs
; Стираем экран
mov dh, 18h
mov dl, 80h
xor cx, cx
mov bh, 7
xor al, al
mov ah, 6
int 10h
; Устанавливаем курсор в левый верхний угол экрана
mov bh, 0
xor dx, dx
mov ah, 2
int 10h
; Выводим сообщение
mov ax, cs
mov ds, ax
mov si, offset hello
call Puts
jmp quit
drvioctl endp
END drvioctl
Драйвер был подготовлен с помощью пакетного файла, представленного в листинге 6.7. Листинг 6.7. Файл drvioctl\mk.bat tasm drvioctl tlink drvioctl,drvioctl.sys /t Для работы с этим драйвером можно использовать программу ctl1.exe (листинг 6.8). Эта программа открывает устройство, вводит из него восемь символов, печатает введенные символы на консоли и выводит их обратно на устройство. Листинг 6.8. Файл drvioctl\ctl1.cpp #include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
int main(void)
{
char buf[10];
int io_handle;
// Открываем устройство с именем IODRIVER
if((io_handle = open("IODRIVER", O_RDWR)) == - 1)
{
// Если открыть не удалось, выводим код ошибки
printf("Open: ошибка %d", errno );
return errno ;
}
// Читаем 8 байт из устройства в буфер buf
if(read(io_handle, buf, 8) == -1)
{
// Если при чтении произошла ошибка,
// выводим ее код
printf("Read: ошибка %d", errno );
return errno ;
}
// Закрываем прочитанную строку нулем
// для последующего вывода функцией printf
buf[8]=0;
printf("\nПолучена строка: %s", buf);
// Записываем только что прочитанные данные
// обратно на то же устройство
if(write(io_handle, buf, 8) == -1)
{
// Если при записи произошла ошибка,
// выводим ее код
printf("Write: ошибка %d", errno );
return errno ;
}
// Закрываем устройство
close(io_handle);
return(0);
}
Эта программа служит примером того, как можно организовать взаимодействие драйвера и прикладной программы, работающей с драйвером. Позже мы приведем пример более сложного драйвера символьного устройства. Ниже мы приведем пример программы ctl2.exe (листинг 6.9), которая сначала устанавливает для драйвера, описанного выше, символьный режима работы и выводит 8 символов. Затем программа выполняет аналогичную операцию в двоичном режиме. При каждом обращении к драйверу для ввода или вывода драйвер выдает сообщение на экран. Запустив программу (и не забыв подключить драйвер), вы увидите, что в символьном режиме для записи или чтения восьми символов драйвер вызывается восемь раз, а в двоичном режиме - только один раз. Листинг 6.9. Файл drvioctl\ctl2.cpp #include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
#include <dos.h>
union REGS inregs, outregs;
struct SREGS segregs;
int main(void)
{
char buf[100];
int io_handle;
// Открываем устройство с именем IODRIVER
if((io_handle = open("IODRIVER", O_RDWR)) == - 1)
{
// Если открыть не удалось, выводим код ошибки
printf("Open: ошибка %d", errno );
return errno ;
}
// Читаем 8 байт из устройства в буфер buf
if(read(io_handle, buf, 8) == -1)
{
// Если при чтении произошла ошибка,
// выводим ее код
printf("Read: ошибка %d", errno );
return errno ;
}
// Закрываем прочитанную строку нулем
// для последующего вывода функцией printf
buf[8] = 0;
printf("\nПрочитана строка: <%s>", buf);
// Выводим только что прочитанные данные
// обратно на то же устройство
if(write(io_handle, buf, 8) == -1)
{
// Если при записи произошла ошибка,
// выводим ее код
printf("Write: ошибка %d", errno );
return errno ;
}
// Получаем информацию об устройстве
inregs.h.ah = 0x44;
inregs.h.al = 0;
inregs.x.bx = io_handle;
intdos ( &inregs, &outregs );
if(outregs.x.cflag == 1)
{
// При ошибке выводим ее код
printf("IOCTL : ошибка %x\n", &outregs.x.ax);
return(-1);
}
// Выводим конфигурацию устройства на экран
printf("\nКонфигурация устройства: %04X\n",
outregs.x.dx);
// Устанавливаем бит 5 (переключаем драйвер
// в двоичный режим обмена данными
inregs.x.dx = (outregs.x.dx | 0x0020) & 0x00ff;
// Устанавливаем конфигурацию устройства
inregs.h.ah = 0x44;
inregs.h.al = 1;
inregs.x.bx = io_handle;
intdos (&inregs, &outregs);
if(outregs.x.cflag == 1)
{
// Выводим код ошибки
printf("IOCTL : ошибка %x\n",&outregs.x.ax);
return(-1);
}
// Выводим конфигурацию устройства
printf("\nКонфигурация устройства: %04X\n",
outregs.x.dx);
// Читаем 8 байт из устройства в буфер buf
// Теперь обмен выполняется в двоичном режиме
if(read(io_handle, buf, 8) == -1)
{
// Если при чтении произошла ошибка,
// выводим ее код
printf("Read: ошибка %d",errno );
return errno ;
}
// Закрываем прочитанную строку нулем
// для последующего вывода функцией printf
buf[8] = 0;
printf("\nПрочитана строка: <%s>", buf);
// Выводим только что прочитанные данные
// обратно на то же устройство
if(write(io_handle, buf, 8) == -1)
{
// Если при записи произошла ошибка,
// выводим ее код
printf("Write: ошибка %d", errno );
return errno ;
}
// Закрываем устройство
close(io_handle);
return(0);
}
Драйвер символьного устройстваПриведем пример драйвера символьного устройства, который вы можете использовать в качестве прототипа. Этот драйвер выполняет следующие действия:
Приведем полный текст драйвера (листинг 6.10). Листинг 6.10. Файл drvchar\drvchar.asm ; Демонстрационный драйвер символьного устройства
;
; (C) Фролов А.В., Фролов Г.В., 1995
.MODEL tiny
.CODE
ORG 0
@@out_ch MACRO c1,c2,c3,c4,c5,c6,c7,c8,c9,c10
mov ah,02h
IRP chr,<c1,c2,c3,c4,c5,c6,c7,c8,c9,c10>
IFB <chr>
EXITM
ENDIF
mov dl,chr
int 21h
ENDM
ENDM
@@out_str MACRO
mov ah,9
int 21h
ENDM
devdrv proc far
dd 0ffffffffh ; адрес следующего драйвера
dw 0C800h ; байт атрибутов
dw dev_strategy ; адрес процедуры стратегии
dw dev_interrupt ; адрес процедуры прерывания
db 'DEVDRIVR' ; имя устройства
;===================================================
; Программа стратегии
;===================================================
dev_strategy:
mov cs:req_seg,es
mov cs:req_off,bx
ret
; Здесь запоминается адрес заголовка запроса
req_seg dw ?
req_off dw ?
;===================================================
;Обработчик прерывания
;===================================================
dev_interrupt:
push es
push ds
push ax
push bx
push cx
push dx
push si
push di
push bp
mov ax, cs:req_seg
mov es, ax
mov bx, cs:req_off
mov al, es:[bx]+2
shl al, 1
sub ah, ah
lea di, functions
add di, ax
jmp word ptr [di]
functions LABEL WORD
dw initialize
dw check_media
dw make_bpb
dw ioctl_in
dw input_data
dw nondestruct_in
dw input_status
dw clear_input
dw output_data
dw output_verify
dw output_status
dw clear_output
dw ioctl_out
dw Device_open
dw Device_close
dw Removable_media
dw reserved
dw reserved
dw reserved
dw generic_ioctl
check_media:
make_bpb:
clear_input:
output_verify:
clear_output:
Removable_media:
reserved:
generic_ioctl:
; Выводим сообщение о том, что вызвана
; команда, которая не поддерживается драйвером
mov ax, cs
mov ds, ax
mov si, offset errmsg
call dpc
; Ожидаем, пока пользователь нажмет
; любую клавишу
mov ax, 0
int 16h
; Устанавливаем признак ошибки
or es:word ptr [bx]+3, 8103h
jmp quit
;==================================================
nondestruct_in:
; Выводим сообщение о начале неразрушающего ввода
mov ax, cs
mov ds, ax
mov si, offset inpmsg_nd
call dpc
; Вводим символ с клавиатуры и помещаем
; его в область запроса
mov ax, 0
int 16h
mov BYTE PTR es:[bx]+0dh, al
jmp quit
;===================================================
input_status:
; Выводим сообщение о вызове команды
; проверки состояния ввода
mov ax, cs
mov ds, ax
mov si, offset statmsg_i
call dpc
; Устанавливаем признак "Занято", так как
; последующая команда чтения приведет к ожиданию
; (буферизация не используется)
or es:word ptr [bx]+3, 0200h
jmp quit
;===================================================
output_status:
; Выводим сообщение о вызове команды
; проверки состояния вывода
mov ax, cs
mov ds, ax
mov si, offset statmsg_o
call dpc
; Бит занятости не устанавливаем, так как
; считаем, что консоль доступна для вывода
or es:word ptr [bx]+3, 0000h
jmp quit
;===================================================
; Обработчик команды вывода данных
output_data:
; Записываем в регистр CL количество
; символов, которые будут выведены
mov cl, es:[bx]+18
push cx
; Выводим сообщение о начале вывода
mov ax, cs
mov ds, ax
mov si, offset outmsg
call dpc
pop cx
; Загружаем в DS:SI адрес буфера данных
mov ax, es:[bx]+16
mov ds, ax
mov si, es:[bx]+14
; Выводим на экран символы из буфера
out_loop:
mov al, ds:byte ptr [si]
@@out_ch al
inc si
loop out_loop
jmp quit
;===================================================
; Обработчик команды ввода данных
input_data:
; Записываем в регистр CL количество
; символов, которые будут введены
mov cl, es:[bx]+18
push cx
; Выводим сообщение о начале ввода
mov ax, cs
mov ds, ax
mov si, offset inpmsg
call dpc
; Загружаем в DS:SI адрес буфера данных
pop cx
mov ax, es:[bx]+16
mov ds, ax
mov di, es:[bx]+14
; Вводим символы с клавиатуры и записываем в буфер
inp_loop:
mov ax, 0
int 16h
mov ds:byte ptr [di], al
@@out_ch al
inc di
loop inp_loop
jmp quit
;===================================================
; Обработчик команды вывода данных IOCTL
ioctl_out:
; Записываем в регистр CL количество
; символов, которые будут вывдедены
mov cl ,es:[bx]+18
; Загружаем в DS:SI адрес буфера данных
mov ax, es:[bx]+16
mov ds, ax
mov si, es:[bx]+14
; Выводим на экран символы из буфера
ioctl_out_loop:
mov al, ds:byte ptr [si]
@@out_ch al
inc si
loop ioctl_out_loop
jmp quit
;===================================================
; Обработчик команды ввода данных IOCTL
ioctl_in:
; Записываем в регистр CL количество
; символов, которые будут введены
mov cl, es:[bx]+18
; Загружаем в DS:SI адрес буфера данных
mov ax, es:[bx]+16
mov ds, ax
mov di, es:[bx]+14
; Вводим символы с клавиатуры и записываем в буфер
ioctl_inp_loop:
mov ax, 0
int 16h
mov ds:byte ptr [di], al
@@out_ch al
inc di
loop ioctl_inp_loop
jmp quit
;===================================================
Device_open:
; Выводим сообщение об открытии устройства
mov ax, cs
mov ds, ax
mov si, offset openmsg
call dpc
jmp quit
;===================================================
Device_close:
; Выводим сообщение о закрытии устройства
mov ax, cs
mov ds, ax
mov si, offset closemsg
call dpc
jmp quit
;===================================================
quit:
or es:word ptr [bx]+3, 100h
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop ds
pop es
ret
;===================================================
parm_off dw ? ; смещение строки параметров
parm_seg dw ? ; сегмент строки параметров
pc_type dw ? ; область памяти для сохранения
int_num dw ? ; значений параметров
out_port dw ?
inp_port dw ?
ctrl_inp_port dw ?
ctrl_out_port dw ?
;===================================================
; Имя interrupt
; Обработчик прерывания INT <nn>
;
; Вход: nn - Номер прерывания, заданный в файле
; config.sys
; AH - Номер выполняемой функции:
; 0 - операция записи;
; 1 - операция чтения
; BH - Адрес (0...7Fh)
; BL - Данные для записи (0...FFh)
; Выход: BL - Прочитанные данные
;
; Описание
;
; Прерывание вызывается командой INT <nn> с
; параметрами:
;
; Вход: nn - Номер прерывания, заданный в файле
; config.sys
; AH - Номер выполняемой функции:
; 0 - операция записи;
; 1 - операция чтения
; BH - Адрес (0...7Fh)
; BL - Данные для записи (0...FFh)
; Выход: BL - Прочитанные данные
;
; Возвращаемое значение
; BL - Прочитанные данные
;===================================================
interrupt:
push ax
push cx
push dx
push bp
push si
push di
push ds
push es
cmp ah, 0 ; команда записи
jz int_write
cmp ah, 1 ; команда чтения
jz int_read
; Обработка неизвестной команды
@@out_ch 13,10,'?','?','?',13,10
; Устанавливаем признак ошибки
mov ax, 0ffffh
jmp int_exit
int_write:
; Выводим сообщение о приходе прерывания,
; предназначенного для записи
@@out_ch 13,10,'W','R','I','T','E',13,10
mov ax, 0
jmp int_exit
int_read:
; Выводим сообщение о приходе прерывания,
; предназначенного для чтения
@@out_ch 13,10,'R','E','A','D',13,10
; Имитация чтения, всегда возвращается значение 55h
mov bl, 55h
mov ax, 0
jmp int_exit
int_exit:
pop es
pop ds
pop di
pop si
pop bp
pop dx
pop cx
pop ax
iret
;===================================================
; Процедура выводит на экран строку
; символов в формате ASCIIZ
dpc proc near
push si
dpc_loop:
cmp ds:byte ptr [si], 0
jz end_dpc
mov al, ds:byte ptr [si]
@@out_ch al
inc si
jmp dpc_loop
end_dpc:
pop si
ret
dpc endp
hello db 13,10,'+--------------------------------+'
db 13,10,'| *DEVDRV* (C)Frolov A., 1995 |'
db 13,10,'+--------------------------------+'
db 13,10,0
outmsg db 13,10,'Вывод на устройство ',0
inpmsg db 13,10,'Ввод с устройства ',0
openmsg db 13,10,'Открываем DEVDRIVR',0
closemsg db 13,10,'Закрываем DEVDRIVR',0
inpmsg_nd db 13,10
db 'Неразрушающий ввод с устройства',0
statmsg_i db 13,10
db 'Чтение состояния ввода ',0
statmsg_o db 13,10
db 'Чтение состояния вывода ',0
errmsg db 13,10
db 'Команда не поддерживается ',0
;===================================================
E_O_P:
initialize:
lea ax, E_O_P
mov es:word ptr [bx]+14, ax
mov es:word ptr [bx]+16, cs
; Смещение и сегмент строки параметров
mov ax, es:word ptr [bx]+18
mov cs:parm_off, ax
mov ax, es:word ptr [bx]+20
mov cs:parm_seg, ax
; Стираем экран
mov dh, 18h
mov dl, 80h
xor cx, cx
mov bh, 7
xor al, al
mov ah, 6
int 10h
; Устанавливаем курсор в левый верхний угол экрана
mov bh, 0
xor dx, dx
mov ah, 2
int 10h
; Выводим сообщение
mov ax, cs
mov ds, ax
mov si, offset hello
call dpc
; Раскодируем строку и проверяем
; корректность заданных параметров
push cs
pop ds
; ES:BX - адрес строки параметров
mov bx, cs:parm_off
mov ax, cs:parm_seg
mov es, ax
; Адрес начала области параметров
mov bp, OFFSET cs:pc_type
; Анализируем параметры
call parm
jc parm_errors
; Устанавливаем вектор прерывания с номером,
; заданным в строке параметров
push cs
pop ds
mov dx, OFFSET cs:interrupt
mov ax, cs:int_num
mov ah, 25h
int 21h
jmp quit
parm_errors:
; Если параметры заданы с ошибкой,
; установку драйвера не выполняем.
; ES:BX указывают на заголовок запроса
mov ax, cs:req_seg
mov es, ax
mov bx, cs:req_off
lea ax, devdrv
mov es:word ptr [bx]+14, ax
mov es:word ptr [bx]+16, cs
jmp quit
;===================================================
; Разбор строки параметров
parm proc near
xor si, si ; индекс в строке параметров
next_chr:
mov al, BYTE PTR es:[bx][si]
cmp al, 0ah ; проверки на конец
je parm_br ; строки параметров
cmp al, 0dh
je parm_br
cmp al, 0h
jz parm_br
; Копируем очередной байт строки параметров в буфер
mov BYTE PTR cs:parm_buffer[si], al
inc si
jmp next_chr
; Закрываем скопированную строку параметров нулем
parm_br:
mov BYTE PTR cs:parm_buffer[si], 0
; Подготавливаем регистры для вызова программы
; анализа параметров и проверяем правильность
; заданных параметров
mov dx, OFFSET sep
mov cl, 6
mov bx, OFFSET cs:parm_buffer
call get_parm
jc err_msg
mov ax,cs:pc_type
cmp ax, 0
jz model_is_valid
cmp ax, 1
jz model_is_valid
jmp err_msg
model_is_valid:
; Если параметры заданы правильно,
; выводим их значения на экран
mov si, OFFSET msg1
call dpc
mov ax, cs:pc_type
call print_word
mov si, OFFSET msg2
call dpc
mov ax, cs:int_num
call print_word
mov si, OFFSET msg3
call dpc
mov ax, cs:out_port
call print_word
mov si, OFFSET msg4
call dpc
mov ax, cs:inp_port
call print_word
mov si, OFFSET msg41
call dpc
mov ax, cs:ctrl_inp_port
call print_word
mov si, OFFSET msg42
call dpc
mov ax, cs:ctrl_out_port
call print_word
@@out_ch 13,10,13,10
clc
jmp end_of_parm
err_msg:
; Если были ошибки в параметрах, выводим
; сообщение об ошибке,
; саму ошибочную строку параметров
; и ожидаем, пока пользователь не нажмет
; на любую клавишу.
; На выходе устанавливаем флаг CARRY
mov si, OFFSET msg5
call dpc
mov ds, cs:parm_seg
mov si, cs:parm_off
call dpline
mov ax, cs
mov ds, ax
mov si, OFFSET msg6
call dpc
mov ax, 0
int 16h
stc
end_of_parm: ret
parm_buffer db 100 DUP (?)
sep db " ",0
msg1 db 13,10,"PC Type ........ ",0
msg2 db 13,10,"Used Interrupt Number ........ ",0
msg3 db 13,10,"Device Input Port ........ ",0
msg4 db 13,10,"Device Output Port ........ ",0
msg41 db 13,10,"Device Inp Control Port ........ ",0
msg42 db 13,10,"Device Out Control Port ........ ",0
msg5 db 13,10,"Invalid Driver Parameter!",13,10,0
msg6 db 13,10,13,10," Press any key...",13,10,0
parm endp
; get_parm
; Разбор строки параметров.
;
; ds:bx - исходная строка, закрытая нулем
; ds:dx - строка разделительных символов,
; закрытая нулем
; ds:bp - буфер параметров
; cx - количество параметров
;
; Разбирается исходная строка ds:bx, разделенная
; символами-сепараторами. Адрес строки,
; содержащей сепараторы, находится в регистрах ds:dx.
; Количество параметров в исходной строке
; находится в регистре cx.
; Строка параметров начинается с полного пути
; к файлу, содержащему драйвер.
; Параметры заданы шестнадцатеричными цифрами.
; Двоичные слова, соответствующие параметрам,
; последовательно записываются в буфер параметров,
; расположенный по адресу ds:bp
;
; В случае ошибки устанавливается флаг переноса
get_parm proc near
push bx
push cx
push ax
push si
xor ch, ch
xor ax, ax
mov si, ax
call strtoc
jc parm_end
parm_loop:
mov ax, 22h
call strtoc
jc parm_end
call hex_to_bin
jc parm_end
mov ds:[bp][si], ax
inc si
inc si
loop parm_loop
parm_end:
pop si
pop ax
pop cx
pop bx
ret
get_parm endp
; strtoc
; Выделение очередного слова из строки
;
; При первом обращении к процедуре:
; Вход:
; ax = 0
; ds:bx - исходная строка, закрытая нулем
; ds:dx - строка сепараторов, закрытая нулем
; Выход:
; ds:bx - подстрока до первого разделителя,
; закрытая нулем
;
; При последующих обращениях
; Вход:
; ax != 0
; ds:dx - строка из сепараторов, закрытая
нулем
; Выход:
; ds:bx - подстрока до следующего
; разделителя, закрытая нулем
;
; При первом вызове функция выделяет из строки первое
; слово до разделителя. При повторном вызове
; возвращает указатель на следующее слово в
; исходной строке. Если все слова из строки
; уже выделены, устанавливается флаг переноса.
strtoc proc near
push bp
push di
mov space, 0
cmp ax, 0
jz first
mov bx, cs:ds1_off
mov ax, cs:ds1_seg
mov ds, ax
first:
cmp BYTE PTR ds:[bx], 0
jz error
mov bp, bx
str_begin:
mov di, dx
compe:
mov ah, BYTE PTR ds:[bp]
cmp ah, 0
jz lab
cmp BYTE PTR ds:[di], ah
jne next
mov BYTE PTR ds:[bp], 0
inc bp
inc BYTE PTR cs:space
jmp str_begin
lab:
mov WORD PTR cs:ds1_off, bp
mov ax, ds
mov WORD PTR cs:ds1_seg, ax
jmp end_proc
next:
inc di
cmp BYTE PTR ds:[di], 0
jnz compe
cmp BYTE PTR cs:space, 0
jnz lab
inc bp
jmp str_begin
error:
stc
end_proc:
pop di
pop bp
ret
ds1_off dw ?
ds1_seg dw ?
space db ?
strtoc endp
; hex_to_bin
; Преобразует шестнадцатеричную строку
; в двоичное число
;
; Вход:
; ds:bx - исходная строка, закрытая нулем
; Выход:
; ax - результат преобразования
;
; Функция преобразует строку из ascii-символов в
; шестнадцатеричном формате в 16-битовый эквивалент.
; В случае переполнения или если строка
; содержит символы, отличные от 0..9, A..F, a..f,
; устанавливается флаг переноса
hex_to_bin PROC NEAR
push bp
push si
push dx
xor ax, ax
mov bp, bx
begin:
cmp BYTE PTR ds:[bp], 0h
jz end_pro
call hex
jc h_error
mov si, 10h
xor dx, dx
mul si
cmp dx, 0
jnz h_error
add ax, bx
inc bp
jmp begin
h_error:
stc
end_pro:
pop dx
pop si
pop bp
ret
hex_to_bin endp
hex proc near
xor bh, bh
mov bl, BYTE PTR ds:[bp]
cmp bl, '0'
jb hex_error
cmp bl, '9'
ja next_big
and bx, 0fh
jmp ok
next_big:
cmp bl, 'A'
jb hex_error
cmp bl, 'F'
ja next_small
sub bl, 37h
jmp ok
next_small:
cmp bl, 'a'
jb hex_error
cmp bl, 'f'
ja hex_error
sub bl, 57h
jmp ok
hex_error:
stc
ok:
ret
hex endp
; dec_to_bin
; Преобразует десятичную строку в двоичное число
;
; Вход:
; ds:bx - исходная строка, закрытая нулем
; Выход:
; ax - результат преобразования
dec_to_bin proc near
push bp
push si
push dx
xor ax, ax
mov bp, bx
d_begin:
cmp BYTE PTR ds:[bp], 0h
jz d_end_pro
cmp BYTE PTR ds:[bp], '0'
jb d_error
cmp BYTE PTR ds:[bp], '9'
ja d_error
mov si, 10
xor dx, dx
mul si
cmp dx, 0
jnz d_error
mov bl, BYTE PTR ds:[bp]
and bx, 0fh
add ax, bx
inc bp
jmp d_begin
d_error:
stc
d_end_pro:
pop dx
pop si
pop bp
ret
dec_to_bin endp
print_word proc near
push ax
push bx
push dx
push ax
mov cl, 8
rol ax, cl
call byte_to_hex
mov bx, dx
@@out_ch bh
@@out_ch bl
pop ax
call byte_to_hex
mov bx, dx
@@out_ch bh
@@out_ch bl
pop dx
pop bx
pop ax
ret
print_word endp
byte_to_hex proc near
push ds
push cx
push bx
lea bx, tabl
mov dx, cs
mov ds, dx
push ax
and al, 0fh
xlat
mov dl, al
pop ax
mov cl, 4
shr al, cl
xlat
mov dh, al
pop bx
pop cx
pop ds
ret
tabl db '0123456789ABCDEF'
byte_to_hex endp
dpline proc near
push si
dpline_loop:
cmp ds:byte ptr [si],0dh
jz end_dpline
cmp ds:byte ptr [si],0ah
jz end_dpline
mov al,ds:byte ptr [si]
@@out_ch al
inc si
jmp dpline_loop
end_dpline:
pop si
ret
dpline endp
devdrv ENDP
END devdrv
Драйвер был подготовлен при помощи пакетного файла, представленного в листинге 6.11. Листинг 6.11. Файл drvchar\mk.bat tasm drvchar tlink drvchar,drvchar.sys /t Для работы с этим драйвером и демонстрации его основных возможностей мы подготовили программу ctl3.exe (листинг 6.12). Листинг 6.12. Файл drvchar\ctl3.cpp // Данная прорамма использует прерывание 80h,
// которое устанавливается демонстрационным
// драйвером. Для правильной установки файл
// config.sys должен содержать, например,
// такую строку:
//
// device=c:\devchar.sys 1 80 378 379 37a 37a
//
// Число 80 означает номер используемого прерывания.
#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
#include <dos.h>
union REGS inregs, outregs;
struct SREGS segregs;
int main(void)
{
char buf[100], ch;
int io_handle;
// Открываем устройство с именем DEVDRIVR
if((io_handle = open("DEVDRIVR", O_RDWR)) == - 1)
{
// Если открыть не удалось, выводим код ошибки
printf("Open: ошибка %d", errno );
return errno ;
}
// Читаем 8 байт из устройства в буфер buf
printf("\nВведите 8 символов с клавиатуры\n");
if(read(io_handle, buf, 8) == -1 )
{
// Если при чтении произошла ошибка,
// выводим ее код
printf("Read: ошибка %d", errno );
return errno ;
}
// Закрываем прочитанную строку нулем
// для последующего вывода функцией printf
buf[8]=0;
printf("\nВведена строка: <%s>", buf);
// Выводим только что прочитанные данные
// обратно на то же устройство
if(write(io_handle, buf, 8) == -1)
{
// Если при записи произошла ошибка,
// выводим ее код
printf("Write: ошибка %d", errno );
return errno ;
}
// Вводим строку IOCTL
printf("\nВведите строку IOCTL (8 символов): ");
inregs.h.ah = 0x44;
inregs.h.al = 2;
inregs.x.bx = io_handle;
inregs.x.dx = (unsigned)buf;
inregs.x.cx = 8;
intdos (&inregs, &outregs);
if(outregs.x.cflag == 1)
{
// При ошибке выводим код ошибки
printf("IOCTL : ошибка %x\n", &outregs.x.ax);
return(-1);
}
buf[8]=0;
printf("\nВведена строка IOCTL : <%s>", buf);
// Выводим строку IOCTL на устройство из buf
printf("\nВыведена строка IOCTL : ");
inregs.h.ah = 0x44;
inregs.h.al = 3;
inregs.x.bx = io_handle;
inregs.x.dx = (unsigned)buf;
inregs.x.cx = 8;
intdos (&inregs, &outregs);
if(outregs.x.cflag == 1)
{
// При ошибке выводим код ошибки
printf("IOCTL : ошибка %x\n", &outregs.x.ax);
return(-1);
}
printf("\n\n\nПроверяем вызов прерывания."
"\n\nНажмите любую клавишу...\n\n");
getch();
printf("\nКоманда записи:\n");
inregs.h.ah = 0x0;
inregs.h.bh = 0x777;
inregs.h.bl = 0x13;
int86 (0x80, &inregs, &outregs);
printf("\nКоманда чтения:\n");
inregs.h.ah = 0x1;
inregs.h.bh = 0x776;
int86 (0x80, &inregs, &outregs);
ch = outregs.h.bl;
printf("Полученное значение: %x\n",ch);
printf("\nНеизвестная команда:\n");
inregs.h.ah = 0x2; // ???
int86 (0x80, &inregs, &outregs);
// Закрываем устройство
close(io_handle);
return(0);
}
Драйвер блочного устройстваПриведем пример драйвера электронного диска, расположенного в основной (не расширенной или дополнительной) памяти компьютера. Этот драйвер предназначен, разумеется, не для замены поставляющегося в составе MS-DOS драйвера ramdrive.sys, однако на его примере можно увидеть, как устроены драйверы блочных устройств. Листинг 6.13. Файл ramdisk\ramdisk.asm ;
; Драйвер электронного диска,
; использует основную память компьютера
;
SEGMENT _TEXT PARA PUBLIC
ASSUME CS:_TEXT,DS:_TEXT
ORG 0
ramdisk PROC far
; Заголовок драйвера
dd 0ffffffffh ; адрес следующего драйвера
dw 2000h ; байт атрибутов
dw dev_strategy ; адрес процедуры стратегии
dw dev_interrupt ; адрес процедуры прерывания
db 1
db 7 dup(?)
; Блок BPB для электронного диска
bpb equ $
dw 512 ; количество байтов в секторе
db 1 ; количество секторов в кластере
dw 1 ; количество зарезервированных секторов
db 2 ; количество копий FAT
dw 64 ; макс. количество файлов в корневом каталоге
dw 360 ; общее количество секторов
db 0fch ; описатель среды носителя данных
dw 2 ; количество секторов на одну копию FAT
bpb_ptr dw bpb ; указатель на блок BPB
; Область локальных переменных драйвера
total dw ? ; количество секторов
verify db 0 ; флаг проверки при записи
start_sec dw 0 ; номер начального сектора
vdisk_ptr dw 0 ; сегмент начала участка памяти,
; в котором расположен диск
user_dta dw ? ; адрес области передачи данных
dw ?
; Образец записи BOOT для инициализации
; первого сектора диска
boot_rec equ $
db 3 dup(0)
db 'MSDOS6.2'
dw 512
db 1
dw 1
db 2
dw 64
dw 360
db 0fch
dw 2
;========================================================
; Программа стратегии
dev_strategy:
mov cs:req_seg,es
mov cs:req_off,bx
ret
req_seg dw ?
req_off dw ?
;=======================================================
;Обработчик прерывания
dev_interrupt:
push es
push ds
push ax
push bx
push cx
push dx
push si
push di
push bp
mov ax, cs:req_seg
mov es, ax
mov bx, cs:req_off
mov al, es:[bx]+2
shl al, 1
sub ah, ah
lea di, functions
add di, ax
jmp word ptr [di]
functions LABEL WORD
dw initialize
dw check_media
dw make_bpb
dw ioctl_in
dw input_data
dw nondestruct_in
dw input_status
dw clear_input
dw output_data
dw output_verify
dw output_status
dw clear_output
dw ioctl_out
dw Device_open
dw Device_close
dw Removable_media
ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:
or es:word ptr [bx]+3, 8103h
jmp quit
;=======================================================
; Построение блока BPB
make_bpb:
push es
push bx
mov cs:WORD PTR start_sec, 0
mov cs:WORD PTR total, 1
call calc_adr
push cs
pop es
lea di, bpb
add si, 11
mov cx, 13
rep movsb
pop bx
pop es
lea dx, bpb
mov es:18[bx], dx
mov es:20[bx], cs
jmp quit
check_media:
; Проверка смены носителя данных.
; Носитель не менялся.
mov es:BYTE PTR 14[bx], 1
jmp quit
; Обработчик команды вывода данных
output_verify:
; Для вывода с проверкой устанавливаем флаг проверки
mov cs:BYTE PTR verify, 1
output_data:
call in_save
mov ax, es:WORD PTR 20[bx]
mov cs:start_sec, ax
mov ax, es:WORD PTR 18[bx]
mov cs:total, ax
call sector_write
mov es, cs:req_seg
mov bx, cs:req_off
cmp cs:BYTE PTR verify, 0
jz no_verify
mov cs:BYTE PTR verify, 0
jmp input_data
no_verify:
jmp quit
;=======================================================
; Обработчик команды ввода данных
input_data:
call in_save
mov ax, es:WORD PTR 20[bx]
mov cs:start_sec, ax
mov ax, es:WORD PTR 18[bx]
mov cs:total, ax
call sector_read
mov es, cs:req_seg
mov bx, cs:req_off
jmp quit
;========================================================
quit:
or es:word ptr [bx]+3, 100h
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop ds
pop es
ret
;========================================================
dpc proc near
push si
dpc_loop:
cmp ds:byte ptr [si], 0
jz end_dpc
mov dl, ds:byte ptr [si]
mov ah, 02h
int 21h
inc si
jmp dpc_loop
end_dpc:
pop si
ret
dpc endp
hello db 13,10,'+--------------------------------+'
db 13,10,'| *RAMDISK* (C)Frolov A., 1995 |'
db 13,10,'+--------------------------------+'
db 13,10,0
;========================================================
; Сохранение адреса буфера и значения счетчика
; из области запроса в области локальных данных
in_save proc near
mov ax, es:WORD PTR 14[bx]
mov cs:user_dta, ax
mov ax, es:WORD PTR 16[bx]
mov cs:user_dta+2, ax
mov ax, es:WORD PTR 18[bx]
xor ah, ah
mov cs:total, ax
ret
in_save endp
; Процедура пересчитывает адрес сектора
; в адрес соответствующего блока памяти.
; В регистре DS возвращается
; сегментный адрес этого блока,
; в CX - общее количество байт во всех секторах.
; Количество секторов задается в total,
; номер начального сектора - в start_sec
calc_adr proc near
mov ax, cs:start_sec
mov cx, 20h
mul cx
mov dx, cs:vdisk_ptr
add dx, ax
mov ds, dx
xor si, si
mov ax, cs:total
mov cx, 512
mul cx
or ax, ax
jnz move_it
mov ax, 0ffffh
move_it:
xchg cx, ax
ret
calc_adr endp
; Чтение сектора из памяти виртуального диска
sector_read proc near
call calc_adr
mov es, cs:user_dta+2
mov di, cs:user_dta
mov ax, di
add ax, cx
jnc read_copy
mov ax, 0ffffh
sub ax, di
mov cx, ax
read_copy:
rep movsb
ret
sector_read endp
; Запись сектора в память виртуального диска
sector_write proc near
call calc_adr
push ds
pop es
mov di, si
mov ds, cs:user_dta+2
mov si, cs:user_dta
mov ax, si
add ax, cx
jnc write_copy
mov ax, 0ffffh
sub ax, si
mov cx, ax
write_copy:
rep movsb
ret
sector_write endp
;========================================================
E_O_P: ;Метка конца программы
;========================================================
initialize:
push cs
pop dx
; Начало памяти, в которой расположен диск
lea ax, cs:vdisk
mov cl, 4
ror ax, cl
add dx, ax
mov cs:vdisk_ptr, dx
; Размер памяти, отведенной для диска
mov ax, 2d00h
add dx, ax
; Записываем в область запроса адрес за
; концом области памяти, отведенной диску
mov es:word ptr [bx]+14, 0
mov es:word ptr [bx]+16, dx
; Количество поддерживаемых логических дисков
; равно 1
mov es:word ptr [bx]+13, 1
; Возвращаем адрес построенного BPB
lea dx, bpb_ptr
mov es:word ptr [bx]+18, dx
mov es:word ptr [bx]+20, cs
; Инициализируем загрузочный сектор
mov es, cs:vdisk_ptr
xor di, di
lea si, boot_rec
mov cx, 24
rep movsb
; Обнуляем два сектора для FAT
mov cs:WORD PTR start_sec, 1
mov cs:WORD PTR total, 2
call calc_adr
push ds
pop es
mov di, si
xor al, al
rep stosb
; Подготавливаем первую копию FAT
mov ds:BYTE PTR [si], 0fch
mov ds:BYTE PTR 1[si], 0ffh
mov ds:BYTE PTR 2[si], 0ffh
; Подготавливаем вторую копию FAT
push ds
push si
mov cs:WORD PTR start_sec, 3
mov cs:WORD PTR total, 2
call calc_adr
push ds
pop es
mov di, si
pop si
pop ds
rep movsb
; Записываем нули в секторы корневого каталога
mov cs:WORD PTR start_sec, 5
mov cs:WORD PTR total, 4
call calc_adr
xor al, al
push ds
pop es
xor di, di
rep stosb
; Выводим сообщение
mov ax, cs
mov ds, ax
mov si, offset hello
call dpc
jmp quit
; Здесь начинается область данных, в которой
; расположен электронный диск. Эта область
; выравнена на границу параграфа.
ALIGN 16
vdisk equ $
ramdisk ENDP
ENDS _TEXT
END ramdisk
Для подготовки этого драйвера мы использовали пакетный файл, представленный в листинге 6.14. Листинг 6.14. Файл ramdisk\mk.bat tasm ramdisk tlink ramdisk,ramdisk.sys /t |

