Структура загружаемого драйвера
Драйвер - это еще одна разновидность программ в дополнение к уже изученным нами программам формата COM и EXE. Иногда говорят, что драйверы - это разновидность COM-программ, но это не так. Скорее способ получения загрузочного модуля драйвера похож на способ получения программы в формате COM. Есть еще одно сходство драйверов и программ в формате COM (которое как раз и появляется из-за одинакового способа их получения) - загрузочные модули этих программ являются точным отображением исходного текста на языке ассемблера без добавления каких-либо управляющих блоков в начало файла, как это происходит в программах формата EXE. (К сожалению, драйвер должен быть написан на языке ассемблера. Авторам этой книги не известны способы составления драйверов на других языках программирования).
Но, оказывается, управляющий блок в самом начале модуля драйвера имеется. Это так называемый заголовок драйвера. Только в отличие от программ формата EXE, этот заголовок создается не редактором связи, а самим программистом и должен быть помещен в самое начало исходного текста программы-драйвера.
При загрузке драйвера в память заголовок драйвера тоже помещается в оперативную память, и в нем операционная система производит некоторые изменения, о которых мы еще будем говорить.
Таким образом, можно говорить и о сходстве драйвера с программами в формате EXE, так как в начале загрузочного модуля драйвера имеется управляющий блок. Только этот управляющий блок в отличие от заголовка EXE-файла является принадлежностью самой программы и загружается вместе с ней в память. Заголовок EXE-программы используется при загрузке EXE-программы, но после загрузки операционная система убирает его из памяти.
Не стоит пытаться запускать драйвер как программу в формате COM, так как управление будет передано в область памяти, содержащую заголовок драйвера, а там нет правильных машинных команд. Поэтому обычно файлы драйверов имеют расширения имени, отличные от COM или EXE. Чаще всего используются расширения SYS, DRV, иногда BIN. На самом деле расширение имени можно задавать любое, так как при описании драйвера в файле CONFIG.SYS указывается его полное имя.
Для драйвера никогда не создается префикс программного сегмента PSP. В начале исходного текста программы-драйвера не ставится директива ORG 100H, как это делается для COM-программы, так как не надо резервировать место для PSP.
Что же представляет из себя загрузочный модуль драйвера?
Как уже было сказано, в начале модуля находится заголовок драйвера. Мы уже немного говорили о нем при описании векторной таблицы связи операционной системы. Приведем формат заголовка:
(0) 4 | next | указатель на заголовок следующего драйвера. Если смещение адреса следующего драйвера равно FFFF, это последний драйвер в цепочке |
(+4) 2 | attrib | атрибуты драйвера |
(+6) 2 | strateg | смещение программы стратегии драйвера |
(+8) 2 | interrupt | смещение программы обработки прерывания для драйвера |
(+10) 8 | dev_name | имя устройства для символьных устройств или количество обслуживаемых устройств для блочных устройств. |
Программист, когда он составляет программу-драйвер, заносит в это поле либо 0FFFFh:0FFFFh, если исходный текст содержит только один драйвер, либо адрес следующего драйвера (в виде дальней ссылки на метку заголовка следующего драйвера). Если исходный текст содержит несколько драйверов, то в заголовке последнего в поле next должно находиться значение 0FFFFh:0FFFFh.
При загрузке драйверов в память операционная система изменит содержимое поля next в заголовках драйверов для того, чтобы это поле указывало на заголовок следующего драйвера в цепочке. (Изменит в памяти, а не в файле драйвера!)
Обычно исходный текст программы содержит один драйвер, и поле next задается следующим образом:
next DD 0FFFFFFFFh
Следующее поле в заголовке драйвера - поле атрибутов драйвера atrib.
Это поле описывает устройство, обслуживаемое данным драйвером. Каждый бит слова отвечает за ту или иную особенность устройства. Прежде чем мы детально рассмотрим назначение всех битов этого слова, заметим, что бит 15 (самый старший бит) указывает, является ли это устройство символьным или блочным.
Следует специально отметить, что все драйверы можно разделить на две группы - драйверы символьных устройств и драйверы блочных устройств. Первые обслуживают устройства посимвольного ввода/вывода, такие как принтеры, клавиатура, контроллеры последовательной передачи данных RS232C и т.д., вторые ориентированы на ввод/вывод блоками - это диски.
Как правило, все нестандартные устройства, начиная от цифрового вольтметра до роботов и систем автоматизации производственных процессов, обслуживаются символьными драйверами. Хотя этот тип драйверов ориентирован на передачу данных посимвольно, для быстродействующих устройств ввода/вывода можно организовать буферизацию (средствами операционной системы).
Блочные драйверы могут Вам потребоваться в основном для обслуживания своих нестандартных дисковых устройств. Например, можно использовать более плотную запись информации на дискетах для повышения их емкости, можно через аппаратуру связи персонального компьютера и ЭВМ серии ЕС создавать псевдо-винчестеры на дисках ЕС (такие "винчестеры" будут восприниматься DOS как обычные стандартные диски). С помощью блочных драйверов можно организовать защиту информации на дисках от несанкционированного доступа, если Ваш драйвер будет шифровать записываемую на диск информацию и предоставлять ее по предъявлению пароля.
Мы рассмотрим оба типа драйверов, каждый раз будем отмечать особенности символьных и блочных устройств.
Приведем назначение отдельных битов слова атрибутов символьного драйвера:
Бит | Назначение |
0 | 1 - драйвер обслуживает стандартное устройство ввода; 0 - этот драйвер не обслуживает стандартное устройство ввода |
1 | 1 - драйвер обслуживает стандартное устройство вывода; 0 - драйвер не обслуживает стандартное устройство вывода |
2 | 1 - это драйвер стандартного устройства NUL; 0 - драйвер не обслуживает устройство NUL |
3 | 1 - драйвер обслуживает часы |
4 | Зарезервировано, бит должен быть равен 0 |
5 | Зарезервировано, бит должен быть равен 0 |
6 | 1 - разрешено использование драйвером функций GENERIC IOCTL (для версий DOS, более поздних, чем 3.2); 0 - функции GENERIC IOCTL не поддерживаются |
7-10 | Эти биты зарезервированы и должны быть равны 0 |
11 | 1 - поддерживаются функции открытия/закрытия устройства (OPEN/CLOSE) для символьных устройств; 0 - функции OPEN/CLOSE для символьных устройств не поддерживаются |
12 | Зарезервировано, бит должен быть равен 0 |
13 | 1 - для символьных устройств поддерживается функция вывода до получения состояния занятости устройства; 0 - функция вывода до состояния занятости не поддерживается |
14 | 1 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются |
15 | 1 - символьное устройство; 0 - блочное устройство |
Для драйверов блочных устройств формат слова атрибутов другой:
Бит | Назначение |
0 | Зарезервировано, бит должен быть равен 0 |
1 | 1 - драйвер поддерживает 32-битовую адресацию сектора (для версий DOS, начиная с 4.00 и более поздних); если установлен этот бит, поле номера сектора всех запросов является двойным словом, добавляемым в конец заголовка запроса, старое поле номера сектора должно содержать -1); 0 - используется 16-битовая адресация сектора |
2-5 | Эти биты зарезервированы и должны быть равны 0 |
6 | 1 - поддерживаются логические устройства (используется блочными драйверами для управления "виртуальными" флоппи-дисками, создаваемые драйвером DRIVER.SYS в DOS версии 3.2 и более поздних версиях); 0 - логические устройства для блочных драйверов не поддерживаются; |
7-10 | Эти биты зарезервированы и должны быть равны 0 |
11 | 1 - единица в этом бите означает, что драйвер поддерживает функцию проверки замены носителя данных в устройстве (например, замены дискеты); используется для DOS версий 3.00 и более поздних; 0 - для блочных устройств функция проверки замены носителя данных не поддерживается |
12 | Зарезервировано, бит должен быть равен 0 |
13 | 1 - драйвер не использует стандартное IBM-устройство, и необходимо выдать запрос на построение блока параметров BIOSBIOS BPB; 0 - используется IBM-устройство |
14 | 1 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются |
15 | 1 - символьное устройство; 0 - блочное устройство |
attrib DW 8000h
После слова атрибутов драйвера находятся два очень важных поля: смещение программы стратегии драйвера strateg и смещение программы обработки прерывания interrupt.
Эти две программы используются DOS для организации обращения к драйверу. Для обращения к драйверу DOS формирует в своей области данных запрос, состоящий из заголовка стандартного формата и переменной части запроса, длина и формат которой зависят от типа запроса. После этого DOS считывает из заголовка драйвера значение смещения программы стратегии и передает ей управление, записав в регистры ES:BX адрес заголовка запроса.
Задача программы стратегии - запомнить этот адрес внутри тела драйвера для дальнейшего использования или организовать очередь запросов обслуживания.
Сразу после вызова программы стратегии DOS вызывает программу обработки прерываний, определив ее адрес из поля interrupt заголовка драйвера.
Программа обработки прерывания извлекает только что записанный программой стратегии адрес заголовка запроса и выполняет ту функцию, номер которой записан в запросе. Номер функции находится в заголовке запроса.
Результаты выполнения функции программа прерывания записывает в специально отведенные поля заголовка запроса, и на этом процедура обращения DOS к драйверу завершается.
Формат заголовка запроса будет приведен ниже, а сейчас покажем, как в заголовке драйвера задаются смещения программ стратегии и прерывания:
strateg DW strateg_proc interrupt DW interrupt_proc
Последнее поле заголовка драйвера dev_name имеет различную интерпретацию для символьных и блочных устройств.
Для символьных устройств в этом поле должно располагаться выровненное по левому краю и дополненное до восьми символов пробелами имя устройства. Это имя будет использоваться для обращения к драйверу. Если Вы собираетесь заменить драйвер стандартного символьного устройства DOS на свой, Вы должны записать имя устройства заглавными буквами:
dev_name DB 'AUX '
Для блочных устройств первый байт поля dev_name содержит количество устройств, обслуживаемых данным драйвером, остальные семь байтов не используются:
dev_name DB 1 DB 7 dup(?)
Таким образом, мы выяснили, что драйвер содержит в самом начале заголовок, и где-то дальше должны располагаться программы стратегии и прерывания. (Не следует путать программу прерывания драйвера с программой обслуживания аппаратных или программных прерываний. Хотя программа прерывания драйвера немного похожа на обработчик программных прерываний, назначение этой программы и механизм ее использования совершенно другой).
Что еще может находиться в программе-драйвере?
Это могут быть области данных, используемые драйвером, и подпрограммы, вызываемые программами стратегии и прерывания. Иногда стандартные драйверы переназначают на себя некоторые вектора прерываний, и тогда они содержат в себе обработчики этих прерываний. В области памяти, отведенной операционной системой драйверу, может располагаться стек драйвера, если размер системного стека недостаточен.
На длину драйвера накладывается такое же ограничение, как и на длину COM-программ - 64 килобайта, то есть один сегмент.