6. Драйверы

6.1. Интерфейс между программным обеспечением и аппаратурой

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

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

Интуитивно ясно, что должна существовать какая-то программная прослойка между аппаратным и программным обеспечением, выполняющая "согласующие" и "унифицирующие" действия. Эта прослойка работает напрямую с аппаратурой, а прикладное (да и системное) программное обеспечение имеет дело только с этой интерфейсной прослойкой.

В разных типах компьютеров и для разных типов операционных систем существуют различные способы устранения зависимости программного обеспечения от аппаратуры. По-видимому, одним из наиболее неудачных следует считать способ, примененный в операционной системе ОС ЕС для ЭВМ ряда ЕС. Для ЭВМ ряда ЕС любое периферийное устройство имеет одинаковый и достаточно сложный аппаратный интерфейс с каналами ЭВМ (канал - это устройство, обеспечивающее ввод/вывод информации в ЭВМ ряда ЕС). Для каждого типа устройства составляются канальные программы (программы, которые будут выполняться каналом). Прикладное программное обеспечение может пользоваться либо вводом/выводом на этом, крайне низком, канальном уровне, либо осуществлять ввод/вывод на уровне так называемых методов доступа. Библиотеки программ методов доступа содержат программы, работающие на канальном уровне. Для любого, даже очень простого нового устройства в этой системе необходим сложный аппаратный интерфейс с каналом. Составление программ своего метода доступа - крайне сложная задача, доступная лишь профессионалам высокого класса. О внесении изменений в существующие программы методов доступа говорить вообще вряд ли стоит, так как они очень сложны, а исходные тексты этих программ, как правило, отсутствуют.

Другой подход используется в операционных системах UNIX и аналогичных ей (XENIX, VENIX и т.д.).

В этих операционных системах для каждого устройства составляется своя программа обслуживания - драйвер. Эта программа выполняет все операции со своим устройством. Сама операционная система имеет дело только с драйвером, а прикладное программное обеспечение для выполнения ввода/вывода вызывает соответствующие модули операционной системы. Такая программная "буферизация" устраняет, с одной стороны, зависимость операционной системы от аппаратуры, с другой - зависимость прикладной программы от операционной системы и от аппаратуры.

Для каждого нового устройства (например, для нового видеоконтроллера) фирма-разработчик поставляет свой драйвер. Если Вы купили новое устройство, Вы просто подключаете новый драйвер к операционной системе и все Ваши любимые старые программы будут теперь работать с новым устройством.

Этот подход имеет тот недостаток, что Вы не сможете легко и свободно переносить Вашу операционную систему с одного типа компьютера на другой, даже совместимый с ним. Драйверы операционной системы UNIX очень сильно привязаны к аппаратуре. На компьютере, который совместим с исходным не полностью, эти драйверы работать не будут.

Обычно операционные системы поставляются с конкретным комплектом драйверов под определенный тип компьютера, и с этим приходится считаться.

Операционная система MS-DOS, работающая на компьютерах фирмы IBM или совместимых с ними, тоже использует механизм драйверов.

Однако драйверы MS-DOS не всегда обращаются напрямую к аппаратуре. Обычно они вызывают функции BIOS, и уже BIOS выполняет все действия по вводу/выводу. Конечно, BIOS содержит программы обслуживания только стандартных устройств ввода/вывода, нестандартные устройства обслуживаются драйверами напрямую.

Использование BIOS как дополнительного интерфейса между драйверами стандартных устройств и аппаратурой резко повышает "живучесть" MS-DOS на не вполне совместимых с IBM персональных компьютерах. И это действительно так - самая распространенная на сегодняшний день операционная система MS-DOS версии 3.30 работает на всех компьютерах, хоть сколько-нибудь совместимых с IBM PC.

Это возможно благодаря тому, что производители совместимых компьютеров учитывают в программах BIOS все аппаратные особенности, и DOS "не видит" отличий. А прикладная программа - тем более.

Почему же этот способ не используется в операционных системах UNIX или OS/2? Дело в том, что к сожалению, программы BIOS не являются реентерабельными. Это не имеет значения для однозадачной MS-DOS, а мультизадачные операционные системы вынуждены сами организовывать обслуживание аппаратуры реентерабельным способом. (Существуют еще проблемы разделения ресурсов между параллельно выполняющимися процессами, которые тоже не решаются в рамках BIOS).

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

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

В разделе, посвященном резидентным программам, мы уже рассказывали о том, как можно организовать обслуживание нестандартных устройств в DOS с помощью TSR-программ. Но это "незаконный" способ с точки зрения DOS, так как при этом DOS не сможет использовать свои стандартные средства ввода/вывода, ориентированные на связь с аппаратурой через драйверы. Поэтому наилучшим способом организации обслуживания аппаратуры является использование драйвера.

6.2. Структура загружаемого драйвера

Драйвер - это еще одна разновидность программ в дополнение к уже изученным нами программам формата COM и EXE. Иногда говорят, что драйверы - это разновидность COM-программ, но это не так. Скорее способ получения загрузочного модуля драйвера похож на способ получения программы в формате COM. Есть еще одно сходство драйверов и программ в формате COM (которое как раз и появляется из-за одинакового способа их получения) - загрузочные модули этих программ являются точным отображением исходного текста на языке ассемблера без добавления каких-либо управляющих блоков в начало файла, как это происходит в программах формата EXE. (К сожалению, драйвер должен быть написан на языке ассемблера. Авторам этой книги не известны способы составления драйверов на других языках программирования).

Но, оказывается, управляющий блок в самом начале модуля драйвера имеется. Это так называемый заголовок драйвера. Только в отличие от программ формата EXE, этот заголовок создается не редактором связи, а самим программистом и должен быть помещен в самое начало исходного текста программы-драйвера.

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

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

Не стоит пытаться запускать драйвер как программу в формате COM, так как управление будет передано в область памяти, содержащую заголовок драйвера, а там нет правильных машинных команд. Поэтому обычно файлы драйверов имеют расширения имени, отличные от COM или EXE. Чаще всего используются расширения SYS, DRV, иногда BIN. На самом деле расширение имени можно задавать любое, так как при описании драйвера в файле CONFIG.SYS указывается его полное имя.

Для драйвера никогда не создается префикс программного сегмента PSP. В начале исходного текста программы-драйвера не ставится директива ORG 100H, как это делается для COM-программы, так как не надо резервировать место для PSP.

Что же представляет из себя загрузочный модуль драйвера?

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

(0) 4 next указатель на заголовок следующего драйвера. Если смещение адреса следующего драйвера равно FFFF, это последний драйвер в цепочке
(+4) 2 attrib атрибуты драйвера
(+6) 2 strateg смещение программы стратегии драйвера
(+8) 2 interrupt смещение программы обработки прерывания для драйвера
(+10) 8 dev_name имя устройства для символьных устройств или количество обслуживаемых устройств для блочных устройств.

Как уже было сказано, все драйверы связаны в цепочку. Самый первый драйвер находится сразу за векторной таблицей связи. Поле next заголовка драйвера указывает на следующий драйвер (на его заголовок). Это поле имеет формат DWORD-указателя и состоит из компоненты адреса сегмента и смещения. Признаком того, что данный драйвер последний в цепочке, служит значение 0FFFFh в компоненте смещения поля next.

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

При загрузке драйверов в память операционная система изменит содержимое поля next в заголовках драйверов для того, чтобы это поле указывало на заголовок следующего драйвера в цепочке. (Изменит в памяти, а не в файле драйвера!)

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

next    DD      0FFFFFFFFh

Следующее поле в заголовке драйвера - поле атрибутов драйвера atrib.

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

Следует специально отметить, что все драйверы можно разделить на две группы - драйверы символьных устройств и драйверы блочных устройств. Первые обслуживают устройства посимвольного ввода/вывода, такие как принтеры, клавиатура, контроллеры последовательной передачи данных RS232C и т.д., вторые ориентированы на ввод/вывод блоками - это диски.

Как правило, все нестандартные устройства, начиная от цифрового вольтметра до роботов и систем автоматизации производственных процессов, обслуживаются символьными драйверами. Хотя этот тип драйверов ориентирован на передачу данных посимвольно, для быстродействующих устройств ввода/вывода можно организовать буферизацию (средствами операционной системы).

Блочные драйверы могут Вам потребоваться в основном для обслуживания своих нестандартных дисковых устройств. Например, можно использовать более плотную запись информации на дискетах для повышения их емкости, можно через аппаратуру связи персонального компьютера и ЭВМ серии ЕС создавать псевдо-винчестеры на дисках ЕС (такие "винчестеры" будут восприниматься DOS как обычные стандартные диски). С помощью блочных драйверов можно организовать защиту информации на дисках от несанкционированного доступа, если Ваш драйвер будет шифровать записываемую на диск информацию и предоставлять ее по предъявлению пароля.

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

Приведем назначение отдельных битов слова атрибутов символьного драйвера:

Бит Назначение
0 1 - драйвер обслуживает стандартное устройство ввода;

0 - этот драйвер не обслуживает стандартное устройство ввода

1 1 - драйвер обслуживает стандартное устройство вывода;

0 - драйвер не обслуживает стандартное устройство вывода

2 1 - это драйвер стандартного устройства NUL;

0 - драйвер не обслуживает устройство NUL

3 1 - драйвер обслуживает часы
4 Зарезервировано, бит должен быть равен 0
5 Зарезервировано, бит должен быть равен 0
6 1 - разрешено использование драйвером функций GENERIC IOCTL (для версий DOS, более поздних, чем 3.2);

0 - функции GENERIC IOCTL не поддерживаются

7-10 Эти биты зарезервированы и должны быть равны 0
11 1 - поддерживаются функции открытия/закрытия устройства (OPEN/CLOSE) для символьных устройств;

0 - функции OPEN/CLOSE для символьных устройств не поддерживаются

12 Зарезервировано, бит должен быть равен 0
13 1 - для символьных устройств поддерживается функция вывода до получения состояния занятости устройства;

0 - функция вывода до состояния занятости не поддерживается

14 1 - поддерживаются функции IOCTL;

0 - функции IOCTL не поддерживаются

15 1 - символьное устройство;

0 - блочное устройство

Для драйверов блочных устройств формат слова атрибутов другой:

Бит Назначение
0 Зарезервировано, бит должен быть равен 0
1 1 - драйвер поддерживает 32-битовую адресацию сектора (для версий DOS, начиная с 4.00 и более поздних); если установлен этот бит, поле номера сектора всех запросов является двойным словом, добавляемым в конец заголовка запроса, старое поле номера сектора должно содержать -1);

0 - используется 16-битовая адресация сектора

2-5 Эти биты зарезервированы и должны быть равны 0
6 1 - поддерживаются логические устройства (используется блочными драйверами для управления "виртуальными" флоппи-дисками, создаваемые драйвером DRIVER.SYS в DOS версии 3.2 и более поздних версиях);

0 - логические устройства для блочных драйверов не поддерживаются;

7-10 Эти биты зарезервированы и должны быть равны 0
11 1 - единица в этом бите означает, что драйвер поддерживает функцию проверки замены носителя данных в устройстве (например, замены дискеты); используется для DOS версий 3.00 и более поздних;

0 - для блочных устройств функция проверки замены носителя данных не поддерживается

12 Зарезервировано, бит должен быть равен 0
13 1 - драйвер не использует стандартное IBM-устройство, и необходимо выдать запрос на построение блока параметров BIOSBIOS BPB;

0 - используется IBM-устройство

14 1 - поддерживаются функции IOCTL;

0 - функции IOCTL не поддерживаются

15 1 - символьное устройство;

0 - блочное устройство

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

attrib  DW      8000h

После слова атрибутов драйвера находятся два очень важных поля: смещение программы стратегии драйвера strateg и смещение программы обработки прерывания interrupt.

Эти две программы используются DOS для организации обращения к драйверу. Для обращения к драйверу DOS формирует в своей области данных запрос, состоящий из заголовка стандартного формата и переменной части запроса, длина и формат которой зависят от типа запроса. После этого DOS считывает из заголовка драйвера значение смещения программы стратегии и передает ей управление, записав в регистры ES:BX адрес заголовка запроса.

Задача программы стратегии - запомнить этот адрес внутри тела драйвера для дальнейшего использования или организовать очередь запросов обслуживания.

Сразу после вызова программы стратегии DOS вызывает программу обработки прерываний, определив ее адрес из поля interrupt заголовка драйвера.

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

Результаты выполнения функции программа прерывания записывает в специально отведенные поля заголовка запроса, и на этом процедура обращения DOS к драйверу завершается.

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

strateg      DW      strateg_proc
interrupt    DW    interrupt_proc

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

Для символьных устройств в этом поле должно располагаться выровненное по левому краю и дополненное до восьми символов пробелами имя устройства. Это имя будет использоваться для обращения к драйверу. Если Вы собираетесь заменить драйвер стандартного символьного устройства DOS на свой, Вы должны записать имя устройства заглавными буквами:

dev_name        DB      'AUX     '

Для блочных устройств первый байт поля dev_name содержит количество устройств, обслуживаемых данным драйвером, остальные семь байтов не используются:

dev_name        DB      1
                DB      7 dup(?)

Таким образом, мы выяснили, что драйвер содержит в самом начале заголовок, и где-то дальше должны располагаться программы стратегии и прерывания. (Не следует путать программу прерывания драйвера с программой обслуживания аппаратных или программных прерываний. Хотя программа прерывания драйвера немного похожа на обработчик программных прерываний, назначение этой программы и механизм ее использования совершенно другой).

Что еще может находиться в программе-драйвере?

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

На длину драйвера накладывается такое же ограничение, как и на длину COM-программ - 64 килобайта, то есть один сегмент.

6.3. Процесс загрузки драйверов

Системный файл DOS IO.SYS содержит некоторые драйверы устройств, составляющие базовую систему ввода/вывода. Эти драйверы появляются в памяти при загрузке операционной системы и связаны в цепочку через поля next в своих заголовках. Такие драйверы являются резидентными драйверами операционной системы.

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

Для подключения драйвера пользователя к операционной системе файл CONFIG.SYS должен содержать команду:

DEVICE=<путь_файла_драйвера>_<параметры>.

Например:

DEVICE=c:\dos\smartdrv.sys 120

В этом примере подключается драйвер smartdrv.sys, который находится в каталоге dos на диске C:. В качестве параметра инициализации драйверу передается число 120. (Параметры считываются драйвером один раз в процессе инициализации драйвера. Об этом мы будем говорить подробно в разделе, посвященном инициализации драйвера).

В списке драйверов драйверы пользователя находятся перед резидентными. В этом можно убедиться, посмотрев на результаты работы программы DRI.COM, описанной ранее:

Device Drivers Information V1.00
Copyright (C)Frolov A.,1990

Address    Attr  Device Name
-------    ----  -----------
02C1:0048  8004  NUL     
112F:0000  8800  RBUSDRIV
10E4:0000  0800  ------> Block Device, Number of Units: 0001
0D86:0000  C800  SMARTAAR
0CC7:0000  A000  XMSXXXX0
0BA5:0000  6842  ------> Block Device, Number of Units: 0003
0070:016E  8013  CON     
0070:0180  8000  AUX     
0070:0192  A040  PRN     
0070:01A4  8008  CLOCK$  
0070:01B6  0842  ------> Block Device, Number of Units: 0003
0070:01CA  8000  COM1    
0070:01DC  A040  LPT1    
0070:01EE  A040  LPT2    
0070:0200  A040  LPT3    
0070:0212  8000  COM2    
0070:0224  8000  COM3    

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

Первым в этом списке всегда идет драйвер с именем устройства NUL. Это нуль-устройство, используемое для тестовых целей. Драйвер псевдоустройства NUL не может быть переназначен, он всегда расположен непосредственно за векторной таблицей связи DOS.

Дальше идут драйверы, описанные в файле CONFIG.SYS следующим образом:

device=sstor.sys
device=e:\C600\BIN\himem.sys
device=e:\c600\bin\smartdrv.sys 530
device=e:\c600\bin\ramdrive.sys 732 512 64 /e 
device=e:\vega\rbusdrv.sys 1 80 378 379 37a 37a

Начиная с драйвера консоли CON, идут резидентные драйверы, имеющие сегментный адрес 0070. Это драйвер последовательного канала связи AUX, драйвер устройства печати PRN, драйвер часов CLOCK$, драйверы последовательных каналов COM1, COM2, COM3 и драйверы устройств печати LPT1, LPT2, LPT3.

Если Ваш драйвер должен заменить стандартный, в поле имени заголовка драйвера укажите имя устройства заглавными буквами (например, LPT1). Система разместит Ваш драйвер в цепочке драйверов до стандартного с именем LPT1.

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

Инициализирующая часть выполняется только один раз при загрузке драйвера. Могут выполняться такие действия, как считывание параметров инициализации из файла CONFIG.SYS, выдача инициализирующих команд на обслуживаемое устройство ввода/вывода, инициализация внутренних областей данных и т.д. Перед завершением своей работы инициализирующая часть драйвера записывает в заголовок запроса размер резидентной части драйвера. Сама инициализирующая часть больше не будет нужна, поэтому эта часть драйвера не оставляется при инициализации резидентной в памяти.

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

Более подробно процесс инициализации будет рассмотрен при описании команды инициализации драйвера.

6.4. Связь драйвера с операционной системой

Рассмотрим теперь более подробно механизм взаимодействия драйвера и операционной системы.

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

next            DD      0FFFFFFFFh
attrib  DW      8000h
strateg DW      strateg_proc
interrupt       DW      interrupt_proc
dev_name        DB      'TESTDRV '

Это символьный драйвер (старший бит поля attrib равен 1), исходный текст содержит только один драйвер (поле next содержит значение 0FFFFFFFFh), имя устройства, которое нужно будет использовать при обращении к драйверу - TESTDRV. Имя устройства не должно совпадать с именем файла, содержащего символьный драйвер, иначе Вы не сможете обратиться к файлу, например, для его переименования - DOS будет работать не с файлом, а с устройством.

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

req_off DW ?
req_seg DW ?

Тогда программа стратегии должна записать содержимое регистра ES в поле req_seg, а регистра BX - в поле req_off:

strateg_proc:   mov cs:req_off,bx
                mov cs:req_seg,es
                ret

Драйвер состоит из одного сегмента кодов, поэтому для адресации данных используется сегментный регистр CS.

Запрос операционной системы к драйверу соcтоит из заголовка, имеющего фиксированный формат и длину 13 байт, и переменной части, размер и формат которой зависит от выполняемой функции.

Приведем формат заголовка запроса:

(0) 1 size Длина запроса в байтах (длина заголовка запроса плюс длина переменной части запроса)
(+1) 1 unit Номер устройства (используется для блочных устройств, указывает, с каким именно устройством, обслуживаемым драйвером, будет работать операционная система)
(+2) 1 cmd Код команды, которую требуется выполнить (может иметь значение от 0 до 18h)
(+3) 2 status Слово состояния устройства, заполняется драйвером перед возвратом управления операционной системе
(+5) 8 reserved Зарезервировано

После вызова программы стратегии DOS передает управление программе прерывания (без параметров). Задача программы прерывания - выполнить команду, код которой находится в поле cmd заголовка запроса. Если драйвер блочного устройства обслуживает несколько логических устройств, то в поле unit находится номер устройства, для которого необходимо выполнить команду.

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

Как результаты выполнения команды возвращаются DOS?

Данные (или адреса данных), полученные драйвером от физического устройства ввода/вывода, помещаются в область переменной части запроса. Кроме того, драйвер должен установить слово соcтояния устройства status в заголовке запроса в соответствии с результатами выполнения команды.

Приведем формат слова состояния устройства:

Бит Назначение
0-7 Код ошибки устройства (если команда выполнена с ошибкой и драйвер установил признак ошибки (бит 15) в единицу, в это поле он должен записать код ошибки).
8 Команда выполнена. Этот бит всегда устанавливается драйвером перед тем, как он возвращает управление операционной системе.
9 Занято. Этот бит устанавливается обработчиком команды, когда физическое устройство занято выполнением предыдущей операции и поэтому не может выполнить требуемую команду. Этот бит используется также для передачи такой информации, как "буфер клавиатуры не пуст", "среда носителя данных заменяемая" (в команде проверки возможности замены среды носителя данных).
10-14 Зарезервировано.
15 Признак ошибки. Устанавливается драйвером, когда он не может обработать запрос или произошла физическая либо логическая ошибка при обработке правильного запроса. Биты 0-7 при этом должны содержать код ошибки.

Приведем таблицу возможных кодов ошибок:

Код Описание
0 Нарушение защиты от записи. Была предпринята попытка записи информации на защищенное от записи устройство.
1 Неизвестное устройство.
2 Устройство не готово.
3 Неизвестная команда. Затребованная команда не поддерживается драйвером.
4 Ошибка CRC. При выполнении команды обнаружена ошибка циклического кода проверки.
5 Неправильная длина запроса. Поле длины в заголовке запроса содержит неверное значение.
6 Ошибка при поиске дорожки (дорожка не найдена).
7 Неизвестный носитель данных.
8 Сектор не найден.
9 Нет бумаги в принтере.
0Ah Ошибка записи.
0Bh Ошибка чтения.
0Ch Общая ошибка.
0Dh Зарезервировано.
0Eh Зарезервировано.
0Fh Неразрешенная замена диска (только для DOS версии 3.0 и более поздних версий).

Общая схема действий программы прерывания драйвера такова:

Приведем фрагмент исходного текста программы прерывания, который выполняет описанные выше функции:

interrupt_proc:

        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    

; Загружаем в регистр AL код команды из заголовка
; запроса и умножаем его на 2 для получения индекса
; в таблице адресов команд

   mov  al,es:[bx]+2
   shl  al,1   

   sub  ah,ah            ;обнуляем AH
   lea  di,functions     ;DI содержит смещение таблицы
                                        ; команд
   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:

; Если функция не поддерживается драйвером, устанавливаем
; в единицу биты 15 (ошибка), 8 (выполнение команды
; завершено). В биты 0-7 записываем код ошибки 3 -
; неизвестная команда.

   or   es:word ptr [bx]+3,8103h  
   jmp  quit

;=======================================================

; Это пример обработчика команды:

Device_open:

; . . . . . . . . . .
; Некоторые действия для открытия устройства.
; . . . . . . . . . .

        jmp quit

;=======================================================

;---выходим, модифицируя байт состояния status в заголовке
;   запроса

quit:
   
        or   es:word ptr [bx]+3,100h  ;устанавливаем бит 8
                                      ;(выполнение команды
                                      ;завершено) 

   pop bp    ;восстанавливаем регистры
   pop di 
   pop si
   pop dx
   pop cx
   pop bx
   pop ax
   pop ds
   pop es
   ret

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

6.5. Функции загружаемого драйвера

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

# 0 - Инициализация драйвера

Эта функция выполняется только один раз при загрузке драйвера и подключении его к операционной системе.

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

Приведем формат запроса для команды инициализации:

(0) 13 header Заголовок запроса.
(+13) 1 n_units Количество устройств, обслуживаемых драйвером. Это поле заполняется только блочным драйвером.
(+14) 4 end_addr Конечный FAR-адрес резидентной части кода драйвера. В это поле драйвер записывает адрес байта памяти, следующего за той частью кода драйвера, которая должна стать резидентной.
(+18) 4 parm FAR-адрес строки параметров инициализации драйвера из файла CONFIG.SYS. Эта строка содержит все, что находится в строке файла после команды 'DEVICE=', она заканчивается символами перевода строки и возврата каретки 0Ah, 0Dh. При возврате драйвер блочного устройства должен записать в это поле адрес массива указателей на блоки параметров BIOSBIOS (BPB), по одному указателю на каждое устройство, обслуживаемое драйвером.
(+22) 1 drive Номер устройства. Для версии DOS 3.0 и более поздних версий в это поле при загрузке драйвера операционная система заносит номер, назначенный устройству, обслуживаемому драйвером. Например, для устройства А: это 0, для B: - 1 и т.д.

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

Затем драйвер может выполнить инициализацию обслуживаемого физического устройства ввода/вывода, инициализацию своих внутренних переменных, вывести на экран какие-либо сообщения либо даже запросить у оператора дополнительные данные - функция инициализации может пользоваться для организации диалога с оператором и других действий функциями прерывания 21h с номерами от 01h до 0Ch, 25h, 30h, 35h и функциями BIOS.

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

Драйверы блочных устройств дополнительно должны возвратить DOS количество обслуживаемых устройств (в поле n_units) и указатель на массив указателей на блоки BPB (в поле parm).

Количество устройств используется DOS для определения логических имен устройств. Например, если Ваш драйвер обслуживает три логических устройства, и на момент его загрузки в системе имеются устройства A:, B: и C:, то устройства, обслуживаемые Вашим драйвером, получат имена D:, E: и F:. Количество устройств необходимо указывать также и в заголовке драйвера, в первом байте поля имени устройства dev_name.

Для каждого логического устройства драйвер должен содержать так называемый блок параметров BIOS (BIOS Parameter Block) BPB.

Блок BPB содержится в загрузочном секторе диска и содержит информацию, необходимую BIOS для работы с диском. Приведем формат BPB:

(0) 2 sect_siz Количество байтов в одном секторе диска.
(+2) 1 clustsiz Количество секторов в одном кластере.
(+3) 2 res_sect Количество зарезервированных секторов.
(+5) 1 fat_cnt Количество таблиц FAT.
(+6) 2 root_siz Максимальное количество дескрипторов файлов, содержащихся в корневом каталоге диска.
(+8) 2 tot_sect Общее количество секторов на носителе данных (в разделе DOS).
(+10) 1 media Байт-описатель среды носителя данных.
(+11) 2 fat_size Количество секторов, занимаемых одной копией FAT.

Подробно формат и назначение полей BPB будут описаны в разделе, посвященном файловой системе DOS.

Приведем фрагмент исходного текста драйвера, возвращающего при инициализации указатель на массив BPB:

        lea     dx,bpb_ptr
        mov     es:[bx+18],dx
        mov     es:[bx+20],cs
        . . . . . . . . . .

В этом примере предполагается, что ES:BX содержит адрес заголовка запроса.

Область данных может содержать, например, такое описание bpb_ptr:

bpb:            DW      512     ; количество байтов на сектор
                DB      1       ; количество секторов на кластер
                DW      1       ; зарезервировано секторов
                DB      2       ; количество копий FAT
                DW      64      ; максимальное количество файлов 
                                ; в корневом каталоге
                DW      360     ; общее число секторов на диске
                DB      0FCh    ; описатель среды
                DW      2       ; количество секторов в FAT
;
bpb_ptr DW      bpb         ; таблица для трех одинаковых
                DW      bpb ; логических устройств
                DW      bpb

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

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

Драйвер символьного устройства при этом может указать в качестве конечного адреса резидентной части программы адрес начала драйвера, т.е. адрес заголовка драйвера. Размер резидентной части при этом будет равен нулю.

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

В обоих случаях включения драйвера в состав операционной системы не произойдет.

Приведем пример простейшего драйвера символьного устройства, который обслуживает только команду инициализации. Фактически этот драйвер не работает ни с каким физическим устройством. Он просто стирает содержимое экрана при инициализации, выводит сообщение и ожидает нажатия оператором любой клавиши. После нажатия драйвер завершает свою работу без включения себя в состав DOS.

Исходный текст драйвера:

          .MODEL tiny
          .CODE        ; Драйвер состоит из одного
                                ; сегмента кода

          org 0        ; Эта строка может отсутствовать

          include sysp.inc

;========================================================

simple    PROC far  ; Драйвер - это FAR-процедура

;========================================================

E_O_P:              ;Метка конца программы,
                                ;устанавливаем ее в начало
                                ;драйвера, т.к. не надо
                                ;оставлять драйвер резидентно
                                ;в памяти

; Заголовок драйвера

          dd   0ffffffffh       ;адрес следующего драйвера
          dw   8000h            ;байт атрибутов
          dw   dev_strategy     ;адрес процедуры стратегии
          dw   dev_interrupt    ;адрес процедуры прерывания
          db   'SIMPLE_D'       ;имя устройство (дополненное
                                            ;пробелами)

;========================================================

; Программа стратегии

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            ; Обнуляем AH
        lea  di,functions     ; DI указывает на смещение
                                          ; таблицы
        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

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,'¦  *SIMPLE* (C)Frolov A., 1990   ¦'
        db 13,10,'+--------------------------------+'
        db 13,10
        db 13,10,'Hit any key...'
        db 13,10,0

;========================================================

initialize:

   lea  ax,E_O_P      ;смещение конца программы в AX
   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 dpc

; Ожидаем нажатия на любую клавишу

        mov   ax,0
        int   16h

        jmp  quit

simple   ENDP

            END simple

В программе используется макро @@out_ch, описанное в файле sysp.inc и предназначенное для вывода символов на экран дисплея. Файл sysp.inc имеется на дискете, прилагающейся к книге. Приведем текст макро @@out_ch:

@@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

Текст этого драйвера транслировался при помощи ассемблера, входящего в состав Quick C 2.01. Полученный объектный модуль обрабатывался пакетным файлом:

link %1.obj;
exe2bin %1.exe %1.sys

Вы можете также использовать макроассемблер MASM версии 5.0 или более поздней версии.

Нетрудно заметить, что процедура получения загрузочного модуля драйвера действительно похожа на процедуру получения COM-программы. Сообщение редактора об отсутствии сегмента стека следует проигнорировать, сегмента стека действительно нет и быть не может, так как драйвер состоит из единственного сегмента кода.

Для испытания этого и других драйверов запишите драйвер в корневой каталог системной дискеты (с которой можно загрузить операционную систему) и поместите в файл CONFIG.SYS, находящийся на этой дискете строку:

DEVICE=a:\simple.sys

Когда Ваш драйвер будет отлажен, его можно переписать на диск и подключить к файлу CONFIG.SYS, находящемуся на диске С:.

# 1 - Проверка замены носителя данных

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

Вообще говоря, довольно трудно определить, заменил ли оператор дискету или там все еще стоит старая, с которой DOS начала работу. Наиболее достоверные результаты можно было бы получить от аппаратуры, если бы она следила за заменой дискет. Но когда аппаратура не отслеживает замены дискет, приходится анализировать метку диска или как-то иначе определять, поменялась дискета или нет. Метка диска - слабое подспорье в этом вопросе, так как обычно при форматировании дискет пользователь не задает метку. Однако MS-DOS 4.01 при форматировании автоматически записывает на дискету уникальный серийный номер, который можно использовать для проверки факта замены дискеты в приемном кармане дисковода.

Можно отслеживать время доступа к диску и считать, что для смены носителя нужно не менее двух секунд.

К чему может привести некорректный ответ DOS на вопрос о замене дискеты?

К очень тяжелым последствиям.

Возможны три варианта ответа на вопрос о замене дискеты:

  1. Да, дискета заменена.
  2. Нет, дискета все та же.
  3. Неизвестно, произошла замена дискеты или нет.

Если DOS получила ответ, что дискета не заменена, она продолжает работу, которой занималась раньше.

Если пришел ответ, что носитель данных заменен, DOS выдает драйверу команду с номером 2. Это запрос драйверу на построение нового BPB. Все буфера, связанные с данным устройством, при этом очищаются, и, если они не были записаны на диск, может произойти потеря информации. Осуществляется чтение каталога и FAT.

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

Приведем формат запроса для команды проверки замены носителя:

(0) 13 header Заголовок запроса.
(+13) 1 media В этом поле драйверу передается байт-описатель среды носителя данных, с которым DOS работала раньше.
(+14) 1 reply В это поле драйвер должен поместить ответ о факте замены среды:
1 - диск не заменялся;
0 - неизвестно;
-1 - диск был заменен.
(+15) 4 vol_id Указатель на предыдущую метку тома (если установлен бит 11 слова атрибута устройства и диск был заменен)

Если драйвер поддерживает функцию проверки замены среды носителя данных (бит 11 слова атрибута установлен в 1) и оказалось, что произошла замена диска, драйвер должен вернуть в поле vol_id указатель на область памяти, содержащую предыдущую метку тома в формате ASCIIZ. Если метка тома не используется драйвером, а бит 11 слова атрибутов установлен, необходимо вернуть указатель на строку "NO_NAME", закрытую двоичным нулем.

Байт-описатель среды media классифицирует используемую среду носителя данных, но делает это неоднозначно. Мы приведем характерные для этого байта параметры дисков:

0FFh 2 стороны, 8 секторов на дорожку;
0FEh 1 сторона, 8 секторов на дорожку;
0FDh 2 стороны, 8 секторов на дорожку;
0FCh 1 сторона, 9 секторов на дорожку;
0F9h 2 стороны, 15 секторов на дорожку;
0F8h жесткий диск;
0FEh, 0FDh используются восьмидюймовые дискеты.

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

# 2 - Построить блок BPB

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

Драйвер должен возвратить адрес нового блока BPB.

Формат запроса для этой команды:

(0) 13 header Заголовок запроса.
(+13) 1 media В этом поле драйверу передается байт-описатель среды носителя данных, с которым DOS работала раньше.
(+14) 4 buf_adr Адрес буфера обмена. Содержимое этого буфера при вызове драйвера зависит от установки бита 13 слова атрибутов устройства (IBM-формат). Если этот бит равен 0 (устройство формата IBM), буфер содержит первый сектор первой копии FAT. В противном случае указатель установлен на буфер свободного сектора.
(+18) 4 bpb_adr Указатель на новый BPB, записывается в это поле драйвером.

Операционная система MS-DOS версии 3.0 и более поздних версий включает поддержку аппаратных средств проверки смены носителя данных. Если драйвер обнаружил, что смена носителя данных произошла неправильно, в неподходящий момент времени, он может вернуть ошибку с кодом 15 (неразрешенная замена диска).

# 3 - IOCTL чтение, 4 - Чтение, 8 - Запись, 9 - Запись с проверкой,

12 - IOCTL запись, 16 - Вывод, пока не занято

Все эти команды используются для чтения/записи информации из устройства или в устройство соответственно. Они имеют практически одинаковый формат запроса, поэтому мы их будем рассматривать вместе.

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

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

Приведем формат запроса для этих команд:

(0) 13 header Заголовок запроса.
(+13) 1 media В этом поле драйверу передается байт-описатель среды носителя данных.
(+14) 4 buf_adr Адрес буфера для передачи данных.
(+18) 2 count Количество передаваемых байтов для символьных устройств или секторов для блочных устройств.
(+20) 2 sector Номер начального сектора, если драйвер использует 16-битовую адресацию секторов или -1 для 32-битовой адресации. Это поле не используется символьными драйверами.
(+22) 4 vol_id Указатель на метку тома в формате ASCIIZ. Возвращается блочным драйвером, если он выставляет ошибку 15 (неправильная смена диска). Это поле должно содержать ссылку на метку требуемого диска.
(+26) 4 sect32 Номер начального сектора, если содержимое поля sector равно -1. Первым идет старшее слово номера сектора. Если обнаружена ошибка с номером 15, в это поле записывается указатель на метку тома.

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

Для команды 9 (запись с проверкой) драйвер должен после выполнения записи проверить записанные данные. Если с консоли введена команда DOS VERIFY ON, то для дисковых устройств DOS вместо команды записи 8 использует команду записи с проверкой 9.

Драйвер может возвратить количество переданных байтов меньше запрошенного, это само по себе не является ошибкой. Иногда может получиться так, что надо выполнить запись 64 Кбайтов. Такая операция может вызвать переход за границу сегмента в буфере передачи данных. В этом случае драйвер должен проигнорировать лишние байты. Например, надо записать 10000h байтов, распределенных по секторам из буфера с адресом ХХХХ:0001. В этом случае драйвер должен проигнорировать последние два байта. Они будут записаны в следующий раз.

IOCTL чтение/запись - это обмен с устройством управляющей информацией. Мы будем подробно говорить об этом ниже.

Команда с кодом 16 - "вывод, пока не занято", предназначена для работы с такими устройствами ввода/вывода, которые имеют свой собственный буфер, например, принтеры.

Приведем исходный текст программы драйвера, использующего команды ввода и вывода. Для ввода драйвер использует клавиатуру, вывод осуществляется на экран дисплея.

          .MODEL tiny
          .CODE        ; Драйвер состоит из одного
                       ; сегмента кода

          org 0        ; Эта строка может отсутствовать

          include sysp.inc

;========================================================

iodrv    PROC far         ;драйвер - это 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

; Устанавливаем 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            ; Обнуляем AH
        lea  di,functions     ; DI указывает на смещение
                                          ; таблицы
        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 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


;========================================================

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

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,'¦   *IODRV* (C)Frolov A., 1990   ¦'
        db 13,10,'+--------------------------------+'
        db 13,10,0

outmsg   DB 13,10,'___ Вывод на устройство IODRIVER ___',0
inpmsg   DB 13,10,'___ Ввод с устройства   IODRIVER ___',0

;========================================================

E_O_P:            ;Метка конца программы

initialize:

   lea  ax,E_O_P      ;смещение конца программы в AX
   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 dpc

        jmp  quit

iodrv   ENDP

                  END iodrv

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

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <errno.h>
#include <dos.h>

int main(void);

int main(void) {

    char buf[100];
         int io_handle;
         unsigned count;

         // Открываем устройство с именем IODRIVER

         if( (io_handle = open("IODRIVER", O_RDWR)) == - 1 ) {

                // Если открыть не удалось, выводим
                // код ошибки

                printf("Ошибка при открытии устройства %d",errno);
                return errno;
         }

         // Читаем 8 байт из устройства в буфер buf

         if( (count = read(io_handle, buf, 8)) == -1 ) {

                // Если при чтении произошла ошибка,
                // выводим ее код

                printf("Ошибка чтения %d",errno);
                return errno;
        }

        // Закрываем прочитанную строку нулем
        // для последующего вывода функцией printf

        buf[8]=0;

        printf("\n___ Введена строка: %s ___",buf);

        // Выводим только что прочитанные данные
        // обратно на то же устройство

        if( (count = write(io_handle, buf, 8)) == -1 ) {

                // Если при записи произошла ошибка,
                // выводим ее код

                printf("Ошибка записи %d",errno);
                return errno;
        }

        // Закрываем устройство
        close(io_handle);
        exit(0);
}

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

# 5 - Неразрушающее чтение без ожидания

Эта команда предназначена для выборки одного байта из буфера символьного устройства. Для блочных устройств эта команда не используется.

Если в буфере символьного устройства есть байты, которые могут быть прочитаны командой чтения, драйвер возвращает следующий символ, который был бы прочитан.

Если в результате проверки выяснится, что в буфере есть символы, драйвер должен установить бит 9 слова состояния устройства (занятость) в 0, в противном случае - в 1.

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

Формат запроса для команды 5:

(0) 13 header Заголовок запроса.
(+13) 1 byte В это поле драйвер записывает извлеченный из буфера байт, который будет считан по следующей команде ввода.

# 6 - Проверить состояние устройства ввода.

10 - Проверить состояние устройства вывода.

Эти команды проверяют состояние устройства ввода и устройства вывода соответственно (только символьные устройства).

Для устройства ввода бит занятости слова состояния (бит 9) сбрасывается в ноль, если буфер устройства не пуст и в нем есть готовые для чтения символы. Этот бит устанавливается драйвером в единицу, когда буфер пуст, и последующая команда чтения приведет к ожиданию ввода от устройства, например, к ожиданию нажатия на клавишу.

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

Для команд проверки состояния запрос состоит только из заголовка, область переменного формата отсутствует.

# 7 - Сброс буфера устройства ввода

11 - Сброс буфера устройства вывода

Эти функции заставляют драйвер сбросить все текущие запросы на ввод/вывод.

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

Запрос состоит только из заголовка.

# 13 - Открыть устройство

14 - Закрыть устройство

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

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

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

Стандартные устройства CON, AUX, PRN всегда открыты.

Запрос для этих команд состоит только из заголовка.

# 15 - Проверка сменяемости диска

Эта команда используется только для тех драйверов, в слове атрибутов которых бит 11 установлен в 1. Драйвер должен сообщить DOS, возможна ли замена носителя данных. Например, замена жесткого диска обычно невозможна, хотя есть накопители со сменными жесткими дисками.

Драйвер возвращает информацию о возможности замены носителя в бите 9 слова состояния устройства. Значение этого бита, равное 0, драйвер устанавливает, если возможна замена носителя данных, в противном случае устанавливается значение, равное 1.

Запрос состоит только из заголовка.

# 19 - Функции управления вводом/выводом (IOCTL)

Команда с кодом 19 предназначена для выполнения нескольких функций и поддерживается только теми драйверами, у которых в слове атрибутов устройства установлен в 1 бит поддержки IOCTL (бит 14). Эта команда поддерживается DOS версии 3.2 и более поздних версий.

Команда используется для выполнения ряда операций с дисками, доступных обычно лишь на уровне BIOS, например, форматирования, чтение/запись секторов по их абсолютному номеру и т.д. Имеется стандартный интерфейс для различных типов дисков, обеспечиваемых драйвером логических дисков DRIVER.SYS.

DOS имеет специальную функцию номер 44h прерывания 21h. Эта функция имеет множество подфункций и предназначена для поддержки IOCTL. Очень скоро мы займемся этой функцией DOS, а сейчас приведем формат запроса для команды с кодом 19:

(0) 13 header Заголовок запроса.
(+13) 1 funct Это поле содержит код функции команды общего IOCTL.
(+14) 1 subfunc Код подфункции для функции funct.
(+15) 2 si_reg Значение регистра SI при вызове функции 44h прерывания 21h. Эта функция DOS предназначена для управления вводом/выводом.
(+17) 2 di_reg Значение, передаваемое при вызове функции 44h прерывания 21h через регистр DI.
(+19) 4 buf Указатель на буфер данных, содержащий управляющую информацию для устройства или предназначенный для приема информации от устройства.

# 23 - Получить активное логическое устройство

24 - Установить активное логическое устройство

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

Команды обеспечивают метод опроса номера текущего активного логического устройства на физическом диске или установления активного логического устройства.

Приведем формат запроса:

(0) 13 header Заголовок запроса.
(+13) 1 unit Код логического устройства, которое должно стать активным при использовании команды 24, или код активного устройства, помещаемый драйвером по команде 23.
(+14) 1 cmd Код команды.
(+15) 4 status Слово состояния.
(+19) 4 reserved Зарезервировано.

По команде 23 (получить активное логическое устройство) драйвер должен поместить идентификатор устройства в поле unit, для устройства А: помещается 1, для В: - 2 и т.д. Если драйвер управляет единственным устройством, он должен записать в поле unit ноль.

После обзора команд перейдем к описанию функции 44h прерывания 21h. Эта функция предназначена для управления вводом/выводом и обладает широкими возможностями.

6.6. Функции управления устройствами ввода/вывода IOCTL

В этом разделе мы рассмотрим средства управления устройствами ввода/вывода IOCTL (сокращение от Input/Output Control). Использование IOCTL - обычный метод организации связи с устройствами или получения информации об открытых файлах.

Описания будут приводиться в порядке номеров подфункций функции 44h прерывания 21h.

Для версии DOS 2.1 и более поздних версий поддерживаются подфункции с номерами от 0 до 7, для версии 3.0 дополнительно могут использоваться подфункции 8, 0Bh, для 3.1 - 9 и 0Ah, для 3.2 и более поздних версий добавляются подфункции 0Dh, 0Eh, 0Fh. Версия DOS 3.3 и более поздние версии поддерживают также подфункцию 0Ch.

# 00h Получить информацию об устройстве

Вызов:

Регистр Содержание
AH 44h
AL 00h
BX Индекс устройства (handle). Значение, которое операционная система возвращает при открытии файла или устройства и которое она затем использует для доступа к открытому файлу или устройству.

Возврат без ошибки:

Регистр Содержание
CF 0
DX Информация об устройстве

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

06h - BX содержит несуществующий или неоткрытый handle.

Функция возвращает в регистре DX информацию об устройстве, которая имеет следующий формат (для устройства):

Бит Значение
0 Это устройство является стандартным устройством ввода.
1 Стандартное устройство вывода.
2 NUL-устройство.
3 Часы.
4 Специальное устройство.
5 1 - двоичный режим работы;

0 - режим ASCII.

6 0 - при чтении достигнут конец файла.
7 1 - это слово информации относится к устройству (данный handle относится к устройству);

0 - слово информации относится к файлу.

8-10 Зарезервировано.
11 1 - Устройство поддерживает команды открытия/закрытия.
12 Сетевое устройство (только для DOS версии 3.0 и более поздних версий).
13 Устройство поддерживает вывод до состояния занятости.
14 Устройство может обрабатывать управляющие строки IOCTL, посылаемые подфункциями 2, 3, 4, 5 функции 44h. Этот бит может быть только прочитан, его установка подфункцией 1 функции 44h не производится.
15 Зарезервировано.

Если при вызове этой подфункции регистр BX содержал индекс файла, формат получаемой в регистре DX информации будет следующий:

Бит Значение
0-5 Номер дисковода (0-А:, 1-В: и т.д.).
6 0 - Была запись в выходной файл.
7 1 - это слово информации относится к устройству (данный handle относится к устройству);

0 - слово информации относится к файлу.

8-11 Зарезервировано.
12 Сетевое устройство (только для DOS версии 3.0 и более поздних версий).
13-14 Зарезервировано.
15 1 - Данный файл является удаленным при работе в сети (только для DOS версии 3.0 и более поздних версий).

Особое внимание следует обратить на бит 5 слова информации об устройстве. Этот бит определяет режим обмена данными DOS и драйвера - двоичный или ASCII. В двоичном режиме управляющие символы CTRL-C, CTRL-P, CTRL-S, CTRL-Z интерпретируются как обычные данные.

# 01h Установить информацию об устройстве

Вызов:

Регистр Содержание
AH 44h
AL 01h
BX Индекс файла (handle)
DH 0
DL Устанавливаемая информация (биты 0-7 слова информации об устройстве, описанного выше)

Возврат без ошибки:

Регистр Содержание
CF 0

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

06h - BX содержит несуществующий handle.

Эта подфункция позволяет устанавливать некоторые биты в слове информации об устройстве.

В зависимости от установки бита 5 (двоичный/ASCII) драйвер по-разному выполняет команды ввода-вывода.

В режиме ASCII обмен с драйвером символьного устройства выполняется по байтам (содержимое поля count в запросе всегда равно 1). Если Вам надо обслуживать быстродействующее устройство ввода/вывода, такой посимвольный обмен может оказаться слишком медленным. В этом случае Вы можете установить бит 5 в единицу и выполнять обмен в двоичном режиме. Разумеется, Вам придется самостоятельно отслеживать управляющие символы.

Ниже мы приведем пример программы, которая сначала устанавливает ASCII-режим для драйвера, описанного выше, выводит на него 8 символов, затем выполняет аналогичную операцию в двоичном режиме. Драйвер при каждом обращении к нему для ввода или вывода выдает сообщение на экран. Запустив программу (и не забыв подключить драйвер), Вы увидите, что в ASCII-режиме для записи или чтения восьми символов драйвер вызывается восемь раз, а в двоичном режиме - только один раз.

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <errno.h>
#include <dos.h>

int main(void);

union REGS inregs, outregs;
struct SREGS segregs;

int main(void) {

    char buf[100];
         int io_handle;
         unsigned count;

         // Открываем устройство с именем IODRIVER

         if( (io_handle = open("IODRIVER", O_RDWR)) == - 1 ) {

                // Если открыть не удалось, выводим
                // код ошибки

                printf("Ошибка при открытии устройства %d",errno);
                return errno;
         }


         // Читаем 8 байт из устройства в буфер buf

         if( (count = read(io_handle, buf, 8)) == -1 ) {

                 // Если при чтении произошла ошибка,
                 // выводим ее код

                 printf("Ошибка чтения %d",errno);
                 return errno;
        }

        // Закрываем прочитанную строку нулем
        // для последующего вывода функцией printf

        buf[8]=0;

        printf("\n___ Введена строка: %s ___",buf);

        // Выводим только что прочитанные данные
        // обратно на то же устройство

        if( (count = write(io_handle, buf, 8)) == -1 ) {

                 // Если при записи произошла ошибка,
                 // выводим ее код

                 printf("Ошибка записи %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 error %x\n",&outregs.x.ax);
                exit(-1);
        }

        // Выводим слово информации об устройстве на экран

        printf("\nDevice Information:  %04X\n", outregs.x.dx);

        // Устанавливаем в 1 бит 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 error %x\n",&outregs.x.ax);
                exit(-1);
        }

        // Выводим слово информации об устройстве на экран

        printf("\nDevice Information:  %04X\n", outregs.x.dx);


         // Читаем 8 байт из устройства в буфер buf
         // Обмен теперь производится в двоичном режиме

         if( (count = read(io_handle, buf, 8)) == -1 ) {

                 // Если при чтении произошла ошибка,
                 // выводим ее код

                 printf("Ошибка чтения %d",errno);
            return errno;
        }

        // Закрываем прочитанную строку нулем
        // для последующего вывода функцией printf

        buf[8]=0;

        printf("\n___ Введена строка: %s ___",buf);

        // Выводим только что прочитанные данные
        // обратно на то же устройство

        if( (count = write(io_handle, buf, 8)) == -1 ) {

                 // Если при записи произошла ошибка,
                 // выводим ее код

                 printf("Ошибка записи %d",errno);
            return errno;
        }

        // Закрываем устройство

        close(io_handle);

        exit(0);
}

# 02h/03h Чтение/запись управляющей информации для символьных устройств

Вызов:

Регистр Содержание
AH 44h
AL 02h/03h
BX Индекс (только для устройства, а не для файла)
CX Количество читаемых/записываемых байтов
DS:DX Указатель на буфер, в который надо прочитать управляющую информацию или из которого надо записать управляющую информацию

Возврат без ошибки:

Регистр Содержание
CF 0
AX Количество действительно прочитанных/записанных байтов

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

06h - BX содержит несуществующий индекс (handle).

Эти подфункции позволяют прикладной программе обмениваться произвольной управляющей информацией с драйвером устройства. Они инициируют вызов драйвера с командами 3 и 12 соответственно для операций чтения и записи управляющей информации.

Для использования этих подфункций драйвер должен поддерживать интерфейс IOCTL. Для проверки можно использвать подфункцию 0 (чтение информации устройства). Если бит 14 установлен, драйвер поддерживает IOCTL.

# 04h/05h Чтение/запись управляющей информации для блочных устройств

Вызов:

Регистр Содержание
AH 44h
AL 04h/05h
BL Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.)
CX Количество читаемых/записываемых байтов
DS:DX Указатель на буфер, в который надо прочитать управляющую информацию или из которого надо записать управляющую информацию

Возврат без ошибки:

Регистр Содержание
CF 0
AX Количество действительно прочитанных/записанных байтов

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

05h - доступ к дисководу запрещен.

Для использования этих подфункций драйвер должен поддерживать интерфейс IOCTL. Для проверки можно использовать подфункцию 0 (чтение информации устройства). Если бит 14 установлен, драйвер поддерживает IOCTL.

# 06h/07h Получить состояние ввода/вывода

Вызов:

Регистр Содержание
AH 44h
AL 06h/07h
BX Индекс (устройства или файла)

Возврат без ошибки:

Регистр Содержание
CF 0
AL Для устройства:
00h - устройство не готово к приему/передаче;
0FFh - устройство готово к приему/передаче.
Для файла:
00h - достигнут конец файла;
0FFh - конец файла не достигнут.

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;
05h - доступ к дисководу запрещен;
06h - BX содержит несуществующий индекс;
0Dh - неправильные данные.

Эти подфункции предназначены для проверки готовности устройства к вводу/выводу данных или для проверки на достижение конца файла.

# 08h Проверить возможность замены носителя данных для блочного устройства

Вызов:

Регистр Содержание
AH 44h
AL 08h
BL Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.)

Возврат без ошибки:

Регистр Содержание
CF 0
AX 00h - замена носителя возможна;

01h - носитель данных несменный

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;
0Fh - неправильный идентификатор дисковода

Эта подфункция проверяет возможность замены носителя данных. Дискета - заменяемый носитель данных, а жесткий диск или электронный (RAM) диск - нет.

# 09h Получить информацию о том, является ли устройство локальным или удаленным (при работе в сети)

Вызов:

Регистр Содержание
AH 44h
AL 09h
BL Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.)

Возврат без ошибки:

Регистр Содержание
CF 0
DX Биты атрибута устройства. Если блочное устройство является локальным (расположено на рабочей станции сети Microsoft Networks), бит 12 установлен в 0, остальные биты копируются из заголовка драйвера устройства. Если устройство является удаленным (находится на сервере), устанавливается в 1 только бит 12, остальные биты зарезервированы и равны нулю.

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;
0Fh - неправильный идентификатор дисковода;

Ошибка 01h может возникнуть, если не загружена команда SHARE.

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

# 0Ah Проверить индекс на локальный/удаленный

Вызов:

Регистр Содержание
AH 44h
AL 0Ah
BX Индекс (только для устройства, а не для файла)

Возврат без ошибки:

Регистр Содержание
CF 0
DX Слово атрибутов устройства, если бит 15 установлен в 1, это удаленное устройство, в противном случае устройство является локальным

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;
06h - BX содержит несуществующий индекс.

Ошибка 01h может возникнуть, если не загружена команда SHARE.

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

# 0Bh Установка количества повторов при обращении к файлу

Вызов:

Регистр Содержание
AH 44h
AL 0Bh
CX Продолжительность паузы (по умолчанию используется значение 1)
DX Число повторов (по умолчанию равно 3) до вызова программы обработки прерывания 24h - прерывания по критической ошибке ввода/вывода

Возврат без ошибки:

Регистр Содержание
CF 0

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

Код ошибки 01h появляется, если задан недопустимый номер подфункции или не загружена команда SHARE.

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

mov  cx,pause
loop $

# 0Ch Переключение кодовых страниц

Вызов:

Регистр Содержание
AH 44h
AL 0Ch
BX Индекс для открытого устройства
CH Код категории устройства:
00 - неизвестное устройство;
01 - устройства COM1, COM2 и т.д.;
03 - консоль CON;
05 - устройства печати LPT1, LPT2 и т.д.
CL Код операции:

45h - установить число повторений операции;
4Ah - выбор кодовой страницы;
4Ch - начало подготовки кодовой страницы;
4Dh - конец подготовки кодовой страницы;
5Fh - установить устройство "дисплей";
65h - получить число повторений операции;
6Ah - получить выбранную кодовую страницу;
6Bh - получить подготовленный список;
7Ah - получить параметры дисплея (ширина, длина и цвет)
DS:DX Указатель на блок параметров.

Возврат без ошибки:

Регистр Содержание
CF 0

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

06h - BX содержит несуществующий индекс.

Для подготовки кодовой страницы сначала вызывают эту подфункцию с кодом операции CL=4Ch, затем должна идти серия вызовов подфункции 03h функции 44h прерывания INT 21h - запись IOCTL на символьное устройство.

Формат записываемых данных зависит от типа устройства. Драйверы DISPLAY.SYS и PRINTER.SYS, входящие в состав дистрибутива DOS, получают эти данные из файлов с расширением имени .CPI, таких как EGA.CPI, LCD.CPI, 4201.CPI и т.д.

Блок параметров имеет различный формат для разных кодов операций:

CL = 45h блок параметров состоит из слова, содержащего количество повторений;
CL = 4Ah, 4Dh, 6Ah блок параметров состоит из двух слов, первое слово - длина данных (0002), второе - идентификатор кодовой страницы;
CL = 4Ch в начале блока параметров расположены три слова - флаги (0000), длина остальной части блока параметров в байтах и количество кодовых страниц, за этими тремя словами следуют слова кодовых страниц. Количество слов кодовых страниц в зависимости от значения MAXFONTS при установке драйвера и может достигать 12;
CL = 6Bh первым располагается слово, содержащее длину остальной части блока в байтах, затем идет количество аппаратных кодовых страниц и слова для этих кодовых страниц, в конце расположено слово, содержащее количество подготовленных кодовых страниц и слова с подготовленными кодовыми страницами. Возвращаемый блок может иметь длину до 56 байтов.

# 0Dh Общее управление вводом/выводом (GENERIC IOCTL)

Вызов:

Регистр Содержание
AH 44h
AL 0Dh
BL Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.)
CH Код категории устройства: 08h - дисковое устройство
CL Операция:
40h - установить параметры устройства;
60h - получить параметры устройства;
41h - записать дорожку на логическом устройстве;
61h - прочитать дорожку на логическом устройстве;
42h - форматировать дорожку на логическом устройстве;
62h - проверить дорожку на логическом устройстве
DS:DX Указатель на блок параметров.

Возврат без ошибки:

Регистр Содержание
CF 0

Возврат с ошибкой:

Регистр Содержание
CF 1
AX Код ошибки

Общая схема использования этой подфункции:

  1. Сохранить параметры устройства операцией 40h.
  2. Установить нужные параметры.
  3. Выполнить операцию ввода/вывода или форматирования.
  4. Восстановить предыдущие параметры.

Формат блока параметров зависит от выполняемой операции:

CL = 40h/60h (получить/установить параметры устройства)

Смещение Размер Содержимое поля
(0) 1 Специальные функции
(+1) 1 Тип устройства, возвращаемый драйвером:
0 - 320/360 К флоппи-диск (5,25");
1 - 1,2 М флоппи-диск (5,25");
2 - 720 К флоппи-диск (3,5");
3 - 8" флоппи-диск нормальной плотности;
4 - 8" флоппи-диск двойной плотности;
5 - жесткий диск;
6 - накопитель на магнитной ленте;
7 - 1,44 М флоппи-диск (3,5")
(+2) 2 Атрибуты устройства, возвращаемые драйвером. В этом поле используются только два младших бита. Бит 0 - признак заменяемости среды носителя данных (0 - заменяемая, 1 - не заменяемая), бит 1 - признак наличия аппаратного контроля замены дискеты (1 - контроль выполняется, 0 - контроль не выполняется). Остальные биты зарезервированы и должны содержать 0.
(+4) 2 Максимальное количество цилиндров на физическом устройстве. Это поле устанавливается драйвером.
(+6) 1 Тип среды носителя данных. Используется для устройств, поддерживающих несколько типов носителей данных, например, для флоппи-дисковода на 1.2М значение этого поля, равное 0, соответствует дискете на 1.2М, а 1 - 360К.
(+7) 31 BPB для устройства. Если бит 0 поля специальных функций сброшен, то в этом поле находится новый BPB для устройства. Если бит 0 установлен, драйвер устройства возвращает BPB для всех последующих запросов на построение BPB.
(+38) ? Таблица разметки дорожки, имеет переменную длину.

Биты байта специальных функций имеют следующее значение:

В операции 60h значение этого бита, равное 1, используется для извлечения текущего BPB, как если бы он был получен по команде драйвера с кодом 2 (построить BPB). Значение этого бита, равное 0, говорит о том, что надо извлечь BPB, используемый по умолчанию.

Для операции с кодом 40h значение бита, равное 1, используется для извлечения текущего BPB, значение 0 приводит к использованию BPB, подготовленного в данном блоке параметров.

Значение этого бита, равное 1, - это указание игнорировать все поля в блоке параметров, кроме поля описания физической структуры дорожки на данном устройстве.

Значение этого бита, равное 1, говорит о том, что все сектора на этой дорожке имеют одинаковый размер.

Таблица разметки дорожки начинается с двухбайтового слова, содержащего общее количество секторов на дорожке. Затем для каждого сектора в таблице находится по два двухбайтовых слова, содержащих номер сектора (1, 2 и т.д.) и размер сектора. То есть для каждого сектора в таблице содержится два слова.

Если в поле "специальные функции" бит 2 установлен в 1, размеры всех секторов должны быть одинаковыми.

CL = 41h/61h (записать/прочитать дорожку)

Смещение Размер Содержимое поля
(0) 1 Специальные функции (это поле всегда содержит 0)
(+1) 2 Номер головки
(+3) 2 Номер дорожки
(+5) 2 Номер начального сектора (нумерация секторов, в отличие от нумерации головок и дорожек начинается с 0)
(+7) 2 Общее количество секторов на дорожке, уменьшенное на единицу
(+9) 4 FAR-указатель на буфер обмена с диском, в который помещается считываемая информация или откуда берется записываемая информация

CL = 42h/62h (форматировать/проверить дорожку)

Смещение Размер Содержимое поля
(0) 1 Специальные функции. Для этой операции определен только бит 0. Перед вызовом команды значение бита, равное 0, требуется для форматирования дорожки. Если этот бит установлен в 1, то проверяется возможность использования заданного формата трека. Если после выполнения команды значение бита 0 равно 0, то поддерживается заданный формат трека и заполненную таблицу разметки дорожки можно использовать. Если значение бита 0 равно 1, то затребованный формат дорожки не поддерживается.
(+1) 2 Номер головки для форматирования/проверки
(+3) 2 Номер дорожки для форматирования/проверки

# 0Eh Получение информации о логическом дисководе

Вызов:

Регистр Содержание
AH 44h
AL 0Eh
BL Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.)

Возврат без ошибки:

Регистр Содержание
CF 0
AL Содержимое этого регистра равно 0, если данному дисководу соответствует только одно логическое устройство, или номеру текущего логического дисковода (1 - А:, 2 - В: и т.д.).

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

0Fh - неправильный идентификатор дисковода;

# 0Fh Установка текущего логического дисковода

Вызов:

Регистр Содержание
AH 44h
AL 0Fh
BL Номер дисковода, который должен стать текущим (0 - текущий дисковод, 1 - дисковод А: и т.д.)

Возврат без ошибки:

Регистр Содержание
CF 0
AL Содержимое этого регистра равно 0, если данному дисководу соответствует только одно логическое устройство, или номеру логического дисковода, который будет использоваться в последующих операциях ввода/вывода (1-А:, 2-В: и т.д.).

Возврат с ошибкой:

Регистр Содержание
CF 1
AX 01h - запрошена несуществующая функция;

0Fh - неправильный идентификатор дисковода;

Последние две команды предназначены для работы с логическими дисководами. На одном физическом дисководе DOS может образовать несколько логических дисков. Эти команды могут использоваться программами, например, для выдачи сообщения о необходимости заменить дискету при переходе от одного логического устройства к другому, если эти устройства расположены на одном физическом накопителе.

6.7. Драйвер системных часов CLOCK$

Операционная система содержит в своем составе драйвер системных часов. Это драйвер символьного устройства, имя устройства - CLOCK$. Символ "$" используется для того, чтобы это имя не конфликтовало с именем файла CLOCK.

Характерный признак драйвера системных часов - бит 3 слова атрибутов устройства установлен в 1. Именно этот признак используется DOS для обнаружения драйвера часов, имя CLOCK$ может быть изменено в последующих версиях операционной системы.

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

Смещение Длина Назначение
(0) 2 Количество дней, прошедших после 1 января 1980 года
(+2) 1 Счетчик минут
(+3) 1 Счетчик часов
(+4) 1 Счетчик сотых долей секунды
(+5) 1 Счетчик секунд

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

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <errno.h>
#include <dos.h>

int main(void);

union REGS inregs, outregs;
struct SREGS segregs;

int main(void) {

         int io_handle;
         unsigned count;

         struct {
                unsigned days;
                unsigned char min;
                unsigned char hours;
                unsigned char sec_per_100;
                unsigned char sec;
         } clock_buf;

         // Открываем устройство с именем CLOCK$

         if( (io_handle = open("CLOCK$", O_RDWR)) == - 1 ) {

                // Если открыть устройство не удалось, выводим
                // код ошибки

                printf("Ошибка при открытии устройства %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 error %x\n",&outregs.x.ax);
        exit(-1);
        }

        // Устанавливаем в 1 бит 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 error %x\n",&outregs.x.ax);
                exit(-1);
        }

        // Выводим слово информации об устройстве на экран

        printf("\nDevice Information:  %04X\n", outregs.x.dx);


         // Читаем 6 байт из устройства в буфер buf
         // Обмен производится в двоичном режиме

         if((count = read(io_handle, &clock_buf, 6)) == -1) {

                 // Если при чтении произошла ошибка,
                 // выводим ее код

                 printf("Ошибка чтения %d",errno);
            return errno;
        }

        printf("\nПолучено от драйвера часов CLOCK$:"
                         "\n"
                         "\nПрошло дней после 01.01.80:  %d"
                         "\nМинуты:                      %d"
                         "\nЧасы:                        %d"
                         "\nСекунды:                     %d"
                         "\nСотые доли секунды:          %d"
                         "\n",
                         clock_buf.days,
                         clock_buf.min,
                         clock_buf.hours,
                         clock_buf.sec,
                         clock_buf.sec_per_100);

        // Закрываем устройство
        close(io_handle);
        exit(0);
}

6.8. Особенности отладки драйверов

Драйверы достаточно сложны для отладки. Это связано прежде всего с тем, что Вы не сможете использовать такие отладчики, как CodeView. На этапе инициализации драйвера (при выполнении команды инициализации) загрузка операционной системы еще не завершена, и воспользоваться обычным отладчиком невозможно.

Прикладная программа также не вызывает драйвер напрямую, а делает это через прерывания DOS. Отладчик CodeView не позволит Вам трассировать прерывание 21h, даже если Вы и сможете это сделать при помощи другого отладчика (например, отладчик Advanced Fullscreen Debugger фирмы IBM позволяет трассировать операционную систему), Вам придется очень долго "добираться" до программы прерывания Вашего драйвера.

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

Можно использовать специально подготовленную системную дискету, записать на нее отлаживаемый драйвер и загрузить операционную систему с дискеты. Если произойдет зависание системы, загрузите DOS с жесткого диска.

Можно порекомендовать следующую методику отладки драйвера.

Программа стратегии обычно очень проста и проблем не вызывает.

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

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

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

;==========================================
; Процедура выводит на экран содержимое
; всех регистров и ожидает нажатия на
; любую клавишу.
; После возвращения из процедуры
; все регистры восстанавливаются.

ntrace proc near

; Сохраняем в стеке регистры,
; содержимое которых будет изменяться

     pushf
     push ax
     push bx
     push cx
     push dx
     push ds
     push bp

     push cs
        pop  ds

        mov bp,sp

; Выводим сообщение об останове

          mov dx,offset cs:trace_msg
          @@out_str

; Выводим содержимое всех регистров

          mov ax,cs        ; cs
       call Print_word
          @@out_ch ':'
       mov ax,[bp]+14   ; ip
       call Print_word

          @@out_ch 13,10,13,10,'A','X','='
       mov ax,[bp]+10
       call Print_word

          @@out_ch ' ','B','X','='
       mov ax,[bp]+8
       call Print_word

          @@out_ch ' ','C','X','='
       mov ax,[bp]+6
       call Print_word

          @@out_ch ' ','D','X','='
       mov ax,[bp]+4
       call Print_word

          @@out_ch ' ','S','P','='
       mov ax,bp
          add ax,16
       call Print_word

          @@out_ch ' ','B','P','='
       mov ax,[bp]
       call Print_word

          @@out_ch ' ','S','I','='
       mov ax,si
       call Print_word

          @@out_ch ' ','D','I','='
       mov ax,di
       call Print_word

          @@out_ch 13,10,'D','S','='
       mov ax,[bp]+2
       call Print_word

          @@out_ch ' ','E','S','='
       mov ax,es
       call Print_word

          @@out_ch ' ','S','S','='
       mov ax,ss
       call Print_word

          @@out_ch ' ','F','='
       mov ax,[bp]+12
       call Print_word

          lea dx,cs:hit_msg
          @@out_str

; Ожидаем нажатия на любую клавишу

          mov ax,0
       int 16h

; Восстанавливаем содержимое регистров

       pop bp
       pop ds
       pop dx
       pop cx
       pop bx
       pop ax
       popf

       ret

trace_msg db 13,10,'>---- BREAK ----> At address ','$'
hit_msg db 13,10,'Hit any key...','$'

ntrace endp

;==========================================
; Процедура выводит на экран содержимое AX

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
;--------------------
; al - input byte
; dx - output hex
;--------------------
     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

     end

Для вывода строки на экран в этой процедуре используется макро @@out_str и @@out_ch, которые определены в файле sysp.inc:

@@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

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

Если у Вас есть отладчик Advanced Fullscreen Debugger, можно использовать его способность оставаться резидентным и вызываться по нажатию комбинации клавиш CTRL+ESC.

В интересующее Вас место драйвера поместите вызов прерывания 16h, ожидающий ввода с клавиатуры, например:

push    ax
mov     ax,0
int     16h
pop     ax

Можно сохранить в стеке и регистр флагов, если его изменение нежелательно.

После того, как драйвер повиснет на ожидании ввода, активизируйте отладчик, нажав комбинацию клавиш CTRL+ESC. Вы окажетесь в теле обработчика прерывания 16h. Выполняя программу по шагам, довольно скоро Вы достигнете выхода из этого обработчика - команды IRET. После выполнения команды IRET управление будет передано команде, следующей за командой int 16h. Эта команда (в приведенном примере - pop ax) принадлежит Вашему драйверу!

Используя известное теперь значение адреса заголовка запроса (регистры ES:BX), можно определить, какая команда выполняется драйвером, и просмотреть сам запрос.

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

Теоретически возможно создать такой отладчик, который сам оформлен в виде драйвера, и подключить его в файле CONFIG.SYS до проверяемого драйвера. Тогда весь процесс загрузки и инициализации будет производиться под контролем этого драйвера-отладчика.

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

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

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

6.9. Пример драйвера символьного устройства

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

Приведем полный текст драйвера:

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

; Демонстрационный драйвер символьного устройства
;
; Copyright (C)Frolov A., 1990

          .MODEL tiny
          .CODE        ; Драйвер состоит из одного
                                ; сегмента кода

          org 0        ; Эта строка может отсутствовать

          include sysp.inc

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

; Устанавливаем 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            ; Обнуляем AH
        lea  di,functions     ; DI указывает на смещение
                                          ; таблицы
        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 ?

;===================================================;**
;
;.Name         INTERRUPT
;.Title        Обработчик прерывания
;.Synopsis
;-
;  int <NN>
;
;  Вход:  NN - Номер прерывания, заданный в файле
;CONFIG.SYS
;         AH - Номер выполняемой функции:
;                0 - операция записи;
;                1 - операция чтения
;         BH - Адрес (0...7Fh)
;         BL - Данные для записи (0...FFh)
;  Выход: BL - Прочитанные данные
;-
;.Description
;-
;     Прерывание вызывается командой INT <NN> с
;параметрами:
;
; Вход:  NN - Номер прерывания, заданный в файле 
;CONFIG.SYS
;        AH - Номер выполняемой функции:
;               0 - операция записи;
;               1 - операция чтения
;        BH - Адрес (0...7Fh)
;        BL - Данные для записи (0...FFh)
; Выход: BL - Прочитанные данные
;-
;.Returns
; BL - Прочитанные данные
;.Version 1.00 (c)Copyright Frolov A., 1990
;-
;**

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., 1990   ¦'
      db 13,10,'+--------------------------------+'
         db 13,10,0

outmsg   DB 13,10,'___ Вывод на устройство DEVDRIVR ___',0
inpmsg   DB 13,10,'___ Ввод с устройства   DEVDRIVR ___',0

openmsg  db 13,10,'___ Открываем DEVDRIVR ___',0
closemsg db 13,10,'___ Закрываем DEVDRIVR ___',0

inpmsg_nd DB 13,10
                DB '___ ND-ввод с устройства    DEVDRIVR ___',0
statmsg_i DB 13,10
                DB '___ Чтение состояния ввода  DEVDRIVR ___',0
statmsg_o DB 13,10
                DB '___ Чтение состояния вывода DEVDRIVR ___',0

errmsg    DB 13,10
                DB '___ Команда не поддерживается DEVDRIVR ___',0

;===================================================
E_O_P:            ;Метка конца программы

initialize:

   lea  ax,E_O_P      ;смещение конца программы в AX
   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

        mov      bx, cs:parm_off   ; ES:BX - адрес строки
                                   ; параметров
        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:

; Если параметры заданы с ошибкой,
; установку драйвера не производим.

        mov  ax,cs:req_seg    ;ES:BX указывают на заголовок
                              ; запроса
        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,"Personal Computer 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,"Driver Parameters Error!",13,10,0

msg6    db      13,10,13,10,"  Press any key...",13,10,0

parm endp

;**
;
;.Name         get_parm
;.Title        Разбор строки параметров.
;.Synopsis
;
;              ds:bx  -  исходная строка, закрытая нулем
;              ds:dx  -  строка из сепараторов, закрытая
;                                       нулем
;              ds:bp  -  буфер параметров
;                 cx  -  число параметров
;
;.Description
;              Исходная строка ds:bx, разделенная
;                       сепараторами ds:dx, состоящая из cx
;                       параметров и начинающаяся с полного пути
;                       программы, разбирается на слова, параметры -
;                       шестнадцатеричные цифры. Двоичные слова,
;                       соответствующие параметрам, последовательно
;                       заносятся в буфер параметров ds:bp.
;
;.Returns      В случае ошибки устанавливается флаг
;                       переноса.
;
;.Version 1.00 (c)Copyright Frolov G., 1990
;**

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


;**
;
;.Name         strtoc
;.Title        Выделяет очередное слово из строки.
;.Synopsis
;
;         1) при первом обращении к процедуре:
;           Вход:
;              ax = 0
;              ds:bx  -  исходная строка, закрытая нулем
;              ds:dx  -  строка из сепараторов, закрытая
;                                       нулем
;           Выход:
;              ds:bx  -  подстрока до первого разделителя,
;                                       закрытая нулем
;         2) при последующих обращениях
;           Вход:
;              ax != 0
;              ds:dx  -  строка из сепараторов, закрытая
;                                       нулем
;           Выход:
;              ds:bx  -  подстрока до следующего
;                                       разделителя, закрытая нулем
;
;.Description    При первом вызове выделяет из строки первое
;                         слово до разделителя. При повторном вызове
;                         возвращает указатель на следующее слово в
;                         исходной строке. Если все слова из строки
;                         уже выделены, устанавливается флаг
;                         переноса.
;
;.Version 1.00 (c)Copyright Frolov G., 1990
;**

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


;**
;
;.Name         hex_to_bin
;.Title        Преобразует  hex строку в двоичное число.
;.Synopsis
;
;           Вход:
;              ds:bx  -  исходная строка, закрытая нулем
;           Выход:
;                 ax  -  соответствующее строке число
;
;.Description  Преобразует строку из ascii символов в hex
;                       форме в ее 16-битовый двоичный эквивалент.
;                       В случае переполнения или если строка
;                       содержит символы, не равные 0..9, A..F, a..f
;                       устанавливается флаг переноса.
;
;.Version 1.00 (c)Copyright Frolov G., 1990
;**
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


;**
;
;.Name         dec_to_bin
;.Title        Преобразует  dec строку в двоичное число.
;.Synopsis
;
;           Вход:
;              ds:bx  -  исходная строка, закрытая нулем
;           Выход:
;                 ax  -  соответствующее строке число
;
;.Description  Преобразует строку из ascii символов в dec
;                       форме в ее 16-битовый двоичный эквивалент. В
;                       случае переполнения или если строка содержит
;                       символы, не равные 0..9, устанавливается флаг
;                       переноса.
;
;.Version 1.00 (c)Copyright Frolov G., 1990
;**
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
;--------------------
; al - input byte
; dx - output hex
;--------------------
          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

Для работы с этим драйвером и демонстрации его основных возможностей мы подготовили следующую программу:

Работа с драйвером символьного устройства

// Данная прорамма использует прерывание 80h,
// которое устанавливается демонстрационным
// драйвером. Для правильной установки файл
// CONFIG.SYS должен содержать, например,
// такую строку для подключения драйвера:
//
// device=e:\sysprg\devdrv.sys  1 80 378 379 37a 37a
//
// Число 80 означает номер используемого прерывания.


#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <malloc.h>
#include <errno.h>
#include <dos.h>

int main(void);

union  REGS  inregs, outregs;
struct SREGS segregs;

int main(void) {

         char buf[100], ch;
         int io_handle;
         unsigned count;


         // Открываем устройство с именем DEVDRIVR

         if( (io_handle = open("DEVDRIVR", O_RDWR)) == - 1 ) {

                // Если открыть устройство не удалось, выводим
                // код ошибки

                printf("Ошибка при открытии устройства %d",errno);
                return errno;
         }

         // Читаем 8 байт из устройства в буфер buf

         printf("\nВведите 8 символов с клавиатуры\n");

         if( (count = read(io_handle, buf, 8)) == -1 ) {

                 // Если при чтении произошла ошибка,
                 // выводим ее код

                 printf("Ошибка чтения %d",errno);
            return errno;
        }

        // Закрываем прочитанную строку нулем
        // для последующего вывода функцией printf

        buf[8]=0;

        printf("\n___ Введена строка: %s ___",buf);

        // Выводим только что прочитанные данные
        // обратно на то же устройство

        if( (count = write(io_handle, buf, 8)) == -1 ) {

                 // Если при записи произошла ошибка,
                 // выводим ее код

                 printf("Ошибка записи %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 error %x\n",&outregs.x.ax);
                exit(-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 error %x\n",&outregs.x.ax);
                exit(-1);
        }

         printf("\n\n\nПроверяем вызов прерывания."
                          "\n"
                          "\nНажмите любую клавишу...\n\n");
         getch();

         printf("\nКоманда записи:\n");

         inregs.h.ah = 0x0; /* WRITE */
         inregs.h.bh = 0x777;
         inregs.h.bl = 0x13;
         int86( 0x80, &inregs, &outregs );

         printf("\nКоманда чтения:\n");

         inregs.h.ah = 0x1; /* READ */
         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);
        exit(0);
}

6.10. Пример драйвера блочного устройства

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

И если когда-нибудь Вам потребуется использовать диски ЭВМ серии ЕС в качестве винчестера персонального компьютера, то разобравшись в том, как работает приведенный ниже драйвер, Вы сможете самостоятельно приспособить его для такой задачи.

;
; Драйвер электронного диска,
; использует основную память компьютера
;
          .MODEL tiny
          .CODE        ; Драйвер состоит из одного
                                ; сегмента кода

          org 0        ; Эта строка может отсутствовать

          include sysp.inc

ram  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 'MSDOS4.0'
        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

; Устанавливаем 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            ; Обнуляем AH
        lea  di,functions     ; DI указывает на смещение
                                          ; таблицы
        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

;========================================================

; Процедура выводит на экран строку
; символов в формате 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,'¦ *RAM/DISK* (C)Frolov A., 1990  ¦'
      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

; Инициализируем BOOT-сектор

        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 $
ram   ENDP
         END ram