Из журнала ZX Format #5, Санкт-Петербург, 12.12.1996 Рубрика "IS-DOS - программистам" No 5. _________________________________________ Леонтьев А. Г. КРАТКИЙ КУРС ПРОГРАММИРОВАНИЯ В СРЕДЕ IS-DOS. (под общей редакцией Елисеева В. А.) От редактора. Уважаемые читатели журнала ZX FORMAT! Этой небольшой заметкой мы открываем но- вую и, как нам кажется, чрезвычайно важ- ную тему, а именно - написание программ в среде операционной системы IS-DOS. В свя- зи с катастрофическим отсутствием инфор- мации о внутренем устройстве системы, до сих пор развитие ее сдерживалось, так как многие из программистов, желавшие напи- сать что-либо в IS-DOS сталкивались с не- обходимостью сначала разобраться, а как, собствено она сама функционирует. Теперь, надеемся, ситуация изменится. Предлагае- мая вашему вниманию статья ведущего спе- циалиста, одного из "отцов" IS-DOS Алек- сея Леонтьева содержит в себе некоторые первоначальные сведения, которые необхо- димы каждому, кто решил попробовать свои силы в программировании под IS-DOS. Итак, передаем слово Алексею: ***************************************** А. Леонтьев ЧТО ДЕЛАТЬ? или КАК ПЕРЕСТАТЬ БЕСПОКОИТЬСЯ И НАЧАТЬ ПРОГРАММИРОВАТЬ В ИСДОСЕ 1. РАСПРЕДЕЛЕНИЕ ПАМЯТИ В iS-DOS: Операционная система iS-DOS изна- чально писалась с расчетом ее работоспо- собности в старом стандарте ZX с 48 KB памяти и хотя бы одним дисководом. Посе- му в iS-DOS Classic младшие 16 KB памя- ти компьютера практически не используют- ся (там находится ПЗУ бейсика). Выше, с адреса #4000, находится экран. Поскольку связь с флоппи-диском осу- ществляется через TR-DOS, то сохранены некоторые переменные бейсика и трдоса. Т.о. нам остается память от 23900(#5D5C) по 65535(#FFFF). Буфер принтера (#5B00.. ..#5BFF) ис- пользуется для командного файла exe- bat.com, необходимого для работы bat-фай- лов. Область с 23900(#5D5C) по 23999(#5DBF) резервируется под программы mon.com и menu.com, которые также как и exebat.com вызывают другие com-файлы. Область выше 24000 обычно использует- ся для загрузки системных или пользова- тельских com-файлов. "Командники" грузят- ся по адресу загрузки (12-ый, 13-ый байты описателя файла). В ту же точку передает- ся управление. 95% всех com-файлов имеет адрес загрузки 24000. Сверху область com-файлов ограничена кэшем блочных устройств. Кэш блочных устройств содержит прочи- танные с помощью некоторых рестартов бло- ки. Он необходим для работы почти всех рестартов уровня файловой службы (с #20 по #38 и с #3B по #3F), многих рестартов уровня командной строки, работающих с файлами и каталогами, и некоторых рестар- тов верхнего уровня: $shsubr(#8E), $shpanl(#90). При запуске com-файлов с помощью рес- тартов $run(#48) или $exebat(#44) система сама следит за неприкосновенностью кэша. Если com-файл не влезает под кэш, гене- рится ошибка 130. Адрес кэша и его размер можно узнать у системы с помощью систем- ного рестарта $g_cnfg(#10): LD C,#10 ;$g_cnfg RST #10 EXX ;в HL - адрес вектора ;конфигурации системы LD BC,5 ;смещение +5 ADD HL,BC LD E,(HL) ;адрес кэша INC HL ;помещаем в DE LD D,(HL) INC HL ;смещение +7 LD A,(HL) ;размер кэша в блоках Теперь будет не сложно проверить, хватает ли памяти, и, если нет, пересоз- дать кэш (если он нужен для работы прог- раммы) размером поменьше с помощью рес- тарта $creat(#00). Если кэш временно не нужен, этой па- мятью можно воспользоваться, но затем обязательно пересоздать кэш рестартом $creat(#00). Выше кэша находится область каналов. Адрес, размер и указатель области кана- лов достаются через уже знакомый рес- тарт $g_cnfg(#10) со смещениями +32, +34 и +36 соответственно. В каналах хранит- ся самая необходимая системная информа- ция, такая как каналы драйверов и ус- тройств, каналы панелей и резидентов, а посему начиная от адреса области кана- лов и до #FFFF лучше ничего не трогать! Над областью каналов находятся впе- ремешку (в порядке загрузки) драйверы и резиденты. Нижним будет резидент(драй- вер) загруженный последним. Адрес нижне- го резидента равен адресу конца области каналов (см. выше). Резиденты и драйверы доступны по именам c помощью рестартов $fndev(#51) и $run(#48) или по номерам каналов драйве- ров или устройств. Так любой драйвер или резидент Вы можете достать, зная номер его канала: ; в A помещаем номер канала LD C,#16 ;$stchn RST #10 RET C ;выход по ошибке - ;нет канала и т. п. EXX ;в HL - адрес ;тела канала INC HL INC HL INC HL INC HL LD E,(HL) INC HL LD D,(HL) ;в DE - адрес ;тела драйвера ... и т.д. (описание $stchn и каналов см. ниже) Резидентную задачу можно запустить, зная номер ее канала, рестартом $exeres(#53). Еще выше расположены 5 уровней сис- темы. Сидят они сверху вниз, т. е. са- мый нижний уровень "DOS" (работа с ус- тройствами, кэшем, каналами и прерыва- ниями) располагается на самом верху. Да- лее идут "DUD" (файловая служба), "COM" (интерпретатор командной строки), "WIN" (рестарты работы с окнами, печати строк и т. п.) и в самом низу "SHELL" (файло- вая оболочка, работа с панелями, меню). Два верхних уровня ("оконная техно- логия" и "работа с панелями") могут быть временно сняты программой set.com с клю- чом /L, но используется это крайне редко. Увидеть, чего где сколько, можно с помощью программы Q:UTIL\show.com. Она позволяет увидеть все основные адреса системы, адреса загрузки уровней, реези- дентов и драйверов, адрес и размер кэша и многое другое. ПРИМЕР РАСПРЕДЕЛЕНИЯ ПАМЯТИ iS-DOS Classic +-----+----------------------+ | 0| ПЗУ бейсика | |16384| экран | |24000| область com-файлов | |35903| кэш | |43443| каналы | |44143| резиденты и драйверы | |50304| уровень 4: shell | |54800| уровень 3: win | |58472| уровень 2: com | |60726| уровень 1: dud | |63296| уровень 0: dos | +-----+----------------------+ Вся дополнительная память ZX (128, 256 и т.д.) используется или для вир- туального электронного диска, к которо- му обращаются через специальный драйвер как к обычному устройству (лучше всего его назначить устройством Q:, тогда ра- бота в системе резко ускоряется), или под буферы (например копировщиками). В январе 1995 года создан еще один вариант iS-DOSa: "iS-DOS Chic" (исдос шик), использующий нижние 16 KB для раз- мещения неизменяемой части ядра системы. Это оказалось возможным на компьютерах KAY-256, Scorpion ZS-256, Spectrum-Profi и др. им подобным. Данные модификации Спектрума позво- ляют в нижней странице памяти размес- тить 0-ой банк памяти. Вместо него в вверхней странице открывается 8-ой банк. В нижнюю страницу помещаются как правило неизменяемые части системных программ (для возможности прошить эту страницу в ПЗУ), знакогенераторы t42 и t64 (2KB+1KB), благодаря чему транзит- ная область расширяется почти вдвое (до 30 KB вместо 15 при минимальном количес- тве резидентов и 29-блочном кэше). ПРИМЕР РАСПРЕДЕЛЕНИЯ ПАМЯТИ iS-DOS Chic +-----+---------------------------------+ |#0010|rst #10 | |#003B|подпрограмма драйвера эл. диска | |#0047|признак типа системы/компьютера | |#0067|таблица для драйвера эл. диска | |#0101|буфер драйвера электронного диска| |#0201|знакогенератор t64 (1KB) | |#0601|не используется | |#093A|ядро системы (неизменяемая часть)| |#3800|знакогенератор t42 (2KB) | |#4000|экран | |#5DC0|область com-файлов | |#A5A2|кэш (49 блоков) | |#D6F0|каналы | |#DAD8|резиденты и драйверы | |#F365|уровень 4: shell -+ | |#F78C|уровень 3: win |изменяемая | |#FA88|уровень 2: com | часть | |#FC0C|уровень 1: dud | | |#FDE6|уровень 0: dos -+ | +-----+---------------------------------+ 2. НЕСКОЛЬКО ОБЩИХ СЛОВ: В системе iS-DOS вызов системных под- программ осуществляется с помощью команды RST 16 (или RST #10, если кто любит шестнадцатиричные). При этом в iS-DOS Classic должен быть открыт 4-ый канал бейсика (после загрузки так оно и есть, если Вы только не вызывали сами процедуру #1601 или не запускали в исдосе скажем MONS-4). Код функции подается в регистре C. Его старшие 3 разряда определяют уро- вень системы. Обычно их (уровней) 5 (с 0 по 4). Дополнительный временный уровень с номером 7 ставит текстовый редактор. Отладчик устанавливает уровень номер 5. Дополнительные уровни устанавливают так- же Базы Данных. Такой способ связи с системой или с собственным ядром пакета из оверлеев че- резвычайно удобен, так как ядро и овер- леи полностью развязываются адресно и после перетрансляции ядра не нужно пере- линковывать все оверлеи. В противном случае после изменения системы пришлось бы перетранслировать все командные и ре- зидентные файлы, многие драйверы и т. д. и т. п. При работе большинства рестартов система сохраняет регистры BC, DE, HL, IX и IY. У многих рестартов выходное значение регистровой пары AF сигнализи- рует об успехе операции. При этом подня- тый в "1" флаг C означает ошибку и ре- гистр A в этом случае содержит ея код. В большинстве случаев при ошибках рекомен- дуем просто отваливать по флагу C: Например: LD C,#02 ;flush RST #10 ;вызов рестарта RET C ;выход при ошибке ... Некоторые рестарты, как например рестарты оконной технологии $wt, $adrwt, $box и др. могут вернуться с любым фла- гом, т.е. ни флаг C, ни какой другой не являются здесь признаком ошибки, и обра- батывать их после вызова рестарта не только не полезно, но и ошибочно. В дальнейшем, при более подробном рассмот- рении рестартов для каждого из них бу- дут указаны возможные ошибки. Для неко- торых рестартов важен не только флаг C на выходе, но и флаг Z, а также содержи- мое регистров. Такие случаи оговаривают- ся особо. Большинство рестартов, как уже было сказано, сохраняют регистровые пары BC, DE, HL, IX. Исключение: рестарты $exebat и $run. Ими передается управление коман- дным файлам или резидентным программам. При выходе по RET'у из вызывавшихся программ мы попадаем прямо в основную программу минуя процедуру восстановле- ния регистров. ВНИМАНИЕ! АЛЬТЕРНАТИВНЫЕ РЕГИСТРЫ ПРИ RST 16 НЕ СОХРАНЯЮТСЯ! Более того, именно ими многие рес- тарты возвращают в программу такую по- лезную информацию, как адреса системных векторов (массивов), адреса или номера блоков устройств, адреса каналов, драй- веров и т. д. и т. п. Входные же данные можно передать лишь через основные ре- гистры. 3. ВАША ПЕРВАЯ ПРОГРАММА Программы в исдосе могут быть двух основных типов: командные файлы и рези- денты. Командные файлы (о резидентах мы по- говорим позднее) имеют расширение *.com и загружаются по адресу, указанному в 12-м и 13-м байтах описателя файла, ина- че говоря, со смещением 12 dec (#0C hex) в описателе файла и запускаются с адре- са загрузки. Вызывать их можно из дру- гих программ по командной строке рестар- том run. Например: LD HL,LINE;адрес командной ;строки помещаем в HL LD C,#48 ;$run RST #10 RET C ;описание командной строки, завершается ;кодом ENTER (#0D) ... LINE DEFM "Q:RES\set user.res" DEFB #0D Из оболочки исдоса командный файл можно запустить подведя к нему файловый курсор и нажав Enter, либо описав его в текстовом файле Q:SHELL\extkey.txt, или из монитора командных строк mon.res (mon+.res, mon.com), а так же из bat-файла (подробнее см. книгу В. Ели- сеева "IS-DOS - Первое знакомство"). Создать свой командный файл не прос- то, а очень просто. Сперва при помощи текстового редактора создайте исходный текстовый файл в стандарте любимого Ва- ми ассемблера. Рекомендуем наш as2.com. Файл должен иметь расширение *.as (неко- торые любят *.asm). В начале программы установите адрес загрузки директивой ORG (желательно не ниже 24000): ORG 24000 Затем разместите тело вашей программы. Выходить в исдос советуем по команде: RET При нормальном выходе флаг C должен быть сброшен, флаг Z установлен, в ре- гистре A помещаем код выходной операции: #00 - ничего не делать #F0 - перепечатать одну текущую панель, при этом на ней сохраняется откры- тым текущий каталог, который был на панели до этого, сохраняется позиция курсора, отметка файлов сбрасывается. #F1 - то же самое, но курсор устанавли- вается в начало панели. #F2 - перепечатать обе панели, сохранив в текущей панели ранее открытый каталог и позицию курсора. Перепе- чатывается также и верхняя строка подсказки, однако, окно монитора командной строки не очищается. #F3 - то же, но курсор устанавливается в начало панели. #F4 - полностью очищает экран и перепе- чатывает обе панели и строку под- сказки. На текущей панели сохра- няется ранее открытый каталог и позиция курсора. #F5 - то же, но курсор устанавливается в начало панели. #F6 - полное обновление экрана, перепе- чатка обеих панелей и подсказки (как и при F4), кроме того заново пересоздается кэш. Размер кэша бе- рется из вектроа g_cnfg со смеще- нием -6. Позиция курсора и ката- лог в текущей панели сохраняются. #F7 - то же, но курсор устанавливается в начало панели. #F8 - перепечатка текущей панели с сох- ранением открытого каталога, пози- ции курсора и отметки файлов. #17 - (в старых версиях - #FB) - перепе- чатка текущей панели, при этом те- кущее устройство на ней открывает- ся заново (перечитывается его кор- невой каталог). При ошибке флаг C должен быть уста- новлен, в регистре A помещается код ошибки. Оттранслируйте программу ассембле- ром, отлинкуйте полученный объектный файл программой link.com. Для их вызова можно написать bat-файл, а можно просто нажимать Enter, наведя курсор сперва на asm-файл, затем на obj (В файле Q:extent.txt должны быть такие строки: asm:Q:AS\as /auto obj:Q:AS\link /old /sym S:AS\rst Пользуйтесь при написании стандар- тными названиями рестартов, тогда Вам пригодится файл глобальных меток rst.obj, а программа будет более чита- бельной. Обязательно сосчитайте кон- трольную сумму com-файла программой ch.com. Для этого установите курсор на Ваш com-файл и вызовите из командной строки программу ch.com. В случае bat-файла опишите вызов ch.com прямо в нем. 4. РЕЗИДЕНТНЫЕ ПРОГРАММЫ Резидентной программой в системе IS-DOS называется программа, постоянно находящаяся в специальной области памя- ти и, поэтому, доступная для работы в любое время без подгрузки ее с диска. Работа с резидентной программой под- разделяется на три этапа: 1. Установка (загрузка) в память с дис- ка, настройка на адрес и инициализа- ция. 2. Основная работа 3. Удаление программы из памяти 1-ый и 3-ий этапы осуществляются командой set.com. Она же создает 18-бай- товый канал, описывающий резидентную программу. Установленные (загруженные) рези- дентные программы можно увидеть при по- мощи программ show.com и eliminat.com. Обращаться к резидентным программам мож- но по именам из командной строки, ис- пользуя встроенную команду DOS "@", на- пример: @date+3 или @scan ch+ *.* Последняя строка содержит сразу 2 имени резидентов. Это вызвано тем, что некоторые резидентные программы предназ- начены для работы в паре со специальными служебными резидентами - scan.res и univ.res. Эти служебные резиденты осу- ществляют подбор файлов (по маске, от- метке и т. п.) и передачу их в качестве параметра резиденту, запускаемому в паре с ними. Т. о. Вы запускаете служебную рези- дентную программу, указав в качестве первого параметра имя рабочего резиден- та, а в качестве второго - ключ или мас- ку для подбора файлов. Служебный рези- дент по имени определяет номер канала рабочего резидента, запоминает его и многократно запускает по номеру, предва- рительно открывая следующий файл, подхо- дящий под указанную маску или ключ. Резиденты, работающие в паре с рези- дентами scan.res и univ.res обычно имеют специальную защиту от прямого обращения к ним через команду "@". Структура резидентной программы: **************************************** смещ. длина комментарий ---------------------------------------- 0 2 Адрес процедуры инициализа- ции. Она вызывается при пе- ремещении (при этом в рег. A подается FF) или удалении (в рег. A подается FE) прог- раммой SET.com. Если этот адрес равен 0, то процедура не вызывается. Эта программа должна подключать, если это необходимо, резидента к це- почке прерываний или к дру- гой програме, перехватывать рестарты и восстанавливать их. 2 2 Адрес главного входа, т.е. процедуры запуска по имени рестартами $exebat(#44) и $run(#48) или по номеру ка- нала рестартом $exeres(#53) Если адрес равен 0, то за- пускается с 4-го байта. 4 R Тело программы - собственно машинный код R+4 2 #FFFF - отделяет тело про- граммы от последующей слу- жебной информации R+6 2n таблица настраиваемых адре- сов - 1. Смещения от начала вычисляются по LSA. Единица вычитается для пущей просто- ты настраивания таких команд как: CALL, JP, LD HL, LD A,(nn), наиболее часто встречающихся в программе. ---------------------------------------- С появлением программ ассемблера и сборщика (as.com и link.com) все заботы о разделителе и таблице настраиваемых адресов у программиста отпали. Чтобы собрать резидентную задачу надо лишь за- резервировать в начале файла 4 байта (2 слова) для адресов точек входа (см. только что приведенную структуру) и от- линковать объектный файл(ы) с ключом /res. Не забудьте лишь установить ORG отличный от нуля, чтобы адрес #FFFF не спутался с разделителем! 5. ДРАЙВЕРЫ УСТРОЙСТВ Драйвер - это резидентная программа специального назначения. Он обслуживает физическое или логическое устройство од- ного из трех типов: 1. Блочные устройства: файлы типа *.blk, номера каналов драйверов F8..FF, номера каналов устройств - 00..07 2. Символьные устройства вывода: файлы типа *.typ и *.lpr, номера каналов драй- веров F0..F7, номера каналов устройств - 08..0F 3. Cимвольные устройства ввода: файлы типа *.key номера каналов драйверов E8..EF, номера каналов устройств - 10..1F Установленный в систему драйвер име- ет только 8-буквенное имя (тип в канале не хранится). typ от lpr можно отличить по 0-му биту в 13-ом байте драйвера (5-ый байт в векторе g_typ(#12)). В на- чале каждого драйвера располагается век- тор стандартной структуры: **************************************** смещ. длина имя комментарий ---------------------------------------- 0 2 INST Программа, инициализа- ции вызываемая как и для всех резидентов программой SET.com, а также при каждом пере- ключении устройства, обслуживаемого драйве- ром. При этом в реги- стре A подается номер устройства 2 2 ENTRY1 1-ая точка входа 4 2 ENTRY2 2-ая точка входа 6 2 ENTRY3 3-я точка входа 8 8 служебная информация, хранящаяся также в описании канала ус- тройства и в векторе устройства - регистры состояния, адреса бу- феров и т. п. 16 ... тело драйвера ---------------------------------------- Точка входа INST может использо- ваться для переключения драйвера на со- ответствующее устройство (задействовано в sys_driv и в драйверах винчестеров), а также информирует драйвер (и резидент) о том, что его передвинули или отключают. Вся информация подается в регистре A: A=0..7 - номер устройства, на которое переключается драйвер A=FE - драйвер (резидент) отключается, т.е. будет снят или просто те- кущее устройство скоро будет сменено. Делается это на тот случай если драйвер в рабочем состоянии перехватывает обраще- ния к какому-либо рестарту или другому драйверу. A=FF - драйвер (резидент) только что передвинут. Входы ENTRY1, 2, 3 соответствуют группам системных рестартов IS-DOS: **************************************** Драйвер ENTRY1 ENTRY2 ENTRY3 ---------------------------------------- blk $read $write $binit typ $type $tycpl $typos key $key $kwait $ktest ---------------------------------------- Приложение: Структура драйверов устройств IS-DOS **************************************** blk - блочное устройство ---------------------------------------- 0 2 INSTL - переключение на устр-во или переинсталляция (если значение равно 0, то процедура не нужна) 2 2 BREAD + точки входа 4 2 BWRIT | для рестартов 6 2 BINIT + (только для дисководов со сменным носителем) 8 1 SCCSR - регистр состояния: биты 0...2 - номер устройства для кэш-памяти бит 3 - 0 - драйвер с буфером 1 - драйвер без буфера бит 4 - 0 - не выполнять autoflush 1 - выполнять autoflush при каждой модифи- кации блоков в кэше бит 5 - 0 - запрет чтения для рестартов 1 - разрешение чтения для рестартов бит 6 - 0 - запрет записи для рестартов 1 - разрешение записи для рестартов бит 7 - 0 - не использовать кэш 1 - использовать кэш 9 1 DRCSR - еще регистр состояния: бит 0 - 0 - не выполнять запрос по адресу QVST (вектор g_cnfg сме- щение +40) при об- ращении к блочному устройству 1 - выполнять запрос бит 1 - флаг autoflush (сбрасы- вается пользователем) бит 2 - 0 - диск TR-DOS 1 - диск не TR-DOS биты 3...6 - не используются бит 7 - 0 - не обрабатывать ошибки ($erdrv) 1 - обрабатывать ошибки 10 2 OFIM2 - смещение от начала драйвера до кода IM2 в процедуре восстанов- ления режима IM2 на вы- ходе из драйвера. Если значение равно 0, то IM2 на выходе не устанавливается 12 1 TPSYS - тип TR-DOS'а: 0 - 5.01 1 - 5.03 и выше) 14 2 TMBUF - адрес буфера драйвера, объем буфера - 1Kb. ........ 20 2 ..... - номер текущего сектора в буфере драйвера ........ 30 1 ..... - тип дисковода: бит 0 - 0 - 40 дорожек 1 - 80 дорожек бит 1 - 0 - односторонний 1 - двухсторонний 31 1 TMWT - задержка начала чтения сектора 32 1 HTIME - время перемещения голо- вок дисковода 33 1 ..... - тип диска: бит 0 - 0 - 40 дорожек 1 - 80 дорожек бит 1 - 0 - односторонний 1 - двухсторонний 34 1 ..... - размер сектора, значе- ния этого байта: 1 - 256 байт 2 - 512 байт 4 - 1024 байта 35 1 ..... - количество секторов на дорожке 36 16 ..... - таблица номеров секто- ров на дорожке -------- Примечание: байты 8...15 хранятся также в канале и в векторе блочного устрой- ства. Байты 33...51 считываются с диска, это не что иное, как байты 23...25 и 64...79 из нулевого блока описателя дис- ка. Подробнее о дисках мы поговорим чуть позже, при описании рестарта $binit. **************************************** typ - устройство вывода на дисплей ---------------------------------------- 0 2 ..... - как правило, не исполь- зуется 2 2 TYPE + точки входа для 4 2 TYCPL | рестартов печати 6 2 TYPOS + символов 8 1 ..... - ширина матрицы символов в точках (6 для ty42 и 4 для ty64). Использу- ется рестартами y___ и n___ уровня WIN 9 4 ..... - зарезервировано 13 1 ..... - должен быть равен 0 (признак typ) 14 2 ..... - зарезервировано 16 2 GSZ размер знакогенератора в байтах 18 GSZ собственно знакогенера- тор драйвера -------- Примечание: байты 8...15 хранятся также в канале и в векторе символьного устрой- ства вывода. Байты, соответствующие за- резервированным используются там для хранения текущих координат печати и ад- реса процедуры обработки ошибок сим- вольного устройства вывода. **************************************** lpr - устройство вывода на принтер ---------------------------------------- 0 2 ..... - инициализация портов принтера, если это не- обходимо 2 2 ..... - процедура печати симво- ла, поданного в рег. A, 4 9 ..... - зарезервировано 13 1 ..... - должен быть равен 1 (признак lpr) 14 2 ..... - зарезервировано -------- Примечание: байты 8...15 хранятся также в канале и в векторе символьного устрой- ства вывода. **************************************** key: ---------------------------------------- 0 2 ..... - как правило, не исполь- зуется 2 2 TTYIN + точки входа для 4 2 KWAIT | рестартов ввода 6 2 KTEST + с клавиатуры 8 1 K_CSR - регистр состояния: бит 0 - 0 - строчные 1 - заглавные бит 1 - 0 - латинские 1 - русские бит 2 - 0 - текст 1 - псевдографика бит 3 - 0 - разрешение kwait 1 - запрет kwait(1) этот бит сбрасывается сам при пустом буфере 9 1 M_CSR - маска разрешений смены соответствующих битов регистра K_CSR 1 - смена разрешена 10 1 KLAST - код последней нажатой клавиши 11 1 REPD - интервал между первыми двумя одинаковыми кла- вишами (задержка авто- повтора), значение по умолчанию - 29 12 1 REPP - интервал между последу- ющими одинаковыми кла- вишами (частота авто- повтора), значение по умолчанию - 1 13 2 IKEYB - адрес процедуры опроса клавиатуры 14 2 ..... - зарезервировано 16 2 KS - Размер таблицы кодов клавиш 18 KS ..... - собственно таблица ко- дов клавиш ............ KS+18 1 PNKEY - число нажатых клавиш KS+19 1 - смещение в буфере кла- виш до кода для TTYIN (INC) KS+20 32 ..... - буфер нажатых клавиш (заполняется и опорож- няется с помощью INC) ---------------------------------------- **************************************** Ну вот на сегодня и все, уважаемые читатели, в следующем номере ZX Format будет напечатано продолжение книги А. Леонтьева, в котором речь пойдет о рес- тартах самого нижнего уровня системы - уровня "DOS" (c #00 по #1F). _________________________________________ Из журнала ZX Format #6, Санкт-Петербург, 30.07.1997 Рубрика "IS-DOS - программистам" No 6. (C)Леонтьев А.Г. ________________________________________ КРАТКИЙ КУРС ПРОГРАММИРОВАНИЯ В СРЕДЕ IS-DOS. (под общей редакцией Елисеева В. А.) Продолжение, начало в No 5. От редактора. Здравствуйте, уважаемые читатели! Сегодня в рубрике "IS-DOS - программис- там" мы продолжаем публикацию новой кни- ги А. Леонтьева, начатую в прошлом номе- ре журнала. Для тех, кто еще не знает, сообщаем, что Леонтьев Алексей Григорье- вич является ведущим системным програм- мистом Отдела IS-DOS, признанным автори- тетом в области нашей системы, одним из "Отцов" IS-DOS. Надеемся, что эта книга поможет Вам в освоении программирования в среде IS-DOS и пополнит ряды программистов, способных писать под IS-DOS, новыми та- лантами, а наш ассортимент программ - но- выми разработками, среди которых, между прочим, могут быть и Ваши. Итак: А. Леонтьев ЧТО ДЕЛАТЬ? или КАК ПЕРЕСТАТЬ БЕСПОКОИТЬСЯ И НАЧАТЬ ПРОГРАММИРОВАТЬ В ИСДОСЕ 6. WAS IST DOS? или САМЫЙ НИЖНИЙ УРОВЕНЬ (0-ой уровень, он же "DOS.SYS") Нижний уровень системы в iS-DOS (рестарты с #00 по #1F) работает с кэшем, блочными и символьными устройствами, ка- налами и прерываниями. Этот уровень не знает ни о файлах, ни о командных стро- ках, ни об оконной технологии. Всем этим занимаются более высокие уровни. Итак, более подробно о рестартах: Первые 7 (с #00 по #06) рестарты уровня "DOS" работают с так называемой "кэш-памятью", поэтому сперва несколько слов о том, что это, собственно, такое. Итак кэш блочных устройств - это системный буфер используемый исключи- тельно для блоков (по 256 байт) из кото- рых состоят все блочные устройства. Кэш создается рестартом $creat(#00). Система автоматически создает его при загрузке, а также при выходе в систему по рестарту $shel0(#80) или по RET'у со зна- чениями #F6 или #F7 в регистре А процес- сора. Его пересоздают также некоторые ко- мандные файлы, например копировщики. Де- лают они это, в основном, чтобы уменьшить размер кэша, и, таким образом, выиграть несколько килобайт памяти под буферы, а также чтоб вернуть после этого кэш в прежнее состояние. Как уже говорилось в самом начале книги, система iS-DOS занимает память ZX сверху вниз, и кэш, создаваемый в послед- нюю очередь лежит под системой. Сверху он ограничен областью каналов. Верхний ад- рес области кэш-памяти можно достать из вектора конфигурации системы g_cnf, ис- пользовав рестарт $g_cnf(#10) со смеще- нием +32: LD C,#10 ;$g_cnf, возвращает в RST #10 ;HL' адрес вектора EXX ;адрес - в HL LD DE,32 ;прибавляем смещение ADD HL,DE LD E,(HL) ;получаем в DE INC HL ;искомый верхний LD D,(HL) ;адрес кэша Нижний адрес кэша, естественно, за- висит от верхнего адреса. Если Вам катас- трофически не хватает памяти, можно уб- рать лишние резиденты (в этом Вам помо- жет set.com с ключом /e). Можно также подсократить область каналов, что умеет делать channel.com. Это вызовет перемеще- ние всей области кэша вверх и, следова- тельно, поднимет и нижнюю его границу, освободив память для Ваших целей. Однако, из Вашей программы проще всего сократить кэш. Размер кэша, устанавливаемый по умолчанию, лежит в векторе g_cnf со сме- щением +7. Адрес нижней границы кэша на- ходится в том же векторе со смещением +5 и +6. При необходимости его можно рас- считать по формуле (1): CACHE = DGCHN - CSIZE * 260 (1) где CACHE - искомый адрес нижней границы кэша, DGCHN - адрес начала области кана- лов, а CSIZE - размер кэша в блоках. По этому адресу располагается ката- лог кэша в виде массива описателей бло- ков (по 4 байта на каждый блок кэша), после которого располагаются сами блоки. Структура кэша: +-------+-------+-----------------------+ |Смеще- |Размер | Описание | |ние | | | +-------+-------+-----------------------+ | 0 |EDSIZ*4| каталог кэша, состоит | | | | из 4-байтовых описа- | | | | телей блоков: | | | | | | 0 |4 | описатель 1-го блока | | 4 |4 | описатель 2-го блока | | 8 |4 | описатель 3-го блока | | . . . | . . | . . . | |EDSIZ*4|256 | 1-й блок + | | |256 | 2-й блок +---+ | | |256 | 3-й блок + | | | . . . | . . | это, собственно, тело | | | | кэша, его адрес хра- | | | | нится в векторе g_cnf | | | | со смещением -5. | +-------+-------+-----------------------+ где EDSIZ - размер кэша. Описатель блока имеет следующую структуру: +----+----+-----------------------------+ |Сме-|Раз-|Описание | |ще- |мер | | |ние | | | +----+----+-----------------------------+ |0 |1 |регистр состояния блока, | | | |совмещенный с номером устрой-| | | |ства. Биты: | | | | 7 - блок модифицирован (1) | | | | 6 - блок защищен (1) | | | | 5 - блок существует (1) | | | | 4 - не используется | | | | 3..0 - номер устройства, | | | |которому принадлежит блок. | +----+----+-----------------------------+ |1 |1 | счетчик обращений к блоку | +----+----+-----------------------------+ |2 |2 | номер блока на устройстве. | +----+----+-----------------------------+ Минимальный размер кэша равен 6 бло- кам. Оптимальный в iS-DOS Classic - от 20 до 30, в CHIC - около 40. Кэш необходим для работы файловой службы. Через него система обращается к устройствам, т.е. читает и пишет описате- ли файлов каталогов, карту занятых бло- ков устройств, заголовки устройств и т.п. При нехватке места в кэше, в жертву при- носится самый старый считаный блок. Запись также осуществляется через кэш, причем система и не имеет других бу- феров, поэтому блок просто видоизменяет- ся, то бишь модифицируется прямо в кэше, а в описателе кэша устанавливается в еди- ницу 7-ой бит. По мере накопления модифицированных блоков их записывают на устройство, а бит модификации сбрасывается. Это действие называется "автоматический флаш" или "ав- то-флаш" (flush по-английски "слив", не путайте с flash - флэш - "вспышка", а также flesh - флеш - "плоть". Просьба произносить с чисто Оксфордским пронон- сом!) И, наконец, рестарты: #00 - $creat Создает и пересоздает кэш. Требуе- мый размер в блоках необходимо подавать в регистре A. Содержимое остальных регис- тров на работу рестарта не влияют. На выходе проверьте флаг C. В слу- чае ошибки он будет установлен в "1" Единственная возможная ошибка - Error 130 - нехватка памяти, т.е. входное значение регистра A так велико, что кэш не вле- зает над адресом Utop (User Top). Достать адрес Utop Вы можете приме- нив рестарт $g_cnf(#10) (смещение +3 в векторе g_cnf). Utop служит, в основном, предохранителем от опускания кэша так низко, что программа set.com в процессе работы убила бы им саму себя. Поэтому обычно Utop=26676. Рестарт $creat(#00) обнуляет блоки размером по 256 байт под нижней границей области каналов ($g_cnf(#10), смещение +32) в количестве, указанном в регистре А, и создает каталог кэша (по 4 байта на каждый блок). Работать с кэшем размером менее 4 блоков небезопасно (программа на это просто не расчитана). Рекомендуемые раз- меры от 6..10 блоков минимум (если Вы хо- тите выиграть побольше памяти под буферы для программ или данных) до 30 или даже может 70 (в iS-DOS Chic, разумеется). Хотя наращивание кэша после 20 бло- ков почти не дает заметного выигрыша в скорости работы программ. Для восстанов- ления размера кэша по-умолчанию советуем такой вот примерчик: LD C,#10 ;$g_cnf RST 16 EXX ;в HL - адрес вектора LD BC,-6 ;смещение -6, здесь ADD HL,BC ;хранится размер кэша ;по умолчанию LD A,(HL) ;помещаем размер в А LD C,#10 ;$creat RST 16 ;пересоздаем кэш RET C При расчетах размера создаваемого кэша, если известны верхняя и нижняя гра- ницы его, Вы можете пользоваться форму- лой (2), логически вытекающей из формулы (1): DGCHN - CACHE CSIZE = -------------- (2) 260 где CSIZE - искомый размер кэша, DGCHN - нижняя граница области каналов, она же - верхняя граница кэша, а CACHE - нижняя граница кэша. Пример: LD C,#10 ;$g_cnf RST 16 EXX ;в HL - адрес вектора LD BC,32 ;смещение +32 ADD HL,BC LD E,(HL) ;в DE - адрес верхней INC HL ;границы кэша LD D,(HL) LD HL,CACHE;это - требуемая ниж- ;няя граница кэша, ;ниже которой вся па- ;мять может быть ис- ;пользована Вашей ;программой EX DE,HL ;вычтем CACHE SBC HL,DE ;из DGCHN XOR A LD DE,260 ;делим результат 1$ SBC HL,DE ;на 260 INC A JR NC,1$ DEC A ;проверяем, не стал CP 6 ;ли CSIZE меньше JR NC,2$ ;6 блоков LD A,130 ;Нет места! Выход с RET ;ошибкой 130 ;код ошибки в рег. А 2$ LD C,#00 ;$creat RST 16 ;пересоздаем кэш RET C ... #01 - $clear Очистка кэша от блоков текущего ус- тройства. Эта операция необходима при смене дискеты в дисководе или при отказе выгрузки на диск блоков, модифицирован- ных рестартами $modwr(#03), $modo(#2E), $wpart(#2A), когда авто-флаш ещё не слу- чился, а так же при записи на диск мимо кэша блоков, считанных через кэш, дабы диск и кэш соответствовали друг другу. При ракботе рестарта вся информация, имеющаяся в кэше теряется. Если в кэше есть модифицированные блоки, и Вы хотите их сохранить, то пе- ред очисткой вызовите рестарт $flush(#02). Ошибок после вызова быть не должно. Входных параметров нет. Пример использования рестарта $clear взят из программы format.com: Это - начало процедуры format. Воз- можно, перед форматированием был сменен диск. Поэтому кэш должен быть очищен рестартом $clear(#01). Этот пример также будет полезен для лучшего понимания рабо- ты рестартов $g_blk(#13), $g_drv(#14), $binit(#0F), которые будут рассмотрены нами далее. Вначале с помощью $g_blk(#13) мы уз- наем текущее блочное устройство и дос- таем адрес тела соответствующего ему драйвера. Далее проверяем, является ли этот драйвер драйвером флоппи диска sys_driv.blk, т.е. можно ли форматиро- вать это устройство через TRDOS. Затем мы пытаемся открыть это ус- тройство как блочное устройство IS-DOS рестартами $binit(#0F) и $open(#20) дабы предотвратить случайное переформатирова- ние существующего диска: ... LD C,#13 ;$g_blk RST 16 ;получаем в А номер ;текущего блочного ;устройства INC C ;$g_drv RST 16 ;узнаем адрес его RET C ;драйвера EXX ;в HL - адрес тела ;драйвера текущего ;блочного устройства ;здесь мы проверяем, является ли этот ;драйвер драйвером дисковода LD BC,9 ADD HL,BC LD (DRCSR+1),HL RES 7,(HL) ;не вызывать $erdrv(1F) при ошибках на ;диске LD C,#01 ;$clear RST 16 ;очищаем кэш LD C,#0F ;$binit RST 16 ;инициализируем ;устройство JR C,FOR_1 ;переход, на про- ;цедуру форматиро- ;вания, если устрой- ;ство не существует LD C,#20 ;$open RST 16 ;пытаемся открыть ;устройство JR C,FOR_1 ;переход на формати- ;рование, если не ;открывается ... FOR_1 ;здесь расположена ;программа формати- ;рования диска ... #02 - $flush Рестарт $flush(#02) необходим для фиксации на любом блочном устройстве (диске, винчестере или электронном диске) изменений, произведённых в кэше после за- писи с помощью рестартов $modwr(#03), $modo(#2E), $wpart(#2A), $crfil(#23), $erfil(#24) и др. Все вышеперечисленные рестарты моди- фицирут блоки в кэше, естественно, что эти изменения должны в конце концов ока- заться на диске. Дело в том, что кэш блочных ус- тройств был создан во-первых для ускоре- ния работы нижних уровней системы, дабы дважды и трижды не читать с устройства одни и те же блоки. К тому же системе был нужен некий свой буфер для работы с ката- логами и файлами. В связи с этим, запись на устрой- ства система в большинстве случаев осу- ществляет не напрямую и не лезет с каж- дым новым блоком на диск, а помечает блок в кэше как "модифицированный", то есть устанавливает в 1 один из разрядов описа- теля блока в кэше. При достижении определенного коли- чества модифицированных блоков (обычно оно равно размеру кэша, деленному на 2 или размеру кэша минус 3) происходит ав- томатический флаш. При этом в 1 устанав- ливается 1-ый бит 1-го байта вектора блочного устройства. Входных параметров у этого рестарта нет. Возможны различные ошибки на выходе, например: Ошибки кэша: 61 - указанный блок не найден 62 - реальное кол-во модифицированных блоков не соответствует значе- нию системной переменной ($QNMOD) 63 - сектор защищен 64 - кэш защищен 65 - нет места в кэше 66 - чтение запрещено 67 - запись запрещена Ошибки блочного драйвера sys_driv.blk: 6 - нет диска 7 - ошибка чтения/записи 20 - обращение к диску прервано Если модифицированных блоков в кэше на текущем (!) устройстве нет, то рес- тарт ничего не делает. Советуем вызывать $flush(#02) в кон- це вашей программы перед выходом в обо- лочку, а так же перед запуском из Вашей программы некоторых системных утилит (copy25.com, например) и перед пересосда- нием кэша при высвобождении памяти. Если Ваша программа писала через кэш на несколько различных блочных устройств (например: текущее T:, системное S:, быс- трое Q:), то не забудьте сделать flush на каждом из этих устройств, переключаясь на них с помощью рестарта $swblk(#1C) или $g_sta(#37). Вот пример выхода из программы, мо- дифицирующей блоки на нескольких устрой- ствах сразу, скажем на Q, S и T: LD A,"Q" ;для устройства "Q" CALL FLUSH ;вызов подпрограммы RET C LD A,"S" ;для устройства "S" CALL FLUSH ;вызов подпрограммы RET C LD A,"T" ;для устройства "T" CALL FLUSH ;вызов подпрограммы RET C ;здесь происходит восстановление среды, ;которая была на момент запуска LD C,#41 ;$fmrst RST #10 ;вызов рестарта RET C ;выход в оболочку с перепечаткой экрана ;стандартным способом XOR A LD A,#F4 RET ;подпрограмма FLUSH ;сначала преобразуем логическое имя ;устройства ("S","Q","T") в физический ;номер (0..7) FLUSH LD C,#4A ;$dvtrn RST #10 LD B,A ;номер устройства ;помещаем в рег. B ;переключаемся на соответствующее ;устройство LD C,#1C ;$swblk RST #10 RET C ;вот теперь, собственно, flush LD C,#02 ;$flush RST #10 RET ;возврат из ;подпрограммы #03 - $modwr Запись одного блока (256 байт) с ад- реса, поданного в HL на текущее блочное устройство под номером блока, поданным в DE. Запись происходит через кэш, т.е. сперва в кэше освобождается место - выки- дывается самый старый считаный блок - и на его место переносятся заданные 256 байт. Новый блок получает свой номер и но- мер устройства (от 0 до 7) и тут же поме- чается как модифицированный. Если пере- полнения модифицированных блоков при этом не произошло, то на этом всё и кончается, иначе происходит автоматический флаш, т.е. физическая запись модифицированных блоков на текущее устройство (посред- ством обращения к драйверу). При работе рестарта возможны ошибки, так что после его вызова советуем прове- рять флаг C. Коды ошибок те же что и у $flush(#02). Рестарт $modwr(#03) удобен при рабо- те непосредственно с устройством (минуя файл). Не забудьте только вызвать $flush(#02) в конце программы! Пример из жизни: программа arzt+.com, а точнее - ее фрагмент, в ко- тором осуществляется запись на устрой- ство измененной таблицы занятых блоков: $MODWR LD HL,$BUFF ;адрес рабочего ;буфера помещаем ;в рег. HL LD DE,1 ;номер 1-го ;блока - в DE LD A,($MLTRD+2) ;число блоков LD B,A ;помещаем в B, ;это - счетчик ;последующего ;цикла LD C,#03 ;$modwr 3$ RST #10 RET C INC DE ;вызов в цикле INC H ;для всех блоков DJNZ 3$ ;по очереди RET #04 - $unird Парный к предыдущему рестарт - чте- ние одного блока устройства с кэша. В HL помещаем адрес, в DE - номер блока на ус- тройстве. Если блока в кэше нет, процедура считывает его через драйвер. Флаг C на выходе сообщает о неудачно проведенной операции. Коды ошибок, как и в предыду- щих двух случаях, суть коды ошибок кэша и драйвера (их описание см. $flush(#02)). В реальной жизни этот рестарт прак- тически не применяется, поэтому в качес- тве примера прочитаем 0-й блок устрой- ства в буфер BUFF, очистив перед этим на всякий случай (если была смена диска) кэш LD C,#01 ;$clear RST #10 ;очистка кэша LD DE,0 ;номер блока = 0 LD HL,BUFF ;адрес буфера LD C,#04 ;$unird RST #10 RET C ... #05 - $mltrd Почти тоже самое, но читается сразу несколько блоков. Число блоков задается в регистре B. Сперва эти блоки ищутся в кэ- ше и копируются по означенному адресу, затем прямо туда, куда заказывали считы- ваются через драйвер недостающие блоки, которые уже затем копируются в кэш. Если число считываемых блоков пре- восходит размер кэша пополам, то считыва- ние происходит мимо кэша, дабы не выби- вать из него такие часто требующиеся бло- ки как блоки каталогов и заголовок ус- тройства, и таким образом не замедлять работу системы с каталогами. Напрямую считываться блоки будут также при наличии в кэше модифицированых блоков. Можно заставить всегда работать этот рестарт напрямую с драйвером, сбро- сив 7-ой бит 0-го байта вектора блочных устройств. Этим рестартом пользуется рестарт чтения части файла $rpart(#29), c по- мощью которого в свою очередь запускают- ся все com-файлы системы. На всякий случай анализируйте флаг C на выходе. Коды ошибок те же, что и у $flush(#02). Пример взят, опять же, из arzt+.com: считывание карты занятых блоков устрой- ства в буфер $BUFF. Байт $MLTRD+2 зара- нее заполняется размером карты: LD C,#35 ;$bkfcb RST #10 ;считываем описатель EXX ;диска и находим в LD BC,-6 ;нем размер карты ADD HL,BC ;Округлим его до целых блоков: ;и поместим по адресу $MLTRD+2 LD A,(HL) INC HL OR A LD A,(HL) JR Z,$+3 INC A LD ($MLTRD+2),A ... CALL $MLTRD ;вызов подпрограммы . ;подпрограмма, использующая $mltrd(#05) $MLTRD LD BC,#05 ;$mltrd LD HL,$BUFF;адрес буфера - в HL LD DE,1 RST #10 RET $BUFF ;это - буфер #06 - $quard Почти тоже что и $unird(#04), но блок считывается только в кэш (или прос- то находится в нем). Поэтому регистр HL на входе не используется, зато на выходе в HL' возвращается адрес блока в кэше, DE' - адрес заголовка блока в каталоге кэша (необходим для дальнейшего использо- вания $modo(#2E)). На входе в DE подается номер блока устройства. Возможны различные ошибки (те же, что и у рестарта $flush(#02)), посему проверяйте флаг C. Удобен для работы ре- зидентов или программ, которым жаль буфе- ров под блок. Пример взят из программы date.com: ;Считаем 0-ой блок устройства. Именно там ;хранится дата загрузки системы: LD DE,0 ;номер блока = 0 LD C,#06 ;$quard RST #10 RET C EXX ;Сохраним адрес считанного блока (HL) и ;адрес описателя этого блока в кэше (DE): LD (TMPHL+1),HL LD (TMPDE+1),DE ... ;...печать и изменение даты... ;(эти процедуры пропущены здесь ;за ненадобностью) ... ;выход из программы: ;в DE подаем новое значение даты. ;Сравним его с хранящимся в 0-ом блоке ;устройства и, если они разнятся, ;положим новую дату в 0-ой блок и сохра- ;ним его на устройстве рестартами ;$modo(#2E) и $flush(#02): TMPHL LD HL,0 ;в этот пустой байт ;помещается вышеупо- ;мянутый адрес счи- ;танного блока LD BC,30 ADD HL,BC LD C,(HL) INC HL LD B,(HL) ;в BC теперь - старая дата, считанная ;с устройства EX DE,HL XOR A PUSH HL ; новая дата SBC HL,BC ; сравнение POP HL EX DE,HL JR Z,SVSTYL ;даты равны. Обход ;Если же даты не равны - кладем новую ;поверх старой прямо в кэше: LD (HL),D DEC HL LD (HL),E ;Промодифицируем блок в кэше: TMPDE LD DE,0 ;в пустой байт после DE, как Вы помните, ;мы ранее поместили адрес описателя блока ;в кэше, полученный из рестарта $quard(6) ;несколькими строками выше. ;Этот адрес необходим для $modo. LD C,#2E ;$modo RST #10 RET C LD C,#02 ;$flush RST #10 RET C SVSTYL ... Следующие 3 рестарта $key(#07), $kwait(#08) и $ktest(#09) работают с кла- виатурой: #07 - $key Ввод символа с клавиатуры. Обращается прямо в 1-ую точку входа драйвера клавиа- туры. Адрес этой точки входа лежит в драйвере со смещением 2). Если буфер драйвера пуст, тогда драйвер обращается к портам клавиатуры и ждет, пока клавиша не будет нажата. Выходное значение возвращается в ре- гистре A. Оно ависит от нажатой клавиши и от режима в котором находится устройство ввода. Режим определяется нулевым байтом вектора символьного устройства ввода g_key, который можно получить, обратясь к рестарту $g_key(#11). 0-ой байт вектора. Биты (0/1): 0 - строчные/ПРОПИСНЫЕ 1 - latin/русские 2 - текст/псевдографика 3 - не ждать отпускания клавиш при $kwait(8), применяется для макросов в редакторе, сбрасывается сам при опустошении буфера драйвера. Флаг С роли не играет. В качестве примера приводится подпрограмма из тек- стового редактора edit.com, зажигающая и гасящая курсор в текущих координатах и опрашивающая клавиатуру с ожиданием или без (управляется 5-ым битом в edcsr): ; Переключим клавиатуру в режим маленьких ;латинских чтобы получить код управляющей ;клавиши, не зависящий от текущего состо- ;яния. 3-ий бит (макро) маскируется вось- ;мёркой дабы сохранить его, биты 0,1,2 ;обнуляются: $KEY LD C,#11 ;$g_key RST #10 EXX LD A,(HL) AND 8 LD (HL),A ;lat ;Переключение на текущий драйвер вывода ;на экран (t42/t64): CALL $TYCUR RET C ;Позиционирование. Пересчитаем координаты ;в тексте xc в позицию на экране и поло- ;жим YX в HL: LD H,(IX+ys) LD A,(IX+ys-1) ADD A,(IX+xc) SUB (IX+X_skip) LD L,A L_F LD C,#0C ;$typos RST #10 ;зажжем мигающий курсор в тек. позиции: LD C,#76 ;$y___ RST #10 LD C,#08 ;$kwait ;Обход ожидания отпускания клавиши: BIT 5,(IX+edcsr) JR Z,$+3 RST #10 DEC C ;7=$key - ввод RST #10 ;клавиши PUSH AF ;сохраним код LD C,#77 ;$n___ RST #10 ;погасить курсор POP AF ;восстановим код RET #08 - $kwait Ожидание отпускания клавиш. Рестарт необходим в некоторых случаях, когда опасна случайно дважды нажатая (удержи- ваемая) клавиша, например в самом начале программы, вызываемой клавишей и также опрашивающей клавиатуру с помощью преды- дущего рестарта. Это - вторая точка входа в драйвер (адрес ее находится в драйвере со смеще- нием 4). Используется в рестартах $smbgt(#6E), $edstr(#7F), $menu(#91). На выходе может быть всё что угодно. Пример - см. предыдущий рестарт. Эта процедура вызывается, скажем, блочным оверлеем ed0+.ovr с ожиданием от- пуска клавиши перед рестартом $analy(7E) после печати нижней строки функций овер- лея и при вызове функции , требую- щей подтверждения. Такой вызов гарантирует надежность. Однако при отметке блока та же процедура вызывается без ожидания отпускания, что позволяет отмечать большие блоки, удержи- вая клавишу и используя автоповтор. Этот рестарт при вызове опустошает буфер драйвера клавиатуры, т.о. драйвер "забывает" все нажатые до вызова рестар- та клавиши. Если бы эта функция драйвера не отключалась бы 3-им битом регистра состояния клавиатуры, макросы работали бы лишь до первого $kwait. #09 - $ktest Этот рестарт - третья точка входа в драйвер клавиатуры. Он определяет, нажа- та хоть одна клавиша на клавиатуре или нет. Выход во флаге Z: Z - не нажата, NZ - нажата. Как и $key(#07), в первую очередь опрашивает буфер драйвера, где накапли- ваются быстро нажатые клавиши, и который используется для макросов или эмуляции нажатой клавиши. Этот рестарт удобен для опроса кла- виатуры без ожидания, например для преры- вания работы циклов. При нажатии клавиши флаг на выходе - NZ, а код клавиши будет в регистре A, а также в буфере драйвера, откуда его необходимо извлечь рестартом $key(#07), иначе на него наткнется еще кто-нибудь. Пример: ;Эта подпрограмма вернется с флагом Z, ;если клавиш нажато не было ;и с флагом NZ и кодом клавиши ;в регистре A в противном случае. KTEST LD C,#09 ;$ktest RST #10 RET Z LD C,#07 ;$key RST #10 ;извлекаем "лишний" ;код из буфера драй- ;вера клавиатуры OR A RET Три следующих рестарта предназначены для символьного вывода на экран или прин- тер, это $type(#0A), $tycpl(#0B) и $typos(#0C): #0A - $type Печать символа из регистра A на те- кущем устройстве символьного вывода (эк- ран или принтер). Выбор устройства (экран или принтер) можно осуществить при помощи рестарта $svtyp(#1B). Флаг на выходе при печати на экране роли не играет. Обращаясь к принтеру мож- но схлопотать следующие ошибки: 150 - ошибка или принтер не готов, 151 - печать прервана. Последняя версия программы print.com не пользуется этим рестартом. Печать на экране происходит в теку- щей позиции, которая хранится в байтах 1..4 вектора символьного устройства выво- да. Вектор можно достать с помощью рес- тарта $g_typ(#12). Байты: 1 - Позиция печати в строке 2 - Номер строки на экране Эти 2 байта подаются в HL на входе рестарта $typos(#0C), который передает их в регистре BC в 3-ю точку входа в драй- вер (смещение 6). 3,4 - Адрес печати на экране Это - левый верхний байт буквы. Нап- ример #4000 для 0-ой позиции в 0-ой стро- ке). Сюда эти байты кладут рестарты $typos(#0C) и $wtpos(#6B), Отсюда берет их для печати следующего символа рестарт $type(#0A). Сюда же он их и возвращает после печати. Рестарт обращается к 1-ой точке вхо- да драйвера (смещение 2 от начала драйве- ра), при этом адрес печати на экране по- дается в регистре DE'. Этим рестартом пользуются рестарты более высоких уров- ней, такие как $str(#6C), $smbgt(#6E) и др. #0B - $tycpl Устанавливает инверсный/нормальный режимы печати на экране в соответствии с содержимым регистра А: A<>0 - инверсная печать A=0 - нормальный режим У драйверов принтеров не использует- ся. Флаг C на выходе значения не имеет. Рестарт используется новой версией текстового редактора для блочной отметки а также программой demon.com. Обращается во 2-ую точку входа в драйвер (адрес по смещению 4). #0C - $typos Задание позиции печати на экране для последующей печати рестартами $type(#0A), $str(#6C), $lnstr(#6D), $smbgt(#6E), $edstr(#7F). Координаты печати задаются в регис- тре HL: H - номер строки, L - позиция в строке. Для позиционирования относительно окна также можно пользоваться рестартом $wtpos(#6B). Флаг C на выходе значения не имеет. Этот рестарт использует 3-ю точку входа в драйвер (смещение 6). У старых драйверов принтера эта точ- ка не использовалась, у новых она отве- чает за печать символа из регистра A без перекодировки. Вот как использует рестарты $typos и $type программа exebat.com для печати ко- мандной строки из системного буфера: ;Проверка наличия 8-го канала (канал ;драйвера символьного вывода), т.к. прог- ;рамма должна работать даже тогда, когда ;печатать нечем: PRINT LD A,8 ;ty42 LD C,#16 ;$stchn RST #10 RET C ;Позиционирование: SBC HL,HL ;HL=0: верхняя строка LD C,#0C ;$typos RST #10 LD C,#45 ;$g_com RST 16 EXX ;HL=адрес системного буфера LD BC,#2A0A ;$type ;B=#2A=42-столько букв помещается в одну ;строку на экране, #0A - $type ;Печатаем строку до символа #D: 1$ LD A,(HL) CP #D JR Z,2$ RST #10 INC HL DJNZ 1$ RET ;Допечатка пробелов: 2$ LD A," " RST 16 ;C=$type (см. выше) DJNZ 2$ RET Три рестарта, обращающиеся к драйверу блочного устройства: #0D - $read Чтение блоков напрямую с драйвера текущего блочного устройства. HL - адрес, DE - номер блока устройства, B - число блоков. Флаг C на выходе - признак ошибки. Ошибки только те, что выдает драйвер. Так драйвер электронного диска обычно не вы- дает ошибок, хотя никто не мешает встроить в него проверку, скажем, номера блока на слишком большой номер или число считываемых блоков на 0 или вставить про- верку контрольной суммы блока. У драйвера sys_driv.blk возможные ошибки следующие: 6 - нет диска 7 - сектор не читается 20 - чтение прервано. Обычно драйвер флоппи-диска отраба- тывает эти ситуации обращаясь к рестарту $erdrv(#1F) (см. ниже). Рестарт обращает- ся к 1-ой точке входа в драйвер. Пример взят из жизни копировщиков: начало рабочего цикла программы abc.com. В байты INDSK+2 и OUTDSK+2 кладутся номе- ра входного и выходного устройств. ;B=число блоков для чтения/записи COPY PUSH BC INDSK LD BC,#1C ;$swblk RST #10 POP BC RET C LD HL,BUFF ;буфер копирования LD C,#0D ;$read RST #10 RET C PUSH BC OUTDSK LD BC,#021C;$swblk - в C RST #10 POP BC RET C LD C,#0E ;$write RST #10 RET C ... #0E - $write Запись B блоков с адреса HL на теку- щее блочное устройство (флоппи диск, электронный диск или винчестер) начиная с блока номер DE. В IS-DOS любое блочное устройство состоит из логических блоков размером 256 (#100 hex) байт, что очень удобно для расчетов в ассемблере. Пересчётом номера блока в трек, сто- рону и сектор занимается драйвер. Драй- вер флоппи диска настраивается на формат IS-DOS дискеты сам рестартом $binit(#0F). Это - 2-ая точка входа в драйвер. Поскольку это прямое обращение к ус- тройству минуя кэш, рекомендуется сразу после вызова этого рестарта вызвать И)$clear(#01) или $creat(#00) во избежании путаницы блоков в кэше и блоков на ус- тройстве! Ситуация с возможными ошибками точ- но такая же как и у $read(#0D). Пример также см. выше. #0F - $binit Автонастройка драйвера флоппи диска на формат IS-DOS диска. Считывает 0-й блок диска, проверяет наличие признака "DSK" со смещением 10. Если признака нет, то выходит с ошибкой 9. Иначе - настраи- вается на параметры диска, лежащие со смещением: +--+------------------------------------+ |22|число цилиндров | |23|тип диска (число сторон) | |24|размер сектора (1/2/4: 256/512/1024)| | |байт | |25|число секторов на дорожке | |64|номера секторов (до 16 штук) | +--+------------------------------------+ Возможна также ошибка 10 - попытка работы с двусторонним диском на односто- роннем дисководе или с 80-дорожечным на 40-трековом. Это 3-ий вход в драйвер. Обычно не используется у драйверов элек- тронного диска и винчестера (там ставят- ся "заглушки" для совместимости). Этот рестарт полезно вызывать при первом обращении к новому блочному ус- тройству для настройки драйвера на диск и чтоб убедиться в том, что это именно IS-DOS устройство. Кроме ошибок 9 и 10 может выдать ошибки чтения: 6, 7 и 20. Драйвер можно настроить и на работу с форматами TR-DOS и MS-DOS, но рестарт здесь уже мало чем поможет. В таких случаях лучше найти драйвер с помощью рестарта $g_drv(#14), убе- диться, что это именно драйвер флоп- пи-диска, и по смещению 34(dec) сменить размер сектора, количество секторов и таблицу секторов. В качестве примера рекомендуем отры- вок из format.com приведенный после опи- сания рестарта $clear(#01). Cтруктура описателя устройства (диска) 0-ой БЛОК: (0-ой сектор 0-ой дорожки) +----+----+-----------------------------+ |сме-|дли-| комментарии | |ще- |на | | |ние | | | +----+----+-----------------------------+ | 0 | 2 |Не используется | | 2 | 8 |Имя устройства | |10 | 3 |Признак iS-DOS: "DSK" | |13 | 5 |Не используется | |18 | 2 |Размер устройства | | | |(в блоках по 256 байт) | |20 | 2 |Номер 0-го блока главного | | | |каталога | |22 | 1 |Количество треков на устр. | |23 | 1 |Тип диска. Биты(0/1): | | | | 0 - 40/80 дорожек | | | | 1 - 1/2 стороны | |24 | 1 |Размер сектора: | | | |1/2/4: 256/512/1024 байт | |25 | 1 |Количество секторов на дор. | |26 | 1 |Не используется | |27 | 1 |Контр. сумма 32 байтового | | | |описателя ?_dos.sys | |28 | 2 |Не используется | |30 | 2 |Дата | |32 | 32 |Описатель ?_dos.sys | |64 | 16 |Таблица номеров секторов | | | |на дорожке | +----+----+-----------------------------+ 1-ый БЛОК: Бит-карта устройства. 1 бит/блок: 0-свободен/1-занят Описатель ?_dos.sys со смещением 32 помещается в 0-ой блок программой con.com и считывается загрузчиком boot.sys. Ну вот и все на сегодня. В следую- щем номере журнала мы обязательно продол- жим публикацию книги А. Леонтьева. Далее будут рассмотрены оставшиеся рестарты уровня DOS.SYS с номерами от #10 до #1F. Мы рады будем выслушать Ваши отзывы и пожелания, а так же замеченные вами не- точности, неясности и опечатки, неизбеж- ные при написании и подготовке подобных текстов. Пишите, звоните нам или в редакцию журнала, помните, что именно благодаря вашему интересу к нашей системе, она до сих пор существует и развивается. _________________________________________