МЕТОДИКА СОЗДАНИЯ И ОТЛАДКИ ДРАЙВЕРА ПЕРИФЕРИЙНОГО УСТРОЙСТВА ДЛЯ ОПЕРАЦИОННОЙ СИСТЕМЫ MS-DOS Росс М. Гринберг Создание драйверов периферийных устройств - это тяжелое и утоми- тельное занятие, доступное лишь глубоким знатокам операционной систе- мы. Решившиеся на этот подвиг могут требовать дополнительную плату за риск, несовершеннолетние должны представить разрешение от родителей либо опекунов, а кроме того, необходимо подписать определенные доку- менты, ограничивающие право на иск при несчастном случае, и иметь по крайней мере миллион долларов надежной страховки. Это весьма распространенное мнение, но в действительности процесс создания драйверов периферийных устройств не так уж отличается от ра- боты по созданию других программ и не является намного более сложной задачей. В настоящей статье исследуются внутренние особенности работы драйверов периферийных устройств и предлагаются некоторые полезные приемы их отладки. Строящийся в этой статье драйвер MDM_DRV служит элементарной ком- муникационной программой для передачи протоколов X-модема с контроль- ной суммой. Несмотря на то, что для корректной работы этого драйвера требуется вторая его копия на другом конце канала передачи, организо- ванного через модем либо проводное соединение, он может загружать файлы с нескольких станций приема/передачи. Драйвер периферийного устройства служит логическим интерфейсом между подсистемой ввода/вывода операционной системы и обслуживаемыми ею аппаратными средствами. В некоторых случаях драйвер заменяет или расширяет определенные аспекты BIOS'а и таким образом несет ответст- венность за обработку характеристик обслуживаемых аппаратных средств. В других драйверах физический ввод/вывод осуществляется исключительно через BIOS. В ранних версиях операционной системы MS-DOS не была пре- дусмотрена процедура установки драйверов. Однако, начиная с версии 2.0, программисты получили возможность дополнять операционную систе- му. Несмотря на отсутствие общих стандартов для резидентных программ, интерфейс между операционной системой и драйвером периферийного уст- ройства жестко определен, поэтому большинство драйверов может рабо- тать друг с другом совместно. Драйверы бывают двух типов: ориентированные на символьный либо блоковый обмен данными. Драйверы, обслуживающие хранение и/или доступ к данным на дисках либо других устройствах прямого доступа, обычно ориентированы на блоковый обмен данными. Блоковые драйверы передают данные фиксированными порциями - отсюда и происходит их название. Данная статья ориентирована на описание драйверов с посимвольной передачей данных. Некоторые затронутые здесь вопросы имеют непосредс- твенное отношение к резидентной программе последовательных прерываний TSRCOMM.ASM, описанной в статье "Поддержка контакта с реальным миром: быстрая обработка последовательного ввода/вывода" ("Keeping Up With the Real World: Speedy Serial I/O Processing" MSJ, Vol.2 No.3). Символьные драйверы должны уметь работать в двух режимах: подго- товленном и неподготовленном. В подготовленном режиме драйвер за одно обращение получает один символ, причем некоторые символы, такие как возврат каретки, перевод строки (в транслированной либо нетранслиро- ванной форме), Ctrl-Z и Ctrl-C требуют специальной обработки. При каждом запросе на ввод/вывод проводится контроль статуса, проверяю- щий, поступил ли с клавиатуры символ Ctrl-C, и в подготовленном режи- ме драйвер позволяет операционной системе при поступлении Ctrl-C прервать вызов. Так как от вызывающей программы не требуется знание того, в каком режиме находится драйвер, MS-DOS должна уметь корректно транслировать типичный многобайтовый запрос на ввод/вывод от вышесто- ящей прикладной программы в последовательность однобайтовых запросов. MS-DOS осуществляет это, используя внутренние буфера и пересыая дан- ные в буфер пользователя после выполнения запроса. В неподготовленном режиме от драйвера и операционной системы не требуются никакие преобразования либо иные обслуживающие действия. В этом режиме нет ограничений на число байтов, запрашиваемых при вво- де/выводе: драйвер должен уметь обработать запрос на любое указанное число байтов. Обычно в таких запросах не требуются никакие внутренние буфера операционной системы, и вся обработка происходит в собственном буфере прикладной программы, что значительно повышает быстродействие неподготовленного режима по сравнению с подготовленным. Пользовательская программа может менять режим из подготовленного в неподготовленный и наоборот вызовом функции IOCTL операционной систе- мы MS-DOS, - INT 21H с AH=44H (см. Техническое руководство по MS-DOS). Структура драйвера периферийного устройства Драйвер состоит из трех основных частей: заголовка, программы стратегий и программы прерываний. Заголовок описывает возможности и атрибуты драйвера, присваивает имя драйверу символьного устройства и содержит NEAR (только смещение, одно слово) указатели на программы стратегий и прерываний, а также FAR (смещение и сегмент, двойное сло- во) указатель на следующий драйвер в цепочке драйверов (цепочка явля- ется однонаправленной, что затрудняет поиск ее начала). Указатель на следующий драйвер устанавливает MS-DOS сразу после завершения проце- дуры инициализации, а в самом драйвере ему должно быть присвоено на- чальное значение -1 (FFFFFFFFH). Драйвер должен помещаться в 64K памяти, т.к. программы стратегий и прерываний содержат лишь смещения внутри выделенного драйверу сегмен- та. Наглядная схема заголовка представлена на рис.1, а описание се- мантики битов атрибутного слова - на рис.2. 00H _______________________________________________________ | Указатель на следующий драйвер, смещение | 02H |-------------------------------------------------------| | Указатель на следующий драйвер, сегмент | 04H |-------------------------------------------------------| | Атрибутное слово драйвера | 06H |-------------------------------------------------------| | Точка входа программы стратегий, смещение | 08H |-------------------------------------------------------| | Точка входа программы прерываний, смещение | 0AH |-------------------------------------------------------| | Логическое имя (8 байтов) для символьного устройства.| | Число элементов (1 байт) для блокового устройства | | с последующими 7 резервными байтами. | ------------------------------------------------------- Рис.1. Стандартный заголовок драйвера Бит Описание 15 0 - если блоковое устройство 1 - если символьное устройство 14 0 - функция IOCTL не поддерживается 1 - функция IOCTL поддерживается 13 0 - (если бит 15 = 0) формат IBM 1 - формат, отличный от IBM 0 - (если бит 15 = 1) не поддерживается режим вывода до сиг- нала занятости (Output-Till-Busy) 1 - режим вывода до сигнала занятости поддерживается 12 0 - не определен 11 0 - использовать только вызовы DOS 2.x 1 - для DOS 3.x поддерживаются вызовы, связанные с открытием/ закрытием устройства и перемещаемой средой (Open/Close/RM), игнорируется для DOS 2.x 10 0 - не определен 9 0 - не определен 8 0 - не определен 7 0 - не определен 6 0 - не определен 5 0 - не определен 4 0 - не определен 3 0 - нормальное устройство 1 - устройство преобразования даты/времени суток 2 0 - нормальное устройство 1 - драйвер пустого (NUL) устройства 1 0 - нормальное устройство 1 - драйвер стандартного вывода (stdout) 0 0 - нормальное устройство 1 - драйвер стандартного ввода (stdin) Рис.2. Атрибутное слово драйвера Программа стратегий Программа стратегий вызывается, когда драйвер устанавливается при загрузке операционной системы, а также при каждом сгенерированном операционной системой запросе на ввод/вывод. Единичный запрос на ввод/вывод от прикладной программы может породить несколько запросов к драйверу. Задача данной программы заключается лишь в том, чтобы сохранить где-нибудь некоторый адрес для дальнейшей обработки. Этот адрес, пе- редаваемый в паре регистров ES:BX, указывает на структуру, называемую заголовком запроса (см. рис.3), которая содержит информацию, сообщаю- щую драйверу, какую операцию он должен выполнить. rlength db 0 ; 0 - длина существенных данных ; в заголовке unit db 0 ; 1 - номер элемента ; (в MDM_DRV не используется) command db 0 ; 2 - фактическая команда status dw 0 ; 3 - состояние после возврата reserve db 8 dup (0) ; 5 - зарезервировано для DOS media db 0 ; 13 - дескриптор среды ; (в MDM_DRV не используется) address dd 0 ; 14 - длинный указатель для ввода/вывода count dw 0 ; 18 - число символов для ввода/вывода ; (целое без знака) sector dw 0 ; 20 - начальный сектор ; (в MDM_DRV не используется) Рис.3. Схема заголовка запроса Существенно то, что программа стратегий не осуществляет никаких операций ввода/вывода, а лишь сохраняет адрес заголовка запроса для последующей обработки программой прерываний. В мультизадачной системе этот адрес должен был бы храниться в некотором массиве, который при последующем вызове процедуры прерываний подвергался бы сортировке с целью оптимального использования устройства. Под управлением MS-DOS программа прерываний вызывается сразу после программы стратегий. Сле- дует обратить внимание на то, что между вызовами программ стратегий и прерываний допустимы прерывания, а это может создать трудности, если драйвер написан в предположении, что между этими двумя вызовами про- ходит "нулевое время". Программа прерываний В программе прерываний выполняется вся фактическая работа драйве- ра, поэтому она является наиболее сложной его частью. При вызове этой программы исследуется командный байт (третий байт) ранее сохраненного заголовка запроса и в зависимости от его значения выполняются те или иные действия. На рис.4 представлен список значений командного байта и соответствующих им действий. Командный байт Действие 00H Инициализировать драйвер. Вызывается только при установке драйвера во время загрузки системы. 01H Проверка среды. Возвращает статус, сигнализирующий об изменении текущей среды (не используется в MDM_DRV). 02H Построить блок параметров BIOS'а (BPB). Вызывается при создании или изменении среды, этот вызов дол- жен построить BPB и вернуть его адрес (не исполь- зуется в MDM_DRV). 03H Читать IOCTL. Скопировать определенную информацию для драйвера в локальный буфер. 04H Читать. Получить определенное число символов из таблиц драйвера. 05H Читать символ без удаления. Получить от устройства следующий символ, не удаляя его из входного буфера. 06H Статус ввода. Выдать статус, сигнализирующий о на- личии символа во входном буфере. 07H Сброс ввода. Очистить входной буфер устройства. 08H Писать. Передать устройству определенное число символов. 09H Писать с проверкой. Вывести символы, затем про- честь их для котроля корректности вывода. 0AH Статус вывода. Выдать статус, сигнализирующий о том, свободно ли устройство для вывода. 0BH Сбросить буфера вывода. Очистить обслуживающие ус- тройство буфера вывода. 0CH Писать IOCTL. Скопировать определенную информацию для драйвера из локального буфера в таблицы драй- вера. 0DH Открыть устройство (только для DOS 3.x). Инициали- зировать устройство. 0EH Закрыть устройство (только для DOS 3.x). Вызывает- ся перед закрытием файлов. 0FH Программа обработки перемещаемой среды (только для DOS 3.x). Только для блоковых устройств. 10H Вывод до сигнала занятости (только для DOS 3.x). Ненормальный вариант обычной функции вывода, до- пускающий "неполный" вывод. Рис.4. Командные байты заголовка запроса и их значения Программа прерываний обычно использует командный байт в качестве индекса для некоторой управляющей таблицы, вызывая таким образом нуж- ную процедуру для каждой команды. Конечно, при желании можно исполь- зовать таблицу переходов. Заголовок запроса содержит всю необходимую информацию для корректной обработки каждой команды и сообщает вызыва- ющей программе (в большинстве случаев таковой является MS-DOS) о сос- тоянии запроса после завершения соответствующей процедуры. Слово, хранящее состояние после возврата, разбито на несколько полей (см. рис.5). Оно содержит бит ошибки, указывающий на то, что в оставшейся части содержится специфический код ошибки, бит выполнения, сигнализи- рующий о том, что требуемая операция была завершена, и бит занятости, призванный в первую очередь сигнализировать о текущем состоянии уст- ройства. 15 0 нет ошибки 1 ошибка 14 зарезервирован 13 зарезервирован 12 зарезервирован 11 зарезервирован 10 зарезервирован 9 1 устройство занято 8 1 операция завершена (если появилось ошибка, данный бит игнорируется) 0-7 специфический код ошибки: 0H нарушение защиты записи 1H запрошен неизвестный элемент 2H дисковод не готов 3H команда не распознана 4H ошибка в данных при чтении 5H структура запроса на ввод/вывод некорректна 6H ошибка при поиске 7H возникла нераспознаваемая среда 8H запрашиваемый сектор не найден 9H сбой принтера (сбой подачи бумаги) AH сбой устройства при записи BH сбой устройства при чтении CH общий сбой устройства DH зарезерверовано EH зарезерверовано FH изменение среды недопустимо Рис.5. Слово в драйвере, хранящее состояние после возврата Вообще говоря, недостаточно знать лишь состояние операции после возврата. В тех случаях, когда это имеет смысл, например, при опера- циях чтения или записи, необходиммо также вернуть число обработанных символов. Несмотря на то, что программа прерываний драйвера вызывается в действительности не так, как это принято для программ прерываний, - она вызывается с помощью FAR CALL и таким образом предполагает FAR RET, - она должна действовать, как настоящая программа обработки прерывания, т.е. сохранить все регистры и флаги и восстановить их после завершения. Команды Команды драйвера, перечисленные на рис.4, встроены в MDM_DRV как вызываемые подпрограммы. Управляющая таблица, расположенная в ассемб- лерном листинге непосредственно вслед за заголовком запроса, указыва- ет на необходимые драйверу подпрограммы. Доступ к таблице осуществля- ет программа прерываний, которая для определения подлежащей вызову подпрограммы использует в качестве индекса входа в эту таблицу коман- дный байт из заголовка запроса. После написания программ стратегий и прерываний необходимо реали- зовать все команды. Если заглянуть в листинг, видно, что здесь ника- ких хитростей нет. Рассматриваемые ниже команды следуют в листинге в том же порядке. Такая структура может послужить базисной моделью для практически любого драйвера независимо от его сложности. Дальнейшую информацию о написании драйверов см. в "Техническом руководстве по MS-DOS" либо в книге: Ray Duncan "Advanced MS-DOS" (Microsoft Press, 1986). Не все допустимые команды используются в MDM_DRV, однако для пол- ноты в программном коде на них имеются ссылки. Неиспользуемые коман- ды - это просто заглушки, которые, ничего не выполняя, осуществляют возврат. Все используемые команды подчинены следующей общей логике: они вы- зываются из программы прерываний путем NEAR CALL, осуществляют свои действия и возвращают состояние в регистре AX. Это состояние склады- вается с битом выполнения и засылается в исходный заголовок запроса (RH) по адресу RH+3. Если для данной операции требуется счетчик числа символов, то он модифицируется внутри команды. Если некоторая подпро- грамма в данном драйвере не используется, она возвращает состояние, сигнализизующее о завершении соответствующей операции. ИНИЦИАЛИЗИРОВАТЬ ДРАЙВЕР (команда 00H). Эта команда вызывается лишь один раз при загрузке драйвера. Она должна инициализировать уст- ройство так, как это необходимо для дальнейшей работы, и может, если это необходимо, послать приветственное сообщение об успешной загруз- ке. На этапе разработки драйвера для удобства отладки можно также попросить ее выдать адрес своего кодового сегмента. Вызов функций MS-DOS допустим лишь через INT 21H с регистром AH, не превосходящим 0CH, либо с AH равным 30H для получения номера вер- сии MS-DOS. Так как некоторые подпрограммы зависят от версии MS-DOS, может потребоваться проверка номера версии и при некорректной версии установка некоторых флагов либо осуществление определенных действий. Существуют специфические операции для блоковых устройств, не расс- матриваемые в данной статье; они касаются установки определенных ста- тических таблиц, в которых MS-DOS получает информацию об атрибутах блоковых устройств, обслуживаемых драйвером. При вызове команды "ИНИЦИАЛИЗИРОВАТЬ ДРАЙВЕР" 4-байтовый указа- тель, размещенный по адресу RH+18 (RH - заголовок запроса), указывает на тот текст, который следовал за знаком равенства в строке файла CONFIG.SYS, требовавшей загрузки драйвера; это позволяет обрабатывать опции. В драйвере MDM_DRV данная строка высвечивается на экране. Самый старший адрес памяти, требуемой драйверу, возвращается в двойном слове, начиная от RH+14, что дает MS-DOS'у информацию о том, где можно разместить следующий драйвер. Так как подпрограмма инициа- лизации вызывается лишь один раз, для экономного использования памяти в заголовке запроса можно вернуть ее собственный адрес. Следует пом- нить, что начиная с этого адреса, память по всей вероятности будет затерта. Драйвер MDM_DRV использует две функции MS-DOS, доступные лишь на- чиная с версии 3.0, а именно, процедуры открытия и закрытия устройст- ва. Более ранние версии MS-DOS не имели таких функций, а так как они необходимы для работы драйвера, в случае некорректной версии прихо- дится прекратить работу. Драйвер MDM_DRV использует подпрограмму инициализации для проверки того, что загружен MS-DOS 3.x, а в противном случае выводит сообщение об ошибке и прекращает работу. Он инициализирует порт модема к 1200 бод с отсутствием контроля четности, восемью битами данных и би- том остановки. В случае некорректной версии MS-DOS можно было бы и не прекращать работу. Немного подправив программу, можно добиться того, чтобы пер- вый вызов подпрограмм чтения или записи мог бы вызывать подпрограмму открытия устройства, а последний вызов либо надлежащая пауза после возврата из чтения/записи могли бы запускать подпрограмму закрытия устройства. Я предпочел не писать этот драйвер для устаревших версий MS-DOS 2.x; всем нужно когда-нибудь обновляться. Наконец выдается приветственное сообщение, и подпрограмма инициализации заканчивается. ОТКРЫТЬ УСТРОЙСТВО (команда 0DH). Под управлением MS-DOS назначе- ние этой команды не определено, но тем не менее MS-DOS 3.x обязатель- но вызовет эту команду раньше любой другой подпрограммы, если включен соответствующий бит в атрибутном слове (бит 11). Драйвер MDM_DRV ис- пользует эту команду с несколькими целями. При первом вызове данная команда повторно инициализирует коммуни- кационный порт к принятым по умолчанию параметрам: 1200,n,8,1 (в дей- ствительности эти параметры могут быть в дальнейшем изменены с по- мощью соответствующих вызовов функции IOCTL). Затем проверяется сос- тояние линии связи с тем, чтобы определить, является ли данный порт активным и связан ли он уже с другим компьютером. Если уже достигнуто состояние готовности, приводимая ниже серия вызовов и/или ответов опускается. Если же связи нет, на экран выдается сообщение с просьбой ввести телефонный номер для вызова. В зависимости от того, был ли введен телефонный номер или же что-нибудь иное, модем либо осуществ- ляет вызов, либо посылает строку ответа, после чего переходит в режим ожидания. Немедленный выход происходит по Ctrl-C. После проверки линии связи вызывается несложная коммуникационная программа, которая посылает символы, вводимые с клавиатуры, как на экран, так и в коммуникационный порт, а символы, обнаруженные в ком- муникационном порту, посылаются непосредственно на экран. Если на од- ном из концов линии связи вводится сигнал выхода (escape), обе сторо- ны покидают этот цикл и продолжают работу. После этого сигнала, либо если линия связи с самого начала уже была готова, принимается квант времени таймера на другом конце линии, т.к. настройка таймера критич- на для протокола X-модема, используемого последующими вызовами чтения и записи. Наконец управление возвращается вызвавшей программе. ЧТЕНИЕ (команда 04H). Это наиболее сложная подпрограмма в драйвере MDM_DRV ввиду ее двойственного характера. Программа, вызвавшая драйвер (в больщинстве случаев это MS-DOS), просит прочитать определенное число символов. Фактический счетчик символов расположен по адресу RH+18. Эти символы возвращаются вызвав- шей программе посредством засылки по адресу, хранящемуся в 4-байтовом указателе RH+14. Однако X-модем передает данные фиксированными блоками из 128 бай- тов данных с некоторой дополнительной протокольной информацией. Для того, чтобы понять, был ли блок правильно получен, он предполагает через определенное время после посылки блока застать выданный получа- телем символ ACK (правильно) либо NAK (неправильно). Рассмотрим ситу- ацию, при которой каждый раз делается запрос на один символ. Для пер- вого вызова нет ожидающих его данных, поэтому он должен ждать прихода целого блока. Если этот блок не проходит проверку в том смысле, что номер блока, его дополнение и байт контрольной суммы не такие, как ожидалось, либо передача блока не уложилась по времени, то немедленно посылается символ NAK, и цикл получения блока начинается с начала. Если блок проходит проверку, символ ACK не может быть послан до тех пор, пока устройство не будет готово для приема следующего полного блока. Однако, если была установлена программа TSRCOMM, символ ACK можно посылать. (TSRCOMM допускает асинхронные прерывания, поэтому принимаемые символы будут обработаны корректно). Если же обработчик асинхронного прерывания не установлен, то немедленная посылка ACK приведет к потере символов. Таким образом ACK нельзя посылать сразу; этот сигнал не посылается до тех пор, пока последний символ в блоке не будет должным образом обработан и сохранен в надлежащей позиции приемного буфера вызывающей программы. Это означает, что дальнейшие запросы на следующий байт должны быть обработаны с учетом того, что этот байт находится в полученном ранее блоке. При этом возникают проблемы, если обработка 128 байтов запросами драйвера на ввод/вывод занимает больше времени, чем это допустимо для задержки между посылкой последнего символа и приходом ACK либо NAK. А что происходит в случае, когда запрашивается сразу большое число символов? Как и ранее, подпрограмма чтения должна ждать прихода цело- го блока, а затем до возврата в вызвавшую программу обработать столь- ко символов, сколько допускает счетчик символов из заголовка запроса. Если число запрошенных байтов превышает число символов, имеющихся в буфере, подпрограмма остается в цикле, отвечающем за получение блока и обработку символов, до тех пор, пока не будет обработано нужное число символов. Подпрограмма отслеживает число обработанных символов, увеличивая счетчик при пересылке каждого символа. После завершения запроса на чтение управление возвращается вызвав- шей программе. Если слишком много раз возникал отказ (NAK) и передачу пришлось отменить, управление возвращается с установленным состоянием ошибки. Управление возвращается без состояния ошибки, но с укорочен- ным счетчиком, если получен сигнал EOT (конец передачи), указывающий на то, что передача завершена, либо если запрос на чтение пришел пос- ле получения этого сигнала. СТАТУС ВВОДА (команда 06H). Эта команда не имеет большого значения для данного драйвера, но тем не менее включена в него. Если есть дос- тупный символ (некоторый блок был получен, но не полностью обрабо- тан), то бит занятости в слове, хранящем состояние после возврата, (адрес RH+3) устанавливается в 0. Если только чтение не происходит порциями, кратными 128 байтов, эта подпрограмма будет после первого запроса на чтение чаще всего возвращать состояние готовности. ЧИТАТЬ СИМВОЛ БЕЗ УДАЛЕНИЯ (команда 05H). Эта команда возвращает следующий символ, подлежащий чтению. Символ возвращается по адресу RH+13, но не удаляется, т.к. это повлияло бы на текущее содержимое входного буфера. Непонятно, что делать, если в момент вызова этой ко- манды входной буфер пуст. Поэтому в слово, хранящее состояние, (адрес RH+3) заносится код ошибки BH (ошибка чтения), счетчику символов (ад- рес RH+18) присваивается 1, а в качестве символа возвращается вопро- сительный знак. СБРОС ВВОДА (команда 07H). Вызов этой команды приводит к очистке входного буфера. Последующий запрос на чтение при очищенном входном буфере не уложится в контроль времени, что приведет к посылке сигнала NAK. Благодаря этому сигналу последний посланный блок посылается вновь, после чего работа продолжается нормально. ПИСАТЬ (команда 08H). Подсистема записи очень проста посравнению со сложностями подсистемы записи. При запросе на запись число байтов, подлежащих передаче, хранится по адресу RH+18. Указатель на начало передаваемых данных хранится в двойном слове по адресу RH+14. Символы один за другим переносятся в буфер вывода. Программа посылки блока вызывается в тот момент, когда в буфере находится 128 символов. Блок посылается до тех пор, пока либо он не будет успешно принят, о чем свидетельствует посланный с другого конца сигнал ACK, либо пока не прийдет достаточное число сигналов сбоя (NAK) или нехватки времени, что свидетельствует об ошибке передачи. Когда уже не осталось символов, предназначенных для передачи, по адресу RH+18 заносится число обработанных символов, которое может от- личаться от первоначально затребованного, после чего управление возв- ращается вызывающей программе. ПИСАТЬ С ПОВЕРКОЙ (команда 09H). Для символьных устройств, а осо- бенно для тех, которые используют Xmodem, эта команда неосмысленна. Вообще говоря, она должна была бы вызвать подпрограмму записи, затем попытаться прочесть только что записанные данные, сравнить их с пер- воначальными и вернуть ошибку в случае расхожения. В случае драйвера MDM_DRV вполне достаточно просто вызвать подпрограмму записи. СТАТУС ВЫВОДА (команда 0AH). Эта команда должна взвести бит заня- тости, если устройство вывода занято. Это невозможно, так как драйвер MDM_DRV не использует буферизацию и не управляется прерываниями; под- программа записи никогда не оставляет себе работу на следующий вызов. СБРОСИТЬ БУФЕРА ВЫВОДА (команда 0BH). В драйвере MDM_DRV данная команда не делает ничего, т.к. очистка текущего буфера вывода может негативно повлиять на происходящую передачу. ЗАКРЫТЬ УСТРОЙСТВО (команда 0EH). Данная команда, так же, как и команда открытия устройства, вызывается лишь в том случае, если драй- вер работает под управлением MS-DOS 3.x, а в атрибутном слове из за- головка взведен соответствующий бит. Эта подпрограмма определяет, ос- тались ли недописанные байты, и при необходимости заставляет програм- му посылки блока переслать неполный блок. Скорее всего, недописанные байты должны встречаться, т.к. подпрограмма записи всегда посылает блоки по 128 байтов. После того, как по последнему боку принят сигнал ACK, посылается сигнал EOT с тем, чтобы на другом конце линии стало известно о конце передачи. Эта подпрограмма вызывается также при срыве передачи и в случае, если во время передачи с консоли вводится Ctrl-C. Таким образом дол- жен проверяться флаг срыва: если он взведен, то посылается символ CAN, который сообщает другому концу линии, что передача была отмене- на. Конечно, в этом случае посылка недописанных блоков не осуществля- ется. Если это был телефонный вызов, то с 1-секундным интервалом по отношению к сигналу "внимание", состоящему из трех плюсов (+++), по- сылается строка отбоя, совместимая с предлагаемой фирмой Hayes. В конце на порт модема посылается символ DTR, перехваченные векто- ра прерываний устанавливаются в первоначальное положение и управление возвращается вызвавшей программе. ЧИТАТЬ УПРАВЛЯЮЩУЮ ИНФОРМАЦИЮ ВВОДА/ВЫВОДА (команда 03H). Данная команда так же, как и команда записи IOCTL, позволяет передавать выз- вавшей программе ту или иную информацию в зависимости от состояния устройства. Она не полностью определена, поэтому ее можно использо- вать для различных целей. В драйвере MDM_DRV данная команда передает копию структуры, содер- жащей информацию, представленную на рис.6. Эта информация, в частнос- ти, позволяет прикладной программе задать, какой порт модема должен использовать драйвер MDM_DRV. Данная команда предоставляет прикладной программе возможность выяснения текущих значений перечисленных пара- метров. Заметим, что счетчик по адресу RH+18 указывает на то, сколько бай- тов можно вернуть при таком вызове. Так как для такого вызова обычно используется буфер ограниченной длины, возврат большего числа байтов по указателю, хранящемуся в двойном слове с адресом RH+14, может при- вести к записи в область памяти, которая не была корректно распреде- лена. Часто требуется запись напрямую в буфер прикладной программы, причем вместо всей структуры запрашивается лишь первое слово, задаю- щее текущий коммуникационный порт. com_port dw 0 ; текущий коммуникационный порт ; COM1 = 0, COM2 = 1 и т.д. init_data db 10000011b ; байт инициализации (см. Техничес- ; кое руководство по BIOS'у) block_num dw 0 ; текущий пересылаемый блок abort_xfer dw 0 ; нужно сорвать текущую передачу ; (TRUE означает "ДА") nak_cnt dw 0 ; сколько было послано либо полу- ; чено символов NAK cancel_flag dw 0 ; было ли нажато Ctrl-C? inrequest dw 0 ; число байтов, не обработанных на ; текущий момент in_block dw 0 ; был ли получен блок? was_dialed dw 0 ; модем был вызван по телефону Рис.6. Структура управляющей информации ввода/вывода для драйвера MDM_DRV ЗАПИСАТЬ УПРАВЛЯЮЩУЮ ИНФОРМАЦИЮ ВВОДА/ВЫВОДА (команда 0CH). Так же, как и предшествующая, данная команда осуществляет обмен информа- цией между прикладной прораммой и драйвером. Она устанавливает в драйвере его внутренние параметры. Обычно сначала вызывается команда чтения IOCTL в локальный буфер, затем модифицируются необходимые байты и вызывается команда записи IOCTL с адресом локального буфера и числом символов, которые необхо- димо изменить. Команды ПРОВЕРКА СРЕДЫ (команда 01H), ПОСТРОИТЬ БЛОК ПАРАМЕТРОВ BIOS'а (команда 02H), ПЕРЕМЕЩАЕМАЯ СРЕДА (команда 0FH) и ВЫВОД ДО СИГНАЛА ЗАНЯТОСТИ (команда 10H) не используются в драйвере MDM_DRV, однако для полноты они вставлены в драйвер. Обычно в таблице смеще- ний используется адрес общей "пустой" подпрограммы. Специфика разработки драйвера При проектировании и написании драйверов возникают некоторые спе- цифические проблемы. Проектирование драйверов во многом сходно с раз- работкой приложений для системы Windows: главные процедуры вызываются внешним заданием, возможно, несколько раз, система не продолжит рабо- ту до тех пор, пока ей не будет возвращено управление, а интерфейс задан довольно жестко. Первая проблема заключается в том, что вызов процедуры открытия устройства (dev_open) вовсе не гарантирует, что когда-нибудь это уст- ройство будет закрыто (вызовом dev_close). Ситуация осложняется еще и тем, что даже простая команда MS-DOS COPY производит серию вызовов dev_open/dev_close прежде, чем устройство действительно будет открыто для соответствующей операции ввода/вывода. (Последовательность дейст- вий может быть такой: dev_open/dev_close/dev_open/[чтение|запись]/ dev_close). Другая проблема, возникающая при разработке драйверов, связана с подсчетом повторных попыток. Где-то в дебрях MS-DOS'а существует чис- ло, которое, как предполагается, способно указывать, сколько должен драйвер сделать попыток прежде, чем он откажется от операции и MS-DOS выдаст сакраментальное сообщение "Abort, Retry, Ignore?" (созданное прерыванием критической ошибки INT 24H). Оказывается, что нет доку- ментированного способа заставить драйвер делать лишь одну попытку от- крытия устройства перед генерацией критической ошибки. Таким образом не удается изящно выходить из "мертвой" линии. Так как вызовы функций MS-DOS за пределами инициализационной части драйвера не допустимы, для посимвольного ввода/вывода приходится ис- пользовать BIOS (можно также напрямую читать с клавиатуры и выводить на экран, но это слишком утомительное занятие). Это означает, что Ctrl-C или Ctrl-Break считается просто очередным символом без всяких специфических атрибутов, тем самым ввод такого символа не приведет к желаемому результату, т.е. из подпрограммы открытия устройства не удастся выйти обратно в MS-DOS и даже прерывание критической ошибки не будет вызвано. Это действительно очень сложная проблема, т.к. драйверы MS-DOS по своей природе не должны быть интерактивными. Разобраться в этой ситу- ации пока не удалось. Тем самым оказывается, что после входа в драй- вер по сути дела нет пути назад. Но это всего лишь одна из проблем, возникающих тогда, когда MS-DOS пытаются заставить совершать действия, для которых он не предназна- чен. Вот проблема, касающаяся внешних программ: заключается она в том, что MS-DOS всегда открывает драйверы символьных устройств в под- готовленном режиме. Если передается файл, в котором случайно встре- тится символ Ctrl-Z, этот символ будет воспринят как конец файла и передаваемый файл будет закрыт преждевременно. В связи с этим необхо- димо держать драйвер MDM_DRV в неподготовленном режиме. Для этого нужно после каждой команды открытия устройства специально вызывать функцию IOCTL с целью перевода драйвера в неподготовленный режим. Установка неподготовленного режима распространяется только на те- кущий дескриптор файла (file handle); последующие команды открытия устройства по-прежнему будут давать подготовленный режим, если только не запустить небольшую резидентную программу SET_MDM. Эта программа перехватывает прерывание по вызову MS-DOS (INT 21H), и если приходит запрос на открытие дескриптора файла (функция"open-with-file-handle") и при этом имя файла, на которое указывает пара регистров DS:DX, со- держит подстроку 'MDM' (ограниченную нулем), то после успешного отк- рытия дескриптор файла переводится в неподготовленный режим. Такие действия не вызывают затруднений у вызывающей программы и вполне кор- ректны, хотя и являются очень грубыми. У каждого программиста должна быть некоторая эталонная плохо ском- понованная программа (kludge) для сравнения; в качестве нового стан- дарта можно взять SET_MDM. Но в конце концов, если программа работа- ет, ею можно пользоваться, как бы плохо она ни была написана. Возможные улучшения Так как MDM_DRV - это главным образом пример функционирования драйвера, его программный код не причесан и в нем есть места, требую- щие улучшения. Так, например, подпрограмма get_num не анализирует, является ли введенный с клавиатуры символ корректным символом в коде ASCII; эта подпрограмма просто ожидает, пока не будет нажат возврата каретки, либо пока не будет введено двадцать каких-нибудь символов. Неплохо было бы, как это обсуждалось выше, иметь возможность изящно выходить из этой подпрограммы (или из какой-нибудь другой подпрограммы драйве- ра). Далее, драйвер считает, что к коммуникационному порту подключен Hayes-совместимый модем, и это предположение существенно используется подпрограммами открытия (dev_open) и закрытия (dev_close) устройства. Если драйвер MDM_DRV будет использован для других модемов или же для других устройств, посаженных на последовательный порт, его придется немного дополнить, возможно, вызовами функции IOCTL. В данном драйвере используется простой протокол X-модема с конт- рольной суммой. Хорошим дополнением к нему было бы использование улучшенной версии этого протокола, т.н. протокола X-модема с цикли- ческим контролем по избыточности (CRC). Для реализации такого улучше- ния требуется совсем немного дополнительного программного кода. Сигналы аварийного снятия (CAN) и конца передачи (EOT) обрабатыва- ются не так, как это указано в спецификациях ввиду того, что реакция на эти сигналы наступает сразу после их прихода. Согласно специфика- циям, первый символ CAN или EOT должен вызывать посылку сигнала NAK с целью проверки того, что пришедший символ не является шумом, и лишь после повторного приема осуществляются надлежащие действия. Отладка Разработка любой программы, решающей достаточно сложную задачу, требует терпения и внимательности. Отлаживать и улучшать драйверы, не имея соответствующих средств отладки, довольно трудно. К сожалению, готовых средств отладки драйверов нет; их приходится создавать самому разработчику. Прежде, чем начинать отлаживать драйвер, необходимо создать полную резервную копию и убедиться в наличии средств загрузки системы без автоматической установки данного драйвера. Можно, например, держать CONFIG.SYS на системном гибком диске и при тестировании драйвера заг- ружаться с него. Ниже перечислены некоторые приемы и идеи, использо- ванные при создании драйвера MDM_DRV. Постройте драйвер как обычный COM-файл и создайте программу, кото- рая бы имитировала вызовы всех его команд и позволяла бы исследовать регистры и заголовок запроса каким-нибудь отладчиком. Такой подход несколько скучен и не позволяет тестировать драйвер в "реальных" си- еуациях, однако это хорошее начало, позволяющее в дальнейшем перейти к следующему методу. Создайте скелет программы без процедуры инициализации. Пусть прог- рамма прерываний состоит лишь из засылки в пару регистров адреса за- головка запроса, который был сохранен программой стратегий. Затем вы- зовите зарезервированное Вами прерывание. Вот пример простой програм- мы прерываний: interrupt proc far push es push bx mov es, cs:[old_segment] mov bx, cs:[old_offset] int 60h pop bx pop es ret interrupt endp Конечно, подразумевается, что есть некоторая процедура обработки прерывания, котора позже будет посажена на выбранное прерывание. Предлагается использовать прерывание 60H, которое определено как "пользовательское". Может пригодиться такой кусок кода: int60 proc far call real_int_routine iret int60 endp Это позволяет строить real_int_routine как FAR-процедуру с FAR-возв- ратом. Смысл всех этих ухищрений заключается в том, чтобы сделать рези- дентную программу, которая бы просто перехватывала указанное прерыва- ние, а затем вела бы себя так же, как драйвер. После отладки этой программы как обычной резидентной (что позволяет делать большинство отладчиков, включая CodeView) готовый код можно перебросить в настоя- щий драйвер, и этим сделано все, кроме подпрограммы инициализации. Аппаратно поддерживаемый отладчик Periscope легко позволяет связы- вать смещения переменных, объявленных "public", с любым кодовым сег- ментом. Если подпрограмма инициализации при загрузке сообщает адрес своего кодового сегмента, нетрудно создать "пустую" программу, кото- рая загружается в отладчик с надлежащей символьной таблицей (symbol map), а затем изменить сегмент символов на первоначальный кодовый сегмент драйвера. Начиная с этого момента, все происходит так, как-будто Вы отлаживаете "обычную" программу. Помните, что при начальном вызове драйвера ему доступна лишь очень маленькая область стекового пространства. В действительности нет воз- можности определить, какой объем памяти доступен драйверу. Лучше все- го сохранить указатель стека и его сегмент, затем воспользоваться своим собственным локальным стеком, а в конце воостановить первона- чальный стек. Драйвер MDM_DRV делает это в программе прерываний. Помните, что все регистры (включая регистр флагов) должны быть восстановлены перед выходом из драйвера. В критических местах при не- обходимости драйвер должен работать с отключенными прерываниями. Необходимо твердо убедиться в том, что счетчик символов показывает истинное число переданных байтов. Неверное значение счетчика приводит к очень странным результатам. При малейших сомнениях убедитесь в том, что драйвер не обращается к памяти, лежащей за пределами начального адреса, переданного подп- рограммой инициализации. Приложение. Выборочные фрагменты программы MDM_DRV.ASM ;; MDM_DRV.ASM Драйвер для X-модема, ;; написанный Россом М. Гринбергом ;; ниже следуют выборочные фрагменты программы . . . code segment public 'CODE' ;; Сам драйвер - это большая программа с именем 'driver' driver proc far assume cs:code, ds:code, es:code ; сегментные регистры ; должны указывать на ; сегмент code org 0 ; драйвер должен начи- ; наться со смещения 0 ;; ЗАГОЛОВОК ДРАЙВЕРА header1 dd -1 ; требуется начальное значение -1, ; это поле заполняет DOS dw 0e800h ; символьное устройство, ; поддерживается функция IOCTL, ; режим вывода до сигнала занятости и ; вызовы, связанные с открытием/закры- ; тием устройства и перемещаемой средой dw strat ; указатель на программу стратегий dw ints ; указатель на программу прерываний db 'MDM ' ; имя устройства, выравненное влево и ; и дополненное пробелами до 8 символов ;; ЗАГОЛОВОК ЗАПРОСА request struc rlength db 0 ; 0 - длина данных в заголовке unit db 0 ; 1 - номер элемента (не используется) command db 0 ; 2 - фактическая команда status dw 0 ; 3 - состояние после возврата reserve db 8 dup (0) ; 5 - зарезервировано для DOS media db 0 ; 13 - дескриптор среды (не исп.) address dd 0 ; 14 - указатель для ввода/вывода count dw 0 ; 18 - число символов для ввода/вывода sector dw 0 ; 20 - начальный сектор (не исп.) request ends ;; Таблица смещений dispatch: dw init ; 0x00 - инициализировать драйвер dw media_chk ; 0x01 - проверка среды (не исп.) dw bid_bpb ; 0x02 - построить BPB (не исп.) dw rd_ioctl ; 0x03 - читать IOCTL dw read ; 0x04 - читать 'count' символов dw nd_read ; 0x05 - читать символ без удаления dw inp_stat ; 0x06 - статус ввода dw inp_flush ; 0x07 - cброс ввода dw write ; 0x08 - передать 'count' символов dw write_vfy ; 0x09 - передать с проверкой dw out_stat ; 0x0A - статус вывода dw out_flush ; 0x0B - сбросить буфера вывода dw wrt_ioctl ; 0x0C - записать IOCTL dw dev_open ; 0x0D - открыть устройство (DOS 3.x) dw dev_close ; 0x0E - закрыть устройство (3.x) dw rem_media ; 0x0F - обработка перемещаемой среды ; (DOS 3.x) dw out_busy ; 0x10 - вывод до сигнала занятости ; (DOS 3.x). ;; ПРОГРАММА СТРАТЕГИЙ strat proc far mov word ptr cs:[rh_ptr], bx mov word ptr cs:[rh_ptr+2], es ret strat endp . . . ;; ПРОГРАММА ПРЕРЫВАНИЙ ints proc far cli mov cs:[old_stack], sp mov cs:[old_stack+2], ss mov sp, cs mov ss, sp mov sp, offset cs:new_stack_end sti PUSHALL push cs pop ds les di, cs:[rh_ptr] mov bl, es:[di.command] xor bh, bh cmp bx, MAX_CMD jle ints1 mov ax, ERROR+UNK_COMMAND jmp ints2 ints1: shl bx, 1 call word ptr dispatch[bx] les di, cs:[rh_ptr] ints2: or ax, DONE ; установить в структуре mov es:[di.status], ax ; флаг выполнения POPALL cli mov ss, cs:[old_stack+2] mov sp, cs:[old_stack] sti ret ints endp ;; ПРОГРАММА ЧТЕНИЯ read proc near DO_PRINT msg_read mov ax, es:[di.count] mov cs:[inrequest], ax ; сохранить запрашиваемое ; число символов xor ax, ax ; число переданных ; символов = 0 mov es:[di.count], ax ; обнулить счетчик символов ; в заголовке запроса lds bx, es:[di.address] ; ds:bx указывает на данные top_read: cmp cs:[in_block], TRUE ; вошли в блок? jnz lp_it jmp read_loop ; да lp_it: mov cs:[_incnt], FALSE mov si, offset cs:[_soh] ; указатель на начало блока mov cs:[timer], TEN_SECONDS ; установка таймера mov cs:[nak_cnt], FALSE ; установка отсутствия NAK cmp cs:[block_num], 1 ; в первый раз? jnz rd_blk ; нет call send_nak ; послать первый NAK STAT LEFT_BRACKET ; выдать '[' rd_blk: call get_char ; есть символы? jc rd_blk2 ; нет cmp cs:[_incnt], FAULSE ; первый символ jnz nxt_char ; нет cmp al, SOH ; первый символ - заголовок ; блока (SOH)? jz nxt_char ; да, сохранить его cmp al, ABORT ; прекратить передачу? jz abort_it ; да, прекратить cmp al, EOT ; конец? jnz rd_blk ; нет, игнорировать символ call send_ack ; обработать EOF STAT RIGHT_BRACKET ; выдать ']' STAT CR ; и пустую строку STAT LF xor ax, ax ret nxt_char: mov cs:[si], al ; сохранить inc cs:[_incnt] ; подправить счетчик inc si ; и указатель rd_blk2: cmp cs:[_incnt], FULLBLKSIZE; поступило достаточно jge chkblk ; символов для проверки? call eat_char ; для Ctrl-C cmp cs:[timer], FALSE ; прошло положенное время? jnz rd_blk ; нет, пробуйте снова STAT DOT ; выдать точку jmp bad_blk2 bad_blk: STAT QUESTION ; выдать знак вопроса bad_blk2: call send_nak ; да, послать NAK cmp word ptr cs:[abort], TRUE ; прервать? jnz rd_blk3 ; нет, заново установить ; таймер и пробовать снова abort_it: call send_abort ; сообщить о прекращении call send_abort ; дважды STAT EXCLAIM ; заключительное восклицание mov ax, 800ch ; отметить ошибку ret rd_blk3: mov cs:[timer], TEN_SECONDS ; установить таймер заново jmp rd_blk ; и пробовать снова ret chkblk: mov ax, cs:[block_num] push ax dec al ; временно cmp al, cs:[_blk1] ; продублировать блок? jnz real_blk ; нет STAT DUP_BLK ; да xor ax, ax mov cs:[_incnt], ax jmp ack_blk real_blk: pop ax ; вернуться к корректному блоку cmp cs:[_blk1], al ; счетчик в порядке? jnz bad_blk ; нет not al cmp cs:[_blk2], al ; а дополнение блока? jnz bad_blk ; нет mov cx, BLKSIZE mov si, offset _buf call do_chksum cmp cs:[_chksum], al jnz bad_blk ; неверная контрольная сумма mov cs:[in_block], TRUE ; похоже, все нормально! mov cs:[_inptr], offset cs:_buf ; установить указатель sub cs:[_incnt], FULLBLKSIZE-BLKSIZE read_loop: mov si, cs:[_inptr] ; установить указатель заново rd_loop: mov al, cs:[si] ; прочесть символ mov ds:[bx], al ; и сохранить его inc bx ; поднять указатели inc si inc word ptr es:[di.count] ; увеличить счетчик dec cs:[_incnt] ; уменьшить число оставшихся jnz stf_nxt_char ; символов inc cs:[block_num] STAT STAR ; выдать '*' ack_blk: call send_ack ; выдать ACK mov cs:[in_block], FALSE ; кончились символы jmp top_read stf_nxt_char: dec cs:[inrequest] ; продолжать? jnz rd_loop xor ax, ax ; нет больше запросов ret ; нет ошибок read endp ;; ПРОГРАММА ЗАПИСИ write proc near PUSHALL_AX DO_PRINT msg_write mov cx, es:[di.count] ; сколько байтов? xor dx, dx ; обнулить счетчик lds bx, es:[di.address] ; ds:bx указывает на данные mov si, cs:[_outptr] ; куда переносить символы wr_lp: mov al, ds:[bx] ; взять символ mov cs:[si], al ; и сохранить в буфере inc bx ; сдвинуть указатель inc cs:[_outcnt] ; общий счетчик символов inc dx ; текущий счетчик символов cmp cs:[_outcnt], BLKSIZE ; послать блок? jnz wr_ok ; пока не надо call send_block ; послать блок cmp cs:[abort_xfer], FALSE ; прекратить передачу? jz blk_ok ; нет, блок послан нормально call send_abort ; да call send_abort ; дважды! mov es:[di.count], FALSE ; ничего не послано mov ax, ERROR+WRITE_ERROR ; отметить ошибку передачи ret blk_ok: mov cs:[_outptr], offset _buf;установить заново указатель mov si, offset _buf ; регистр mov cs:[_outcnt], FALSE ; и число символов wr_ok: inc si ; указать на след. символ loop wr_lp mov es:[di.count], dx ; сколько послано нормально mov cs:[_outptr], si ; сохранить xor ax, ax ; нет ошибок POPALL_AX ret write endp ;; ПРОГРАММА ОТКРЫТИЯ УСТРОЙСТВА dev_open proc near PUSHALL_AX DO_PRINT msg_dev_open mov cs:[cancel_flag], FALSE mov cs:[_inptr], offset cs:_soh mov cs:[_incnt], FALSE mov cs:[_outptr], offset cs:_buf mov cs:[_outcnt], FALSE mov cs:[block_num_outcnt], 1 call set_timer mov dx, cs:[com_port] ; инициализ. комм. порт mov al, cs:[init_data] mov ah, 0 int 14h eat_loop: mov dx, ds:[com_port] mov ah, 3 ; установить состояние int 14h test ah, 1 ; есть готовые данные? jz continue ; нет mov ah, 2 int 14h ; захватить символ и jmp eat_loop ; отправиться за следующим continue: test al, 080h ; линия готова jz off_line jmp on_line ; да off_line: mov dx, offset cs:offline ; выдать сообщение mov ah, 9 int 21h mov si, offset cs:numbuf mov cx, 19 ; максимальная длина call get_num cmp cs:[kb_len], 0 ; просто возврат? jnz dial_it mov dx, offset cs:await mov ah, 9 int 21h mov si, offset cs:answerstring call out_string mov cs:[timer], FOREVER jmp offline_lp ; ожидать линию dial_it: mov dx, offset cs:dialing mov ah, 9 int 21h mov si, offset cs:dialstring call outstring mov si, offset cs:numbuf call outstring mov si, offset cs:return call outstring mov cs:[timer], ONE_MINUTE mov cs:[was_dialed], TRUE ; отметка телефонного вызова offline_lp: mov ah, 3 ; онлайн? mov dx, cs:[com_port] int 14h test al, 080h ; линия готова? jnz made_con ; да call eat_char ; для Ctrl-C cmp cs:[timer], 0 ; прошло положенное время jnz offline_lp ; нет abort_call: mov cs:[was_dialed], FALSE mov dx, offset cs:no_con mov ah, 9 int 21h xor ax, ax mov es:[di.count], ax ; обнулить счетчик mov ax, ERROR+GEN_FAILURE ; отметить ошибку POPALL_AX ret made_con: mov dx, offset cs:con mov ah, 9 int 21h term_em_lp: call get_char jc get_term_char cmp al, ESCAPE jz term_exit ; выход! mov ah, 02h mov dl, al int 21h get_term_char: mov ah, 0bh int 21h ; есть символы? or al, al jz term_em_lp ; нет mov ah, 1 ; да, взять символ int 21h mov ah, 1 mov dx, ds:[com_port] int 14h cmp si, ESCAPE jz term_exit ; выход! mov ah, 02h mov dl, al int 21h jmp get_term_char term_exit: mov dx, offset cs:xfer ; сообщение о состоянии mov ah, 9 int 21h on_line: xor ax, ax POPALL_AX ret dev_open endp ;; ПРОГРАММА ЗАКРЫТИЯ УСТРОЙСТВА dev_close proc near DO_PRINT msg_dev_close cmp cs:[cansel_flag], TRUE ; была отмена? jnz no_cancel ; нет mov ah, 1 ; послать сигнал прекращения mov dx, cs:[com_port] mov al, ABORT int 14h jmp no_send ; и нормально выйти no_cancel: cmp cs:[_outcnt], FALSE ; остались символы? jz no_send ; нет call send_block ; да, послать оставшиеся STAT RIGHT_BRACKET STAT CR STAT LF mov cs:[_outptr], offset _buf;установить заново указатель mov si, offset _buf ; регистр mov cs:[_outcnt], FALSE ; и число символов inc cs:[block_num] no_send: cmp cs:[block_num], 1 ; хоть один блок послан? jz no_blk_sent ; нет mov ah, 1 ; послать конец передачи(EOT) mov dx, cs:[com_port] mov al, EOT int 14h no_blk_sent: cmp cs:[was_dialed], TRUE ; вызов мы осуществляли? jnz no_hang mov cs:[timer], ONE_SECOND ; подождать 1 секунду sil_lp1: call set_char ; для Ctrl-C cmp cs:[timer], FALSE ; время истекло? jnz sil_lp1 mov si, offset cs:pluses ; получить атрибуты модемов call out_string mov cs:[timer], ONE_SECOND ; подождать 1 секунду sil_lp2: call eat_char ; для Ctrl-C cmp cs:[timer], FALSE ; время истекло? jnz sil_lp2 mov si, offset cs:hangup ; отбой call out_string no_hang: mov cs:[was_dialed], FALSE mov cs:[cancel_flag], FALSE call reset_timer ret dev_close endp ;; ПРОГРАММА ЧТЕНИЯ БЕЗ УДАЛЕНИЯ <---+ ;; ПРОГРАММА ВЫДАЧИ СОСТОЯНИЯ ВВОДА | ;; ПРОГРАММА СБРОСА БУФЕРОВ ВВОДА | В полном листинге ;; ПРОГРАММА ВЫДАЧИ СОСТОЯНИЯ ВЫВОДА |-> программы эти ;; ПРОГРАММА СБРОСА БУФЕРОВ ВЫВОДА | процедуры располо- ;; ПРОГРАММА ЧТЕНИЯ IOCTL | жены здесь ;; ПРОГРАММА ЗАПИСИ IOCTL | ;; ПРОГРАММА ПОСЫЛКИ БЛОКА <---+ . . . ;; ПРОГРАММА ИНИЦИАЛИЗАЦИИ ДРАЙВЕРА init proc near DO_PRINT msg_init mov ah, 030h ; узнать версию DOS int 21h cmp ah, 3 ; версия 3.x? jge okay_dos ; да, не ниже mov dx, offset cs:wrong_dos ; выдать сообщение mov ah, 9 int 21h endless_loop: cli jmp enless_loop ; безобразно, но эффективно! okay_dos: mov dx, offset cs:greetings ; выдать сообщение mov ah, 9 int 21h push ds mov ds, es:[[di.count+2] ; получить сегмент строки ; файла CONFIG.SYS mov si, es:[[di.count] ; получить смещение строки ; файла CONFIG.SYS call output_chars pop ds mov dx, offset cs:end_greetings ; выдать сообщение mov ah, 9 int 21h mov word ptr es:[di.address], offset init mov word ptr es:[di.address+2], cs xor ax, ax ret init endp wrong_dos db '??Must run this driver under DOS 3.0 or higher' ; Драйвер должен работать под DOS версии не ниже 3.0 db CR, LF, ' System Halted! $' ; Останов системы! greetings db CR, LF, LF, 'MDM_DRV being installed...', CR, LF ; Загрузка драйвера MDM_DRV... db 'CONFIG.SYS Line is: $' ; Строка файла CONFIG.SYS: end_reetings db CR, LF, LF, LF, '$' output_chars proc near output_loop: mov dl, ds:[si] ; получить символ cmp dl, LF ; перевод строки? jnz outit ; нет ret outit: mov ah, 2 ; выдать символ int 21h inc si jmp output_loop output_chars endp driver endp code ends end