Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

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);
}

Драйвер символьного устройства

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

  • принимает и анализирует строку параметров из команды "device=" файла config.sys , преобразует параметры из символьной формы в двоичную и проверяет их корректность;
  • если параметры заданы неправильно, в процессе инициализации выводится сообщение и драйвер не подключается к операционной системе;
  • драйвер встраивает обработчик одного программного прерывания, номер которого задается в строке параметров;
  • встроенный обработчик прерывания моделирует выполнение функций ввода и вывода;
  • демонстрируется использование функций IOCTL и ввода/вывода (драйвер вводит данные при помощи клавиатуры; данные предназначенные для вывода, отображаются на консоли).

Приведем полный текст драйвера (листинг 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

[Назад] [Содеожание] [Дальше]