йтный регистр только для записи, поэтому программа не может просто прочитать его, изменить значение бита 3 и вернуть прочи- танный байт. Вместо этого Вам необходимо определить также пра- вильную установку всех остальных битов (перечисленных в [4.1.2]). Для PCjr этот бит расположен в регистре управления режимом 1 массива ворот дисплея. В [4.1.1] объяснено как получить доступ и запрограммировать этот регистр. 4.5.2 Сдвиг текстового экрана горизонтально. Горизонтальный сдвиг иногда требуется в специальных программах обработки текста, таких как текстовые редакторы. Операционная система не имеет для этого специальных средств. По этой причине данная задача немного сложнее чем вертикальный сдвиг - но несу- щественно. Рассмотрим случай, когда Вы хотите, чтобы экран сдви- гался влево на 5 позиций. При этом левые 5 столбцов исчезнут, весь остальной текст сдвигается влево, а самые правые 5 столбцов должны быть очищены. Поскольку видеобуфер представляет из себя одну длинную строку, то если каждый символ буфера сдвинуть на 10 байтов вниз, то суммарный эффект будет состоять в том, что самые левые 5 символов каждой строки будут передвинуты в последние 5 позиций предыдущей строки. Таким образом, весь экран будет сдви- нут влево на 5 позиций, передвигая 5 ненужных столбцов в правую часть экрана. Все что после остается - это очистить правые 5 столбцов. Это легко делается с помощью процедуры вертикального сдвига [4.5.1], которая может выполняться для любой части экрана и которая очищает указанную область если указать сдвиг на 0 строк. Рисунок 4-6 иллюстрирует этот метод. Низкий уровень. В этом примере осуществляется сдвиг на 5 позиций влево. Легко изменить его для сдвига вправо или для другого значения позиций сдвига. При использовании прямого отображения в память этот метод дает практически моментальный сдвиг экрана. ;---сдвигаем все вниз на 10 байтов MOV AX,0B000H ;указываем на буфер монохромного MOV ES,AX ;дисплея MOV DS,AX ; MOV SI,10 ;сдвигаем из SI ... MOV DI,0 ;... в DI MOV CX,1995 ;сдвигаем все кроме последних 5 байт REP MOVSW ;осуществляем сдвиг ;---очищаем правый край MOV AH,6 ;функция вертикального сдвига MOV AL,0 ;сдвиг на 0 строк чистит окно MOV CH,0 ;строка левого верхнего угла MOV CL,75 ;столбец левого верхнего угла MOV DH,24 ;строка правого нижнего угла MOV DL,79 ;столбец правого нижнего угла MOV BH,7 ;атрибут для очищаемых позиций INT 10H ;чистим окно 4.5.3 Переключение между текстовыми страницами. Поскольку все видеосистемы, кроме монохромного дисплея, имеют достаточно памяти для нескольких видеобуферов, то одновременно могут быть сконструированы несколько экранов, каждый из которых может быть выведен в нужный момент. Вместо того, чтобы передви- гать данные в видеопамяти, монитор посылает данные из другой области видеопамяти. Число доступных страниц может меняться в зависимости от видеосистемы и режима дисплея. Приводим краткую сводку: Режим Тип Число страниц Начало буфера 0 алфавитноцифровой 8 B800 1 алфавитноцифровой 8 B800 2 алфавитноцифровой 8 B800 3 алфавитноцифровой 8 B800 4 графический 1 B800 5 графический 1 B800 6 графический 1 B800 7 алфавитноцифровой 1/8 B800 8 графический переменное B800 9 графический переменное B800 A графический переменное B800 D графический 2/4/8 A000 E графический 1/2/4 A000 F графический 1/2 A000 10 графический 1/2 A000 Режимы 8-A - графические режимы PCjr; число страниц для них ме- няется в зависимости от того, сколько оперативной памяти отведено под видеобуфер. Размер страницы равен 2K или 4K для алфавитноциф- ровых режимов, 32K - для четырех цветов при высоком разрешении или 16 цветов при умеренном разрешении и 16K - для всех остальных режимов. Режимы D-10 поддерживаются EGA. Количество страниц ме- няется в зависимости от установленной памяти. Режимы F и 10 тре- буют наличия не менее 128K памяти. Режим 7 разрешает одну страни- цу для монохромного адаптора и 8 страниц для EGA. Монохромный адаптор не имеет памяти для дополнительных стра- ниц. Однако нет никаких причин, по которым часть основной памяти нельзя было бы использовать как буфер дисплея. В этом случае страничная организация осуществляется за счет быстрого обмена всего содержимого буфера в памяти с видеобуфером (адрес которого B000:0000). Буфер в основной памяти можно рассматривать как "псевдостраницу". Хотя это и не настоящее разбиение на страницы, но результат будет почти такой же, если для пересылки данных Вы будете использовать ассемблерную процедуру. При использовании страниц надо позаботиться о том, чтобы опе- рации вывода на экран направлялись на нужную страницу. Программа не обязана выводить данные на ту страницу, которая в данный мо- мент изображается на экране. На самом деле, часто наоборот жела- тельно конструировать экран "за кулисами", а затем моментально выводить уже готовое изображение. Этот метод особенно полезен, когда необходимо конструировать сложный вывод в Бейсике, у кото- рого вывод очень медленный. BIOS хранит в своей области данных однобайтную переменную, указывающую, какая из страниц выводится в данный момент. Диапазон значений этой переменной от 0 до 7. Она расположена по адресу 0040:0062. Высокий уровень. Бейсик использует команду SCREEN для установки страницы, на которую будет идти вывод (активной страницы) и выводимой страницы (видимой страницы). Страницы нумеруются от 0 до 3 для текстов с 80 символами в строке и от 0 до 7 для 40-символьных. Третий пара- метр за командой SCREEN устанавливает активную страницу. SCREEN,,2 приводит к тому, что все операторы PRINT будут работать со страницей 2. Четвертый параметр устанавливает видимую страни- цу. SCREEN,,,1 приводит к тому, что на экран будет выводиться страница 1. Когда видимая страница не указывается, то автомати- чески принимается, что она совпадает с активной. Для выделения памяти под страницы на PCjr используется опера- тор CLEAR. Этот оператор устанавливает общее количество памяти, отводимое под буфер экрана, которое при старте равно 16384 байта. Чтобы добавить вторую страницу размером 16K, напишите CLEAR,,,32768. Добавочные текстовые страницы требуют 4096 байтов каждая. При условии, что таким образом была отведена память, команды оператора SCREEN для работы со страницами работают опи- санным образом. Только PCjr имеет добавочный параметр оператора SCREEN, который стирает страницу (т.е. переводит ее в цвет фона). Детали описаны в руководстве по Бейсику. Оператор PCOPY также уникален для PCjr. Он копирует изображение из одной страницы в другую. Например, PCOPY 2,1 целиком копирует страницу 2 на стра- ницу 1. Хотя монохромный адаптор не имеет памяти для страниц дисплея, однако имеется способ устроить своего рода "псевдостраницы". Нижеприведенная процедура на машинном языке рассматривает блок памяти как дисплейную страницу. При вызове этой процедуры она обменивает содержимое видеобуфера с содержимым этой области памя- ти. В результате мы имеем как бы две дисплейные страницы. (В приложении Г объясняется как включать подпрограммы на машинном языке в программы на Бейсике.) Вы должны отвести блок памяти размером 4000 байт для псевдост- раницы, помимо памяти, содержащей программу на машинном языке. В примере блок начинается с адреса сегмента &H2000, а процедура помещена по адресу &H2200. Сегментный адрес блока содержится в 9-м и 10-м байтах машинного кода и Вы легко можете изменить его. Видно, что адрес &H2000 представлен как &H00, &H20 в операторе DATA. Это следствие того, что младшие цифры всегда размещаются в младших ячейках памяти. Если Вы хотите разместить блок, скажем по адресу 1234:0000, то надо изменить байты 9 и 10 на &H34, &H12. Вам может потребоваться очистить псевдостарницу от всякой ерунды, оставшейся от других программ. В строках 230-260 это достигается за счет засылки символа пробела (ASCII 32) в каждый байт (32 служит "нормальным" байтом атрибутов). Программа может осуществлять вывод на экран обычным образом, а затем переносить содержимое на псевдостраницу. Но если хотите, то Вы можете осу- ществлять вывод прямо на псевдостраницу, используя прямое отобра- жение в память. 100 '''машинный код 110 DATA &H1E, &H06, &HB8, &H00, &HB0, &H8E, &HC0 120 DATA &HB8, (3&H00, &H20), &H8E, &HD8, &HBF, &H00 130 DATA &H00, &HBE, &H00, &H00, &HFC, &HB9, &HD0 140 DATA &H07, &H26, &H8B, &H1D, &HAD, &HAB, &H89 150 DATA &H5D, &HFE, &HE2, &HF6, &H07, &H1F, &HCB 160 '''помещаем код в память 170 DEF SEG = &H2200 'указываем адрес процедуры 180 FOR N = 0 TO 34 'начинаем с первого байта 190 READ Q 'читаем байт процедуры 200 POKE N,Q 'пишем его в память 210 NEXT ' 220 '''чистим псведостраницу 230 DEF SEG = &H2000 'адрес начала псевдостраницы 240 FOR N = 0 TO 3999 'для каждого символа и атрибута 250 POKE N,32 'помещаем код 32 260 NEXT 'пока не очистим весь буфер 500 '''пишем прямо в псевдостраницу 510 DEF SEG = &H2000 'указываем на ее адрес 520 S$ = "PSEUDOPAGE" 'выводим слово посреди страницы 530 M = LEN(S$) 'получаем длину строки 540 FOR N = 1 TO M 'для каждого символа строки 550 POKE N*2+2000, ASC(MID$(S$,N,1)) 'помещаем его в буфер 560 NEXT ' 600 '''теперь используем процедуру 610 PRINT "SCRREN 1" 'печатаем сообшение на экран 620 DEF SEG = &H2200 'указываем на процедуру 630 PSEUDOPAGE = 0 'начинаем с начала процедуры 640 CALL PSEUDOPAGE 'обмениваем страницы 650 CALL PSEUDOPAGE 'повторяем обмен 660 ... Средний уровень. Функция 5 прерывания 10H выбирает текущую страницу дисплея для вывода. Надо просто поместить номер страницы в AL: ;---установка видимой страницы MOV AH,5 ;номер функции MOV AL,2 ;номер страницы (начиная с 0) INT 10H ;устанавливаем страницу Однако эта функция не устанавливает страницу, на которую будет идти вывод. Любое из прерываний BIOS, которые выводят на экран (функции прерывания 10H), требует чтобы номер страницы был указан в качестве входного параметра в одном из регистров. Однако все прерывания вывода на экран MS DOS пишут на текущую видимую стра- ницу. Таким образом, для "закулисных" операций Вам необходимо пользоваться прерыванием 10H. Для получения информации о текущей странице надо выполнить функцию F прерывания 10H, которая возвращает статус дисплея. Номер страницы при этом возвращается в BH. Низкий уровень. Дисплейные страницы выбираются за счет изменения точки видео- памяти, начиная с которой монитор принимает данные. Эта точка памяти устанавливается регистрами 12 (старший байт) и 13 (младший байт) микросхемы 6845, которые называются регистрами стартового адреса. Значения адресов раздела страниц для буфера, начинающего- ся с B800 такие: 40 символов 80 символов страница 0 0000H 0000H 1 0400H 0800H 2 0800H 1000H 3 0C00H 1800H 4 1000H 5 1400H 6 1800H 7 1C00H В [4.1.1] объясняется как программировать регистры микросхемы 6845, а в [4.5.4] содержится пример программирования стартового адреса. В последнем примере надо просто присвоить BX одно из значений вышеприведенной таблицы. Конечно, при этом устанавли- вается только выводимая страница. Для записи в определенную стра- ницу на низком уровне надо использовать одно из значений таблицы в качестве смещения в видеобуфере при прямом отображении в па- мять. Поскольку прямое отображение в память работает очень быстро, то иллюзия страниц может быть легко создана на монохромном дисп- лее. Выделите блок размером 4000 байтов для хранения страницы. Хотя монохромный адаптор не может непосредственно читать из обыч- ной памяти, содержимое этого буфера и видеобуфера можно обменять настолько быстро, что никто не зметит разницы. Следующая процеду- ра обменивает содержимое этих двух областей. ;---в сегменте данных PPAGE DW 2000 DUP(720H) ;заполняем буфер пробелами ;---пересылка между псевдостраницей и видеобуфером MOV AX,0B000H ;указываем на видеобуфер MOV ES,AX ; MOV AX,SEG PPAGE ;указываем на псевдостраницу MOV DS,AX ; REPEAT: MOV DI,0 ;DI на начало видеобуфера MOV SI,OFFSET PPAGE ;SI на начало псевдостраницы CLD ;направление вперед MOV CX,2000 ;будем пересылать 2000 слов NEXT_WORD: MOV BX,ES:[DI] ;берем слово из видеобуфера в BX LODSW ;слово из псевдостраницы в AX STOSW ;слово из AX в видеобуфер MOV DS:[DI]-2,BX ;слово из BX в псевдостраницу LOOP NEXT_WORD ; PCjr хранит регистр страницы в порте с адресом 3DFH. Значение битов этого регистра следующее: биты 2-0 какая страница выводится (от 0 до 7) 5-3 какая страница пишется (от 0 до 7) при выводе по адресу сегмента B800H 7-6 = 00 для всех текстовых режимов = 01 для графических режимов с 16K = 11 для графических режимов с 32K 4.5.4 Сдвиг между страницами текста. Поскольку страницы текста прилегают друг к другу в видеобуфе- ре, то небольшой текстовый массив может целиком помещаться в этой памяти. В этом случае текст сдвигаться вверх и вниз по экрану не передвигаясь реально в буфере. Вместо этого экран начинает пока- зывать содержимое буфера, начиная с различных точек и тем самым создавая иллюзию сдвига. Этот метод называется аппаратным сдви- гом. Аппаратный сдвиг достигается за счет изменения стартового адреса дисплея, который является числом, указывающим на символ в видеобуфере, который будет выводиться в левом верхнем углу экра- на. Добавление 80 к этому числу "сдвигает" весь экран на одну строку вверх, а вычитание 80 - на одну строку вниз. В режиме с 40 символами в строке надо вместо 80 прибавлять или вычитать 40. На рис. 4-7 приведена диаграмма аппаратного сдвига. Отметим, что регистр стартового адреса не считает байты атри- бутов, поэтому Вы должны вычислять адреса памяти по-другому, чем при прямом отображении в память. Имейте также ввиду, что несмотря на наличие разрывов памяти между границами страниц (96 байтов между 80-символьными страницами и 48 байтов между 40-символьными страницами) микросхема 6845 пропускает эти области и сдвиг непре- рывно происходит с одной страницы на следующую. Аппаратный сдвиг происходит настолько быстро, что Вам может оказаться необходимым вставить процедуру задержки, чтобы пользователь имел возможность увидеть насколько сдвинулся экран. BIOS хранит текущее значение регистра стартового адреса в переменной в своей области данных. Эта двухбайтная переменная расположена по адресу 0040:004EH. Низкий уровень. Стартовый адрес содержится в регистрах 12 (старший байт) и 13 (младший байт) микросхемы 6845. В [4.1.1] объясняется работа этой микросхемы. Прежде чем адресуемый байт направляется в порт с адресом 3D5H, необходимо послать номер адресуемого регистра в порт 3D4H. В данном примере экран сдвигается вверх на одну стро- ку. Переменная START_ADDRESS содержит адрес первого символа теку- щей верхней строки экрана. MOV BX,START_ADDRESS ;начинаем с начала буфера ADD BX,80 ;сдвигаем на 1 строку (80 символов) MOV DX,3D4H ;вывод в адресный регистр MOV AL,12 ;адресуем регистр 12 OUT DX,AL ;посылаем запрос INC DX ;теперь выводим в командный регистр MOV AL,BH ;старшее слово в AL OUT DX,AL ;посылаем его в регистр 12 DEC DX ;обратно к адресному регистру MOV AL,13 ;адресуем регистр 13 OUT DX,AL ;посылаем запрос INC DX ;снова командный регистр MOV AL,BL ;младшее слово в AL OUT DX,AL ;посылаем в регистр 13 Глава 5. Дисковые накопители. Раздел 1. Управление распределением диска. Все диски, как гибкие, так и жесткие, организованы одинаковым образом. Поверхность диска разделена на ряд концентрических ко- лец, называемых дорожками, а дорожки делятся радиально на секто- ра. Например, стандартная дискета с диаметром 5 1/4 дюйма имеет 40 дорожек и в системе MS DOS 2.0 каждая дорожка разбита на 9 секторов (15 секторов на дискете емкостью 1.2 Мбайта и 17 на фиксированном диске). Размер сектора 512 байт, и 512 байт * 9 секторов * 40 дорожек * 2 стороны дает в итоге емкость дискеты 360K. Все типы дисков используют размер сектора 512 байт в MS DOS. Файл распределен по такому количеству секторов, которое необ- ходимо, чтобы вместить его. Только несколько секторов на внешнем ободе дискеты зарезервированы для специальных нужд. Остальные доступны на основе правила "первый подошел - первого обслужат". Это означает, что по мере заполнения диска данными сектора посте- пенно заполняются по направлению к центру диска. При уничтожении файла сектора освобождаются и со временем свободные области ста- новятся разбросанными по диску, разбивая новые файлы и замедляя доступ к ним для чтения и записи. Фиксированные диски имеют некоторые специальные характеристи- ки. Часто они состоят из двух или более параллельных пластин, у каждой из которых есть две головки, чтобы читать обе их стороны. Все дорожки, расположенные на данном расстоянии от центра, вместе называются цилиндром. Поскольку головки всех дисков двигаются тандемом, то достигается экономия перемещений если заполнять все дорожки одного цилиндра, прежде чем переходить к следующему. Группы цилиндров могут относиться к различным операционным систе- мам. Программа DOS FDISK может разбивать фиксированный диск на несколько разделов (до четырех) разного размера. По этой причине параметры фиксированного диска могут сильно отличаться. Дисковые сектора определяются магнитной информацией, которую записывает утилита форматизации диска. Информация включает иден- тификационный номер каждого сектора. BIOS нумерует сектора 1-8, 1-9 или 1-15, в зависимости от емкости диска. Дорожки не марки- руются, вместо этого они определяются механически по смещению головки чтения/записи от внешнего края диска. Дорожки нумеруются от 0 до 39 для дискет диаметром 5 1/4 дюйма, а для дисков большей емкости их может быть больше. Дисковые функции BIOS обращаются к определенному сектору, указывая номера дорожки и сектора. Однако функции DOS рассматривают все сектора диска, как одну цепь, кото- рая нумеруется подряд, начиная от 0, поэтому каждый сектор имеет свой логический номер сектора. Для дискет первый сектор (дорожка 0, сектор 1) содержит запись начальной загрузки, которая является небольшой программой, позво- ляющей компьютеру считать с дискового накопителя остальные части MS DOS. Затем идут две копии таблицы размещения файлов, которые содержат информацию о распределении дискового пространства (вто- рая копия хранится из соображений безопасности). Затем идет кор- невой каталог, который содержит список файлов и ссылок на подка- талоги, а также указывает в каком месте диска они начинаются. Наконец, далее идут две небольшие программы DOS IBMBIO.COM и IBMDOS.COM, которые считываются при старте и обеспечивают компью- тер возможностями необходимыми для нахождения и загрузки файла COMMAND.COM, который несомненно является основной частью опера- ционной системы. Фиксированные диски имеют главную запись загрузки, которая содержит таблицу разделов, позволяющую разделить диск между нес- колькими операционными системами. Таблица разделов содержит ин- формацию о том, где на диске начинается раздел DOS, а также пер- вый сектор какого раздела содержит запись начальной загрузки. В остальном раздел организован так же, как и дискета. 5.1.1 Чтение таблицы размещения файлов. Диск использует таблицу размещения файлов (FAT) для отведения дискового пространства файлам и хранения информации о свободных секторах. Из соображений безопасности на всех дисках хранятся две копии FAT. Они хранятся последовательно, в секторах с самыми младшими доступными логическими номерами, начиная со стороны 0, дорожки 0, сектора 2 (сектор 1 также занят записью начальной загрузки). Число секторов, занимаемых FAT определяется размером и типом диска. Отметим, что в MS DOS 3.0 размер записи FAT может быть 16 битов для фиксированного диска. Здесь мы будем рассматри- вать только 12-битные записи; для получения информации о 16-бито- вых записях, смотрите Техническое руководство по MS DOS. Таблица размещения файлов хранит информацию о каждом кластере секторов на диске. Кластер это группа стандартных секторов разме- ром 512 байт (независимо от типа диска MS DOS всегда работает с 512-байтными секторами). Группа секторов используется, чтобы уменьшить размер FAT. Однако большие кластеры, используемые на фиксированном диске напрасно расходуют дисковое пространство при записи маленьких файлов (утилита размером 500 байт берет 4K дис- кового пространства). Имеется набор размеров кластеров и размеров FAT, используемых в IBM PC: Тип диска Секторов на кластер Размер FAT дискета 160K 1 1 дискета 180K 1 1 дискета 320K 2 2 дискета 360K 2 2 дискета 1.2M 1 7 винчестер 10M 8 8 винчестер 20M 4 40 При большем размере кластера напрасно расходуется дисковое пространство, но когда большие диски имеют малый размер кластера, то таблица размещения файлов становится слишком большой. При работе с дисками DOS загружает копию FAT в память, по возможности сохраняя ее там, поэтому при большом размере FAT может расходо- ваться много оперативной памяти. Поскольку большинство AT имеют достаточно много памяти, то для них приемлемы намного большие FAT. Поэтому для 20M винчестера взяты меньшие размеры кластеров, чем для 10M, обеспечивая экономию дискового пространства. Для дискет емкостью 1.2M выбран кластер размером в 1 сектор, так как их основное назначение состоит в хранении копий жесткого диска, а следовательно компактность очень важна. Каждая позиция в таблице размещения файлов соответствует опре- деленной позиции кластера на диске. Обычно файл занимает несколь- ко кластеров и запись в каталоге файлов содержит номер стартового кластера, в котором записано начало файла. Просмотрев позицию FAT, соответствующую первому кластеру, DOS находит номер класте- ра, в котором хранится следующая порция этого файла. Этому клас- теру соответствует своя запись в FAT, которая в свою очередь содержит номер следующего кластера в цепочке. Для последнего кластера, занятого файлом FAT содержит значения от FF8H до FFFH. Неиспользуемым (или освобожденным) кластерам записывается значе- ние 000, а плохим секторам - FF7H. Наконец, значения от FF0H до FF7H приписываются резервным кластерам. Номер кластера содержит 3 шестнадцатиричные цифры, для хране- ния которых требуется 1 1/2 байта. Для уменьшения размеров FAT числа для двух соседних кластеров хранятся в трех последователь- ных байтах таблицы. MS DOS автоматически производит все необходи- мые вычисления. Первые три байта FAT не используются для номеров кластеров. Первый байт содержит код, определяющий тип диска (см. [1.1.5]), а следующие 2 байта оба равны FFH. Поскольку эти позиции таблицы заняты, то кластеры нумеруются, начиная с 2, причем кластеры 2 и 3 занимают вторую тройку байт таблицы. MS DOS 3.0 может создавать FAT с записями размером 16 бит. Такие записи необходимы для фиксированных дисков размером более 10M, которые имеют больше, чем 4086 кластеров. На рис. 5-1 пока- зана связь между FAT и кластерами на диске. Очень редко имеются причины вносить изменения прямо в таблицу размещения файлов. MS DOS заботится обо всех файловых операциях и обеспечивает процедуры, анализирующие таблицу на предмет наличия свободного пространства на диске. Однако для некоторых специаль- ных целей, таких как восстановление удаленных файлов или написа- ние драйвера блочного устройства, необходим прямой доступ к FAT. При прямом доступе к FAT надо соблюдать следующие правила. Для нахождения следующего кластера файла: 1. Умножьте номер кластера на 1.5. 2. Прочитайте 2 байта с полученным смещением (окгругляя вниз). 3. Если номер кластера четный, то возьмите младшие 12 бит, иначе возьмите старшие 12 бит. Для преобразования номера кластера в логический номер сектора: 1. Вычтите 2 из номера кластера. 2. Умножьте результат на число секторов в кластере. Высокий уровень. В данном примере читается FAT и поределяется значение, храня- щееся для кластера номер 6. В [5.4.2] объясняется начальный код, читающий сектора FAT. Результатом является 12-битное число, представленное в виде трех шестнадцатиричных цифр (4 бита каж- дая), возвращаемое в виде строки. В примере пары чисел, состоящих из двух цифр, объединены и в качестве результата берутся правые или левые три цифры. Когда Бейсик преобразует символ в 16-ную форму, то он возвращает только одну цифру, если первая была ну- лем, поэтому удаленный ноль должен быть восстановлен, чтобы этот метод работал правильно. 100 '''чтение секторов FAT 110 DEFINT A-Z 120 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B 130 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76 140 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C 150 DATA &H8E, &HD8, &H8B, &HC3, &HBB, &H00, &H00, &HCD 160 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00 170 DEF SEG = &H1000 'помещаем машинный код с этого адреса 180 FOR N = 0 TO 38 'читаем 39 байтов данных 190 READ Q: POKE N,Q 'переносим их в память 200 NEXT ' 210 READSECTOR = 0 'начинаем процедуру с 1-го байта 220 BUFFER = &H2000 'адрес буфера приема данных 230 LOGICALNUMBER = 1 'начальные сектора FAT 240 NUMBERSECTORS = 2 '2 сектора в FAT 250 DRIVE = 0 'читаем накопитель A 260 CALL READSECTOR(BUFFER,LOGICALNUMBER,NUMBERSECTORS,DRIVE) 270 '''определяем номер следующего кластера файла 280 DEF SEG = &H2000 'буфер, где хранится FAT 290 CLUSTERNUMBER! = 6 'кластер номер 6 300 C! = CLUSTERNUMBER! 'делаем копию 310 C! = INT (C!*1.5) 'умножаем на 1.5 и округляем 320 X = PEEK(C!) 'читаем 2 байта с этой позиции 330 Y = PEEK(C!+1) ' 340 X$ = HEX$(X): Y$ = HEX$(Y) 'переводим в 16-ные строки 350 IF LEN(X$) = 1 THEN X$ = "0"+X$ 'делаем из 2-символьными 360 IF LEN(Y$) = 1 THEN Y$ = "0"+Y$ ' 370 H$ = Y$ + X$ 'объединяем числа в одну строку 380 '''проверяем кластер на четность 390 IF CLUSTERNUMBER! MOD 2 <> 0 THEN 420 'уход, если нечетный 400 NEXTCLUSTER$ = RIGHT$(H$,3) 'если четный, то правые 3 цифры 410 GOTO 430 420 NEXTCLUSYER$ = LEFT$(H$,3) 'если нечетный, то левые 430 PRINT NEXTCLUSTER$ 'печатаем результат Средний уровень. Функция DOS 1CH дает информацию о таблице размещения файлов, но не дает саму FAT. Поместите номер накопителя в DL, где 0 = накопитель по умолчанию, 1 = A, и т.д. При возврате DX содержит число кластеров в FAT, а CX - число байтов в секторе. DS:BX ука- зывает на байт, содержащий первый байт FAT, т.е. на код, указы- вающий тип диска; эти коды перечислены в [1.1.5]. Низкий уровень. Намного легче получить доступ к FAT в языке ассемблера. Отме- тим, что умножение номера кластера на 1.5 производится копирова- нием числа, сдвигом копии вправо на 1 бит для деления пополам и сложением копии с оригиналом. Этот метод автоматически окгруляет результат вниз. Код, считывающий сектора FAT в память, обсуждает- ся в [5.4.2]. ;---в сегменте данных BUFFER DB 1024 DUP(0) ;отводим место для 2 секторов ;---читаем FAT в память LEA BX,BUFFER ;указываем на буфер данных MOV DX,1 ;логический номер сектора MOV CX,2 ;2 сектора MOV AL,0 ;накопитель A INT 25H ;читаем сектора POP CX ;восстанавливаем стек ;---получаем номер кластера MOV AX,3 ;номер кластера в AX MOV CX,AX ;делаем копию MOV DX,AX ;делаем вторую копию SHR DX,1 ;делим вторую копию на 2 ADD CX,DX ;складываем между собой ADD BX,CX ;добавляем как смещение MOV DX,[BX] ;получаем 2 байта из этого места TEST AX,1 ;номер кластера нечетный? JNZ ODD_CLUSTER ;уход, если да AND DX,0000111111111111B ;получаем номер JMP SHORT CONTINUE ;уход через обработку нечетного ODD_CLUSTER: MOV CL,4 ;подготовка к сдвигу вправо SHR DX,CL ;сдвигаем вниз старшие 12 битов CONTINUE: 5.1.2 Определение доступного дискового пространства. Хотя в следующем подразделе объянено как восстановить ситуаци- цию при ошибке из-за нехватки места на диске, но нет лучшего лекарства, чем предусмотрительность. Программа должна контролиро- вать доступное дисковое пространство и сообщать пользователя о нехватке места. Если места не хватает, то пользователь может выйти из программы и устранить проблему без потери информации. Высокий уровень. Следующая ассемблерная подпрограмма возвращает в переменную CLUSTERS число свободных кластеров на указанном диске. Надо по- местить номер накопителя в DRIVENUM, где 1 = A, 2 = B и т.д. В приложении Г объясняется как ассемблерные подпрограммы включаются в программы на Бейсике. 10 DEFINT A-Z 'используем целые переменные 20 DRIVENUM = 1 'сюда помещаем номер накопителя 30 CLUSTERS = 0 'инициализируем переменную 40 DATA &H55, &H8B, &HEC, &H8B, &H76, &H06, &H8B 50 DATA &H14, &HB4, &H36, &HCD, &H21, &H8B, &H7E 60 DATA &H08, &H89, &H1D, &H5D, &HCA, &H04, &H00 70 DEF SEG = &H1000 'помещаем подпрограмму 80 FOR N = 0 TO 20 'берем каждый байт 90 READ Q: POKE N,Q 'читаем его и помещаем в память 100 NEXT ' 110 FREESPACE = 0 'указатель на начало процедуры 120 CALL FREESPACE(CLUSTERS,DRIVENUM) 'вызов процедуры 130 PRINT "CLUSTERS: ";CLUSTERS 'печать числа кластеров Средний уровень. Функция 36H прерывания 21H сообщает сколько имеется свободного пространства на диске. Единственный входной регистр DL, который должен содержать номер накопителя. Накопитель по умолчанию обоз- начается 0, накопитель A - 1 и т.д. При возврате BX содержит число доступных кластеров, AX - число секторов в кластере, а CX - количество байт в секторе. Небольшое упражнение в умножении дает желаемый результат. В следующем примере проверяется, что на двухсторонней дискете осталось по меньшей мере 2K дискового пространства: MOV AH,36H ;номер функции MOV DL,1 ;накопитель A INT 21H ;получаем информацию CMP BX,2 ;имеется ли 2 свободных кластера? JL RUNNING_OUT ;если нет, то сообщаем об этом 5.1.3 Получение/установка размера файла. Программа может пожелать проверить размер файла по разным причинам. Одна из возможных причин состоит в определении числа записей, содержащихся в файле. Другая - в определении позиции конца файла, с тем чтобы файловый указатель был установлен верно для добавления в файл новых данных, без изменения существующих. Конечно, размер файла устанавливается автоматически функцией DOS. Иногда программа может нуждаться в резервировании дискового пространства для дальнейшего использования. В этом случае надо открыть файл в режиме прямого доступа и записать такой номер записи, чтобы файл имел достаточную длину. Записи между "фиктив- ной" и реально относящимися к файлу будут заполнены теми данными, которые случайно окажутся в дисковых секторах, отведенных для файла при этой операции. Высокий уровень. В Бейсике функция LOF (длина файла) возвращает точное число байтов, отведенных файлу (предупреждаем, однако, что старые вер- сии Бейсика - 1.х - возвращают число 128-байтных блоков, исполь- зуемых файлом). Файл должен быть открыт и ссылаться на него надо по номеру, под которым был открыт файл. Формат X = LOF(1). В следующем примере определяется сколько 64-байтных записей содер- жится в файле, открытом как #3: 100 OPEN "FILENAME" AS #3 'открываем файл 110 RECORDLEN = 64 'определяем длину записи 120 NUMBREC = LOF(3)/RECORDLEN 'вычисляем число записей Средний уровень. FCB функция 23H прерывания 21H сообщает число записей в файле. Если приписать файлу длину записи в 1 байт, то его размер будет возвращен в байтах. DS:DX должны указывать на управляющий блок открытого файла. Затем вызовите функцию. Если файл не найден, то в AL возвращается FF. В противном случае в AL возвращается 0, а число записей помещается в поле номера записи прямого доступа FCB (байты 33-36). Для правильной работы поле длины записи FCB должно быть установлено после открытия файла, но перед вызовом функции; это двухбайтное поле расположено по смещению 14 в FCB. Если раз- мер файла неточно делится на длину записи, то сообщаемое число записей округляется вверх. Вот пример, в котором используется длина записи равная 1: ;---определение размера файла LEA DX,FCB ;DS:DX указывает на FCB MOV BX,DX ;копируем указатель в BX MOV CX,1 ;размер записи в CX MOV [BX]+14,CX ;пишем в поле размера записи FCB MOV AH,23H ;функция сообщающая размер файла INT 21H ;вызов функции MOV AX,[BX]+33 ;получаем младшую часть размера файла MOV CX,[BX]+35 ;получаем старшую часть размера файла Можно также устанавливать длину файла, используя управляющие блоки файла. Для этого надо использовать функцию записи блока с прямым доступом, которая обсуждается в [5.4.5]. У этой функции имеется частный случай, когда число записанных записей устанавли- вается равным нулю, то длина файла устанавливается равной числу записей, указанному в поле записи прямого доступа. Метод, использующий дескриптор файла (file handle) не имеет функции, которая непосредственно сообщала бы длину файла, однако имеется возможность вычислить размер, передвинув указатель файла с начала на конец файла. При открытии файла указатель файла авто- матически устанавливается на первый байт файла. Указатель файла перемещается функцией 42H прерывания 21H. Надо поместить в AL кодовое число 2, напраляющее указатель на конец файла. В BX дол- жен быть указан номер файла, а CX:DX содержит смещение от конца файла до позиции, в которую должен быть установлен указатель, поэтому поместите 0 в оба этих регистра. Затем вызовите функцию. При возврате DX:AX будет содержать новую позицию указателя, отно- сительно его предыдущей позиции - т.е. будет содержать длину файла (DX содержит старший байт). При возникновении ошибки будет установлен флаг переноса, а в AX будет возвращено 1, если непра- вилен номер функции и 6, если неправилен номер файла. Не забудьте затем снова вернуть указатель на начало файла, если это необходи- мо. Поместите 0 в AL, CX и DX и вызовите функцию снова. Вот при- мер: ;---открываем файл LEA DX,FILE_PATH ;DS:DX указывают на путь файла MOV AL,0 ;открываем для чтения MOV AH,3DH ;функция открытия файла INT 21H ;открываем его JC OPEN_ERROR ;проверка на ошибку MOV HANDLE,AX ;запоминаем номер файла ;---определяем длину файла MOV AH,42H ;функция перемещения указателя MOV AL,2 ;код установки на конец файла MOV BX,HANDLE ;номер файла в BX MOV CX,0 ;0 в CX и DX MOV DX,0 ; INT 21H ;сдвигаем указатель JC POINTER_ERROR ;ошибка? MOV FSIZE_HIGH,DX ;запоминаем размер файла MOV FSIZE_LOW,DX ; 5.1.4 Восстановление после ошибок, связанных с нехваткой пространства на диске. При попытке записи на полный диск может произойти крах прог- раммы. Часто легко избежать этого, даже в Бейсике, проверив пред- варительно наличие дискового пространства [5.1.2]. Однако, если ошибка произошла, то постарайтесь дать пользователю возможность исправить ее. Позвольте ему сохранить только часть данных или стереть какой-нибудь другой файл и повторить попытку. Или, еще более радикальное средство, позвольте пользователю вставить дру- гую дискету. Последний подход должен реализовываться с большой осторожностью. Сначала закройте все открытые файлы. Затем выдайте запрос на смену дискеты. После того, как пользователь сообщит, что новая дискета на месте, создайте новый файл и запишите туда данные. Высокий уровень. В Бейсике надо установить процедуру обработки ошибок, как показано в [7.2.5]. Если оператор Бейсика делает попытку писать на полный диск, то возвращается код ошибки #61. При этом управле- ние может быть передано процедуре обработки ошибок, которая ин- формирует пользователя о проблеме и позволяет ему справиться с ней, не теряя данных. 100 ON ERROR GOTO 5000 'разрешаем обработку ошибок . . 200 OPEN FNAME$ FOR OUTPUT AS #1 'открываем файл 210 FOR N = 1 TO ARRLEN 'начинаем писать массив на диск 220 PRINT #1, ARRAY$(N) 'записываем один элемент 230 NEXT ' . . 5000 IF ERR = 61 THEN 5100 'диск полон? 5100 IF ERR = ... 'другие ошибки ... . 5100 '''восстановление при переполнении диска 5110 BEEP: PRINT "Disk full - choose an option:" 5120 PRINT "(A) - Re-edit the file" 5130 PRINT "(B) - Delete some other file from disk" 5140 PRINT "(C) - Use different diskette" . (здесь идет процедура восстановления) . 5500 RESUME Средний уровень. Все функции DOS, которые пишут на диск, выдают определенный код ошибки при попытке записи на полный диск. Вот сводка этих кодов: Метод доступа Функция Название Код ошибки FCB 15H Последовательная запись AL = 1 FCB 22H Прямая запись AL = 1 FCB 27H Прямая запись блока AL = 1 Дескриптор 40H Запись в файл/устройство CX <> BX Проверяйте эти ошибочные условия после каждой записи на диск. Поскольку критической ошибки не происходит, то восстановление не вызывает проблем. Надо только проверять на ошибку каждый раз когда Вы вызываете одну из этих функций и создать хорошую проце- дуру обработки ошибок по Вашему вкусу. Раздел 2. Работа с каталогами диска. Каждый диск имеет один корневой каталог, с которого начинается поиск всех остальных каталогов. Корневой каталог может содержать элементы, указывающие на подкаталоги, которые в свою очередь могут содержать ссылки на другие подкаталоги, образуя древовидную структуру каталогов. Корневой каталог всегда расположен в опреде- ленных секторах диска; подкаталоги хранятся как обычные дисковые файлы, поэтому они могут быть расположены в любом месте диска. Отметим, что фиксированный диск может содержать до четырех корне- вых каталогов, если он разбит на разделы, хотя MS DOS "видит" только один корневой каталог. Каталоги могут иметь различные размеры, в зависимости от размера диска и его разбиения на разде- лы. В следующей таблице приведены размеры и позиции корневых каталогов для разных типов дисков: Тип диска Размер каталога Число элементов Начальный сектор дискета 160K 4 сектора 64 9 дискета 180K 4 сектора 64 9 дискета 320K 7 секторов 112 15 дискета 360K 7 секторов 112 15 дискета 1.2M 14 секторов 224 29 жесткий 10M ----------переменные------------ жесткий 20M ----------переменные------------ В зависимости от разбиения на разделы фиксированный диск может иметь различные размеры каталога и номер начального сектора. Если весь диск отведен для MS DOS, то на XT и AT под корневой каталог отводится 32 сектора, что позволяет иметь в нем 512 элементов. Как корневой каталог, так и подкаталоги, используют 32 байта для хранения информации об одном файле, независимо от типа диска. Таким образом в каждом секторе может храниться информация о 16-ти элементах каталога. Каждое 32-байтное поле разбито следующим образом: байты 0-7 Имя файла 8-10 Расширение файла 11 Атрибут файла 12-21 Зарезервировано 22-23 Время последнего доступа к файлу 24-25 Дата последнего доступа к файлу 26-27 Начальный кластер 28-31 Размер файла Точка между именем файла и его 3-байтным расширением не хранится. Все поля выравнены на левую границу, а пустые байты заполняются пробелами (код ASCII 32). Атрибут файла определяет является ли файл спрятанным, защищенным от записи и т.д. [5.2.6]. Он опред- ляет также специальные элементы каталога, такие как подкаталоги или метка тома. Информация о времени и дате упакована, поэтому для чтения этих значений требуются битовые операции [5.2.5]. Начальный кластер указывает на позицию в таблице размещения файлов (FAT), которая обсуждалась в [5.1.1]. FAT хранит информа- цию о свободном пространстве на диске, а также отводит сектора при записи файла. FAT отводит дисковое пространство порциями, большими чем 1 сектор, которые называются кластерами. Файл распо- ложен в цепочке кластеров и FAT содержит соответствующую цепочку элементов, указывающих, где эти кластеры расположены на диске. Каталог должен указывать на начальное звено цепочки элементов файла в FAT, и эта информация содержится в поле начальный номер кластера. Поскольку файл обычно занимает последний отведенный ему кластер не целиком, то поле размер файла хранит точную длину файла в байтах. 5.2.1 Чтение/изменение корневого каталога. Каталоги диска подразделяются на корневой каталог (обсуждаемый здесь) и подкаталоги (обсуждаемые в [5.2.3]). Когда пользователь программы вводит имя какого-либо файла для работы, то бывает полезным проверить, имеется ли этот файл на самом деле. Обычно изменения в корневом каталоге производятся в ходе обычных файло- вых операций или с помощью специальных функций DOS. Однако можно работать с каталогом напрямую. Большая нужда в таком подходе возникает при работе на языках высокого уровня, где утилиты DOS по большей части недоступны. Корневой каталог читается и изменяется загрузкой его в память с использованием подхода, показанного в [5.4.2], когда читаются абсолютные сектора диска. Эти операции не оставляют места между секторами, когда они загружаются в память. Буфер, содержащий данные сектора, может рассматриваться как набор 32-байтных полей и пара указателей, которые могут использоваться для движения по каталогу. Один указатель всегда кратен 32 и указывает на начало элемента каталога. Второй указатель добавляется к первому и ука- зывает на одно из полей в 32-байтном элементе. Данные в памяти могут быть изменены требуемым образом, а затем весь буфер записы- вается обратно на диск. Имеется два метода чтения абсолютных секторов диска и в обоих случаях только одно число отличает случаи чтения и записи. Пос- кольку ошибка при записи на диск может легко повредить все содер- жимое диска, то надо действовать аккуратно. Сначала убедитесь, что операция чтения сектора выполнена верно во всех отношениях. После этого можно попробовать записать на диск, взяв точную копию кода, использованного для чтения и заменив только номер функции. Высокий уровень. Бейсик выводит каталог по команде FILES. При этом выводятся только имена файлов. FILES дает каталог накопителя по умолчанию; для указания накопителя напишите FILES "A:" и т.д. Можно потребо- вать, чтобы была выведена информация об отдельном файле, написав FILES "A:MYFILE.DAT". Как и в операционной системе имя файла может содержать * и ?. Оператор FILES снабжает информацией поль- зователя, но иногда наличие некоторого файла хочет проверить программа. В этом случае надо открыть файл для последовательного чтения и если он не существует, то возникнет ошибочная ситуация. Смотрите обсуждение и пример в [5.2.3]. Для поиска любой информации, относящейся к корневому каталогу, используйте процедуру на машинном языке, приведенную в [5.4.2]. После того как данные каталога в памяти, установите указатели, как описано выше, и ведите поиск по буферу памяти с 32-байтным интервалом. Нижеприведенный пример ищет элемент каталога, относя- щийся к стертому файлу. Когда файл стирается, то первый байт имени файла заменяется на E5H, но все остальное содержимое данно- го элемента остается неизменным. Конечно, при этом освобождается дисковое пространство, отведенное файлу в FAT. Процедура восста- новления удаленного файла должна знать номер начального кластера в FAT. В примере этот 2-байтный номер кластера помещается со смещением 26 в элементе каталога. 100 '''чтение секторов каталога в память с сегмента &H2000 110 INPUT "Enter erased filename ", FNAME$ 120 IF LEN(FNAME$) > 12 THEN BEEP: GOTO 110 130 IF INSTR(FNAME$,".") > 9 THEN BEEP: GOTO 110 140 '''дополнение имени и расширения файла нулями 150 Y = INSTR(FNAME$,".") 160 IF Y = 0 THEN FIRSTPART$ = FNAME$: GOTO 230 170 EXTEN$ = LEFT$(FNAME$, LEN(FNAME$) - Y) 180 EXTEN$ = EXTEN$ + STRING$(3 - LEN(EXTEN$),"") 190 FIRSTPART$ = RIGHT$(FNAME$,Y - 1) 200 FIRSTPART$ = FIRSTPART$ + STRING$(8 - LEN(FIRSTPART$),"") 210 FNAME$ = FIRSTPART$ + EXTEN$ 220 '''теперь хотим найти удаленный файл 230 MID$(FNAME$,1,1) = CHR$(&HE5) 'заменяем первый символ 240 DIRPTR = 0 'указатель на элемент 250 FIELDPTR = 26 'указатель на номер кластера 260 FOR N = 1 TO 112 'на дискете 112 элементов 270 X$ = "" 'чистим X$ 280 FOR M = 0 TO 10 'читаем имя файла из каталога 290 X$ = X$ + PEEK(DIRPTR + M) 'берем по символу 300 NEXT ' 310 IF X$ = FNAME$ THEN 340 'совпадает с введенной строкой 320 NEXT 'если нет, то следующий 330 PRINT "Too late - file entry obliterated": END 'уже нет 340 X = PEEK(DIRPTR + FIELDPTR) 'нашли его, берем 1-й байт и 350 Y = PEEK(DIRPTR + FIELDPTR + 1) '2-й байт номера кластера 360 Z = X + 256*Y 'теперь номер кластера в Z Средний уровень. MS DOS обеспечивает две пары функций для поиска файлов, одна для файлов, открытых методом управляющих блоков файла, а другая - для файлов, открытых методом дескриптора файлов. Одна из функций каждой пары ищет первое появление имени файла в каталоге, а дру- гая ищет последующие появления, когда в имени файла содержатся джокеры. Только метод, использующий дескриптор файла позволяет искать подкаталоги. Метод FCB: Функция 11H прерывания 21H ищет первое появление файла. Уста- новите DS:DX на неоткрытый FCB и выполните функцию. При возврате AL будет содержать 0, если файл найден, и FF - если нет. DTA заполняется информацией из каталога. Для обычных FCB первый байт DTA содержит номер накопителя (1 = A и т.д.), а следующие 32 байта содержат элемент каталога. Для расширенного FCB первые 7 байтов файла копируются в первые 7 байтов расширенного FCB, вось- мой байт указывает на накопитель, а следующие 32 байта - элемент каталога. ;---в сегменте данных FCB DB 1,'NEWDATABAK',25DUP(0) ;---ищем файл MOV AH,11H ;функция поиска в каталоге LEA DX,FCB ;указываем на FCB INT 21H ;ищем CMP AL,0 ;успешно? JNE NO_FILE ;если нет, то процедура обработки ошибки LEA BX,DTA ;теперь DS:BX указывает на элемент каталога После использования функции 11H можно использовать функцию 12H для поиска следующих подходящих элементов, когда имя файла содер- жит джокеры. В данном случае в имени файла допустим только символ "?", но не "*". Эта функция работает в точности так же, как и первая, и если найден второй файл, то информация о первом файле в DTA будет уничтожена повторной записью. Метод дескриптора файлов: Функция 4EH прерывания 21H ищет файл с данным именем. DS:DX должны указывать на строку, дающую путь файла. Например, B:\EURO- PE\FRANCE\PARIS указывает на файл PARIS. Строка может содержать до 63 символов и завершаться символом ASCII 0. Имя файла может содержать джокеры, включая как "?", так и "*". Поместите атрибут файла в CX; если он обычный то 0, в противном случае проконсуль- тируйтесь в [5.2.6] относительно значений атрибута. При возврает устанавливается флаг переноса, если файл не най- ден. Если файл найден, то функция заполняет DTA информацией о файле. Отметим частный случай использования DTA методом дескрип- тора файлов - обычно, DTA используется функциями MS DOS для рабо- ты через FCB. Первые 21 байт DTA зарезервированы DOS для поиска следующих совпадающих файлов. Двадцать второй байт дает атрибут файла, за ним следуют два байта, содержащие время и еще два байта содержащие дату. Следующие 4 байта содержат размер файла (младшее слово сначала). И, наконец, дается имя файла в виде строки пере- менной длины, заканчивающейся байтом ASCII 0. Точка (ASCII 46) разделяет имя и расширение и не один из этих элементов не запол- нен пробелами. ;---в сегменте данных PATH DB 'B:FRANCE\PARIS\4EME',0 ;---ищем файл MOV AH,4EH ;номер функции LEA DX,PATH ;DS:DX указывают на путь MOV CX,0 ;обычный атрибут файла INT 21H ;ищем файл JC NO_FILE ;уход, если не найден LEA BX,DTA ;DS:BX указывают на DTA MOV AL,[BX]+21 ;теперь атрибут файла в AL Следующее появление имени файла (когда используются джокеры) ищется с помощью функции 4FH прерывания 21H. Она готовится в точности так же, как и функция 4EH, при этом указатель DTA не должен меняться. Когда других совпадений не найдено, то устанав- ливается флаг переноса, а в AX появляется 18. 5.2.2 Создание/удаление подкаталога. Программа может создавать или удалять подкаталоги, при выпол- нении некоторых условий. Для создания подкаталога необходимо, чтобы было по крайней мере одно пустое место в корневом каталоге. Для удаления подкаталога необходимо, чтобы он не содержал файлов или ссылок на другие подкаталоги. Кроме того, Вы не можете уда- лить подкаталог, который является Вашим текущим каталогом (тот, с которым по умолчанию выполняются все операции над каталогами). Отметим также, что невозможно удалить корневой каталог. Высокий уровень. Бейсик предоставляет команды MKDIR (создай каталог) и RMDIR (удали каталог). За обеими должны следовать стандартные пути указания каталога, содержащие до 63 символов, включая имя накопи- теля. Путь должен быть заключен в кавычки. Чтобы добавить подка- талог с именем STORKS в подкаталог BIRDS напишите MKDIR "B:MAM- MALS\BIRDS\STORKS". После выполнения этой команды будет создан файл STORKS, используемый как подкаталог и факт его существования будет отражен в создании элемента с именем STORKS в подкаталоге с именем BIRDS. Для удаления этого подкаталога надо сначала удалить из него все файлы [5.3.2]. Затем надо использовать команду RMDIR "B:MAMMALS\BIRDS\STORKS". В этих примерах предполагалось, что Вашим текущим каталогом являлся корневой каталог. Однако, если Ваш текущий каталог нахо- дится где-то на пути к подкаталогу, над которым осуществляются операции, то нет необходимости указывать весь путь. Поэтому, если Вашим текущим каталогом является BIRDS, то для создания или уда- ления подкаталога STORKS можно использовать команды MKDIR "\STORKS" или RMDIR "\STORKS". Средний уровень. Поскольку управляющие блоки файлов обслуживают только корневой каталог, то для создания или удаления подкаталога надо использо- вать дескрипторы файлов. Создание подкаталога: DS:DX должны указывать на строку, дающую накопитель и путь к каталогу, в котором должен быть создан подкаталог. Строка должна завершаться байтом ASCII 0. Для открытия подкаталога с именем PRIMATES в корневом каталоге накопителя A: надо записать строку в виде "A:\PRIMATES". Для открытия подкаталога в другом подкаталоге с именем MAMMALS напишите "A:\MAMMALS\PRIMATES". Имя накопителя A: может быть опущено если Вы работаете с накопителем, используе- мым по умолчанию, и путь может начинаться с текущего каталога. Поместите в AH 39H и выполните прерывание 21H; если указан пра- вильный путь, то будет создан новый каталог. В противном случае будет установлен флаг переноса, а AX будет содержать код ошибки 3 (путь неверен) или 5 (нет доступа). В примере создается подката- лог PRIMATES: ;---в сегменте данных PATH DB 'A:MAMMALS\PRIMATES',0 ;---создаем подкаталог с именем PRIMATES LEA DX,PATH ;DS:DX должны указывать на путь MOV AH,39H ;номер функции INT 21H ;создаем подкаталог JC ERROR_ROUT ;обработка ошибок Удаление подкаталога: Для удаления подкаталога надо сформировать строку, в точностью совпадающую с той, которую Вы указывали при создании каталога. Затем поместите в AH 3AH и выполните прерывание 21H. Опять при невыполнении функции в AX будут возвращены коды 3 или 5 (код 5 может указывать, что каталог непустой). 5.2.3 Чтение/изменение подкаталога. Подкаталоги во многом подобны корневому каталогу, за исключе- нием того, что они хранятся как обычные файлы, а не в заранее предопределенных секторах. Подкаталоги невозможно спутать с обыч- ными файлами, поскольку объект каталога, относящийся к подкатало- гу, имеет специальный байт атрибутов (с установленным битом 5 - см. [5.2.6]). Подкаталоги начинаются с двух специальных 32-байт- ных объектов, первый из которых имеет имя точка, а второй - две точки. Они ориентируют подкаталог среди окружающих каталогов. Ссылки на подкаталоги нижнего уровня записываются как обычные ссылки на файлы. Предполагается, что подкаталог может быть прочитан как любой другой файл, поэтому вроде бы не составляет труда загрузить его в память. Но, к сожалению, создатели MS DOS поместили 0 в поле длины файла для элементов, относящихся к подкаталогам. В резуль- тате DOS считает, что этот файл имеет нулевую длину и отказывает- ся читать его. Нет простого способа преодолеть эту проблему. Высокий уровень. В Бейсике команда FILES может использовать стандартные имена путей для вывода подкаталога; например, FILES "B:MAMMALS\BIRDS" выводит все файлы, содержащиеся в подкаталоге BIRDS. Эта команда может быть использована и для получения информации о наличии в каталоге определенного файла. Например, FILES "LEVEL1\NEWDATA" ищет файл NEWDATA и выводит его имя, если он найден. Хотя это может быть полезным для пользователя, но часто самой программе необходимо знать существует или нет указанный файл. Чтобы устано- вить это попытайтесь открыть файл для последовательного чтения. Если он не будет найден, то возникнет ошибочное условие 63. Соз- дайте процедуру обработки ошибок, как описано в [5.4.8]. Затем используйте переменную, чтобы отметить был ли найден требуемый файл (в нашем примере переменная "EXISTS"). Если программе не нужно, что этот файл был открыт, то закройте его перед тем как двинуться дальше. 100 ON ERROR GOTO 1000 'процедура обработки ошибок 110 EXISTS = 1 'начальное значение "флага" 120 INPUT "Enter filename: ",S$ 'запрос имени файла 130 OPEN S$ FOR INPUT AS #3 'открываем его для послед. чтения 140 IF EXISTS = 0 THEN BEEP: PRINT "File does not exist" . . 1000 IF ERR = 53 THEN 1500 'файл не существует? 1010 IF ERR = 64 THEN ... 'другие ошибки . 1500 EXISTS = 0 'меняем значение флага 1510 RESUME 140 'продолжаем выполнение программы Средний уровень. Функции работы через дескрипторы файлов, которые использова- лись для доступа к корневому каталогу [5.2.1] могут так же просто обращаться к любому подкаталогу. Чтобы вывести все содержимое каталога надо просто использовать функцию 4EH для поиска файлов *.*, а затем повторять поиск, используя функцию 4FH. Когда больше не будет файлов, то будет установлен флаг переноса, а AL будет содержать 18. Каждый раз, когда будет обнаружен очередной эле- мент, в DTA будет записана информация о файле, включая полный его путь (отмечаем использование DTA в функциях, использующих деск- риптор файла). Следующий пример выводит полные пути всех обычных файлов подкаталога. ;---в сегменте данных PATH DB 'A:MAMMALS\*.*',0 DTAH DB 256 DUP(?) ;---установка DTA LEA DX,DTA ;DS:DX указывают на DTA MOV AH,1AH ;функция установки DTA INT 21H ;устанавливаем DTA ;---ищем первый файл MOV AH,4EH ;номер функции LEA DX,PATH ;указываем на строку пути MOV CX,0 ;только нормальные атрибуты INT 21H ;ищем *.* JC ERROR ;обработка ошибок ;---выводим имя файла NEXT_LINE: LEA BX,DTA ;BX указывает на DTA ADD BX,30 ;смещение для имени файла NEXT_CHAR: MOV DL,[BX] ;получаем символ из имени CMP DL,0 ;проверка на конец строки JE END_STR ;уход, если конец MOV AH,2 ;иначе, выодим символ INT 21H ; INC BX ;увеличиваем указатель JMP SHORT NEXT_CHAR ;следующий символ ;---возврат каретки/перевод строки в конце строки END_STR: MOV AH,2 ;функция вывода символа MOV DL,13 ;код возврата каретки INT 21H ;выводим MOV DL,10 ;код перевода строки INT 21H ;выводим ;---ищем следующий файл LEA DX,PATH ;указываем на строку пути MOV AH,4FH ;номер функции INT 21H ;ищем следующий файл JC FINISHED ;если нет, то выход JMP SHORT NEXT_LINE ;иначе выводим имя файла FINISHED: 5.2.4 Получение/установка текущего каталога. Текущий каталог это каталог, в котором DOS ищет файл, для которого не указан путь. Если не установлено противного, то теку- щий каталог является корневым каталогом. Высокий уровень. Бейсик устанавливает текущий каталог с помощью команды CHDIR. За командой должна следовать строка, указывающая путь к каталогу, на который надо перейти. Строка может содержать до 63-х символов, включая имя накопителя, и должна быть заключена в кавычки. CHDIR "C:MAMMALS\PRIMATES\GIBBONS" делает подкталог GIBBONS текущим каталогом. Чтобы перейти в корневой каталог напишите CHDIR "\" или CHDIR "B:\". Бейсик версии 3.0 может сообщать путь к текущему каталогу, как это делает команда DOS PATH. Просто введите PRINT ENVI- RON$("PATH"). Средний уровень. Функция 3BH прерывания 21H устанавливает текущий каталог. DS:DX должны указывать на путь к каталогу в стандартном виде и эта строка должна завершаться байтом ASCII 0. Например, B:BIRDS\- PARROTS\POLLY делает POLLY текущим каталогом. B: может быть опу- щено, если это текущий накопитель по умолчанию [5.3.1]. Чтобы сделать текущим корневой каталог накопителя A: напишите A:\. В примере текущим каталогом устанавливается POLLY: ;---в сегменте данных PATH DB 'B:BIRDS\PARROTS\POLLY',0 ;---делаем POLLY текущим каталогом MOV AH,3BH ;номер функции LEA DX,PATH ;DS:DX должны указывать на путь INT 21H ;устанавливаем текущий каталог Чтобы определить какой каталог является текущим надо использо- вать функцию 47H прерывания 21H. DS:SI должны указывать на об- ласть данных размером 64 байта, в которую будет записан путь. В DL указывается накопитель, причем 0 = "по умолчанию", 1 = A, 2 = B и т.д. При возврате функция возвращает строку без имени накопи- теля. Если был указан несуществующий накопитель, то в AL возвра- щается код ошибки 15. Строка начинается с имени первого подката- лога цепочки, а не с обратной косой черты. Байт ASCII 0 сигнали- зирует о конце строки. В данном примере имя текущего каталога присваивается переменной "CURRENT_DIR": ;---в сегменте данных CURRENT_DIR DB 64 DUP(?) ;---получить текущий каталог MOV AH,47H ;номер функции LEA SI,CURRENT_DIR ;указываем на область данных MOV DL,1 ;накопитель A INT 21H ;помещает строку по адресу DS:SI 5.2.5 Получение/установка времени и даты последнего доступа к файлу. Если отсчитывать от нуля, то байты 22-23 32-байтного элемента каталога содержат время последнего доступа к файлу. Байты 24-25 - содержат дату. Значение битов следующее: Время: биты 11-15 часы (0-23) 5-10 минуты (0-59) 0-4 секунды (0-29 с 2-секундным интервалом) Дата: биты 9-15 год (0-119, смещение с 1980 года) 5-8 месяц (1-12) 0-4 число (1-31) День недели не записывается; DOS вычисляет его по остальной ин- формации. Отметим также, что как всегда, младший байт этих 2-байтных значений расположен раньше в памяти, чем старший. Средний уровень. Метод доступа к файлу с использованием управляющего блока файла позволяет получить дату последнего доступа к файлу, но не время. Когда FCB открывается функцией 0FH прерывания 21H, то заполняется двухбайтное поле даты в вышеприведенном формате. Это поле расположено в FCB со смещением 14H [5.3.5]. С другой стороны, доступ к файлу с помощью дескриптора файла позволяет как получить, так и установить дату и время последнего доступа к файлу. Функция 57H прерывания 21H выполняет все опера- ции. Для установки времени и даты поместите номер файла в BX, и 0 в AL. Для получения даты и времени надо поместить в AL 1. В обоих случаях дата содержится в DX, а время в CX. Значение битов совпа- дает с тем, что описано в таблице. В техническом руководстве по MS DOS утверждается, что младшие байты информации находятся в CH и DH, и наоборот. На самом деле это не так. При возникновении ошибки устанавливается флаг переноса, а в AX возвращается 1, если в AL указано неправильное число и 6, если плохой дескриптор фай- ла. В следующем примере определяется час, в который был последний лоступ к файлу: ;---в сегменте данных PATH DB 'B:NEWDATA.BAK',0 ;---открываем файл LEA DX,PATH ;указываем на строку пути MOV AH,3DH ;функция открытия файла MOV AL,0 ;открываем для чтения INT 21H ;открываем файл JC OPEN_ERROR ;переход на обработку ошибки ;---получаем дату и время доступа к файлу MOV BX,AX ;помещаем номер файла в BX MOV AL,0 ;код для чтения времени MOV AH,57H ;номер функции INT 21H ;получаем время доступа JC TIME_ERROR ;переход на обработку ошибок ;---сдвигаем биты, относящиеся к часам, в начало CH MOV CL,3 ;готовим сдвиг SHR CH,CL ;теперь CH содержит час доступа 5.2.6 Спрятанные и защищенные от записи файлы. DOS использует шесть различных атрибутов файлов, которые дают данному файлу определенный статус. Файл может иметь несколько из этих атрибутов одновременно (но не все). Атрибуты устанавливаются 12-м байтом 32-байтного элемента каталога. Младшие шесть битов имеют значение, а остальные должны быть равны нулю. Биты такие: если бит 5 = 1, то файл был изменен со времени последней архивации 4 = 1, то файл является подкаталогом 3 = 1, то этот элемент является не файлом, а меткой тома 2 = 1, то файл является "системным" 1 = 1, то файл спрятан при поиске по каталогу 0 = 1, то файл объявлен только для чтения Бит 5 это архивный бит, используемый программами BACKUP и RESTORE DOS. Этот бит сьрасывается в 0 после архивации и устанавливается, когда с файлом снова работали. При следующей архивации неизменен- ные файлы могут быть обнаружены и проигнорированы. Высокий уровень. Бейсик не позволяет Вам устанавливать атрибуты файла прямо. Справьтесь в [5.2.1], как считать каталог в память, найти нужный файл, сделать изменения и снова записать его на диск. Как только каталог помещается в память, байты атрибутов находятся по смеще- ниям 11, 43, 75 и т.д. Если нужно, то Вы можете прочитать текущие атрибуты и изменить только один бит, используя технику битовых операций, описанную в приложении Б. Но легче просто переписать все атрибуты заново. Будьте внимательны, ошибки могут быть фа- тальными. В данном примере считываются атрибуты файла с именем "NEWDATA.AAA". 100 'читаем сектора каталога, начиная с &H2000 и затем ... 110 DEF SEG = &H2000 'указываем на область каталога 120 FILENAME$ = "NEWDATAAAA" 'ищем имя файла без точки 130 DIRPTR = 0 'указатель в каталоге 140 FOR N = 1 TO 112 'проверяем все элементы 150 X$ = "" 'временная строка для имени файла 160 FOR M = 0 TO 10 'для каждого символа имени 170 X$ = X$+PEEK(DIRPTR+M) 'добавляем его к строке 180 NEXT ' 190 IF X$ = FILENAME$ THEN 220 'если имя найдено, то уходим 200 NEXT ' 210 PRINT "File not found": END 'нет такого файла 220 X = PEEK(DIRPTR+11) 'получаем атрибуты нужного файла 230 IF X AND 32 <> 0 THEN PRINT "File not baked up" 240 IF X AND 16 <> 0 THEN PRINT "File is a subdirectory" 250 IF X AND 8 <> 0 THEN PRINT "Volume label - not a file" 260 IF X AND 4 <> 0 THEN PRINT "File is a system file" 270 IF X AND 2 <> 0 THEN PRINT "File is a hidden file" 280 IF X AND 1 <> 0 THEN PRINT "File is read-only" Средний уровень. Функция 43H прерывания 21H может как находить, так и изменять атрибуты файла, но только если файл был открыт с помощью метода дескриптора файлов, а не с помощью метода управляющего блока файла. Нет аналогичной функции для FCB. Байт атрибутов может быть установлен при создании файла [5.3.2], используя расширенный управляющий блок файла. Но если Вы последовательно откроете FCB, измените установку атрибутов и затем закроете файл, то у него останутся первоначальные атрибуты. Хотя, конечно, Вы можете изме- нить атрибуты каким-нибудь обходным путем, но намного проще ис- пользовать функцию, использующую метод дескриптора файлов. Чтобы использовать функцию 43H, поместите 1 в AL, чтобы прис- воить файлу байт атрибутов, содержащийся в CX (на самом деле в CL, поскольку CH равен 0). Можно наоборот поместить в AL 0, чтобы в CX был возвращен текущий байт атрибутов файла. В обоих случаях DS:DX должны указывать на строку, дающую путь к файлу. Конец строки отмечается байтом ASCII 0 (который не входит в число 63-х символов). В примере статус "hidden" (спрятанный) присваивается файлу OVERDUE: ;---в сегменте данных PATH DB 'A:ACCOUNTS',0 ;---включаем признак спрятанного файла MOV AH,43H ;номер функции MOV AL,0 ;читаем байт атрибутов LEA DX,PATH ;DS:DX указывают на путь INT 21H ;байт атрибутов в CX JC ERROR_ROUTINE ;обработка ошибок OR CL,10B ;включаем бит 1 MOV AH,43H ;номер функции MOV AL,1 ;заменяем байт атрибутов INT 21H ;теперь файл стал спрятанным Флаг переноса устанавливается при возникновении ошибки. В этом случае в AX возвращается 2 - если файл не найден, 3 - если не найден путь и 5 - при других ошибках (нет доступа). 5.2.7 Чтение/изменение метки тома. Метка тома для дискеты - это элемент каталога, имеющий спе- циальный атрибут. Метка занимает первые 11 байтов элемента, отно- сящиеся к имени и расширению файла. Байт атрибутов по смещению 11 содержит значение 8 (бит 3 = 1). Поля времени и даты заполняются обычным образом. Одним из свойств этого атрибута является то, что данный элемент не выводится по команде DIR. Метка может занимать любую позицию в каталоге. Она ищется перебором всех байтов атрибутов, пока не будет найдено значение 8. Чтобы стереть метку надо просто поместить E5 в первый байт соответствующего элемента - сам байт атрибутов можно не менять. Чтобы изменить метку надо записать новые 11 символов (остаток надо заполнить пробелами). Чтобы присвоить метку тома диску, который не имел ее, надо найти пустое место в каталоге и записать туда метку и соответствующий атрибут, ничего больше не требуется. Высокий уровень. Обсуждение в [5.4.2] объясняет как читать и писать абсолютные сектора в Бейсике. Для стандартной двухсторонней дискеты надо использовать номер стороны 0, номер дорожки 0, номер сектора - 6 и число секторов для чтения/записи - 7. После того, как данные записаны в отведенный буфер, примеры, приведенные здесь могут быть использованы для изменения или добавления метки тома. Затем сектора должны быть перезаписаны на диск. Будьте внимательны: ошибка может привести к потере всей информации на диске. Данный пример ищет метку тома и изменяет ее: 100 'сектора загружены, начиная скажем с &H1000 110 DEF SEG = &H1000 120 DIRPTR = 11 'указатель на байт атрибутов 130 FOR N = 1 TO 112 'проверяем все элементы каталога 140 IF PEEK(DIRPTR) = 8 THEN 180 'уход если метка тома 150 DIRPTR = DIRPTR + 32 'указываем на след. элемент 160 NEXT 'проверяем его атрибут 170 PRINT "No volume label found": END 'метки нет 180 INPUT "Enter new volume label", V$ 'запрос метки 190 IF LEN(V$) > 11 THEN BEEP: PRINT "11 chars only": GOTO 180 200 V$ = V$ + STRING$(11-LEN(V$),32) 'дополняем пробелами 210 DIRPTR = DIRPTR - 11 'возвращаемся на начало элемента 220 FOR N = 1 TO LEN(V$) 'помещаем все символы метки 230 POKE N,MID$(V$,N,1) 'в память 240 NEXT ' 250 'теперь осталось перезаписать сектора на диск Низкий уровень. В нижеприведенном примере предполагается, что Вы создали буфер данных размером 3584 байт, для хранения всех семи секторов ката- лога дискеты емкостью 360K. Буфер называется DIR_AREA. В первом примере метка тома ищется и выводится, или, если она не найдена, то выводится сообщение об ее отсутствии. Для удобства область буфера для секторов отводится в сегменте данных; лучше отвести память для задачи, а затем освободить ее [1.3.1]. ;---в сегменте данных VOL_STRING DB 'The volume label is $' NO_LABEL DB 'There is no volume label $' DIR_AREA DB 3584 DUP(?) ;---читаем 7 секторов каталога MOV AX,SEG DIR_AREA ;сегмент буфера MOV ES,AX ; MOV BX,OFFSET DIR_AREA ;смещение буфера MOV DL,0 ;номер накопителя MOV DH,0 ;номер головки MOV CH,0 ;номер дорожки MOV CL,6 ;стартовый сектор MOV AL,7 ;число секторов каталога MOV AH,2 ;номер функции чтения INT 13H ;читаем каталог в память ;---ищем метку тома, сравнивая байт атрибутов с 8 MOV CX,112 ;число элементов ADD BX,11 ;смещение для атрибутов TRY_AGAIN: MOV AL,[BX] ;берем 1-й элемент CMP AL,8 ;это метка тома? JE GOT_IT ;если да, то уход ADD BX,32 ;иначе на след. элемент LOOP TRY_AGAIN ; ;---выводим сообщение об отсутствии метки тома MOV AH,9 ;функция вывода строки LEA DX,NO_LABEL ;указываем на строку INT 21H ;выводим ее JMP SHORT CONTINUE ;на конец ;---выводим строку, дающую метку тома GOT_IT: MOV AH,9 ;функция вывода строки LEA DX,VOL_STRING ;указываем на строку INT 21H ;выводим ее SUB BX,11 ;указатель на метку MOV CX,11 ;пишем 11 символов MOV AH,2 ;функция вывода символов NEXT_CHAR: MOV DL,[BX] ;символ в DL INT 21H ;выводим символ INC BX ;переходим к следующему LOOP NEXT_CHAR ; CONTINUE: Чтобы стереть метку поместите следующий код в GOT_IT: GOT_IT: MOV AL,0E5H ;код отметки пустого элемента SUB BX,11 ;указатель на начало элемента MOV [BX],AL ;меняем первый байт Чтобы изменить метку тома, надо вместо этого использовать в GOT_IT следующий код. Предполагается, что Вы подготовили где-то 11-байтную строку NEW_LABEL. GOT_IT: LEA SI,NEW_LABEL ;SI должен указывать на строку SUB BX,11 ;BX указывает на начало метки MOV DI,BX ;помещаем указатель в DI MOV CX,11 ;пересылка 11 символов REP MOVSB ;пересылаем строку Чтобы создать метку можно использовать тот же самый код, но надо также установить байт атрибутов равный 8 (Вы можете просто добавить ASCII 8 к строке, содержащей новую метку, так как байт атрибутов непосредственно следует за самой меткой). И, наконец, во всех случаях изменения каталога, необходимо записать каталог обратно на диск. Ошибки при этом непростительны. ;---запись измененных секторов назад на диск MOV AX,SEG DIR_AREA ;регистры как и при чтении MOV ES,AX ; MOV BX,OFFSET DIR_AREA ; MOV DL,0 ; MOV DH,0 ; MOV CH,0 ; MOV CL,6 ; MOV AL,7 ; MOV AH,3 ;номер функции записи секторов INT 13H ; Раздел 3. Подготовка к работе с файлами. Программы, написанные на языках высокого уровня могут просто открыть файл и вся подготовительная работа для операций с файлами будет выполнена автоматически. Однако программисты на языке ас- семблера должны создать специальные области данных, которые ис- пользуются при операциях ввода/вывода. MS DOS использует два метода доступа к файлам, метод управляющего блока файла (FCB) и метод дескриптора файла. Метод FCB сохранился с тех пор, когда MS DOS не работала с древовидной структурой каталогов, поэтому с его помощью можно получить доступ только к файлам, находящимся в текущем каталоге. Метод дескриптора файла позволяет получить доступ к любому файлу, независимо от того, какой каталог является текущим. Поскольку теперь древовидная структура каталогов широко ис- пользуется, то метод FCB становится анахронизмом, однако MS DOS продолжает поддерживать этот метод, чтобы сохранить совместимость со старым программным обеспечением и по этой причине мы рассмот- рим и его. Однако в своих программах всегда используйте метод дескриптора файла. Метод дескриптора файла имеет дополнительное преимущество в том, что он требует меньше подготовительной рабо- ты. Однако в некоторых приложениях сами операции ввода/вывода при его использовании могут оказаться более сложными, чем в методе FCB. Например, операции чтения файла с прямым доступом с исполь- зованием метода дескриптора файла требуют чтобы программа вычис- ляла смещение каждой записи в файле, в то время как соответствую- щая функция FCB получает номер записи и делает необходимые вычис- ления сама. Прежде чем читать или писать данные файл должен быть открыт. Открыть файл это значит создать и инициализировать специальную область данных, используемую MS DOS, которая содержит важную информацию о файле, такую как имя файла, имя накопителя, размер записи файла и т.д. Языки высокого уровня, такие капк Бейсик, создают эти области автоматически. Одной из таких областей яв- ляется управляющий блок файла и когда используется метод FCB, то программа создает этот блок, а MS DOS читает и манипулирует его содержимым. Первоначально FCB содержит только имя файла и имя накопителя; после того как файл открывается в него добавляется информация о размере записи файла и о текущей позиции, с которой к нему будет осуществляться доступ. С другой стороны, при доступе с помощью дескриптора файла MS DOS автоматически создает область данных для файла в произвольном месте. Затем MS DOS создает уникальный 16-битный код номера файла и впоследствии этот "номер" используется функциями DOS для иден- тификации того, с каким из открытых файлов производится операция. Все что нужно для нахождения файла - это стандартная строка пути, в которой может быть необязательное имя накопителя и имена подка- талогов должны быть разделены обратной косой чертой. Эти строки отличаются от стандартного запроса MS DOS только тем, что они должны завершаться байтом ASCII 0, с тем чтобы программа могла найти конец строки (такие строки называются строками ASCIIZ). Операции по пересылке данных из или в файл требуют, чтобы была указана область памяти в которую или из которой будут направлять- ся данные. Такой буфер определяется отведением ему места в памяти и установкой указателя на его первый байт (т.е. на младший адрес буфера в памяти). Если передано слишком много данных, то буфер переполняется и может разрушить данные, расположенные в следующих адресах памяти. Буфер может использоваться как промежуточный буфер, работающий только с небольшой порцией данных для операций чтения или записи. Или буфер может помещаться в область памяти, в которой программа действительно хранит и обрабатывает данные. Функции доступа через управляющий блок файла определяют проме- жуточный буфер с помощью указателя, которой все время хранится операционной системой. Этот буфер называется область обмена с диском (disk transfer area) или DTA. К сожалению, техническая документация по IBM PC часто называет термином DTA указатель на буфер, хотя на самом деле правильно называть его указателем на DTA. После того как указатель на DTA установлен с помощью спе- циальной функции, все файловые операции используют его до тех пор пока он не будет изменен. С другой стороны, функции, использующие дескриптор файла, должны указывать стартовый адрес буфера обмена каждый раз при вызове функции и они игнорируют указатель на DTA, используемый функциями управляющего блока файла. Рисунок 5-2 показывает два метода доступа к файлам. 5.3.1 Установка/проверка накопителя по умолчанию. Программы могут экономить часть работы, назначая накопитель по умолчанию, на котором содержатся файлы данных. Если в начале программы запросить у пользователя какой накопитель он будет использовать, то впоследствии не будет сомнений к какому накопи- телю следует обращаться. Высокий уровень. В приведенной программе на Бейсике текущий накопитель по умол- чанию переключается с помощью процедуры на машинном языке. Проце- дура имеет длину всего 7 байтов. Она помещается в строку X$, а переменная Z служит указателем на первый байт процедуры. В прило- жении Г объясняется как вставлять ассемблерные процедуры в прог- раммы на Бейсике. Номер накопителя устанавливается в строке 110, причем 0 = A, 1 = B и т.д. Если назначить накопителем по умолча- нию несуществующий накопитель, то ошибки не будет, поэтому будьте внимательны. Не пытайтесь объединить строки 120 и 130 этой проце- дуры, поскольку в этом случае интерпретатор Бейсика будет обраба- тывать их неправильно. 100 DEF SEG 'сегмент на начало области Бейсика 110 NUM = 0 'выбираем накопитель A 120 X$ = CHR$(180)+CHR$(14)+CHR$(178)+CHR$(NUM)+CHR$(205)+ CHR(33)+CHR$(223) 130 Y = VARPTR(X$) 'получаем дескриптор строки (адрес в Y+1) 140 Z = PEEK(Y+1)+PEEK(Y+2)*256 'вычисляем адрес строки 150 CALL Z 'выполняем машинную процедуру Средний уровень. Функция EH прерывания 21H устанавливает накопитель по умолча- нию. Надо просто поместить номер накопителя (0 = A, 1 = B и т.д.) в DL и выполнить прерывание. Эта функция возвращает в AL число накопителей на машине. Отметим, что когда у машины имеется только один накопитель, то возвращается число 2. Лучший способ определе- ния числа накопителей у машины описан в [1.1.5]. MOV AH,0EH ;номер функции MOV DL,1 ;код для накопителя B INT 21H ;устанавливаем накопитель по умолчанию Функция 19H прерывания 21H сообщает какой из накопителей яв- ляется накопителем по умолчанию. Для этой функции нет входных регистров. При возврате в AL содержится кодовый номер, где 0 = A, 1 = B и т.д. 5.3.2 Создание/удаление файла. Можно создать файл, не помещая в него никакой информации. Создается элемент каталога, а длина файла устанавливается равной 0. При удалении файла соответствующий элемент каталога на самом деле не удаляется, он просто становится недействующим за счет изменения первого байта элемента (первого символа имени файла) на E5H. Впоследствии этот элемент может быть перезаписан при созда- нии нового файла. Во время удаления файла вносятся также измене- ния в таблицу размещения файлов, с тем чтобы сектора используемые этим файлом были доступны для других файлов. Само содержимое этих секторов при этом не стирается. Поэтому можно восстановить уда- ленный файл - однако предупреждаем, что операции с таблицей раз- мещения файлов надо производить очень осторожно. Высокий уровень. Бейсик не имеет специальной команды для создания файла. Вместо этого при открытии файла указанное имя ищется в каталоге и, если оно не найдено, то создается новый файл. Если открыть новый файл, а затем закрыть его не производя в него записи, то он останется в каталоге с длиной 1 байт и ему будет отведен кластер дискового пространства (единственный байт - это символ Ctrl-Z - ASCII 26 - который используется в качестве признака конца стандартного текс- тового файла). Детали оператора OPEN см. в [5.3.3]. Наоборот, оператор CLOSE не уничтожает файл. Вместо этого для уничтожения файла используется оператор KILL. Для того чтобы уничтожить файл его не надо открывать. Просто поместите имя файла в кавычках, например KILL "A:ACCOUNT.DAT". Или, если файл нахо- дится в другом подкаталоге, то надо использовать стандартный путь к файлу, например KILL "A:\FINANCES\ACCOUNT.DAT". В обоих случаях имя накопителя необходимо указывать только если файл находится не на накопителе по умолчанию. Отметим, что Вы не можете воспользо- ваться этим методом, чтобы удалить подкаталог (который является одним из видов файла) - вместо этого используйте RMDIR. Средний уровень. Файл может быть создан или уничтожен с помощью либо метода управляющего блока файла, либо метода дескриптора файла. Создание файла одним из методов ни в коей мере не ограничивает будущий доступ к нему только этим методом. Но, поскольку одновременно с созданием он открывается, то при создании необходимо использовать тот метод, с помощью которого Вы будете работать с этим файлом на этот раз. Когда файл создается, а затем закрывается и при этом в него ничего не записывается, то ему соответствует элемент катало- га с нулем в поле длины файла, однако дисковое пространство этому файлу не отводится. Важно понимать, что когда последовательный файл открывается для записи (а не для добавления) данных, то используется именно эта функция создания файла, поэтому файл обрезается до нулевой длины и затем полностью перезаписывается. Метод FCB: Функция 16H прерывания 21H создает и открывает файл. Создайте FCB с именем файла и накопителя и пусть DS:DX указывает на него. Затем вызовите функцию. Просматривается каталог и если найден совпадающий элемент, то снова используется именно этот элемент каталога, при этом новый файл перекрывает старый с тем же именем. Чтобы избежать непреднамеренного разрушения файлов, сначала про- верьте на наличие файла с таким именем, используя функцию 11H прерывания 21H [5.2.1]. Если нет файла с таким именем, то соз- дается новый элемент каталога и в AL возвращается 0; если каталог полон, то в AL возвращается FF. Чтобы присвоить файлу специальные атрибуты (например, статус только для чтения) [5.2.6] используйте расширенный управляющий блок файла [5.3.5]. При открытии новый файл инициализируется с нулевой длиной и ему отводится кластер дискового пространства. Вот пример: ;---в сегменте данных FCB DB 1,'MYFILE DAT',25 DUP(0) ;---проверка наличия такого файла MOV AH,11H ;функция поиска файла LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;ищем файл CMP AL,0 ;AL = 0 если файл существует JE WARN_USER ;если да, то сообщаем об этом ;---создание файла MOV AH,16H ;номер функции создания файла INT 21H ;создаем файл Для создания файла со специальными атрибутами, например стату- сом только для чтения, надо использовать расширенный управляющий блок файла. Байт атрибутов файла обсуждается в [5.2.6]. К обычно- му FCB надо добавить 7-байтный заголовок, начиная с байта FFH, затем должны следовать 5 байтов ASCII 0, а затем нужный байт атрибутов. Для создания спрятанного файла необходимо, чтобы был установлен бит 1 байта атрибутов. Чтобы спрятать файл, открытый в приведенном примере, напишите: FCB DB 0FFH,5 DUP(0),2,1,'MYFILE DAT',25 DUP(0) Функция 13H прерывания 21H уничтожает файл. Надо чтобы DS:DX указывали на неоткрытый FCB и выполнить функцию. Если не найдено файла с указанным именем, то в AL возвращается FF, иначе 0. В имени файла могут использоваться джокеры (знаки вопроса, но не звездочки) и в этом случае за одно обращение к функции может быть удалено несколько файлов. Вот пример: ;---в сегменте данных FCB DB 1,'MYFILE DAT',25 DUP(0) ;---удаляем файл MOV AH,13H ;номер функции удаления файла LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;удаляем файл CMP AL,0FFH ;проверка на ошибку JE DELETE_ERROR ;уход на обработку ошибки Метод дескриптора файла: Функция 3CH прерывания 21H создает и открывает новый файл методом дескриптора файла. DS:DX должны указывать на строку, дающую путь к файлу и имя файла в стандартном формате MS DOS, включая имя накопителя, если файл находится не на накопителе по умолчанию. Строка должна завершаться байтом ASCII 0. Байт атрибу- тов файла [5.2.6] поместите в CX (0 - для нормального файла). Затем выполните функцию. При успешном выполнении флаг переноса будет равен нулю, а в AX будет возвращен номер нового файла. При возникновении ошибки флаг переноса устанавливается в 1, а в AX содержится код ошибки, который может быть равен 3, если не найден путь, 4 - если уже открыты все буфера для файлов и 5 - если ката- лог полон или файл уже существует со статусом только для чтения. Отметим, что если в каталоге уже существует файл с таким именем, то существующий файл обрезается до нулевой длины, и тем самым разрушается. Для избежания ошибок надо предварительно использо- вать функцию 4EH прерывания 21H для проверки. ;---в сегменте данных PATH DB 'B:LEVEL1\LEVEL2\FILENAME.EXT',0 ;---проверка наличия файла в каталоге MOV AH,4EH ;функция поиска в каталоге LEA DX,PATH ;DS:DX указывают на путь INT 21H ;проверка наличия файла JNC WARN_USER ;если есть, то сообщаем ;---создание файла MOV AH,3CH ;функция создания файла MOV CX,0 ;нормальные атрибуты INT 21H ;создаем файл JC OPEN_ERROR ;уход на обработку ошибки MOV HANDLE,AX ;запоминаем номер файла В MS DOS 3.0 добавлена новая функция для создания файла мето- дом дескриптора файла. Это функция номер 5BH прерывания 21H. Она работает в точности так же, как и описанная функция 3CH, за иск- лючением того, что она возвращает расширенные коды ошибок, что позволяет лучше обрабатывать ошибочные ситуации. Они объяснены в [7.2.5]. Для уничтожения файла методом дескриптора файла используйте функцию 41H прерывания 21H. И опять DS:DX должны указывать на строку, дающую путь и имя файла. Джокеры в имени файла не допус- каются. Затем вызовите функцию. Если при возврате флаг переноса установлен, то функция не выполнена; в этом случае AL будет со- держать 2, если файл не найден и 5 - если произошла ошиька на диске. Отметим, что с помощью этой функции Вы не можете удалить файл со статусом только для чтения; измените атрибуты файла [5.2.6] перед его удалением. Вот пример: ;---в сегменте данных PATH DB 'B:LEVEL1\LEVEL2\FILENAME.EXT',0 ;---уничтожаем файл MOV AH,41H ;номер функции уничтожения LEA DX,PATH ;DS:DX указывают на путь INT 21H ;уничтожаем файл JC DELETE_ERROR ;на обработку ошибки MS DOS версии 3.0 имеет специальную функцию (5AH прерывания 21H) для создания временного "безымянного" файла. Операционная система сама генерирует имя для файла и проверяет, что такого файла еще нет в каталоге. При этом исключается всякая возможность что при создании временного файла будет разрушен существующий файл с совпадающим именем. При входе DS:DX должны указывать на строку пути к каталогу, в котором должен быть создан временный файл. Строка должна завершаться обратной косой чертой. Поместите атрибуты файла в CX (обычно 0). При возврате AX будет содержать номер файла, если только флаг переноса не установлен, в этом случае AX содержит информацию об ошибке. Произвольное имя файла добавляется к концу строки пути. Эта функция может возвращать расширенные коды ошибок, которые существуют только в MS DOS 3.0; они объясняются в [7.2.5]. Файл, созданный этой функцией не унич- тожается автоматически - программа должна использовать функцию 41H (см. выше). В данном примере программа создает временный файл, а затем уничтожает его: ;---в сегменте данных PATH DB 'B:LEVEL1\LEVEL2\',12 DUP(0) ;---создаем временный файл MOV AH,5AH ;номер функции LEA DX,PATH ;DS:DX указывают на путь INT 21H ;создаем временный файл JC CREATION_ERROR ;уход на обработку ошибки . . MOV AH,41H ;номер функции LEA DX,PATH ;DS:DX указывают на путь INT 21H ;уничтожаем временный файл JC DELETION_ERROR ;уход на обработку ошибки 5.3.3 Открытие/закрытие файла. "Открыть" файл - это значит создать небольшие блоки памяти, которые будут содержать информацию о файле и служить промежуточ- ной станцией (буфером), через которую данные будут передаваться между файлом и памятью. Языки высокого уровня автоматически соз- дают для Вас эти блоки, а язык ассемблера - нет. При открытии файла каталог проверяется на его наличие. Если файла найден, то MS DOS берет информацию из каталога о размере и дате создания файла. Затем, при закрытии файла, система обновляет информацию в каталоге. Закрытие файла также очищает все системные буфера пере- носа, посылая на диск оставшуюся информацию. Если Вы не закроете файл перед завершением программы, то это может привести к потере данных. Если программа работает со многими файлами, то надо постоянно иметь ввиду сколько имеется одновременно открытых файлов. MS DOS 2.1 позволяет иметь до 99 одновременно открытых файлов, причем по умолчанию только 8 (измените это число с помощью команды MS DOS FILES). Бейсик позволяет иметь не более 15 открытых файлов. Каж- дый файл занимает место для блока параметров и буфера. Поскольку память для каждого файла отводится отдельно перед тем, как файлы открываются, то эта память недоступна для программ, даже если указанное число файлов не используется в настоящий момент. По этой причине Вы можете экономить память, устанавливая максимально допустимое число открытых файлов именно таким, которое требуется, с помощью описанного метода. Высокий уровень. При открытии файла в Бейсике идет поиск в каталоге и если файл не найден, то создается новый файл с данным именем. Имеется два способа записи оператора открытия файла и в большинстве случаев оба эти способа эквивалентны. Единственное отличие состоит в том, что один из этих способов более закодирован, в то время как дру- гой ближе к естественному языку. В обоих операторах Вы должны указать по меньшей мере три сорта информации. Во-первых требуется имя файла и, поскольку это строка, оно должно быть заключено в кавычки. Во-вторых, число, начиная с 1, присваивается файлу, как идентификационный номер, по которому другие операторы читают или пишут в файл. И в-третьих Вы должны указать для какой цели откры- вается данный файл, т.е. открыт ли он для прямого или для после- довательного доступа. Для открытия файла MYFILE.TXT для записи в последовательный файл, причем этот файл будет иметь номер 2, запишите или OPEN "O",#2,"MYFILE.TXT" или OPEN "MYFILE.TXT" FOR OUTPUT AS #2 Отметим, что в обоих случаях номер 2 относится к буферу файла #2. Число может быть любым, не превосходящим числа разрешенных буфе- ров для файлов. Если поддерживается одновременная работа с шестью файлами, то число должно быть в интервале от 0 до 6. Однако буфер файла номер 1 не обязательно использовать раньше, чем файла номер 2. По умолчанию Бейсик устанавливает число буферов равное 8, но Вы можете изменить это число на другое от 4 до 15. Из этих файлов четыре используются Бейсиком для своих нужд, поэтому по умолчанию только 4 файла доступны Вам для ввода/вывода. Для того чтобы установить число доступных буферов используйте параметр F: при запуске Бейсика. Например, если Вы при старте Бейсика напишите BASICA/F:10, то будет создано 10 буферов, шесть из которых дос- тупны для файловых операций. Второй параметр, S:, устанавливает размер буферов файла. Все буфера имеют один и тот же размер. По умолчанию берется размер в 128 байтов, однако допустимы размеры до 32767 байтов. Для файлов последовательного доступа этот размер может быть установлен рав- ным 0, что позволяет немного сэкономить память. Для файлов прямо- го доступа он должен быть не меньше максимального размера записи. Отметим, что есчи размер записи равен 512 байтам и размер буфера тоже 512 байт, то это приводит к ускорению дисковых операций. Команда BASICA/S:512/F:10 открывает 10 буферов размером 512 байт. Каждый файл требует 188 байтов плюс размер буфера, поэтому для такой конфигурации потребуется 7K памяти. Число буферов не может быть больше, чем разрешено иметь открытых файлов в DOS. Кодированная форма: Первая из форм оператора OPEN использует одну букву для обоз- начения желаемого типа операций над файлом. Имеется три возмож- ности: "O" открыть файл с последовательным доступом для вывода "I" открыть файл с последовательным доступом для ввода "R" открыть файл с прямым доступом для ввода/вывода Последовательные файлы не могут записываться, когда они открыты для чтения и наоборот. В типичных случаях, последовательные файлы открываются для чтения, затем считываются целиком в память и закрываются. После того как необходимые изменения внесены, файл снова открывается, но теперь для вывода и записываетсяобратно на диск, перекрывая то, что было записано в его секторах и, возмож- но, захватывая новые сектора. Следует отметить несколько моментов, относящихся к этой форме оператора OPEN. Имя файла должно содержать имя накопителя, если файл не найден на накопителе по умолчанию (т.е. накопителе, с которого запущен Бейсик). Имя файла может также содержать путь к файлу, находящемуся в подкаталоге, например OPEN "I",#1,"A:\LE- VEL1\LEVEL2\MYFILE.TXT". Кроме того, Вы можете поместить указание размера записи в конце оператора OPEN "R",#3,"MYFILE.TXT",52. В этом случае каждая запись будет занимать 52 байта дискового пространства. Если в операторе FIELDS не используются все 52 байта, то остаток пропадет. Этот параметр существенен при опера- циях с файлами прямого доступа. Большинство операций с файлами последовательного доступа не требуют указания длины записи, одна- ко Вы можете ускорить файловые операции, установив размер записи равным 512 байтам. Длина записи может быть в диапазоне от 1 до 32767 байтов и по умолчанию равна 128 байтам. Форма естественного языка: Вторая форма оператора OPEN делает совершенно то же самое, что и первая, но использует полные слова. Вместо того, чтобы писать "O" или "I", Вы должны писать INPUT или OUTPUT (без кавычек), например, OPEN "FILENAME" FOR INPUT AS #1. Для файлов с прямым доступом не указывается этот параметр: OPEN "MYFILE.TXT" AS #2. Кроме того, Вы можете указать режим APPEND, чтобы записать данные начиная с конца последовательного файла, не уничтожая уже сущест- вующих данных: OPEN "B:MYFILE.TXT" FOR APPEND AS #3. Как и для первой формы в операторе может быть указана необязательная длина записи. Надо просто добавить в кгнце оператора LEN = число. Нап- ример OPEN "C:MYFILE.TXT" AS #1 LEN = 52 открывает файл прямого доступа с записями длиной 52 байта. Часто программа должна получать имя файла от пользователя программы. Чтобы использовать это имя файла в операторе OPEN просто подставьте вместо строки имени файла имя строки, содержа- щей это имя. При этом необходима проверка на правильность введен- ного имени. 100 INPUT "Enter file name: ",F$ 'получаем имя файла 110 IF INSTR(F$,".") <> 0 THEN 130 'есть ли расширение? 120 IF LEN(F$) > 8 THEN 500 ELSE 150 'длиннее 8 символов? 130 IF LEN(F$) > 12 THEN 500 'длиннее 12 символов? 140 IF LEN(F$) - INSTR(F$,".") > 3 THEN 500 'тип длиннее 3-х 150 OPEN F$ FOR INPUT AS #1 'открываем файл . . 500 INPUT "Improper filename - enter another: ",F$ 510 GOTO 110 'если имя неверное, новый запрос Закрытие файла: Закрытие файла тривиально. Чтобы закрыть все открытые файлы напишите CLOSE. Чтобы закрыть определенный файл или несколько файлов напишите CLOSE #1 или CLOSE #1, #3. Важно закрыть все файлы перед завершением программы. Если этого не сделать, то в файле могут остаться данные, которые не записаны на диск. Отме- тим, что команды END, NEW, RESET, SYSTEM и RUN закрывают все буфера файлов, но не очищают эти буфера. Уже закрытый файл всегда может быть снова открыт с использованием любого доступного буфера файла. Средний уровень. MS DOS обеспечмвает различные функции для открытия и закрытия файла, в зависимости от того использовала ли программа для досту- па к файлу метод управляющего блока файла или метод дескриптора файла. В обоих случаях могут быть открыты только файлы, которые существовали до этого. Для создания новых файлов существует спе- циальная функция [5.3.2]. Метод FCB: Функция 0FH прерывания 21H открывает существующий файл. Вы должны сначала создать управляющий блок файла, как показано в [5.3.5]. Перед открытием FCB должен содержать только имя файла и имя накопителя (0 = по умолчанию, 1 = A и т.д.). DS:DX должны указывать на FCB, а затем надо выполнить функцию. При возврате AL будет содержать 0, если файл успешно открыт и FF, если файл не найден. Если для указания накопителя используется 0, то он будет заменен на код, соответствующий накопителю по умолчанию. Только после того как файл открыт Вы должны установить размер записи (по умолчанию - 128 байт), а также поля записи прямого доступа и текущей записи (они обсуждаются в разделе, относящемся к операциям с последовательным и прямым доступом). При открытии поле текущего блока заполняется нулем, а поля даты и времени информацией из каталога. Чтобы закрыть файл с помощью метода FCB, надо установить DS:DX на открытый FCB и вызвать функцию 10H прерывания 21H. При удаче информация о размере файла, дате и времени будет записана в ката- лог, а в AL будет возвращен 0. Однако если имя файла не будет обнаружено в каталоге или оно будет найдено в другой позиции, то изменения на диске будут индицированы возвратом FF в AL. ;---в сегменте данных FCB DB 1,'FILENAMEEXT',25 DUP(0) ;---открытие файла MOV AH,0FH ;номер функции LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;открываем файл CMP AL,0 ;проверка на ошибку JNE OPEN_ERROR ;на обработку ошибки . . ;---закрытие файла MOV AH,10H ;номер функции LEA DX,FCB ;DS:DX указывают на FCB INT 21H ;закрываем файл CMP AL,0 ;проверка на ошибку JNE CLOSE_ERROR ;на обработку ошибки Метод дескриптора файла: Для открытия файлов используйте функцию 3DH прерывания 21H. DS:DX должны указывать на строку, дающую путь и имя файла, вклю- чая имя нкакопителя, если это необходимо. Вся строка должна быть не длиннее 63-х байтов и завершать