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 |