6. Драйверы

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

На деле ни о какой полной совместимости на уровне портов ввода/вывода и протоколов обмена данными говорить не приходится. Поэтому, например, вместе с текстовым процессором Microsoft Word for MS-DOS поставляются десятки принтерных драйверов, по одному для каждой модели принтера или семейства моделей принтеров.

Может ли программист учесть все особенности существующей аппаратуры, а также той, что появится в будущем? Очевидно, не может, если, конечно, у него нет дара ясновидения, а также неограниченного запаса времени на тестирование своей программы для бесконечных конфигураций и моделей аппаратного обеспечения. Нам встречалось множество программ, которые могли работать только с той аппаратурой, что имелась в наличии у разработчика. Например, бухгалтерские программы, которые "умели" печатать документы только на принтере Epson FX-850, программы, которые устанавливались только с диска A: емкостью 360 Кбайт и т. д.

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

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

Все современные операционные системы обращаются к периферийным устройствам через специальные программные модули - драйверы. В таких операционных системах, как Windows NT, OS/2 или UNIX , обычная программа не может вызывать команды ввода/вывода. Для обращения к периферийному устройству она прямо или косвенно (через операционную систему) должна вызвать драйвер.

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

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

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

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

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

Тем не менее, BIOS не используется в операционных системах Windows NT , UNIX или OS/2. Почему?

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

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

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

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

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

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

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

Не стоит пытаться запускать драйвер как com-программу, так как управление будет передано в область памяти, содержащую заголовок драйвера, а там нет правильных машинных команд. Поэтому обычно файлы драйверов имеют расширения имени, отличные от com или exe. Чаще всего используются расширения sys, drv, иногда bin. На самом деле можно использовать любое расширение имени, так как при описании драйвера в файле config.sys указывается его полное имя.

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

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

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

Смещение, байт Размер, байт Имя поля Описание
0 4 next Указатель на заголовок следующего драйвера. Если смещение адреса следующего драйвера равно FFFFh, это последний драйвер в цепочке
4 2 attrib Атрибуты драйвера
6 2 strateg Смещение программы стратегии драйвера
8 2 interrupt Смещение программы обработки прерывания для драйвера
10 8 dev_name Имя устройства для символьных устройств или количество обслуживаемых устройств для блочных устройств

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

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

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

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

next	DD	0FFFFFFFFh

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

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

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

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

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

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

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

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

Бит Назначение
0 1 - драйвер обслуживает стандартное устройство ввода;
0 - этот драйвер не обслуживает стандартное устройство ввода
1 1 - драйвер обслуживает стандартное устройство вывода;
0 - драйвер не обслуживает стандартное устройство вывода
2 1 - драйвер стандартного устройства NUL;
0 - драйвер не обслуживает устройство NUL
3 1 - драйвер обслуживает часы
4 Для работы с устройством можно использовать средство быстрого символьного вывода - прерывание INT 29h. Это прерывание предназначено для быстрого вывода символа на консоль, код символа необходимо записать в регистр AL
5 Зарезервировано, бит должен быть равен 0
6 1 - разрешено использование драйвером функций IOCTL ;
0 - функции IOCTL не поддерживаются
7 1 - устройство может принимать запросы IOCTL (интерфейс IOCTL будет описан позже)
8-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-битовую адресацию сектора (для MS-DOS версии 4.00 и более поздних версий).Если установлен этот бит, поле номера сектора всех запросов является двойным словом, добавляемым в конец заголовка запроса, старое поле номера сектора должно содержать -1;
0 - используется 16-битовая адресация сектора
2-5 Эти биты зарезервированы и должны быть равны 0
6 1 - поддерживаются логические дисковые устройства;
0 - логические устройства не поддерживаются
7 1 - устройство может принимать запросы
8-10 Эти биты зарезервированы и должны быть равны 0
11 1 - единица в этом бите означает, что драйвер поддерживает функцию проверки замены носителя данных в устройстве (например, замены дискеты);
0 - функция проверки замены носителя данных не поддерживается
12 Зарезервировано, бит должен быть равен 0
13 1 - драйверу необходимо выдать запрос на построение блока параметров BIOS BPB (для выполнения этого запроса драйвер считывает сектор логического диска, содержащий начало таблицы FAT , в этом секторе находится байт идентификации носителя данных);
0 - запрос не требуется, драйвер пользуется FAT
14 1 - поддерживаются функции чтения и записи через интерфейс IOCTL ;
0 - функции чтения/записи через интерфейс IOCTL не поддерживаются
15 1 - символьное устройство;
0 - блочное устройство

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

attrib	DW	8000h

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

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

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

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

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

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

strateg      DW	strateg_proc
interrupt    DW	interrupt_proc

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

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

dev_name	DB	'AUX      '

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

dev_name	DB	1
		DB	7 dup(?)

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

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

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

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

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

Системный файл MS-DOS с именем io.sys содержит драйверы для некоторых устройств. Эти драйверы появляются в памяти при загрузке операционной системы и связаны в цепочку через поля next в своих заголовках. Такие драйверы мы будем называть резидентными драйверами MS-DOS.

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

Для подключения драйвера к операционной системе файл config.sys должен содержать строку следующего вида:

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

Например:

DEVICE=c:\dos\smartdrv.sys 120

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

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

Device Drivers Information V1.1
(C) Фролов А.В., 1995

Address    Attr  Device Name
-------    ----  -----------
011C:0048  8004  NUL     
0E5D:2192  08C2  --------> Block Device, Number of Units: 0004
DD61:0000  C053  CON      
EC03:0000  D000  IFS$HLP$
C94C:0000  C800  MITSUMI 
02CB:003A  C000  $MMXXXX0
02CB:0000  C000  EMMQXXX0
0282:0000  A000  XMSXXXX0
025D:0000  8000  SETVERXX
0070:0023  8013  CON      
0070:0035  8000  AUX      
0070:0047  A0C0  PRN      
0070:0059  8008  CLOCK$   
0070:006B  08C2  --------> Block Device, Number of Units: 0004
0070:007B  8000  COM1     
0070:008D  A0C0  LPT1     
0070:009F  A0C0  LPT2     
0070:00B8  A0C0  LPT3     
0070:00CA  8000  COM2     
0070:00DC  8000  COM3    
0070:00EE  8000  COM4    

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

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

Дальше идут драйверы, описанные в файле config.sys .

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

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

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

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

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

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

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

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

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

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

Как уже было сказано, перед обращением к драйверу MS-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.

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

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

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

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

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

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

Данные (или адреса данных), полученные драйвером от физического периферийного устройства, помещаются в переменную область запроса. Кроме того, драйвер должен установить слово состояния устройства 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 Замена диска не разрешается

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

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

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         ;записываем 0 в 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.4. Команды для загружаемого драйвера

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

Инициализация драйвера (0)

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

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

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

Смещение, байт Размер, байт Имя поля Описание
0 13 header Заголовок запроса
13 1 n_units Количество устройств, обслуживаемых драйвером. Это поле заполняется только блочным драйвером. Для символьного драйвера в это поле нужно записать значение 0
14 4 end_addr Дальний адрес нижней границы резидентной части кода драйвера. В это поле драйвер записывает адрес байта памяти, следующего за областью драйвера, которая должна стать резидентной
18 4 parm Дальний адрес строки параметров инициализации драйвера из файла config.sys . Эта строка содержит все, что находится в строке файла после команды 'DEVICE='. Она заканчивается символами перевода строки и возврата каретки 0Ah, 0Dh. При возврате драйвер блочного устройства должен записать в поле parm адрес массива указателей на блоки параметров BIOS (BPB ), по одному указателю на каждое устройство, обслуживаемое драйвером
22 1 drive Номер устройства. В это поле при загрузке драйвера операционная система заносит номер, назначенный устройству, обслуживаемому драйвером. Например, для устройства А: это 0, для B: - 1 и т. д.
23 1 msg_flag 1 - отображение сообщения об ошибке, если в поле status заголовка запроса установлены соответствующие биты;0 - сообщение об ошибке не отображается

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

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

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

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

Поле количества устройств используется MS-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 Общее количество секторов на носителе данных (в разделе MS-DOS)
10 1 media Байт-описатель среды носителя данных
11 2 fat_size Количество секторов, занимаемых одной копией FAT

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

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

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

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

Указатель и массив BPB может быть определен, например, следующим образом:

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.

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

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

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

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

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

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

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

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

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

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

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

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

Значение Количество сторон Количество секторов Диаметр, дюймы Емкость, Кбайт
F0h 2 18 3,5 1440
- " - 2 36 3,5 2880
- " - 2 15 5,25 1200
F8h - -   Жесткий диск любой емкости
F9h 2 9 3,5 720
- " - 2 15 5,25 1200
FAh 1 8 5,25 320
FBh 2 8 3,5 640
FCh 1 9 5,25 180
FDh 2 9 5,25 360
FEh 1 8 5,25, 8 160
FFh 2 8 5,25, 8 320

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

Построить блок BPB (2)

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

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

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

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

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

Чтение через интерфейс IOCTL (3)
Чтение (4)
Запись (8)
Запись с проверкой (9)
Запись через интерфейс IOCTL (0Ch)
Вывод, пока не занято (10h)

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

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

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

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

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

Поля media, sector, vol_id и sect32 используются только для функций с кодом 4, 8 и 9.

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

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

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

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

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

Команда с кодом 10h ("вывод, пока не занято") предназначена для работы с такими периферийными устройствами, которые имеют свой собственный буфер, например, принтеры.

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

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

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

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

Эта команда используется перед командой чтения для того чтобы проверить, есть ли в буфере данные, готовые для чтения. Такая проверка позволяет избежать длительного ожидания готовности данных при вводе. Кроме того, команда используется для проверки наличия в буфере клавиатуры кода комбинации клавиш <Ctrl+Break>.

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

Смещение, байт Размер, байт Имя поля Описание
0 13 header Заголовок запроса
13 1 byte В это поле драйвер записывает извлеченный из буфера байт, который будет считан следующей командой ввода

Проверить состояние устройства ввода (6)
Проверить состояние устройства вывода (0Ah)

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

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

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

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

Если у устройства нет буфера ввода, бит занятости должен быть равен 0.

Освобождение буфера устройства ввода (7)
Освобождение буфера устройства вывода (0Bh)

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

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

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

Открыть устройство (0Dh)
Закрыть устройство (0Eh)

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

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

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

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

Проверка возможности замены носителя данных (0Fh)

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

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

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

Функции общего управления вводом/выводом GENERIC IOCTL (13h)

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

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

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

Смещение, байт Размер, байт Имя поля Описание
0 13 header Заголовок запроса
13 1 category Категория устройства:01h - последовательное устройство;03h - консоль (видеомонитор);05h - параллельный принтер;08h - диск
14 1 subfunc Код подфункции для функции funct
15 2 si_reg Значение регистра SI при вызове функции 44h прерывания INT 21h
17 2 di_reg Значение, передаваемое при вызове функции 44h прерывания INT 21h через регистр DI
19 4 buf Указатель на область памяти, содержащую управляющую информацию для устройства или предназначенную для приема информации от устройства

Определить активное логическое устройство (17h)
Установить активное логическое устройство (18h)

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

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

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

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

Команда 18h позволяет установить активное устройство, определенное в поле unit.

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

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

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

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

Получить конфигурацию устройства (4400h)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

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

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

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

Установить слово конфигурации устройства (4401h)

Вызов:

Регистр Содержание
AH 44h
AL 01h
BX Идентификатор файла
DH 0
DL Биты 0-7 нового слова конфигурации устройства

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

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

Чтение управляющей информации для символьных устройств (4402h)
Запись управляющей информации для символьных устройств (4403h)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

Чтение управляющей информации для блочных устройств (4404h)
Запись управляющей информации для блочных устройств (4405h)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

Для использования этих подфункций драйвер должен поддерживать интерфейс IOCTL .

Определение состояния устройства ввода (4406h)
Определение состояния устройства вывода (4407h)

Вызов:

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

Выполнение без ошибки:

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

Для файла:
00h - достигнут конец файла;
0FFh - конец файла не достигнут

Выполнение с ошибкой:

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

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

Проверка возможности замены носителя данных для блочного устройства (4408h)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

Определение расположения устройства - локальное или удаленное (4409h)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

Проверка идентификатора: локальный или удаленный (440Ah)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

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

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

Переключение кодовых страниц (440Ch)

Вызов:

Регистр Содержание
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, входящие в состав MS-DOS, получают эти данные из файлов с расширением имени .cpi, таких как ega.cpi, lcd.cpi, 4201.cpi и т. д.

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

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

Из-за ограниченного объема книги мы не можем привести подробную информацию об использовании кодовых страниц. При необходимости обратитесь к руководству программиста, которое поставляется в составе Microsoft Developer Network (MSDN Level 1) на компакт-диске Development Library.

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

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

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

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

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

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

Получение информации о логическом дисководе (440Eh)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

Установка текущего логического дисковода (440Fh)

Вызов:

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

Выполнение без ошибки:

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

Выполнение с ошибкой:

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

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

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

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

Символ "$" используется для того, чтобы это имя не конфликтовало с именем файла clock. Дело в том, что пользователь может создать файл с именем clock, но с именем clock$ - едва ли (разве лишь он пожелает закодировать в этом имени фразу "время - деньги").

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

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

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

Приведем исходный текст программы gettime (листинг 6.1). Эта программа открывает устройство CLOCK$ , задает двоичный режим обмена данных и вводит 6 байт, имеющих только что описанную структуру. Определенные таким способом показания часов выводятся на экран.

Листинг 6.1. Файл gettime\gettime.cpp

#include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
#include <dos.h>

union REGS  inregs, outregs;
struct SREGS  segregs;

int main(void)
{
  int io_handle;

  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("Open: ошибка %d", errno );
    return errno ;
  }

  // Определяем конфигурацию устройства
  inregs.h.ah = 0x44;
  inregs.h.al = 0;
  inregs.x.bx = io_handle;
  intdos (&inregs, &outregs);

  if(outregs.x.cflag == 1)
  {
    // При возникновении ошибки выводим ее код
    printf("IOCTL : ошибка %x\n", &outregs.x.ax);
    return(-1);
  }

  // Устанавливаем бит 5 (переключаем драйвер
  // в двоичный режим обмена данными)
  inregs.x.dx = (outregs.x.dx | 0x0020) & 0x00ff;

  // Устанавливаем слово конфигурации устройства
  inregs.h.ah = 0x44;
  inregs.h.al = 1;
  inregs.x.bx = io_handle;
  intdos ( &inregs, &outregs );

  if(outregs.x.cflag == 1)
  {
    // При возникновении ошибки выводим ее код
    printf("IOCTL : ошибка %x\n", &outregs.x.ax);
    return(-1);
  }

  // Выводим слово конфигурации устройства
  printf("\nКонфигурация устройства:  %04X\n",
     outregs.x.dx);

  // Читаем 6 байт из устройства в буфер buf
  // Обмен выполняется в двоичном режиме
  if(read(io_handle, &clock_buf, 6) == -1)
  {
    // Если при чтении произошла ошибка,
    // выводим ее код
    printf("Read: ошибка %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);

   return(0);
}

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

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

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

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

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

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

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

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

Листинг 6.2. Файл ntrace\ntrace.asm

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

  .MODEL tiny
  .CODE
  .STARTUP
  
  mov ax, 1234h
  mov bx, 5678h
  call ntrace

  .EXIT 0

;==============================================
; Функция выводит на экран содержимое
; всех регистров и приостанавливает выполнение
; программы до тех пор, пока пользователь не
; нажмет на любую клавишу.
; После возвращения из процедуры
; все регистры восстанавливаются.
;==============================================
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 аt address ','$'
hit_msg db 13,10,'Press 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

;==============================================
; Преобразование байта в два символа
; AL - преобразуемый байт
; DX - его символьное представление
;==============================================
Byte_to_hex proc near
  push ds
  push cx
  push bx

  lea bx, tabl
  mov dx, cs
  mov ds, dx

  push ax
  and al, 0fh
  xlat
  mov dl, al

  pop ax
  mov cl, 4
  shr al, cl
  xlat
  mov dh, al

  pop bx
  pop cx
  pop ds
  ret

tabl db '0123456789ABCDEF'
Byte_to_hex endp

  END

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

Листинг 6.3. Файл ntrace\mk.bat

tasm ntrace
tlink ntrace /t

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

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

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

push	ax
mov	ax,0
int	16h
pop	ax

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

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

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

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

И еще одно замечание.

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

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

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

6.8. Примеры драйверов

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

Простейший драйвер DRVSIMP.SYS

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

Исходный текст драйвера приведен в листинге 6.4.

Листинг 6.4. Файл drvsimp\drvsimp.asm

  .MODEL tiny
  .CODE

drvsimp proc far

; Отмечаем конец области памяти, которая
; будет резидентной в памяти. Так как наш
; драйвер не устанавливается резидентно,
; располагаем эту метку в начале драйвера

DriverEnd:

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

; ------------------------------------------------
; Программа стратегии 
; ------------------------------------------------
dev_strategy:
  mov  cs:req_seg, es
  mov  cs:req_off, bx
  ret

; Здесь запоминается адрес заголовка запроса

req_seg  dw ?
req_off  dw ?

; ------------------------------------------------
; Обработчик прерывания
; ------------------------------------------------
dev_interrupt:

; Сохраняем регистры  
  push es 
  push ds
  push ax
  push bx
  push cx
  push dx
  push si
  push di
  push bp

; Устанавливаем ES:BX на заголовок запроса
  mov  ax, cs:req_seg
  mov  es, ax
  mov  bx, cs:req_off

; Получаем код команды из заголовка запроса и умножаем
; его на два, чтобы использовать в качестве индекса
; в таблице адресов обработчиков команд
  mov  al, es:[bx]+2
  shl  al, 1

  sub  ah, ah        ; записывем 0 в регистр AH
  lea  di, functions ; смещение таблицы
  add  di, ax        ; добавляем смещение в таблице
  jmp  word ptr [di] ; переходим на адрес из таблицы

; ------------------------------------------------
; Таблица функций
; ------------------------------------------------
functions   LABEL  WORD    
   dw   initialize
   dw   check_media
   dw   make_bpb
   dw   ioctl_in
   dw   input_data
   dw   nondestruct_in
   dw   input_status
   dw   clear_input
   dw   output_data
   dw   output_verify
   dw   output_status
   dw   clear_output
   dw   ioctl_out
   dw   Device_open
   dw   Device_close
   dw   Removable_media

; Выходим, если функция не поддерживается
check_media:
make_bpb:
ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_verify:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:
output_data:
input_data:

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

quit:
  or   es:word ptr [bx]+3, 100h
  pop bp
  pop di
  pop si
  pop dx
  pop cx
  pop bx
  pop ax
  pop ds
  pop es
  ret

; ------------------------------------------------
; Процедура выводит на экран строку
; символов в формате ASCIIZ 
; ------------------------------------------------
Puts proc near
  push si

puts_loop:
  cmp  ds:byte ptr [si],0
  jz   end_puts

  mov   ah, 02h
  mov   dl, ds:byte ptr [si]
  int   21h

  inc si
  jmp puts_loop

end_puts:
  pop si
  ret
Puts endp

hello   db 13,10,'+--------------------------------+'
        db 13,10,'|  *DRVSIMP* (C)Frolov A., 1995  |'
        db 13,10,'+--------------------------------+'
        db 13,10
        db 13,10,'Press any key...'
        db 13,10,0

; ------------------------------------------------
; Выполнение инициализации драйвера
; ------------------------------------------------
initialize:

; Записываем адрес конца драйвера в заголовок
  lea  ax, DriverEnd
  mov  es:word ptr [bx]+14, ax 
  mov  es:word ptr [bx]+16, cs   

; Стираем экран
  mov   dh, 18h
  mov   dl, 80h

  xor   cx, cx
  mov   bh, 7
  xor   al, al
  mov   ah, 6
  int   10h

; Устанавливаем курсор в левый верхний угол экрана
  mov   bh, 0
  xor   dx, dx
  mov   ah, 2
  int   10h

; Выводим сообщение
  mov ax, cs
  mov ds, ax
  mov si, offset hello
  call Puts

; Ждем, когда пользователь нажмет любую клавишу
  mov   ax, 0
  int   16h

  jmp  quit
drvsimp   ENDP

  END drvsimp

Текст этого драйвера транслировался при помощи ассемблера Turbo Assembler версии 3.0. Соответствующий пакетный файл представлен в листинге 6.5.

Листинг 6.5. Файл drvsimp\mk.bat

tasm drvsimp
tlink drvsimp,drvsimp.sys /t

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

Обратите внимание, что при запуске редактора связей tlink.exe мы указали имя загрузочного файла драйвера drvsimp.sys явным образом. Расширение имени .com указывать нельзя, так как в противном случае будет создан обычный com-файл, для которого стартовый адрес всегда равен 100h. Для драйвера стартовый адрес должен быть равен нулю.

Для испытания этого и других драйверов запишите драйвер, например, в корневой каталог диска C: и поместите в файл config.sys такую строку :

device=c:\simple.sys

Драйвер DRVIOCTL.SYS

Приведем исходный текст программы драйвера DRVIOCTL.SYS, который обрабатывает команды ввода и вывода (листинг 6.6). Для ввода драйвер использует клавиатуру, вывод выполняется на экран консоли.

Листинг 6.6. Файл drvioctl\drvioctl.asm

  .MODEL tiny                                                                  
  .CODE
  ORG 0

drvioctl proc far

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

;===================================================
; Программа стратегии 
;===================================================
dev_strategy:
  mov  cs:req_seg, es
  mov  cs:req_off, bx
  ret

; Здесь запоминается адрес заголовка запроса
req_seg         dw ?
req_off         dw ?

;===================================================
; Обработчик прерывания
;===================================================

dev_interrupt:
  push es
  push ds
  push ax
  push bx
  push cx
  push dx
  push si
  push di
  push bp

  mov  ax, cs:req_seg
  mov  es, ax
  mov  bx, cs:req_off

  mov  al,es:[bx]+2
  shl  al,1
  sub  ah,ah         
  lea  di,functions 
  add  di,ax 
  jmp  word ptr [di] 

functions   LABEL  WORD

   dw   initialize
   dw   check_media
   dw   make_bpb
   dw   ioctl_in
   dw   input_data
   dw   nondestruct_in
   dw   input_status
   dw   clear_input
   dw   output_data
   dw   output_verify
   dw   output_status
   dw   clear_output
   dw   ioctl_out
   dw   Device_open
   dw   Device_close
   dw   Removable_media

check_media:
make_bpb:
ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_verify:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:

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

;===================================================
; Обработчик команды вывода данных
;===================================================

output_data:

; Записываем в регистр CL количество
; выводимых символов

  mov  cl, es:[bx]+18
  push cx

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

  mov ax, cs
  mov ds, ax
  mov si, offset outmsg
  call Puts

  pop cx

; Загружаем в DS:SI адрес буфера данных

  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  si, es:[bx]+14

; Выводим на экран символы из буфера

out_loop:
  mov   ah, 02h
  mov   dl, ds:byte ptr [si]
  int   21h
  inc si
  loop out_loop
  jmp  quit

;===================================================
; Обработчик команды ввода данных
;===================================================

input_data:

; Записываем в регистр CL количество
; вводимых символов

  mov  cl, es:[bx]+18
  push cx

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

  mov ax, cs
  mov ds, ax
  mov si, offset inpmsg
  call Puts

; Загружаем в DS:SI адрес буфера данных

  pop  cx
  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  di, es:[bx]+14

; Вводим символы с клавиатуры и записываем в буфер

inp_loop:
  mov  ax,0
  int  16h
  mov  ds:byte ptr [di], al
  mov   ah, 02h
  mov   dl, al
  int   21h
  inc di
  loop inp_loop
  jmp  quit

quit:
  or   es:word ptr [bx]+3, 100h
  pop bp
  pop di
  pop si
  pop dx
  pop cx
  pop bx
  pop ax
  pop ds
  pop es
  ret

;===================================================
; Процедура выводит на экран строку
; символов в формате ASCIIZ 
;===================================================

Puts proc near
  push si
Puts_loop:
  cmp  ds:byte ptr [si], 0
  jz   end_Puts
  mov   ah, 02h
  mov   dl, ds:byte ptr [si]
  int   21h
  inc si
  jmp Puts_loop

end_Puts:
  pop si
  ret
Puts endp

hello db 13,10,'+--------------------------------+'
      db 13,10,'| *DRVIOCTL* (C)Frolov A., 1995  |'
      db 13,10,'+--------------------------------+'
      db 13,10,0

outmsg   DB 13,10,'Выведено: ',0
inpmsg   DB 13,10,'Введите символ: ',0

E_O_P:
initialize:

  lea  ax, E_O_P 
  mov  es:word ptr [bx]+14, ax
  mov  es:word ptr [bx]+16, cs

; Стираем экран

  mov   dh, 18h
  mov   dl, 80h

  xor   cx, cx
  mov   bh, 7
  xor   al, al
  mov   ah, 6
  int   10h

; Устанавливаем курсор в левый верхний угол экрана

  mov   bh, 0
  xor   dx, dx
  mov   ah, 2
  int   10h

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

  mov ax, cs
  mov ds, ax
  mov si, offset hello
  call Puts
  jmp  quit

drvioctl endp

  END drvioctl

Драйвер был подготовлен с помощью пакетного файла, представленного в листинге 6.7.

Листинг 6.7. Файл drvioctl\mk.bat

tasm drvioctl
tlink drvioctl,drvioctl.sys /t

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

Листинг 6.8. Файл drvioctl\ctl1.cpp

#include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>

int main(void)
{
  char buf[10];
  int io_handle;

  // Открываем устройство с именем IODRIVER
  if((io_handle = open("IODRIVER", O_RDWR)) == - 1)
  {
    // Если открыть не удалось, выводим код ошибки
    printf("Open: ошибка %d", errno );
    return errno ;
  }

  // Читаем 8 байт из устройства в буфер buf
  if(read(io_handle, buf, 8) == -1)
  {
    // Если при чтении произошла ошибка,
    // выводим ее код
    printf("Read: ошибка %d", errno );
    return errno ;
  }

  // Закрываем прочитанную строку нулем
  // для последующего вывода функцией printf
  buf[8]=0;
  printf("\nПолучена строка: %s", buf);

  // Записываем только что прочитанные данные
  // обратно на то же устройство
  if(write(io_handle, buf, 8) == -1)
  {
    // Если при записи произошла ошибка,
    // выводим ее код
    printf("Write: ошибка %d", errno );
    return errno ;
  }

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

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

Ниже мы приведем пример программы ctl2.exe (листинг 6.9), которая сначала устанавливает для драйвера, описанного выше, символьный режима работы и выводит 8 символов. Затем программа выполняет аналогичную операцию в двоичном режиме.

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

Листинг 6.9. Файл drvioctl\ctl2.cpp

#include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
#include <dos.h>

union REGS  inregs, outregs;
struct SREGS  segregs;

int main(void)
{
  char buf[100];
  int io_handle;

  // Открываем устройство с именем IODRIVER
  if((io_handle = open("IODRIVER", O_RDWR)) == - 1)
  {
    // Если открыть не удалось, выводим код ошибки
    printf("Open: ошибка %d", errno );
    return errno ;
  }

  // Читаем 8 байт из устройства в буфер buf
  if(read(io_handle, buf, 8) == -1)
  {
    // Если при чтении произошла ошибка,
    // выводим ее код
    printf("Read: ошибка %d", errno );
    return errno ;
  }

  // Закрываем прочитанную строку нулем
  // для последующего вывода функцией printf
  buf[8] = 0;
  printf("\nПрочитана строка: <%s>", buf);

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

  if(write(io_handle, buf, 8) == -1)
  {
    // Если при записи произошла ошибка,
    // выводим ее код
    printf("Write: ошибка %d", errno );
    return errno ;
  }

  // Получаем информацию об устройстве
  inregs.h.ah = 0x44;
  inregs.h.al = 0;
  inregs.x.bx = io_handle;
  intdos ( &inregs, &outregs );
  if(outregs.x.cflag == 1)
  {
    // При ошибке выводим ее код
    printf("IOCTL : ошибка %x\n", &outregs.x.ax);
    return(-1);
  }

  // Выводим конфигурацию устройства на экран
  printf("\nКонфигурация устройства:  %04X\n",
     outregs.x.dx);

  // Устанавливаем бит 5 (переключаем драйвер
  // в двоичный режим обмена данными
  inregs.x.dx = (outregs.x.dx | 0x0020) & 0x00ff;

  // Устанавливаем конфигурацию устройства
  inregs.h.ah = 0x44;
  inregs.h.al = 1;
  inregs.x.bx = io_handle;
  intdos (&inregs, &outregs);
  if(outregs.x.cflag == 1)
  {
    // Выводим код ошибки
    printf("IOCTL : ошибка %x\n",&outregs.x.ax);
    return(-1);
  }

  // Выводим конфигурацию устройства
  printf("\nКонфигурация устройства:  %04X\n",
    outregs.x.dx);

  // Читаем 8 байт из устройства в буфер buf
  // Теперь обмен выполняется в двоичном режиме
  if(read(io_handle, buf, 8) == -1)
  {
    // Если при чтении произошла ошибка,
    // выводим ее код
    printf("Read: ошибка %d",errno );
    return errno ;
  }

  // Закрываем прочитанную строку нулем
  // для последующего вывода функцией printf
  buf[8] = 0;

  printf("\nПрочитана строка: <%s>", buf);

  // Выводим только что прочитанные данные
  // обратно на то же устройство
  if(write(io_handle, buf, 8) == -1)
  {
    // Если при записи произошла ошибка,
    // выводим ее код
    printf("Write: ошибка %d", errno );
    return errno ;
  }

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

  return(0);
}

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

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

Приведем полный текст драйвера (листинг 6.10).

Листинг 6.10. Файл drvchar\drvchar.asm

; Демонстрационный драйвер символьного устройства
;
; (C) Фролов А.В., Фролов Г.В., 1995

  .MODEL tiny
  .CODE
  ORG 0

@@out_ch MACRO c1,c2,c3,c4,c5,c6,c7,c8,c9,c10
  mov  ah,02h
  IRP  chr,<c1,c2,c3,c4,c5,c6,c7,c8,c9,c10>
    IFB  <chr>
      EXITM
    ENDIF
    mov  dl,chr
    int  21h
  ENDM
ENDM

@@out_str MACRO
  mov   ah,9
  int   21h
ENDM

devdrv proc far

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

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

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

; Здесь запоминается адрес заголовка запроса
req_seg dw ?
req_off dw ?

;===================================================
;Обработчик прерывания
;===================================================

dev_interrupt:
  push es
  push ds
  push ax
  push bx
  push cx
  push dx
  push si
  push di
  push bp

  mov  ax, cs:req_seg
  mov  es, ax
  mov  bx, cs:req_off
  mov  al, es:[bx]+2
  shl  al, 1
  sub  ah, ah
  lea  di, functions    
  add  di, ax           
  jmp  word ptr [di]   

functions   LABEL  WORD

  dw   initialize
  dw   check_media
  dw   make_bpb
  dw   ioctl_in
  dw   input_data
  dw   nondestruct_in
  dw   input_status
  dw   clear_input
  dw   output_data
  dw   output_verify
  dw   output_status
  dw   clear_output
  dw   ioctl_out
  dw   Device_open
  dw   Device_close
  dw   Removable_media
  dw   reserved
  dw   reserved
  dw   reserved
  dw   generic_ioctl
  
check_media:
make_bpb:
clear_input:
output_verify:
clear_output:
Removable_media:
reserved:
generic_ioctl:

; Выводим сообщение о том, что вызвана
; команда, которая не поддерживается драйвером

  mov ax, cs
  mov ds, ax
  mov si, offset errmsg
  call dpc

; Ожидаем, пока пользователь нажмет 
; любую клавишу

  mov ax, 0
  int 16h

; Устанавливаем признак ошибки

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

;==================================================
nondestruct_in:

; Выводим сообщение о начале неразрушающего ввода

  mov ax, cs
  mov ds, ax
  mov si, offset inpmsg_nd
  call dpc

; Вводим символ с клавиатуры и помещаем
; его в область запроса

  mov  ax, 0
  int  16h
  mov  BYTE PTR es:[bx]+0dh, al

  jmp  quit

;===================================================
input_status:

; Выводим сообщение о вызове команды
; проверки состояния ввода

  mov ax, cs
  mov ds, ax
  mov si, offset statmsg_i
  call dpc

; Устанавливаем признак "Занято", так как
; последующая команда чтения приведет к ожиданию
; (буферизация не используется)

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

;===================================================
output_status:

; Выводим сообщение о вызове команды
; проверки состояния вывода

  mov ax, cs
  mov ds, ax
  mov si, offset statmsg_o
  call dpc

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

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

;===================================================
; Обработчик команды вывода данных

output_data:

; Записываем в регистр CL количество
; символов, которые будут выведены

  mov  cl, es:[bx]+18
  push cx

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

  mov ax, cs
  mov ds, ax
  mov si, offset outmsg
  call dpc

  pop cx

; Загружаем в DS:SI адрес буфера данных

  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  si, es:[bx]+14

; Выводим на экран символы из буфера

out_loop:
  mov  al, ds:byte ptr [si]
  @@out_ch al
  inc si
  loop out_loop

  jmp  quit

;===================================================
; Обработчик команды ввода данных

input_data:

; Записываем в регистр CL количество
; символов, которые будут введены

  mov  cl, es:[bx]+18
  push cx

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

  mov ax, cs
  mov ds, ax
  mov si, offset inpmsg
  call dpc

; Загружаем в DS:SI адрес буфера данных

  pop  cx
  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  di, es:[bx]+14

; Вводим символы с клавиатуры и записываем в буфер

inp_loop:
  mov  ax, 0
  int  16h
  mov  ds:byte ptr [di], al
  @@out_ch al
  inc di
  loop inp_loop

  jmp  quit

;===================================================
; Обработчик команды вывода данных IOCTL 

ioctl_out:

; Записываем в регистр CL количество
; символов, которые будут вывдедены

  mov  cl ,es:[bx]+18

; Загружаем в DS:SI адрес буфера данных

  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  si, es:[bx]+14

; Выводим на экран символы из буфера

ioctl_out_loop:
  mov  al, ds:byte ptr [si]
  @@out_ch al
  inc si
  loop ioctl_out_loop

  jmp  quit

;===================================================
; Обработчик команды ввода данных IOCTL 

ioctl_in:

; Записываем в регистр CL количество
; символов, которые будут введены

  mov  cl, es:[bx]+18

; Загружаем в DS:SI адрес буфера данных

  mov  ax, es:[bx]+16
  mov  ds, ax
  mov  di, es:[bx]+14

; Вводим символы с клавиатуры и записываем в буфер

ioctl_inp_loop:
  mov  ax, 0
  int  16h
  mov  ds:byte ptr [di], al
  @@out_ch al
  inc di
  loop ioctl_inp_loop

  jmp  quit

;===================================================
Device_open:

; Выводим сообщение об открытии устройства

  mov ax, cs
  mov ds, ax
  mov si, offset openmsg
  call dpc

  jmp quit

;===================================================
Device_close:

; Выводим сообщение о закрытии устройства

  mov ax, cs
  mov ds, ax
  mov si, offset closemsg
  call dpc

  jmp quit

;===================================================
quit:
  or  es:word ptr [bx]+3, 100h
  pop bp
  pop di
  pop si
  pop dx
  pop cx
  pop bx
  pop ax
  pop ds
  pop es
  ret

;===================================================
parm_off dw ? ; смещение строки параметров
parm_seg dw ? ; сегмент строки параметров

pc_type  dw ? ; область памяти для сохранения
int_num  dw ? ;   значений параметров
out_port dw ?
inp_port dw ?
ctrl_inp_port dw ?
ctrl_out_port dw ?

;===================================================
;  Имя        interrupt
;             Обработчик прерывания INT <nn>
;
;  Вход:  nn - Номер прерывания, заданный в файле
;              config.sys 
;         AH - Номер выполняемой функции:
;                0 - операция записи;
;                1 - операция чтения
;         BH - Адрес (0...7Fh)
;         BL - Данные для записи (0...FFh)
;  Выход: BL - Прочитанные данные
;
; Описание
;
;     Прерывание вызывается командой INT <nn> с
;         параметрами:
;
; Вход:  nn - Номер прерывания, заданный в файле 
;             config.sys 
;        AH - Номер выполняемой функции:
;               0 - операция записи;
;               1 - операция чтения
;        BH - Адрес (0...7Fh)
;        BL - Данные для записи (0...FFh)
; Выход: BL - Прочитанные данные
;
; Возвращаемое значение
; BL - Прочитанные данные
;===================================================

interrupt:
  push ax
  push cx
  push dx
  push bp
  push si
  push di
  push ds
  push es

  cmp ah, 0      ; команда записи
  jz int_write
  cmp ah, 1      ; команда чтения
  jz int_read

; Обработка неизвестной команды

  @@out_ch 13,10,'?','?','?',13,10

; Устанавливаем признак ошибки

  mov ax, 0ffffh
  jmp int_exit

int_write:

; Выводим сообщение о приходе прерывания,
; предназначенного для записи

  @@out_ch 13,10,'W','R','I','T','E',13,10

  mov ax, 0
  jmp int_exit

int_read:

; Выводим сообщение о приходе прерывания,
; предназначенного для чтения

  @@out_ch 13,10,'R','E','A','D',13,10

; Имитация чтения, всегда возвращается значение 55h

  mov bl, 55h
  mov ax, 0
  jmp int_exit

int_exit:
  pop es
  pop ds
  pop di
  pop si
  pop bp
  pop dx
  pop cx
  pop ax

  iret

;===================================================
; Процедура выводит на экран строку
; символов в формате ASCIIZ 

dpc proc near
  push si
dpc_loop:
  cmp  ds:byte ptr [si], 0
  jz   end_dpc
  mov  al, ds:byte ptr [si]
  @@out_ch al
  inc si
  jmp dpc_loop

end_dpc:
  pop si
  ret
dpc endp

hello db 13,10,'+--------------------------------+'
      db 13,10,'|  *DEVDRV* (C)Frolov A., 1995   |'
      db 13,10,'+--------------------------------+'
      db 13,10,0

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

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

inpmsg_nd db 13,10
  db 'Неразрушающий ввод с устройства',0
statmsg_i db 13,10
  db 'Чтение состояния ввода ',0
statmsg_o db 13,10
  db 'Чтение состояния вывода ',0
errmsg  db 13,10
  db 'Команда не поддерживается ',0

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

E_O_P:
initialize:

  lea  ax, E_O_P
  mov  es:word ptr [bx]+14, ax 
  mov  es:word ptr [bx]+16, cs

; Смещение и сегмент строки параметров  

  mov  ax, es:word ptr [bx]+18
  mov  cs:parm_off, ax
  mov  ax, es:word ptr [bx]+20
  mov  cs:parm_seg, ax

; Стираем экран

  mov   dh, 18h
  mov   dl, 80h
  xor   cx, cx
  mov   bh, 7
  xor   al, al
  mov   ah, 6
  int   10h

; Устанавливаем курсор в левый верхний угол экрана

  mov   bh, 0
  xor   dx, dx
  mov   ah, 2
  int   10h

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

  mov ax, cs
  mov ds, ax
  mov si, offset hello
  call dpc

; Раскодируем строку и проверяем
; корректность заданных параметров

  push cs
  pop  ds

; ES:BX - адрес строки параметров
  
  mov  bx, cs:parm_off   
  mov  ax, cs:parm_seg
  mov  es, ax

; Адрес начала области параметров

  mov  bp, OFFSET cs:pc_type

; Анализируем параметры

  call parm
  jc   parm_errors

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

  push cs
  pop  ds
  mov  dx, OFFSET cs:interrupt
  mov  ax, cs:int_num
  mov  ah, 25h
  int  21h

  jmp  quit

parm_errors:

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

  mov  ax, cs:req_seg 
  mov  es, ax
  mov  bx, cs:req_off

  lea  ax, devdrv  
  mov  es:word ptr [bx]+14, ax
  mov  es:word ptr [bx]+16, cs

  jmp quit

;===================================================
; Разбор строки параметров

parm proc near
  xor  si, si ; индекс в строке параметров

next_chr: 
  mov  al, BYTE PTR es:[bx][si]
  cmp  al, 0ah  ; проверки на конец
  je   parm_br  ; строки параметров
  cmp  al, 0dh
  je   parm_br
  cmp  al, 0h
  jz   parm_br

; Копируем очередной байт строки параметров в буфер

  mov  BYTE PTR cs:parm_buffer[si], al
  inc  si
  jmp  next_chr

; Закрываем скопированную строку параметров нулем

parm_br:  
  
  mov   BYTE PTR cs:parm_buffer[si], 0

; Подготавливаем регистры для вызова программы
; анализа параметров и проверяем правильность
; заданных параметров

  mov   dx, OFFSET sep
  mov   cl, 6
  mov   bx, OFFSET cs:parm_buffer

  call  get_parm
  jc    err_msg

  mov   ax,cs:pc_type
  cmp   ax, 0
  jz    model_is_valid
  cmp   ax, 1
  jz    model_is_valid

  jmp   err_msg

model_is_valid:

; Если параметры заданы правильно,
; выводим их значения на экран

  mov   si, OFFSET msg1
  call  dpc
  mov   ax, cs:pc_type
  call  print_word

  mov   si, OFFSET msg2
  call  dpc
  mov   ax, cs:int_num
  call  print_word

  mov   si, OFFSET msg3
  call  dpc
  mov   ax, cs:out_port
  call  print_word

  mov   si, OFFSET msg4
  call  dpc
  mov   ax, cs:inp_port
  call  print_word

  mov   si, OFFSET msg41
  call  dpc
  mov   ax, cs:ctrl_inp_port
  call  print_word

  mov   si, OFFSET msg42
  call  dpc
  mov   ax, cs:ctrl_out_port
  call  print_word

  @@out_ch  13,10,13,10
  clc
  jmp   end_of_parm
err_msg:

; Если были ошибки в параметрах, выводим
; сообщение об ошибке,
; саму ошибочную строку параметров
; и ожидаем, пока пользователь не нажмет 
; на любую клавишу.
; На выходе устанавливаем флаг CARRY

  mov   si, OFFSET msg5
  call  dpc

  mov   ds, cs:parm_seg
  mov   si, cs:parm_off
  call  dpline

  mov   ax, cs
  mov   ds, ax
  mov   si, OFFSET msg6
  call  dpc

  mov   ax, 0
  int   16h
  stc
end_of_parm:  ret

parm_buffer db  100 DUP (?)

sep   db " ",0
msg1  db 13,10,"PC Type                 ........ ",0
msg2  db 13,10,"Used Interrupt Number   ........ ",0
msg3  db 13,10,"Device Input Port       ........ ",0
msg4  db 13,10,"Device Output Port      ........ ",0
msg41 db 13,10,"Device Inp Control Port ........ ",0
msg42 db 13,10,"Device Out Control Port ........ ",0

msg5  db 13,10,"Invalid Driver Parameter!",13,10,0

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

parm endp

; get_parm
; Разбор строки параметров.
;
;       ds:bx  -  исходная строка, закрытая нулем
;       ds:dx  -  строка разделительных символов,
;                 закрытая нулем
;       ds:bp  -  буфер параметров
;          cx  -  количество параметров
;
; Разбирается исходная строка ds:bx, разделенная
; символами-сепараторами. Адрес строки,  
; содержащей сепараторы, находится в регистрах ds:dx.
; Количество параметров в исходной строке
; находится в регистре cx.
; Строка параметров начинается с полного пути 
; к файлу, содержащему драйвер.
; Параметры заданы шестнадцатеричными цифрами. 
; Двоичные слова, соответствующие параметрам, 
; последовательно записываются в буфер параметров, 
; расположенный по адресу ds:bp
;
; В случае ошибки устанавливается флаг  переноса

get_parm proc near

  push  bx
  push  cx
  push  ax
  push  si

  xor   ch, ch
  xor   ax, ax
  mov   si, ax

  call  strtoc
  jc    parm_end

parm_loop:
  
  mov   ax, 22h
  call  strtoc
  jc    parm_end
  call  hex_to_bin
  jc    parm_end
  mov   ds:[bp][si], ax
  inc   si
  inc   si
  loop  parm_loop

parm_end:
		
  pop   si
  pop   ax
  pop   cx
  pop   bx

  ret

get_parm endp

; strtoc
; Выделение очередного слова из строки
;
; При первом обращении к процедуре:
;   Вход:
;     ax = 0
;     ds:bx  -  исходная строка, закрытая нулем
;     ds:dx  -  строка сепараторов, закрытая нулем
;   Выход:
;     ds:bx  -  подстрока до первого разделителя,
;               закрытая нулем
;
; При последующих обращениях
;   Вход:
;     ax != 0
;     ds:dx  -  строка из сепараторов, закрытая
		нулем
;   Выход:
;     ds:bx  -  подстрока до следующего
;               разделителя, закрытая нулем
;
;  При первом вызове функция выделяет из строки первое
;  слово до разделителя. При повторном вызове
;  возвращает указатель на следующее слово в
;  исходной строке. Если все слова из строки
;  уже выделены, устанавливается флаг переноса.

strtoc proc near

  push  bp
  push  di

  mov   space, 0

  cmp   ax, 0
  jz    first

  mov   bx, cs:ds1_off
  mov   ax, cs:ds1_seg
  mov   ds, ax

first:    
  
  cmp   BYTE PTR ds:[bx], 0
  jz    error

  mov   bp, bx

str_begin:  

  mov   di, dx

compe:    

  mov   ah, BYTE PTR ds:[bp]
  cmp   ah, 0
  jz    lab
  cmp   BYTE PTR ds:[di], ah
  jne   next
  mov   BYTE PTR ds:[bp], 0
  inc   bp
  inc   BYTE PTR cs:space
  jmp   str_begin

lab:      

  mov   WORD PTR cs:ds1_off, bp
  mov   ax, ds
  mov   WORD PTR cs:ds1_seg, ax
  jmp   end_proc

next:     
  
  inc   di
  cmp   BYTE PTR ds:[di], 0
  jnz   compe
  cmp   BYTE PTR cs:space, 0
  jnz   lab
  inc   bp
  jmp   str_begin

error:    
  
  stc

end_proc: 

  pop   di
  pop   bp
  ret

ds1_off dw ?
ds1_seg dw ?
space   db ?

strtoc endp

; hex_to_bin
; Преобразует шестнадцатеричную строку 
; в двоичное число
;
; Вход:
;    ds:bx  -  исходная строка, закрытая нулем
; Выход:
;    ax     -  результат преобразования
;
; Функция преобразует строку из ascii-символов в 
; шестнадцатеричном формате в 16-битовый эквивалент.
; В случае переполнения или если строка
; содержит символы, отличные от 0..9, A..F, a..f,
; устанавливается флаг переноса

hex_to_bin PROC NEAR

  push  bp
  push  si
  push  dx

  xor   ax, ax
  mov   bp, bx

begin:    

  cmp   BYTE PTR ds:[bp], 0h
  jz    end_pro
  call  hex
  jc    h_error

  mov   si, 10h
  xor   dx, dx
  mul   si
  cmp   dx, 0
  jnz   h_error

  add   ax, bx
  inc   bp
  jmp   begin

h_error:  
  
  stc

end_pro:
  
  pop  dx
  pop  si
  pop  bp
  ret
hex_to_bin endp

hex proc near
  xor   bh, bh
  mov   bl, BYTE PTR ds:[bp]

  cmp   bl, '0'
  jb    hex_error
  cmp   bl, '9'
  ja    next_big
  and   bx, 0fh
  jmp   ok

next_big: 

  cmp   bl, 'A'
  jb    hex_error
  cmp   bl, 'F'
  ja    next_small
  sub   bl, 37h
  jmp   ok

next_small: 
  
  cmp   bl, 'a'
  jb    hex_error
  cmp   bl, 'f'
  ja    hex_error
  sub   bl, 57h
  jmp   ok

hex_error:  

  stc

ok:         
  
  ret

hex endp

; dec_to_bin
; Преобразует  десятичную строку в двоичное число
;
; Вход:
;    ds:bx  -  исходная строка, закрытая нулем
; Выход:
;    ax     -  результат преобразования

dec_to_bin proc near

  push  bp
  push  si
  push  dx

  xor   ax, ax
  mov   bp, bx

d_begin:  

  cmp   BYTE PTR ds:[bp], 0h
  jz    d_end_pro

  cmp   BYTE PTR ds:[bp], '0'
  jb    d_error
  cmp   BYTE PTR ds:[bp], '9'
  ja    d_error
  mov   si, 10
  xor   dx, dx
  mul   si
  cmp   dx, 0
  jnz   d_error
  mov   bl, BYTE PTR ds:[bp]
  and   bx, 0fh
  add   ax, bx
  inc   bp
  jmp   d_begin

d_error:    

  stc

d_end_pro:
  pop  dx
  pop  si
  pop  bp
  ret

dec_to_bin endp

print_word proc near
  push ax
  push bx
  push dx

  push ax
  mov  cl, 8
  rol  ax, cl
  call byte_to_hex
  mov  bx, dx
  @@out_ch bh
  @@out_ch bl

  pop  ax
  call byte_to_hex
  mov  bx, dx
  @@out_ch bh
  @@out_ch bl

  pop dx
  pop bx
  pop ax
  ret
print_word endp

byte_to_hex proc near
  push ds
  push cx
  push bx

  lea  bx, tabl
  mov  dx, cs
  mov  ds, dx

  push ax
  and  al, 0fh
  xlat
  mov  dl, al

  pop ax
  mov  cl, 4
  shr  al, cl
  xlat
  mov  dh, al
  
  pop  bx
  pop  cx
  pop  ds
  ret

tabl db '0123456789ABCDEF'
byte_to_hex endp

dpline proc near
  push si
dpline_loop:
  cmp  ds:byte ptr [si],0dh
  jz   end_dpline
  cmp  ds:byte ptr [si],0ah
  jz   end_dpline

  mov  al,ds:byte ptr [si]
  @@out_ch al
  inc si
  jmp dpline_loop

end_dpline:
  pop si
  ret
dpline endp

devdrv  ENDP

  END devdrv

Драйвер был подготовлен при помощи пакетного файла, представленного в листинге 6.11.

Листинг 6.11. Файл drvchar\mk.bat

tasm drvchar
tlink drvchar,drvchar.sys /t

Для работы с этим драйвером и демонстрации его основных возможностей мы подготовили программу ctl3.exe (листинг 6.12).

Листинг 6.12. Файл drvchar\ctl3.cpp

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

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno .h>
#include <dos.h>

union  REGS   inregs, outregs;
struct SREGS  segregs;

int main(void)
{
  char buf[100], ch;
  int io_handle;

  // Открываем устройство с именем DEVDRIVR
  if((io_handle = open("DEVDRIVR", O_RDWR)) == - 1)
  {
    // Если открыть не удалось, выводим код ошибки
    printf("Open: ошибка %d", errno );
    return errno ;
  }

  // Читаем 8 байт из устройства в буфер buf
  printf("\nВведите 8 символов с клавиатуры\n");

  if(read(io_handle, buf, 8) == -1 )
  {
    // Если при чтении произошла ошибка,
    // выводим ее код
    printf("Read: ошибка %d", errno );
    return errno ;
  }

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

  buf[8]=0;
  printf("\nВведена строка: <%s>", buf);

  // Выводим только что прочитанные данные
  // обратно на то же устройство
  if(write(io_handle, buf, 8) == -1)
  {
    // Если при записи произошла ошибка,
    // выводим ее код
    printf("Write: ошибка %d", errno );
    return errno ;
  }

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

  inregs.h.ah = 0x44;
  inregs.h.al = 2;
  inregs.x.bx = io_handle;
  inregs.x.dx = (unsigned)buf;
  inregs.x.cx = 8;
  intdos (&inregs, &outregs);

  if(outregs.x.cflag == 1)
  {
    // При ошибке выводим код ошибки
    printf("IOCTL : ошибка %x\n", &outregs.x.ax);
    return(-1);
  }

  buf[8]=0;
  printf("\nВведена строка IOCTL : <%s>", buf);

  // Выводим строку IOCTL  на устройство из buf
  printf("\nВыведена строка IOCTL : ");

  inregs.h.ah = 0x44;
  inregs.h.al = 3;
  inregs.x.bx = io_handle;
  inregs.x.dx = (unsigned)buf;
  inregs.x.cx = 8;
  intdos (&inregs, &outregs);

  if(outregs.x.cflag == 1)
  {
    // При ошибке выводим код ошибки
    printf("IOCTL : ошибка %x\n", &outregs.x.ax);
    return(-1);
  }

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

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

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

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

  inregs.h.ah = 0x1;
  inregs.h.bh = 0x776;
  int86 (0x80, &inregs, &outregs);
  ch = outregs.h.bl;

  printf("Полученное значение: %x\n",ch);

  printf("\nНеизвестная команда:\n");

  inregs.h.ah = 0x2; // ???
  int86 (0x80, &inregs, &outregs);

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

Драйвер блочного устройства

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

Листинг 6.13. Файл ramdisk\ramdisk.asm

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

  SEGMENT _TEXT PARA PUBLIC
  ASSUME CS:_TEXT,DS:_TEXT
  ORG 0

ramdisk  PROC far

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

  dd   0ffffffffh      ; адрес следующего драйвера
  dw   2000h           ; байт атрибутов
  dw   dev_strategy    ; адрес процедуры стратегии
  dw   dev_interrupt   ; адрес процедуры прерывания
  db   1
  db   7 dup(?)

; Блок BPB  для электронного диска

bpb   equ $

  dw 512   ; количество байтов в секторе
  db 1     ; количество секторов в кластере
  dw 1     ; количество зарезервированных секторов
  db 2     ; количество копий FAT 
  dw 64    ; макс. количество файлов в корневом каталоге
  dw 360   ; общее количество секторов
  db 0fch  ; описатель среды носителя данных
  dw 2     ; количество секторов на одну копию FAT 

bpb_ptr     dw bpb   ; указатель на блок BPB 

; Область локальных переменных драйвера

total       dw ?  ; количество секторов
verify      db 0  ; флаг проверки при записи
start_sec   dw 0  ; номер начального сектора
vdisk_ptr   dw 0  ; сегмент начала участка памяти,
		  ; в котором расположен диск

user_dta    dw ?  ; адрес области передачи данных
	    dw ?

; Образец записи BOOT для инициализации
; первого сектора диска

boot_rec equ $

  db 3 dup(0)
  db 'MSDOS6.2'
  dw 512
  db 1
  dw 1
  db 2
  dw 64
  dw 360
  db 0fch
  dw 2

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

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

req_seg dw ?
req_off dw ?

;=======================================================
;Обработчик прерывания

dev_interrupt:
  push es    
  push ds
  push ax
  push bx
  push cx
  push dx
  push si
  push di
  push bp

  mov  ax, cs:req_seg
  mov  es, ax
  mov  bx, cs:req_off
  mov  al, es:[bx]+2
  shl  al, 1
  sub  ah, ah          
  lea  di, functions 
  add  di, ax 
  jmp  word ptr [di]   

functions   LABEL  WORD

  dw   initialize
  dw   check_media
  dw   make_bpb
  dw   ioctl_in
  dw   input_data
  dw   nondestruct_in
  dw   input_status
  dw   clear_input
  dw   output_data
  dw   output_verify
  dw   output_status
  dw   clear_output
  dw   ioctl_out
  dw   Device_open
  dw   Device_close
  dw   Removable_media

ioctl_in:
nondestruct_in:
input_status:
clear_input:
output_status:
clear_output:
ioctl_out:
Removable_media:
Device_open:
Device_close:

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

;=======================================================
; Построение блока BPB 

make_bpb:

  push  es
  push  bx

  mov   cs:WORD PTR start_sec, 0
  mov   cs:WORD PTR total, 1
  call  calc_adr

  push  cs
  pop   es

  lea   di, bpb
  add   si, 11
  mov   cx, 13
  rep   movsb

  pop   bx
  pop   es

  lea   dx, bpb
  mov   es:18[bx], dx
  mov   es:20[bx], cs

  jmp  quit

check_media:

; Проверка смены носителя данных.
; Носитель не менялся.

  mov   es:BYTE PTR 14[bx], 1
  jmp  quit
  
; Обработчик команды вывода данных

output_verify:

; Для вывода с проверкой устанавливаем флаг проверки

  mov   cs:BYTE PTR verify, 1

output_data:

  call  in_save
  mov   ax, es:WORD PTR 20[bx]
  mov   cs:start_sec, ax

  mov   ax, es:WORD PTR 18[bx]
  mov   cs:total, ax

  call  sector_write

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

  cmp   cs:BYTE PTR verify, 0
  jz    no_verify

  mov   cs:BYTE PTR verify, 0
  jmp   input_data

no_verify:

  jmp  quit

;=======================================================
; Обработчик команды ввода данных

input_data:

  call  in_save
  mov   ax, es:WORD PTR 20[bx]
  mov   cs:start_sec, ax

  mov   ax, es:WORD PTR 18[bx]
  mov   cs:total, ax

  call  sector_read

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

  jmp  quit

;========================================================
quit:
  or  es:word ptr [bx]+3, 100h
  pop bp
  pop di
  pop si
  pop dx
  pop cx
  pop bx
  pop ax
  pop ds
  pop es
  ret

;========================================================
dpc proc near
  push si
dpc_loop:
  cmp  ds:byte ptr [si], 0
  jz   end_dpc
  mov  dl, ds:byte ptr [si]
  mov  ah, 02h
  int   21h

  inc si
  jmp dpc_loop

end_dpc:
  pop si
  ret
dpc endp

hello db 13,10,'+--------------------------------+'
      db 13,10,'| *RAMDISK* (C)Frolov A., 1995   |'
      db 13,10,'+--------------------------------+'
      db 13,10,0

;========================================================
; Сохранение адреса буфера и значения счетчика
; из области запроса в области локальных данных

in_save proc near

  mov   ax, es:WORD PTR 14[bx]
  mov   cs:user_dta, ax

  mov   ax, es:WORD PTR 16[bx]
  mov   cs:user_dta+2, ax

  mov   ax, es:WORD PTR 18[bx]
  xor   ah, ah
  mov   cs:total, ax
  ret
in_save endp

; Процедура пересчитывает адрес сектора
; в адрес соответствующего блока памяти. 
; В регистре DS возвращается
; сегментный адрес этого блока,
; в CX - общее количество байт во всех секторах.
; Количество секторов задается в total,
; номер начального сектора - в start_sec

calc_adr proc near

  mov   ax, cs:start_sec
  mov   cx, 20h
  mul   cx

  mov   dx, cs:vdisk_ptr
  add   dx, ax
  mov   ds, dx

  xor   si, si
  mov   ax, cs:total
  mov   cx, 512
  mul   cx

  or    ax, ax
  jnz   move_it

  mov   ax, 0ffffh

move_it:

  xchg  cx, ax
  ret
calc_adr endp

; Чтение сектора из памяти виртуального диска

sector_read proc near

  call  calc_adr
  mov   es, cs:user_dta+2
  mov   di, cs:user_dta

  mov   ax, di
  add   ax, cx
  jnc   read_copy
  mov   ax, 0ffffh
  sub   ax, di
  mov   cx, ax

read_copy:
	
  rep   movsb
  ret
sector_read endp

; Запись сектора в память виртуального диска

sector_write proc near

  call  calc_adr
  push  ds
  pop   es
  mov   di, si
  mov   ds, cs:user_dta+2
  mov   si, cs:user_dta

  mov   ax, si
  add   ax, cx
  jnc   write_copy
  mov   ax, 0ffffh
  sub   ax, si
  mov   cx, ax

write_copy:
  
  rep   movsb
  ret

sector_write endp

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

;========================================================
initialize:

  push  cs
  pop   dx
  
; Начало памяти, в которой расположен диск
  lea   ax, cs:vdisk 
  
  mov   cl, 4
  ror   ax, cl
  add   dx, ax
  mov   cs:vdisk_ptr, dx

; Размер памяти, отведенной для диска
  mov   ax, 2d00h    
  
  add   dx, ax

; Записываем в область запроса адрес за
; концом области памяти, отведенной диску

  mov   es:word ptr [bx]+14, 0
  mov   es:word ptr [bx]+16, dx

; Количество поддерживаемых логических дисков 
; равно 1

  mov   es:word ptr [bx]+13, 1

; Возвращаем адрес построенного BPB 

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

; Инициализируем загрузочный сектор

  mov   es, cs:vdisk_ptr
  xor   di, di
  lea   si, boot_rec
  mov   cx, 24
  rep   movsb

; Обнуляем два сектора для FAT 

  mov   cs:WORD PTR start_sec, 1
  mov   cs:WORD PTR total, 2
  call  calc_adr

  push  ds
  pop   es
  mov   di, si
  xor   al, al
  rep   stosb

; Подготавливаем первую копию FAT 

  mov   ds:BYTE PTR [si],  0fch
  mov   ds:BYTE PTR 1[si], 0ffh
  mov   ds:BYTE PTR 2[si], 0ffh

; Подготавливаем вторую копию FAT 

  push  ds
  push  si

  mov   cs:WORD PTR start_sec, 3
  mov   cs:WORD PTR total, 2
  call  calc_adr

  push  ds
  pop   es
  mov   di, si

  pop   si
  pop   ds

  rep   movsb

; Записываем нули в секторы корневого каталога

  mov   cs:WORD PTR start_sec, 5
  mov   cs:WORD PTR total, 4
  call  calc_adr

  xor   al, al
  push  ds
  pop   es
  xor   di, di
  rep   stosb


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

  mov ax, cs
  mov ds, ax
  mov si, offset hello
  call dpc

  jmp  quit

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

  ALIGN 16
  vdisk equ $

ramdisk   ENDP

  ENDS _TEXT
  END ramdisk

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

Листинг 6.14. Файл ramdisk\mk.bat

tasm ramdisk
tlink ramdisk,ramdisk.sys /t