Справочник программиста на персональном компьютере фирмы IBM. Дисковые накопители
ОГЛАВЛЕНИЕ
Чтение таблицы размещения файлов.
Определение доступного дискового пространства.
Получение/установка размера файла.
Восстановление после ошибок, связанных с нехваткой
пространства на диске.
Раздел 2. Работа с каталогами диска.
Чтение/изменение корневого каталога.
Создание/удаление подкаталога.
Чтение/изменение подкаталога.
Получение/установка текущего каталога.
Получение/установка времени и даты последнего доступа к
файлу.
Спрятанные и защищенные от записи файлы.
Чтение/изменение метки тома.
Раздел 3. Подготовка к работе с файлами.
Установка/проверка накопителя по умолчанию.
Создание/удаление файла.
Открытие/закрытие файла.
Переименование файла; изменение позиции файла в каталоге.
Подготовка к файловым операциям.
Анализ информации командной строки.
Раздел 4. Чтение и запись файла.
Программирование контроллера НГМД 765 и микросхемы пря-
мого доступа к памяти 8237.
Чтение/запись определенных секторов.
Запись в последовательные файлы.
Чтение из последовательных файлов.
Запись в файлы прямого доступа.
Чтение из файлов прямого доступа.
Проверка данных после операций чтения/записи.
Определение дисковых ошибок и восстановление после них.
Глава 5. Дисковые накопители.
Раздел 1. Управление распределением диска.
Файл распределен по такому количеству секторов, которое необ-
ходимо, чтобы вместить его. Только несколько секторов на внешнем
ободе дискеты зарезервированы для специальных нужд. Остальные
доступны на основе правила "первый подошел - первого обслужат".
Это означает, что по мере заполнения диска данными сектора посте-
пенно заполняются по направлению к центру диска. При уничтожении
файла сектора освобождаются и со временем свободные области ста-
новятся разбросанными по диску, разбивая новые файлы и замедляя
доступ к ним для чтения и записи.
Фиксированные диски имеют некоторые специальные характеристи-
ки. Часто они состоят из двух или более параллельных пластин, у
каждой из которых есть две головки, чтобы читать обе их стороны.
Все дорожки, расположенные на данном расстоянии от центра, вместе
называются цилиндром. Поскольку головки всех дисков двигаются
тандемом, то достигается экономия перемещений если заполнять все
дорожки одного цилиндра, прежде чем переходить к следующему.
Группы цилиндров могут относиться к различным операционным систе-
мам. Программа 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-х байтов и завершаться символом ASCII 0. В AL надо
поместить код доступа, причем 0 открывает файл для чтения, 1 -
для записи, а 2 - для чтения/записи. При возврате AX будет содер-
жать 16-битный номер файла, по которому файл впоследствии иденти-
фицируется. Файловый указатель устанавливается на начало файла.
Размер записи устанавливается равным 1 байту - это связано с тем,
что операции прямого доступа при использовании метода дескриптора
файла не имеют специальных буферов: на самом деле файлы с прямым
доступом рассматриваются как последовательные и с ними работают
одни и те же функции. Эта функция позволяет открывать как обыч-
ные, так и спрятанные файлы. При возврате флаг переноса равен 0,
если файл открыт успешно. В противном случае флаг переноса уста-
навливается, а AX содержит 2 - если файл не найден, 4 - если
программа хочет открыть слишком много файлов, 6 - при ошибке на
диске и 12 - если неправильно указан код доступа в AL. Вот при-
мер:
;---в сегменте данных
PATH DB 'A:LEVEL1\FILENAME.EXT',0
;---открываем файл для чтения/записи
MOV AH,3DH ;номер функции
MOV AL,2 ;открываем для чтения/записи
LEA DX,PATH ;DS:DX указывают на путь
INT 21H ;открываем файл
JC OPEN_ERROR ;уход на обработку ошибок
MOV HANDLE,AX ;сохраняем номер файла
Функция 3EH прерывания 21H закрывает файл, открытый методом
дескриптора файла. Надо просто поместить номер файла в BX и вы-
полнить функцию. При возврате флаг переноса равен 0, если все в
порядке, иначе он равен 1, а AX = 6, если указан неверный номер
файла.
;---закрытие файла
MOV AH,3EH ;номер функции
MOV BX,HANDLE ;номер файла
INT 21H ;закрываем файл
JC CLOSE_ERROR ;уход на обработку ошибки
Функция 45H прерывания 21H создает второй дескриптор файла из
существующего открытого дескриптора. В BX должен быть указан
существующий номер, а в AX будет возвращен новый. Функция 46H
прерывания 21H связывает второй дескриптор (помещаемый в CX) с
открытым файлом (номер которого в BX) таким образом, что первый
будет относиться к тому же файлу и устройству, что и последний.
5.3.4 Переименование файла; изменение позиции файла в каталоге.
Переименование файла может заключаться лишь в изменении первых
11-ти символов элемента каталога. Однако в древовидном каталоге
весь элемент каталога может быть перенесен в другой подкаталог,
переопределяя тем самым путь к файлу. Одна команда может как
переименовать файл, так и перенести его в другой каталог.
Высокий уровень.
В Бейсике файл переименовывается командой NAME. С помощью этой
команды он может быть также перенесен в другой каталог. Напишите
сначала существующее имя, а затем новое имя файла, оба заключен-
ные в кавычки, например NAME "OLDFILE.EXT" AS "NEWFILE.EXT". В
этом случае будет переименован файл в корневом каталоге. Для
изменения имен файлов, расположенных в подкаталогах, могут быть
использованы пути к файлу. Например, NAME "B:LEVEL1\OLDFILE.EXT"
AS "B:LEVEL1\NEWFILE.EXT" изменяет имя файла в подкаталоге LE-
VEL1.
Отметим, что для нового имени файла должен быть указан полный
путь. Если Вы запишете NAME "B:LEVEL1\OLDFILE.EXT" AS "NEWFI-
LE.EXT", то файл будет не только переименован, но и перенесен в
корневой каталог. Для переноса файла из одного подкаталога в
другой без изменения его имени напишите команду NAME "A:SUBDIR1-
\OLDFILE.EXT" AS "A:SUBDIR2\OLDFILE.EXT". Таким методом нельзя
перенести файл с диска на диск. Поскольку файлы, расположенные в
разных каталогах могут иметь одно и то же имя, то возможна ошибка
при попытке переноса файлов с одинаковыми именами. В этом случае
будет возвращен код ошибки 58 [5.4.8].
Средний уровень.
MS DOS может переименовывать файлы, используя как метод управ-
ляющего блока файла, так и метод дескриптора файла. Первый из них
может применяться только к файлам, расположенным в текущем ката-
логе.
Метод FCB:
Используйте функцию 17H прерывания 21H. DS:DX должны указывать
на открытый управляющий блок файла. Поместите новое имя файла в
FCB, начиная со смещения 11H (это "резервная" область блока).
Новое имя может использовать символ "?", в этом случае символы,
находящиеся в этих позициях, не будут изменяться. При возврате,
если новое имя уже существовало в каталоге, то AL будет равно FF,
иначе AL = 0. В примере имя файла ACCOUNTS.DAT меняется на
DEBTS.DAT.
;---в сегменте данных
FCB DB 'FILENAMEEXT',25 DUP(0)
NEWNAME DB 'NEWNAME EXT', ;11 символов нового имени
;---помещаем новое имя файла в переменную NEWNAME
MOV SI,OFFSET NEWNAME ;DS:SI указывают на новое имя
MOV AX,SEG FCB ;ES:DI указывают на FCB
MOV ES,AX ;
MOV DI,OFFSET FCB ;
ADD DI,11H ;начинаем со смещения 11H
MOV CX,11 ;имя файла содержит 11 символов
REP MOVSB ;переносим 11 байтов
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,17H ;функция изменения имени
INT 21H ;изменяем имя
CMP AL,0FFH ;проверка на ошибку
JE RENAME_ERROR ;уход на обработку ошибки
Метод дескриптора файла:
Функция 56H прерывания 21H переименовывает и перемещает файлы.
DS:DX должны указывать на строку, дающую путь и имя переименуемо-
го файла (до 63-х символов) и завершающуюся символом ASCII 0.
ES:DI должны указывать на вторую строку, которая дает новые имя и
путь файла. Имена накопителей, если они присутствуют, должны
совпадать. Если пути различны, то файл переноносится в другой
подкаталог. Чтобы перенести файл без переименования надо во вто-
рой строке указать то же самое имя, но другой путь. При возврате,
если произошла ошибка, то устанавливается флаг переноса, а AX
будет содержать 3 - если один из путей не найден, 5 - при ошибке
на диске и 17 - при попытке переноса между разными накопителями.
В примере файл ACCOUNTS.DAT переносится из подкаталога GAINS в
подкаталог LOSSES.
;---в сегменте данных
OLDPATH DB 'A:GAINS\ACCOUNTS.DAT',0
NEWPATH DB 'A:LOSSES\ACCOUNTS.DAT',0
;---изменение пути файла
LEA DX,OLDPATH ;DS:DX указывают на старый путь
MOV AX,SEG NEWPATH ;ES:DI указывают на новый путь
MOV ES,AX ;
MOV DI,OFFSET NEWPATH ;
MOV AH,56H ;номер функции
INT 21H ;переносим файл
JC ERROR_ROUTINE ;уход на обработку ошибки
5.3.5 Подготовка к файловым операциям.
Языки высокого уровня, такие как Бейсик, выполняют подготови-
тельную работу для файловых операций автоматически. Однако прог-
раммы на языке ассемблера имеют достаточно работы перед тем как
создать или открыть файл. Требования отличаются, в зависимости от
того используется ли для доступа к файлу метод управляющего блока
файла или метод дескриптора файла. Для обоих методов Вам необхо-
димо строку или блок параметров, указывающих на файл и буфер для
переноса данных. MS DOS предоставляет различные наборы функций
чтения/записи для двух методов.
Средний уровень.
Метод управляющего блока файла:
Этот метод доступа к файлам требует, чтобы Вы создали блок
параметров, котрый первоначально должен содержать такую информа-
цию, которая позволяет найти файл в каталоге. Хотя FCB имеет
много полей, вообще говоря, только некоторые из них должны быть
заполнены; MS DOS заполняет большинство остальных полей информа-
цией после того, как файл открывается. Отметим, что к началу FCB
может добавляться специальное поле для создания расширенного FCB,
который объяняется ниже. Вот структура FCB:
Накопитель (DB) Число, определяющее на каком накопителе
будет искаться файл, 1 = A, 2 = B и т.д.
Если указан 0, то берется накопитель по
умолчанию, а затем система заменяет 0 на код
этого накопителя.
Имя и расширение Восьмибайтное имя файла, выравненное по
(11 байтов) левому краю должно быть дополнено пробелами
(ASCII 32), если оно меньше 8 байтов. То же
относится и к трехбайтному расширению. Между
ними не должна стоять точка.
Текущий блок (DW) DOS организует файлы блоками по 128 записей,
пронумерованных от 0 до 127. Например, сис-
тема рассматривает запись #129 файла прямого
доступа, как запись #0 блока #1 (отсчет как
для записей, так и для блоков ведется с 0).
В файлах нет специальных ограничителей ни
для блоков ни для записей. Вместо этого
смещение для блоков и записей вычисляется
исходя из длины записи, которая устанавли-
вается следующим полем FCB.
Размер записи (DW) Все функции MS DOS, связанные с чтением или
записью в файл, работают в терминах записи.
Для файлов прямого доступа важно, чтобы
размер записи был установлен равным размеру
записей, помещенных в файл. Для последова-
тельных файлов размер записи не столь важен,
однако маленький размер записи будет замед-
лять дисковые операции. Поскольку размер
сектора 512 байтов, то оптимальным является
размер записи 512 байтов. Система автомати-
чески помещает значение по умолчанию 80H
(128) в поле длины записи при открытии фай-
ла. Поэтому не забудьте установить это поле
после открытия файла.
Размер файла (DD) Размер указывается с точностью до байта. Это
поле заполняется системой при открытии фай-
ла.
Дата файла (DW) Дата записывается системой при открытии FCB.
Ее формат приведен в [5.2.5].
Текущая запись (DB) Текущая запись используется совместно с
полем текущего блока. Записи нумеруются от 0
до 127. Запись прямого доступа #200, распо-
ложенная в блоке 1, имеет номер текущей
записи равный 71 ((200 - 128) - 1).
Номер записи пря- Вместо того, чтобы требовать от программы,
мого доступа (DD) чтобы она вычисляла текущие значения блока и
записи для файла прямого доступа, MS DOS
делает эту работу сама. При операциях с
файлами прямого доступа просто поместите
номер записи в это 4-хбайтное поле. При
выполнении операции с файлом прямого доступа
MS DOS поместит нужные значения в поля теку-
щего блока и текущей записи. Помните, что
старший байт расположен в старшей ячейке.
Связь между полями текущей записи, текущего блока и номер записи
прямого доступа показана на рис. 5-3.
Простейший путь создать FCB как переменную в сегменте данных
программы. Если имя открываемого файла не меняется, то это имя
может быть прямо записано в это поле. Остаток блока инициализи-
руйте байтами ASCII 0. Только после того как FCB будет открыт (с
помощью функции 0FH прерывания 21H, как показано в [5.3.3]) Вы
должны записать в блок остальную информацию. Отметим, что FCB для
работы с простым последовательным файлом с длиной записи 128
байтов не требует дальнейших приготовлений. После создания FCB
дальнейшие операции требуют, чтобы DS:DX указывали на него. Прос-
тейшая форма его такая:
FCB DB 1,'FILENAMEEXT',25 DUP(0)
Можно также создать FCB как структуру:
FCB STRUC
DRIVE_NUM DB 0
FILE_NAME DB 8 DUP(?)
FILE_EXT DB 3 DUP(?)
BLOCK_NUM DW 0
RECORD_SIZE DW 0
FILE_SIZE DD 0
FILE_DATE DW 0
RESERVED DB 10 DUP(0)
CURRENT_REC DB 0
RANDOM_REC DD 0
FCB ENDS
При таком подходе программе проще помещать данные в FCB, посколь-
ку метки существуют для каждого поля. В зависимости от типа фай-
ловых операций на поля могут накладываться следующие ограничения:
1. Для файлов прямого доступа Вы должны установить размер
записи и номер записи в поле записи прямого доступа.
2. Для доступа к последовательным файлам с начала Вы должны
установить только размер записи, при условии, что Вы инициализи-
ровали поля текущего блока и текущей записи в 0 (просто обнулите
весь FCB, за исключением имен накопителя и файла). При открытии
поле размера записи будет установлено равным 128, если это значе-
ние устаривает Вас, то дальнейшая подготовка не нужна.
3. Для доступа к последовательному файлу с середины или с
конца Вы должны установить поля текущего блока и текущей записи
(в этом случае Ваша программа должна будет производить вычисления
сама).
Префикс программного сегмента [1.3.0] имеет достаточно большое
поле, чтобы содержать управляющий блок файла. Это пространство
предоставляется для каждой программы, поэтому экономно использо-
вать его, особенно в программах типа .COM. Поле FCB расположено
со смещением 5CH в префиксе программного сегмента. В программах
COM используйте ORG для создания FCB следующим образом (здесь
помечен также используемый по умолчанию DTA, который будет обсуж-
даться ниже):
;---в начале кодового сегмента
ORG 5CH
FCB LABEL BYTE
DRIVE_NUM DB 0
FILE_NAME DB 8 DUP(?)
FILE_EXT DB 3 DUP(?)
BLOCK_NUM DW 0
RECORD_SIZE DW 0
FILE_SIZE DD 0
FILE_DATE DW 0
RESERVED DB 10 DUP(0)
CURRENT_REC DB 0
RANDOM_REC DD 0
ORG 80H
DTA LABEL BYTE
ORG 100H
ASSUME CS:CSEG, DS:DSEG, SS:SSEG
...
Расширенный FCB используется для создания или доступа к файлу,
имеющему специальные атрибуты, например, к спрятанному файлу или
файлу только для чтения. Различные атрибуты объяснены в [5.2.6].
Расширенный FCB на 7 байтов длиннее, причем эти 7 байтов пред-
шетсвуют обычному блоку. Первый байт равен FF, что указывает на
специальный статус. За ним следуют 5 байтов ASCII 0, а затем байт
атрибутов. При открытии файла с использованием расширенного FCB
DS:DX должны указывать на первый из дополнительных семи байтов, а
не на имя накопителя, как для обычного FCB. Вот обычная форма,
где 2 - значение байта атрибутов, а 1 - указывает на накопитель:
FCB DB 0FFH, 5 DUP(0),2,1,'FILENAMEEXT',25 DUP(0)
Метод дескриптора файла:
Этот метод требует меньшей подготовки чем метод FCB. Для него
Вы должны только создать строку, указывающую путь к файлу, такую
как в стандартных командах DOS. Например B:COMPILE\UTILITY\PASCAL
указывает на файл PASCAL в подкаталоге UTILITY. Строка ограничена
длиной в 63 символа, включая имя накопителя. При открытии файла
(с использованием функции 3DH прерывания 21H - см. [5.3.3]),
DS:DX должны указывать на первый байт этой строки. Система выпол-
няет всю работу по анализу строки и нахождению файла, а после
того как файл открыт она возвращает 16-битный идентификационный
номер файла в AX. Его называют номером файла и он используется во
всех последующих операциях с этим файлом.
Буфера данных:
Программа должна указать место в памяти, куда должны помещать-
ся принимаемые данные или откуда должны браться выводимые. Это
пространство в памяти может быть временным буфером, который будет
использоваться данными как промежуточная станция. Или это прост-
ранство может быть именно тем местом, где данные реально обраба-
тываются. Обычно временный буфер устанавливается размером в одну
запись и бывает удобно описать его как строковую переменную в
сегменте данных, как это сделано в нижеприведенном примере. С
другой стороны, большие рабочие области данных должны распреде-
ляться с помощью методов распределения памяти, предоставляемых
операционной системой [1.3.1]. Ведь создание, например, области
данных размером в 10000 байт в сегменте данных сделает программу
на диске на 10000 байт длиннее, что совершенно ненужно.
Буфер используемый методом FCB доступа к файлам называется
областью обмена с диском или DTA. На этот буфер указывает словный
указатель, который хранится операционной системой и который может
быть изменен Вашей программой. В фирменной документации этот
указатель на DTA часто сам называют DTA. Поскольку указано только
начало буфера, то ничто не мешает данным занять область прилегаю-
щую к DTA, поэтому Вы сами должны следить, чтобы этого не прои-
зошло. Указатель на DTA устанавливается специальной функцией DOS
и после того как он установлен все функции чтения/записи автома-
тически обращаются к нему. Это означает, что сами функции не
должны содержать адрес временного буфера.
Когда DTA совпадает с областью данных, в которой обрабатывают-
ся данные, то необходимо постоянно менять DTA, с тем чтобы файло-
вые операции могли получать доступ к различным фрагментам данных.
При простой операции последовательного чтения или при операции
чтения одного блока с прямым доступом система автоматически поме-
щает в DTA одну запись за другой. Необходимо отвести пространст-
во, достаточное для числа записей, которые будут затребованы
программой. DTA не может иметь размеры больше одного сегмента
(64K).
Для установки указателя на DTA используйте функцию 1AH преры-
вания 21H. DS:DX должны указывать на первый байт DTA, а затем
надо выполнить функцию. Это все что нужно. Вот пример:
;---в сегменте данных
DTA 256 DUP (?)
;---установка DTA
LEA DX,DTA ;DS:DX указывают на DTA
MOV AH,1AH ;функция установки DTA
INT 21H ;установка DTA
Функция 2FH прерывания 21H сообщает текущую установку указате-
ля DTA. У нее нет входных регистров. При возврате ES:BX содержат
сегмент и смещение DTA.
Префикс программного сегмента [1.3.0] обеспечивает каждую
программу 128-байтным встроенным DTA, начиная со смещения 80H и
до 9FH. Вы можете использовать его при нехватке памяти. Первона-
чально указатель на DTA указывает именно на этот буфер, поэтому
если Вы будете использовать его, то нет нужды устанавливать ука-
затель. Этот буфер по умолчанию особенно удобно использовать с
COM файлами, где DS указывает на начало префикса программного
сегмента. Для файлов EXE может потребоваться небольшой добавочный
код, чтобы использовать DTA по умолчанию. Отметим, что для опре-
деления текущей установки указателя на DTA Вы должны использовать
функцию 2FH прерывания 21H. У нее нет входных регистров, а при
выходе ES:BX указывают на DTA.
Указатель на DTA не используется при доступе к файлу методом
дескриптора файла. Функции чтения или записи данных всегда содер-
жат адрес, по которому расположен буфер данных. Целиком на Вашей
совести лежит определение того, будут ли данные передаваться
через временный буфер или непосредственно в то место, где они
будут использоваться.
5.3.6 Анализ информации командной строки.
При запуске многие программы позволяют пользователю поместить
добавлчную информацию в командной строке, обычно указывающую имя
файла, с которым программа будет работать. Эта информация записы-
вается в 128-байтную область, начинающуюся со смещения 80H в
префиксе программного сегмента [1.3.0]. (Эта же область исполь-
зуется как DTA по умолчанию, как обсуждалось в [5.3.5].) Первый
байт содержит длину строки, а затем идет сама строка.
Для программ, использующих метод дескриптора файла для работы
с файлами, имя файла, вводимое в командной строке, должно иметь
адекватную форму. Требуется, чтобы пользователь программы исполь-
зовал стандартный протокол MS DOS для строки пути. С другой сто-
роны, управляющий блок файла требует, чтобы строка вида 'A:ACCT.-
BAK' была преобразована к виду 1,'ACCT BAK'. MS DOS имеет
специальную функцию, которая выполняет такое преобразование над
первой порцией информации, следующей за именем программы в ко-
мандной строке. Эта процедура называется разбором строки (par-
sing).
Средний уровень.
Имя файла должно быть первой информацией, следующей за именем
загружаемой программы. Оно должно быть отделено от имени програм-
мы одним из следующих символов : . ; , = + табуляцией или пробе-
лом. Конец имени файла должен быть указан одним из символов : . ;
, = + \ < > | / " [ ] табуляцией, пробелом или одним из управляю-
щих символов (коды ASCII от 1 до 31).
Функция 29H прерывания 21H производит разбор имени файла.
DS:SI должны указывать на смещение 81H в PSP. Помните, что при
загрузке программы как DS, так и ES указывают на начало PSP.
ES:DI должны указывать на область памяти, которая будет служить
управляющим блоком для нового файла. Установка битов в AL опреде-
ляет как будет выполняться разборка. Имеют значение только биты
0-3:
бит 0 1 = начальный ограничитель игнорируется
1 1 = байт, идентифицирующий накопитель, устанавливается
в FCB, только если он указан в командной строке
2 1 = имя файла в FCB меняется только если командная
строка содержит имя файла
3 1 = расширение файла в FCB меняется только если коман-
ная строка содержит расширение файла
После того как эта информация установлена, программа может вызы-
вать функцию. Если в командной строке не указан накопитель, то
берется накопитель по умолчанию. Если отсутствует расширение
файла, то предполагается, что оно пробельное (ASCII 32). Если в
имени файла указана звездочка, то она заменяется на нужное число
вопросительных знаков в поле имени файла FCB. AL возвращает 1,
если имя файла содержит * или ? и FF, если указан неверный нако-
питель.
При возврате DS:SI указывают на первый символ, следующий за
именем файла, которое начинается со смещения 81H. Дальнейшая
информация, содержащаяся в командной строке должна расшифровы-
ваться Вашей программой. ES:DI указывают на первый байт вновь
сформированного FCB. Если в FCB не создано допустимого имени
файла, то содержимое ES:[DI]+1 равно пробелу. Вот пример, который
помещает код в область FCB в PSP, начиная со смещения 5CH:
;---разбираем командную строку, создавая FCB со смещением 5CH
;---в PSP
MOV AH,29H ;
MOV SI,81H ;
MOV DI,5CH ;
MOV AL,1111B ;
INT 21H ;
MOV AL,ES:[DI]+1 ;
CMP AL,32 ;
JE ERROR_ROUTINE ;
Раздел 4. Чтение и запись файла.
Имеются два основных метода доступа к файлу - последовательный
и прямой. Хотя в вычислительной литературе часто используют тер-
мины "последовательный" файл и файл "прямого доступа", сами по
себе файлы хранятся на диске одинаково: как непрерывная последо-
вательность байтов. Ни в каталоге ни в каком-либо другом месте
нет индикатора, указывающего, что данный файл является последова-
тельным или файлом прямого доступа. Реально эти два типа файлов
различаются по расположению данных в них и по методу доступа к
ним. К любому файлу прямого доступа можно получить последователь-
ный доступ, а к любому последовательному файлу - прямой доступ,
хотя редко имеются причины делать это, особенно во втором случае.
Последовательные файлы помещают элементы данных один за дру-
гим, независимо от их длины, разделяя эти элементы парой симво-
лов, сначала возвратом каретки (ASCII 13), а затем переводом
строки (ASCII 10). Языки высокого уровня, такие как Бейсик,
вставляют эти символы автоматически, в то время как программы на
ассемблере должны сами заботиться о вставке этих символов после
записи каждой переменной в файл. В последовательных файлах могут
храниться как числа, так и строки. Строки требуют по одному байту
на каждый символ строки. Числа по соглашению записываются в стро-
ковом виде, хотя они могут писаться и в числовом виде. Таким
образом Бейсик записывает значение "128" в виде строки из трех
цифр, хотя программа на ассемблере может записать это число в
виде двухбайтного целого или даже однобайтного кода - все опреде-
ляется тем, что при повторном чтении файла программа должна пони-
мать используемый формат. Для соместимости рекомендуется записы-
вать числа в виде строк.
Необязательно, чтобы каждое число строки было отделено парой
возврат каретки/перевод строки, однако если эта пара опущена, то
программа должна обеспечить способ отделения данных. Например, 10
целых чисел могут быть записаны как 20-байтный элемент данных. С
другой стороны, очень большие элементы данных, такие как парагра-
фы текста, могут быть разделены на несколько элементов данных
(стандартный текстовый файл представляет из себя документ, разби-
тый на строки удобного размера, записанные последовательно).
Поскольку элементы данных имеют переменную длину, то невозможно
узнать где в файле расположен определенный элемент. Поэтому для
того чтобы найти нужный элемент программа должна читать файл,
начиная с начала и отсчитывая нужное число пар возврат карет-
ки/перевод строки. По этой причине файлы такого формата называют
последовательными. Как правило с диска в память передается весь
такой файл.
Файлы прямого доступа заранее отводят фиксированное место под
каждый элемент данных. Если какой-то элемент данных не занимает
все отведенное пространство, то остаток заполняется пробелами.
Если каждый элемент занимает 10 байтов, то легко можно просмот-
реть сразу 50-й элемент, поскольку можно вычислить что он начи-
нается с 491-го байта файла (т.е. с байта #490, поскольку отсчет
начинается с 0). Как правило связанный набор элементов группи-
руется в запись. Каждая запись содержит несколько полей, которые
создают набор номеров байтов, начиная с которых пишутся данные
элементы. Например, запись может иметь поля возраст, вес и рост.
Соответствующие поля могут занимать 2, 3 и 5 байтов. Вместе они
образуют запись длиной в 10 байтов. Файл прямого доступа может
состоять из тысяч таких записей. Каждая запись следует непос-
редственно за предшествующей без всяких ограничителей, таких как
пары возврат каретки/перевод строки, используемые в последова-
тельных файлах. При этом записи могут писаться в любом порядке и
можно записать запись 74, хотя запись 73 еще не была записана
(при этом записи 73 отведено дисковое пространство и она будет
содержать те данные, которые случайно оказались в том секторе, в
котором отведено место для этой записи). В отличии от последова-
тельных файлов файлы прямого доступа остаются на диске. В памяти
присутствуют только определенные записи, с которыми идет работа в
данный момент времени.
Когда для прямого доступа к файлу используется управляющий
блок файла, то системе сообщается размер записи файла (все записи
данного файла должны иметь одинаковую длину). Это позволяет прог-
рамме запросить любую запись по номеру, а MS DOS точно вычислит
где эта запись расположена на диске. При работе с файлами прямого
доступа методом дескриптора файла программа должна сама вычислять
положение требуемой записи.
Система хранит файловый указатель для каждого буфера файла. Он
указывает на n-ный байт файла, определяя место в файле, с которо-
го будет начинаться следующая операция чтения или записи. При
последовательной операции перезаписи файловый указатель первона-
чально устанавливается на начало файла и постоянно сдвигается по
мере того, как все новые и новые данные записываются в файл.
Когда данные добавляются к последовательному файлу, то файловый
указатель первоначально устанавливается на конец файла. При дос-
тупе к одной записи в файле прямого доступа положение записи
вычисляется в виде смещения относительно начала файла и указатель
устанавливается равным этому значению; затем нужная запись чи-
тается или пишется. Обычно за файловым указателем следит система,
однако программа может сама управлять им и манипулировать указа-
телем для своих специальных нужд.
Единственным примером низкого уровня в данном разделе является
чтение/запись одного сектора. Чтение или запись целых файлов
состоит в последовательности таких чтений или записей одного
сектора, программируя микросхему контроллера НГМД заново для
каждого сектора. Полномасштабные файловые операции очень сложны
на этом уровне, что следует хотя бы из больших размеров файла
COMMAND.COM. Однако, изучив обсуждение операций низкого уровня, а
также имея информацию о таблице размещения файлов [5.1.1] и дис-
ковых каталогах [5.2.1] Вы можете представить как работает дис-
ковая операционная система.
5.4.1 Программирование контроллера НГМД 765 и микросхемы прямого доступа к памяти 8237.
Микросхема контроллера НГМД 765 фирмы NEC управляет мотором и
головками накопителя на дискетах и обрабатывает потоки данных,
направляемые в или из дисковых секторов. Один контроллер, уста-
новленный на плате адаптора дисков, может обслуживать до четырех
НГМД. За исключением случаев, связанных с защитой от копирования,
программистам не приходится программировать микросхему контролле-
ра НГМД прямо. Процедуры работы с дисками, предоставляемые DOS и
BIOS эффективны и удобны, кроме того, очень рисковано писать свои
собственные процедуры, поскольку ошибки в них могут разрушить
дисковый каталог или таблицу размещения файлов, что вызовет пол-
ное разрушение информации на диске.
Нижеследующее обсуждение служит цели дать Вам только общее
представление. Листинг ROM-BIOS, приведенный в конце каждого
технического руководства по MS DOS, содержит код тщательно разра-
ботанных процедур для форматирования дискет, чтения и записи
секторов, а также сброса и получения статуса накопителей. После
того, как Вы усвоите приведенный здесь материал, изучите процеду-
ры ROM-BIOS для продолжения Вашего образования в области операций
с дисками на низком уровне. Вам потребуется также документация по
микросхеме контроллера НГМД 8272A фирмы Intel, которая аналогична
микросхеме фирмы NEC. В данной документации перечислены прерыва-
ния, генерируемые контроллером НГМД, в то время как в документа-
ции по IBM PC этого списка нет. Информация о микросхеме 8272A
может быть найдена во втором томе Справочника по компонентам
микросистем (Microsystem Components Handbook).
Контроллер НГМД может выполнять 15 операций, из которых здесь
будут обсуждаться только три: операции поиска и чтения или записи
одного сектора. Понимание того как они работают позволит Вам
выполнить любую из оставшихся двенадцати, при условии, что у Вас
будет вышеупомянутая информация. Чтение файла состоит в поиске
его в каталоге [5.2.1], определении его положения на диске с
помощью таблицы размещения файлов [5.1.1] и затем наборе операций
чтения одного сектора. Эта процедура включает 6 шагов:
1. Включение мотора и короткое ожидание, пока он наберет обороты.
2. Выполнение операции поиска и ожидание прерывания, указывающего
на завершение этой операции.
3. Инициализация микросхемы DMA для пересылки данных в память.
4. Посылка команды чтения контроллеру НГМД и ожидание прерывания,
указывающего, что пересылка данных завершена.
5. Получение информации о статусе контроллера НГМД.
6. Выключение мотора.
Контроллер НГМД работает через три порта ввода/вывода. На
самом деле микросхема имеет больше, чем три регистра, но доступ
к большинству из них осуществляется через один порт. Эти три
порта такие:
3F2H регистр цифрового вывода
3F4H регистр статуса
3F5H регистр данных
Первый шаг состоит в доступе к регистру цифрового вывода.
Значение его битов следующее:
биты 1-0 выбор накопителя, где 00 = A
01 = B
10 = C
11 = D
2 0 = сброс контроллера НГМД
3 1 = разрешение прерывания FDC и доступа DMA
7-4 1 = включение мотора накопителя D-A (бит 4 = A)
Это регистр только для записи, поэтому необходимо заботиться обо
всех его битах. В нижеприведенном примере используется накопитель
A, поэтому цепочка битов должна выглядеть 00011100. Такая уста-
новка битов выбирает накопитель A, сохраняет установленным бит 2,
разрешающий работу с НГМД и включает мотор накопителя A. Не сбра-
сывайте бит 2 в ноль, так как в этом случае Вам придется произво-
дить перекалибровку накопителя, действие, которое необходимо
очень редко.
"Перекалибровка" накопителя подразумевает возврат его головки
на нулевую дорожку. Эта операция осуществляется посылкой простой
последовательности команд контроллеру НГМД. Контроллер НГМД уп-
равляет текущей позицией головки, за счет запоминания всех изме-
нений позиции головки после ее начальной установки на нулевую
дорожку. Когда контроллер НГМД сбрасывается, за счет изменения
бита 2 регистра цифрового вывода, то значение текущей позиции
головки устанавливается в ноль, независимо от того, на какой
дорожке находится головка на самом деле, что делает необходимым
перекалибровку. Обычно сброс контроллера НГМД производится только
в случае такой серьезной ошибки накопителя, после которой неиз-
вестно текущее состояние контроллера НГМД и накопителя.
Отметим, что выбор накопителя и включение его мотора - это
отдельные действия. Контроллер НГМД может иметь доступ только к
одному накопителю в данный момент времени, но мотры могут быть
включены у нескольких. Моторы могут оставаться включенными еще
несколько секунд после завершения обмена данными, в ожидании
следующего доступа к накопителю. Такая стратегия позволяет избе-
жать потери времени на повторное ожидание пока мотор наберет
скорость. Напротив, мотор нельзя оставлять постоянно включенным,
так как это приведет к преждевременному износу дискет.
Работа микросхемы контроллера НГМД разделяется на три фазы:
командная фаза, фаза выполнения и фаза результата. В командной
фазе один или более байтов посылаются в регистр данных. Последо-
вательность байтов строго фиксирована и она меняется от команды к
команде. Затем контроллер НГМД выполняет команду и в это время он
находится в фазе выполнения. Наконец, во время фазы результата,
ряд байтов статуса считываются из регистра данных. При этом обя-
зательно, чтобы не было ошибки в числе передаваемых или считывае-
мых данных в регистр данных в фазах командной и результата.
Число байтов команды и результата меняется в зависимости от
выполняемой контроллером дисковой операции. В техническом руко-
водстве по IBM PC приведены данные для всех 15 операций. Первый
байт команды является кодом, определяющим требуемую операцию.
Номер кода содержится в младших 5-ти битах байта и в некоторых
случаях в старших трех битах закодирована добавочная информация.
В большинстве случаев второй байт команды содержит номер накопи-
теля (0-3) в младших двух битах и номер головки (0 или 1) в бите
2, все остальные биты игнорируются контроллером НГМД. При опера-
ции поиска требуется дополнительно еще только один байт, в кото-
ром должен содержаться номер новой дорожки. Чтение или запись
сектора требует семи дополнительных командных байтов, которые
идентичны в этих двух случаях. Байты с третьего по пятый содержат
текущий номер дорожки, номер головки и номер сектора. За ними
следуют четыре байта, содержащие техническую информацию, необхо-
димую для контроллера НГМД.
Первый байт этой технической информации относится к числу
байтов в секторе, которое кодируется как 0 для 128, 1 для 256, 2
для 512 и 3 для 1024. Конечно дискеты, созданные в MS DOS имеют
сектора размером 512 байт. Затем идут данные конца дорожки (EOT),
которые дают максимальный номер сектора для цилиндра; это значе-
ние равно 9 для дискет емкостью 360K. Наконец, идет байт дающий
длину сдвига (GPL, равный 2AH) и длину данных (DTL, равный FFH).
Техническое руководство по IBM PC содержит таблицу, в которой
объясняются другие вхожные параметры, например те, которые ис-
пользуются при форматировании диска. MS DOS хранит четыре техни-
ческих параметра в памяти, в специальной таблице параметров,
называемой базой диска (disk base). Вектор прерывания 1EH указы-
вает на эту таблицу. Четыре значения хранятся в том порядке, в
котором они должны быть переданы контроллеру НГМД, начиная со
смещения 3. В следующей таблице показана командная последователь-
ность для трех операций, используемых в нижеприведенном примере.
В цепочках битов черех X обозначены биты, значение которых несу-
щественно, через H - номер головки, а через DD - номер накопите-
ля.
Операция # байта Функция Установка для головки 0
дорожки 15, сектора 1
Поиск 1 номер кода 00001111 1FH
2 головка и накопитель 00H
XXXXXHDD
Чтение 1 номер кода 01100110 66H
сектора 2 головка и накопитель 00H
XXXXXHDD
3 номер дорожки 0FH
4 номер головки 00H
5 номер сектора 01H
6 байтов в секторе 02H
7 конец дорожки 09H
8 длина сдвига 1AH
9 длина данных FFH
Запись 1 номер кода 01000101 45H
сектора 2-9 те же, что и для чтения сектора
Вы должны быть уверены, что контроллер НГМД готов прежде чем
Вы пошлете или прочитаете байт из регистра данных. Биты 7 и 6
регистра статуса предоставляют эту информацию. Вот значение битов
этого регистра:
биты 3-0 1 = накопитель D-A в режиме поиска
4 1 = контроллер НГМД выполняет команду чтения/записи
5 1 = контроллер НГМД не в режиме DMA
6 1 = регистр данных контроллер НГМД готов к приему
данных
0 = готов к посылке данных
7 1 = контроллер НГМД готов к посылке или приему данных
Перед началом дисковых операций неплохо проверить, что бит 6
равен нулю, индицируя что контроллер НГМД ожидает команду. Если
он ожидает посылки данных, то произошла ошибка. Когда байт данных
посылается в регистр данных, то бит 7 регистра статуса становится
равным нулю; продолжайте чтение регистра до тех пор, пока бит не
изменится обратно на 1, а затем посылайте следующий байт команды.
Аналогично, проверяйте этот бит статуса перед чтением байта ста-
туса в фазе результата. Нижеприведенный пример кончается двумя
процедурами, которые выполняют эти функции.
Когда операция поиска завершена, то контроллер НГМД инициирует
прерывание 6, прерывание от НГМД. Хотя так же просто можно узнать
об окончании операции поиска проверяя регистр статуса, в примере
это делается за счет обработки прерывания. Когда происходит пре-
рывание, то обработчик прерывания BIOS устанавливает бит 7 байта
статуса поиска в области данных BIOS, расположенного по адресу
0040:003E. Это единственный результат обработки прерывания. Можно
проверять этот байт до тех пор, пока бит 7 не будет установлен, а
затем переходить к следующему шагу операции чтения сектора.
Следующий шаг состоит в инициализации микросхеиы прямого дос-
тупа к памяти 8237. Эта микросхема занимается обменом данных
между периферийными устройствами и памятью, работой, которой
может заниматься также процессор. На самом деле, в PCjr, где нет
микросхемы DMA, контроллер НГМД посылает данные прямо в процес-
сор, который в свою очередь пересылает их в память. Тактовая
частота процессора адекватна этой задаче, однако при пересылке
данных все прерывания должны быть запрещены, с тем чтобы не
происходило потери данных. Это означает, что в PCjr при передаче
данных ввод с клавиатуры или из модема запрещен. Прерывания тай-
мера также игнорируются, однако впоследствии счетчик времени
суток обновляется специальной процедурой, использующей канал 1
микросхемы таймера 8253 для подсчета импульсов, прошедших за
время дисковых операций. Все остальные модели IBM PC имеют мик-
росхему DMA, поэтому процессор свободен при передаче данных.
IBM PC и XT используют 4-хканальную микросхему DMA 8237. Канал
0 предназначен для "освежения" памяти (memory refresh); он пос-
тоянно восстанавливает заряд ячеек оперативной памяти. Если Вы
будете работать по этому каналу, то это приведет скорее всего к
краху машины. Канал 2 предназначен для дисковых операций, а два
другие канала, с номерами 1 и 3, доступны (через разъемы расшире-
ния) для дополнительного оборудования. К сожалению, обмен па-
мять-память требует двух каналов и одним из них должен быть канал
0, поэтому такой обмен недоступен на IBM PC и XT. Однако AT имеет
7 каналов прямого доступа к памяти и DMA автоматически исполь-
зуется инструкциями MOVS, существенно увеличивая производитель-
ность.
Перед инициализацией канала программа должна послать в микрос-
хему код, сообщающий будет ли происходить чтение или запись в
контроллер НГМД. Этот однобайтный код равен 46H для чтения и 4AH
- для записи. Этот код должен быть послан в каждый из двух портов
с адресами 0BH и 0CH.
Каждый канал микросхемы 8237 использует три регистра. Один
16-битный регистр, регистр счетчика, содержит число передаваемых
байтов данных. Его величина должна быть на единицу меньше, чем
требуемое число байтов. Для канала 2 доступ к этому регистру
осуществляется через порт 05H; пошлите в него два последователь-
ных байта, причем сначала младший байт.
Остальные два регистра содержат адрес буфера в памяти, с кото-
рым будет происходить обмен данными. Этот адрес задается как
20-битное число, поэтому, например, адрес 3000:ABCD задается как
3ABCD. Младшие 16 битов посылаются в регистр адреса, который для
канала 2 имеет адрес порта 04H. Сначала посылается младший байт.
Старшие 4 бита идут в регистр страницы, который для канала 2
имеет адрес порта 81H. Когда байт посылается по этому адресу, то
имеют значение только 4 младших бита. Если буфер создается в
сегменте данных, то Вам нужно сложить значение DS и смещение
буфера для получения 20-битного значения. Сложение может привести
к переносу в значение регистра страницы. Например, если DS равен
1F00H, а смещение буфера - 2000H, то результирующий адрес будет
равен 1F00 + 2000 = 21000H.
После того как эти три регистра установлены, пошлите 2 в порт
с адресом 0AH, чтобы разрешить канал 2. Это оставляет микросхему
DMA в состоянии ожидания данных от накопителя, а программа должна
немедленно начать посылку командных байтов в контроллер НГМД. Вот
краткий перечень шагов при программировании микросхемы 8237:
1. Послать код чтения или записи.
2. Вычислить 20-битный адрес памяти буфера, в который будут пос-
ланы данные, и заслать его в регистры адреса и страницы канала 2.
3. Поместить значение числа передаваемых байтов (минус 1) в ре-
гистр счетчика канала 2.
4. Разрешить канал.
После посылки командных байтов, снова ожидайте прерывания и
обращайтесь с ним так же, как и после операции поиска. Затем
прочитайте байты статуса. Они таковы:
Операция # байта Функция
Поиск нет
Чтение 1 байт статуса 0
2 байт статуса 1
3 байт статуса 2
4 номер дорожки
5 номер головки
6 номер сектора
7 код байтов на сектор (0-3)
Запись 1-7 то же, что и для чтения
Вот значения битов трех байтов статуса:
Байт статуса 0:
биты 7-6 00 = нормальное завершение
01 = начато выполнение, не может завершиться
10 = неверная команда
11 = невыполнено, т.к. накопитель не подключен
5 1 = выполняется операция поиска
4 1 = ошибка накопителя
3 1 = накопитель не готов
2 номер выбранной головки
1-0 номер выбранного накопителя
Байт статуса 1:
бит 7 1 = номер затребованного сектора больше максимума
6 не используется (всегда 0)
5 1 = ошибка передачи данных
4 1 = переполнение данных
3 не используется (всегда 0)
2 1 = не может найти или прочитать сектор
1 1 = не может записать из-за защиты от записи
0 1 = отсутствует адресная метка при форматизации
Байт статуса 2:
бит 7 не используется (всегда 0)
6 1 = встречена адресная метка удаленных данных
5 1 = ошибка циклического контроля четности данных
4 1 = проблема с идентификацией дорожки
3 1 = условие команды сканирования удовлетворено
2 1 = условие команды сканирования не удовлетворено
1 1 = плохая дорожка
0 1 = отсутствует адресная метка
Как Вы видите большая часть информации относится к форматиро-
ванию диска, которое нас в настоящий момент не интересует. Однако
имеется еще четвертый байт статуса, который содержит полезную
информацию:
Байт статуса 3:
бит 7 1 = ошибка накопителя
6 1 = диск защищен от записи
5 1 = накопитель готов
4 1 = текущая позиция головки известна
3 1 = дискета двухсторонняя
2 номер выбранной головки
1-0 номер выбранного накопителя
Вы можете получить этот четвертый байт статуса, послав контролле-
ру НГМД команду "Определи статус накопителя" (Sense Drive Sta-
tus). Первый байт этой двухбайтной команды это число 4, а второй
байт содержит номер накопителя в битах 1 и 0, и номер головки в
бите 2. Единственным результатом этой операции является байт
статуса 3. Отметим, что после каждой дисковой операции, если Вы
используете процедуры DOS или BIOS, результирующие байты статуса
помещаются в область данных BIOS, начиная с адреса 0040:0042.
Операционная система хранит также байт статуса дискеты по адресу
0040:0041, значение битов которого следующее:
Значение бита Ошибка
80H нет ответа на присоединение накопителя
40H операция поиска неуспешна
20H ошибка контроллера НГМД
10H ошибка данных при чтении (ошибка CRC)
09H попытка прямого доступа за границу 64K
08H переполнение DMA
04H затребованный сектор не найден
02H не найдена адресная марка
01H послана неверная команда контроллеру НГМД
Процедура чтения диска
В заключение приводим полную процедуру чтения диска, которая
читает один сектор данных с дорожки 12, сектор 1, сторона 0 нако-
пителя A в 512-байтный буфер в сегменте данных. Семь байтов ста-
туса также считываются в отведенный буфер. Эта процедура предназ-
начена для IBM PC и XT. Вам необходимо воспользоваться техничес-
ким руководством по PCjr или AT, если Вы работаете на этих маши-
нах. На AT надо изменить циклы задержки, чтобы учесть большую
скорость процессора, и не забывать добавлять оператор JMP SHORT
$+2 между последовательными командами OUT, относящимися к одному
и тому же порту. Работа с фиксированным диском осуществляется
аналагично, поэтому Вы можете перенести изученные Вами концепции
на другие ситуации.
;---в сегменте данных
BUFFER DB 512 DUP(?)
STATUS_BUFFER DB 7 DUP(?)
SECTOR_READ PROC ;начало процедуры чтения одного сектора
;---включение мотора
STI ;прерывания должны быть разрешены
MOV DX,3F2H ;адрес регистра цифрового вывода
MOV AL,28 ;устанавливаем биты 2, 3 и 4
OUT DX,AL ;посылаем команду
;---ожидаем пока мотор наберет скорость (около 1/2 сек.)
MOV CX,3500 ;счетчик цикла задержки (для IBM PC и XT)
MOTOR_DELAY: LOOP MOTOR_DELAY ;ожидаем 1/2 секунды
;---выполняем операцию поиска
MOV AH,15 ;номер кода
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,0 ;номер накопителя
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,12 ;номер дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
CALL WAIT_INTERRUPT ;ожидаем прерывания от НГМД
;---ожидаем установки головки (25 мсек.)
MOV CX,1750 ;счетчик цикла задержки (для IBM PC и XT)
WAIT_SETTLE: LOOP WAIT_SETTLE ;ожидаем 25 мсек.
;---начинаем инициализацию микросхемы DMA
MOV AL,46H ;код чтения данных контроллера НГМД
OUT 12,AL ;посылаем код по двум адресам
OUT 11,AL ;
;---вычисляем адрес буфера
MOV AX,OFFSET BUFFER ;берем смещение буфера в DS
MOV BX,DS ;помещаем DS в BX
MOV CL,4 ;готовим вращение старшего нибла
ROL BX,CL ;вращаем младшие 4 бита
MOV DL,BL ;копируем DL в BL
AND DL,0FH ;чистим старший нибл в DL
AND BL,0F0H ;чистим младший нибл в BX
ADD AX,BX ;складываем
JNC NO_CARRY ;если не было переноса, то # страницы в DL
INC DL ;увеличиваем DL, если был перенос
NO_CARRY: OUT 4,AL ;посылаем младший байт адреса
MOV AL,AH ;сдвигаем старший байт
OUT 4,AL ;посылаем младший байт адреса
MOV AL,DL ;засылаем номер страницы
OUT 81H,AL ;посылаем номер страницы
;---конец инициализации
MOV AX,511 ;значение счетчика
OUT 5,AL ;посылаем младший байт
MOV AL,AH ;готовим старший байт
OUT 5,AL ;посылаем старший байт
MOV AL,2 ;готовим разрешение канала 2
OUT 10,AL ;DMA ожидает данные
;---получаем указатель на базу диска
MOV AL,1EH ;номер вектора, указывающего на таблицу
MOV AH,35H ;номер функции
INT 21H ;выполняем функцию
;---посылаем параметры чтения
MOV AH,66H ;код чтения одного сектора
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,0 ;номера головки и накопителя
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,12 ;номер дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,0 ;номер головки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,1 ;номер записи
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+3 ;код размера сектора
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+4 ;номер конца дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+5 ;длина сдвига
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+6 ;длина данных
CALL OUT_FDC ;посылаем контроллеру НГМД
CALL WAIT_INTERRUPT ;ожидаем прерывание от НГМД
;---читаем результирующие байты
MOV CX,7 ;берем 7 байтов статуса
LEA BX,STATUS_BUFFER ;помещаем в буфер статуса
NEXT: CALL IN_FDC ;получаем байт
MOV [BX],AL ;помещаем в буфер
INC BX ;указываем на следующий байт буфера
LOOP NEXT ;повторяем операцию
;---выключение мотора
MOV DX,3F2H ;адрес регистра цифрового вывода
MOV AL,12 ;оставляем биты 3 и 4
OUT DX,AL ;посылаем новую установку
RET ;конец процедуры
SECTOR_READ ENDP
WAIT_INTERRUPT PROC ;ожидание прерывания от НГМД
;---управление статусом прерывания 6 в байте статуса BIOS
MOV AX,40H ;сегмент области данных BIOS
MOV ES,AX ;помещаем в ES
MOV BX,3EH ;смещение для байта статуса
AGAIN: MOV DL,ES:[BX] ;получаем байт
TEST DL,80H ;проверяем бит 7
JZ AGAIN ;до тех пор пока не установлен
AND DL,01111111B ;сбрасываем бит 7
MOV ES:[BX],DL ;заменяем байт статуса
RET
WAIT_INTERRUPT ENDP
OUT_FDC PROC ;посылаем байт из AH FDC
MOV DX,3F4H ;адрес порта регистра статуса
KEEP_TRYING: IN AL,DX ;получаем значение
TEST AL,128 ;бит 7 установлен?
JZ KEEP_TRYING ;если нет, то снова проверяем
INC DX ;указываем на регистр данных
MOV AL,AH ;передаваемое значение в AH
OUT DX,AL ;посылаем значение
RET
OUT_FDC ENDP
IN_FDC PROC ;получаем байт от FDC в AL
MOV DX,3F4H ;адрес порта регистра статуса
ONCE_AGAIN: IN AL,DX ;получаем значение
TEST AL,128 ;бит 7 установлен?
JZ KEEP_TRYING ;если нет, то проверяем снова
INC DX ;указываем на регистр данных
IN AL,DX ;читаем байт из регистра данных
RET
IN_FDC ENDP
5.4.2 Чтение/запись определенных секторов.
Чтение или запись определенных секторов диска в основном ис-
пользуется при доступе к каталогам диска или его таблице размеще-
ния файлов, сектора для которых всегда расположены в одном и том
же месте. В то время как чтение секторов достаточно безобидно,
запись абсолютного сектора требует чтобы код был тщательно прове-
рен перед первым использованием. Ошибка может сделать каталог или
таблицу размещения файлов нечитаемыми, что эквивалентно разруше-
нию всех данных на диске.
Как DOS так и BIOS предоставляют функции для чтения и записи
определенных секторов. Однако они указывают сектора по-разному.
Для IBM PC, XT и PCjr процедура BIOS требует информации о номере
стороны (0 или 1), номере дорожки (0-39) и номере сектора (1-8).
Из-за ограничения максимального номера сектора равного 8 этот
метод практически бесполезен для этих машин. Однако для AT номер
сектора может меняться до 8, 9 или 15, а число дорожек может
меняться до 39 или 79. Функции DOS указывают сектор одним номе-
ром, который называется логическим номером сектора. Начиная с
наружного обода диска, секторам присваиваются последовательно
возрастающие номера. Этот метод может быть использован для дисков
произвольного размера и типа.
Отсчет логисеких секторов начинается со стороны 0 дорожки 0
сектора 1 и продолжается на стороне 1 с дорожки 0, после чего
переходит на сторону 0 дорожку 1 и т.д. (На больших фиксированных
дисках сначала проходится весь внешний цилиндр.) В зависимости от
того как был форматирован диск, при переходе на следующую дорожку
логический номер сектора увеличивается на определенную величину.
Для дискет емкостью 360K каждая дорожка (с учетом обеих сторон)
добавляет к логическому номеру 18. Однако вычисления немного
усложняются тем, что отсчет начинается с нуля. Таким образом
первый сектор на дорожке 3 стороны 2 должен иметь номер равный
3*18 для дорожек 0-2 плюс 9 для стороны 0 дорожки 3 плюс единица,
указывающая на первый сектор дорожки 3 стороны 1. Эта сумма равна
64. Логический номер сектора на 1 меньше этого числа. На рис. 5-4
сравнивается методы указания сектора DOS и BIOS.
Высокий уровень.
Бейсик не предоставляет прямого доступа к секторам диска. Надо
использовать следующую процедуру на машинном языке. В приложении
Г объясняется логика взаимодействия с этой процедурой. В примере
читается 9 секторов дорожки 3 стороны 1 дискеты емкостью 360K.
Сама процедура размещается в памяти, начиная с адреса сегмента
&H1000, а содержимое секторов размещается, начиная с сегментного
адреса &H2000 (напоминаем, что абсолютный адрес равен сегментному
адресу, умноженному на 16). Для того чтобы записать на диск со-
держимое этого буфера надо изменить в данных программы седьмой
байт с конца &H25 на &H26. Все остальное остается неизменным.
100 DEFINT A-Z 'все переменные будут целыми
110 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B
120 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76
130 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C
140 DATA &H8E, &HD8, &H8B, &HC3, &H8B, &H00, &H00, &HCD
150 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00
160 DEF SEG = &H1000 'помещаем процедуру по адресу &H10000
170 FOR N = 0 TO 38 'для каждого байта процедуры
180 READ Q: POKE N,Q 'читаем байт и помещаем его в память
190 NEXT 'следующий байт
200 READSECTOR = 0 'выполняем код, начиная с первого байта
210 BUFFER = &H2000 'буфер для данных имеет адрес &H20000
220 LOGICALNUMBER = 62 'логический номер сектора равен 62
230 NSECTORS = 9 'число считываемых секторов
240 DRIVE = 0 'номер накопителя (0 = A)
250 CALL READSECTOR (BUFFER, LOGICALSECTORS, NSECTORS, DRIVE)
260 'теперь сектора в памяти, начиная с адреса 2000:0000
Средний уровень.
BIOS использует функцию 2 прерывания 13H для чтения секторов и
функцию 3 прерывания 13H для записи секторов. В обоих случаях DL
должен содержать номер накопителя от 0 до 3, где 0 = A, 1 = B и
т.д., DH - номер головки (стороны), 0-1. CH должен содержать
номер дорожки от 0 до 39, а CL - номер сектора от 0 до 8. AL
содержит число секторов, которое необходимо считать. Допускается
сразу читать не более восьми секторов, что более чем достаточно
для большинства целей. ES:BX должны указывать на начало буфера в
памяти, куда будут помещаться данные или откуда они будут брать-
ся. При возврате AL будет содержать число прочитанных или запи-
санных секторов. Если операция успешна, то флаг переноса будет
равен нулю. Если он равен 1, то AH будет содержать байт статуса
дисковой операции, описанный в [5.4.8].
;---в сегменте данных
BUFFER DB 4000 DUP(?) ;создаем буфер
;---читаем сектора
MOV AX,SEG BUFFER ;ES:BX должны указывать на буфер
MOV ES,AX ;
MOV BX,OFFSET BUFFER ;
MOV DL,0 ;номер накопителя
MOV DH,0 ;номер головки
MOV CH,0 ;номер дорожки
MOV CL,1 ;номер сектора
MOV AL,1 ;число секторов для чтения
MOV AH,2 ;номер функции чтения
INT 13H ;
Прерывания DOS 25H и 26H читают и записывают абсолютные секто-
ра диска, соответственно. Надо поместить логический номер старто-
вого сектора в DX, а DS:BX должны указывать на буфер. CX содержит
число секторов для чтения или записи, а AL - номер накопителя,
где 0 = A, 1 = B и т.д. Процедуры портят все регистры, кроме
сегментных. При возврате регистр флагов остается на стеке, остав-
ляя стек невыровненным. Не забудьте вытолкнуть это значение со
стека сразу после возврата (в примере это значение выталкивается
в CX).
;---в сегменте данных
BUFFER DB DUP 5000(?) ;создаем буфер
;---читаем сектора
PUSH DS ;сохраняем регистры
MOV AX,SEG BUFFER ;DS:BX должны указывать на буфер
MOV DS,AX ;
MOV BX,OFFSET BUFFER ;
MOV DX,63 ;логический номер сектора
MOV CX,9 ;читаем всю дорожку
MOV AL,0 ;накопитель A
INT 25H ;функция чтения секторов
POP CX ;выталкиваем со стека флаги
POP DS ;восстанавливаем регистры
JNC NO_ERROR ;если нет ошибки, то на продолжение
CMP AH,3 ;проверка возможных ошибок
.
.
NO_ERROR: ;продолжение программы
Если при возврате флаг переноса равен 1, то произошла ошибка и
в этом случае AH и AL содержат два отдельных байта статуса ошиб-
ки. Если AH = 4, то указанный сектор не найден, а если AH = 2, то
диск неверно отформатирован. Если AH = 3, то была попытка записи
на дискету, защищенную от записи. Все остальные значения AH гово-
рят об аппаратной ошибке.
Низкий уровень.
Дисковые операции на низком уровне требуют прямого программи-
рования микросхем контроллера НГМД и прямого доступа к памяти.
Поскольку эти операции взаимосвязаны, то они рассматриваются
вместе в разделе [5.4.1].
5.4.3 Запись в последовательные файлы.
С точки зрения программиста языки высокого уровня работают с
последовательными файлами порциями в одну единицу данных. Один
оператор "записывает" содержимое переменной в последовательный
файл, ограничивая ее парой возврат каретки/перевод строки. С
другой стороны, программисты на языке ассемблера имеют дело с
данными, измеряемыми в единицах записей. Они помещают данные в
буфер, который может содержать одну или несколько записей, добав-
ляя пары возврат каретки/перевод строки между элементами данных,
а не между записями. Некоторые элементы данных могут принадлежать
двум записям. Тогда для записи используется функция MS DOS, поз-
воляющая записать на диск одну или несколько записей. На всех
уровнях программирования DOS может не производить физической
записи на диск каждый раз, когда была подана команда вывода.
Вместо этого, в целях экономии, DOS ожидает пока его выходной
буфер будет заполнен, прежде чем записать данные на диск.
Отметим, что Бейсик автоматически добавляет в конец записывае-
мого им последовательного файла символ с кодом ASCII 26 (Ctrl-Z).
Это требование стандартных текстовых файлов. Функции DOS не до-
бавляют этот символ; Ваша программа должна сама записать его в
конец элемента данных. Файлы прямого доступа не ограничиваются
символом ASCII 26.
Высокий уровень.
Бейсик готовит файлы к последовательной записи, открывая файл
в режиме последовательного доступа оператором OPEN. Этот оператор
имеет две формы и какую из них Вы выбираете это дело вкуса. Фор-
маты этого оператора такие:
100 OPEN "MYFILE" FOR OUTPUT AS #1
или
100 OPEN "O", #1, "MYFILE"
Во второй форме буква "O" обозначает вывод (output). Символ #1
обозначает кодовый номер, по которому Вы будете впоследствии
обращаться к файлу в операторах доступа, таких как WRITE #1 или
INPUT #1. В обоих случаях открывается файл с именем MYFILE для
приема данных в последовательном режиме. Если файл с таким именем
не найден на диске, то оператор OPEN создаст его. Если же такой
файл существует, то он будет перезаписан, т.е. после его закрытия
он будет содержать только новые записанные в него данные. Чтобы
добавить данные в конец существующего последовательного файла, не
изменяя его предыдущего содержимого, нужно открыть его, используя
первую форму оператора OPEN в виде OPEN "MYFILE" FOR APPEND AS
#1. Более подробно об этом см. [5.3.3].
Данные записываются в файл с помощью операторов PRINT# и WRI-
TE#. Они имеют одинаковую форму:
100 PRINT #1, S$
или
100 WRITE #1, X
#1 относится к идентификационному номеру файла (дескриптору фай-
ла), присваиваемому ему оператором OPEN. В первом примере в файл
записывается значение строковой переменной, а во втором численное
значение, но можно любым из них записывать и то и другое. Числен-
ные значения записываются в последовательные файлы в строковом
виде, хотя они и берутся не из строковых переменных. Например,
232 является 2-хбайтным целым в строковой форме, однако если X =
232, то оператор PRINT #1, X помещает в файл три байта, используя
коды ASCII для цифр 2, 3 и 2.
Операторы PRINT# и WRITE# отличаются способом отделения эле-
ментов данных в файле. Какой из них более подходящий определяется
характеристиками данных. Основное различие между двумя оператора-
ми состоит в том, что WRITE# вставляет дополнительные ограничите-
ли между элементами данных. Рассмотрим случай, когда оператор
выводит несколько переменных в виде 100 PRINT #1, A$, Z, B$ или
100 WRITE #1, A$, Z, B$. В этом случае пара возврат каретки/пере-
вод строки будет помещена в файл только за последней из трех
переменных (отметим, что строковые и числовые переменные могут
быть перемешаны). Как же можно впоследствии выделить эти три
переменные? Если был использован оператор PRINT#, то никак. Все
три переменные будут объединены в непрерывную строку. Если же был
использован оператор WRITE#, то каждый элемент данных будет зак-
лючен в кавычки, а между ними будут стоять запятые. Затем, при
чтении этих элементов из файла, Бейсик будет автоматически уда-
лять кавычки и запятые, которые были добавлены оператором WRITE#.
Имеется еще ряд менее важных вопросов. Один из них состоит в
том, что вся проблема с ограничителями может быть снята, если
использовать для вывода каждой переменной отдельный оператор
PRINT# или WRITE#. В этом случае PRINT# будет отделять все эле-
менты парами возврат каретки/перевод строки, а WRITE# будет де-
лать то же самое, но по-прежнему каждый элемент будет заключен в
кавычки (что напрасно расходует файловое пространство). Более
того, для вывода строк, которые сами содержат кавычки, оператор
WRITE# использовать нельзя, поскольку первая же внутренняя кавыч-
ка будет при чтении ошибочно воспринята как признак конца пере-
менной. И, наконец, отметим, что когда в одном операторе выводит-
ся несколько переменных, то оба оператора форматируют данные в
точности так же, как они форматировались бы при выводе на терми-
нал. Таким образом PRINT #1, A$, B$ отделяет B$ от A$, в то время
как PRINT #1, A$; B$ - нет; файл будет добавляться нужным числом
пробелов. Оператор PRINT# может быть испоьзован в форме PRINT #1
USING..., где могут быть использованы все обычные экранные форма-
ты PRINT USING для форматирования вывода в файл.
Вообще говоря, более экономично использовать оператор PRINT#,
записывая каждый раз по одной переменной. Этот метод избавляет от
излишних ограничителей и позволяет затем безошибочно считывать
строки любого вида. Более сложные схемы ограничителей, используе-
мые при записи нескольких переменных одним оператором PRINT# или
WRITE# могут привести к проблемам, особенно если одна переменная
будет считана как две, что приведет к потере текущей позиции в
файле.
После того как все данные будут записаны в файл, просто зак-
ройте его, чтобы обезопасить содержащиеся в нем данные. Напишите
CLOSE, чтобы закрыть все открытые файлы, CLOSE #1 - чтобы закрыть
файл #1 и CLOSE #1, #3 - чтобы закрыть файлы #1 и #3. Хотя в
некоторых случаях Бейсик прощает незакрытые файлы, но это не тот
случай. Операторы WRITE# и PRINT# выводят данные в файловый бу-
фер, который записывается на диск только тогда, когда они запол-
нены информацией. Последние введенные данные записываются на диск
оператором CLOSE. Отсутствие этого оператора может привести к
потере данных. Вот пример:
100 OPEN "A:NEWSEQ" FOR OUTPUT AS #1 'открываем файл
110 A$ = "aaaaa" 'готовим три строки
120 B$ = "bbbbb" '
130 C$ = "ccccc" '
140 WRITE #1, A$, B$, C$ 'запись строк
150 CLOSE 'очистка буфера
Средний уровень.
MS DOS может писать последовательные файлы как методом управ-
ляющего блока файла, так и методом дескриптора файлов. Метод FCB
предоставляет функцию специально сконструированную для записи
последовательных файлов. Метод дескриптора файлов, с другой сто-
роны, имеет только функцию записи в файл общего назначения, но ее
легко использовать и для этой цели. В любом случае, способ, ко-
торым был открыт файл, важен при последовательных операциях. Если
данные должны добавляться к последовательному файлу, то должна
быть использована обычная функция открытия файла. Однако, если
файл должен быть перезаписан заново, то требуется функция "созда-
ния" файла. Эта функция обрезает файл до нулевой длины, поэтому
его длина будет равна длине записанных в него данных.
Метод FCB:
Функция 15H прерывания 21H предназначена для записи в последо-
вательный файл. Надо подготовить управляющий блок файла и область
обмена с диском, как показано в [5.3.5]. Если файл должен быть
перезаписан, то его надо открыть с помощью функции 16H, которая
"создает" файл, обрезая его до нулевой длины. Если Вы откроете
файл с помощью функции 0FH, то остаток старого файла останется в
конце файла, если длина нового файла будет меньше, чем старого. С
другой стороны, если Вы хотите добавить данные к файлу, то ис-
пользуйте функцию открытия файла.
После того как файл открыт, Вы должны установить DS:DX на
начало FCB и вызвать функцию 15H для того чтобы заприсать одну
запись данных. Количество данных в записи зависит от величины,
которая помещена в поле длины записи, расположенное со смещением
14 в обычном FCB, по умолчанию это значение равно 128 байтам.
Если размер записи меньше, чем размер сектора диска 512 байт, то
данные будут буферизоваться, до тех пор пока не накопится доста-
точно данных, чтобы произвести реальную запись на диск; поэтому
записи в последовательный файл могут успешно записываться даже
если накопитель не включен. При закрытии файла все данные остав-
шиеся в буфере сбрасываются на диск. При возврате из функции 15H,
AL равен 0, если операция успешна, 1 - если диск полон и 2 - если
сегмент области обмена данных слишком мал.
В следующем примере на диск записываются 5 записей длиной 256
байтов. Записи могут быть набором текстовых данных. Эти данные
расположены в области памяти, помеченной меткой WORKAREA. Указа-
тель на DTA первоначально устанавливается на начало этой области,
а после записи каждой записи установка DTA меняется таким обра-
зом, чтобы он указывал на 256 байтов выше. Отметим, что обычно
для такой рабочей области отводится специальная область памяти
[1.3.1], но в данном примере для простоты используется буфер
расположенный в сегменте данных.
;---в сегменте данных
WORKAREA DB 2000 DUP (?) ;буфер данных
FCB DB 1,'FILENAMEEXT',25 DUP (0)
;---DTA должен указывать на рабочую область
LEA DX,WORKAREA ;DS:DX указывают на DTA
MOV DI,DX ;сохраняем копию
MOV AH,1AH ;функция установки DTA
INT 21H ;устанавливаем DTA
;---открываем файл
MOV AH,16H ;номер функции
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;открываем файл
;---устанавливаем размер записи
LEA BX,FCB ;BX указывает на FCB
MOV AX,256 ;размер записи 256 байтов
MOV [BX]+14,AX ;записываем в поле размера записи
;---посылаем данные в файл
MOV CX,5 ;число записей
NEXT_REC: MOV AH,15H ;функция записи
LEA DX,FCB ;указываем на FCB
INT 21H ;записываем данные
CMP AL,2 ;проверка на ошибки
JE CONTINUE ;и их обработка
CMP AL,1 ;
JE DISK_FULL ;
;---перенос выполнен, переустанавливаем DTA
ADD DI,256 ;сдвигаемся на 1 запись
MOV DX,DI ;DS:DX указывают на новый DTA
MOV AH,1AH ;функция установки DTA
INT 21H ;установка новой позиции
LOOP NEXT_REC: ;идем на следующую запись
;---позднее, закрываем файл
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,10H ;функция закрытия файла
INT 21H ;закрываем файл
Метод управляющего блока файла не слишком удобен для добавле-
ния записей в конец существующего последовательного файла. В
отличии от метода дескриптора файла, который позволяет указать на
конец файла, здесь Вы должны манипулировать полями текущей записи
и текущего блока. Нужно считать последнюю, несущую информацию,
запись в DTA, а затем заполнить пустое пространство в нем первой
записью данных, которые Вы хотите добавить. Затем перезапишите
запись на ее старое место в файле, после чего Вы можете добавлять
сколько хотите новых записей. Файл должен быть открыт функцией
0FH.
Метод дескриптора файла:
Необходима внимательность при открытии файла для последова-
тельного вывода методом дескриптора файла. Поскольку та же самая
функция используется для записи в файл прямого доступа, то при
закрытии файла его длина не устанавливается равной последней
позиции файлового указателя. Возьмем, например, случай, когда
текстовый файл размером 2000 байтов считывается с диска, а затем
в процессе обработки в памяти его длина уменьшается до 1000 байт.
Если файл был открыт простой командой открытия файла (функция
3DH), то после того, как новая, более короткая, версия файла
будет записана на диск и файл будет закрыт, его длина останется
равной 2000 байтам, из которых новый текст будет занимать первую
тысячу байтов. По этой причине, при открытии последовательного
файла для перезаписи надо использовать функцию 3CH прерывания 21H
[5.3.2]. Эта функция обычно создает новый файл, но если файл уже
существует, то он обрезается до нулевой длины. Для добавления
данных в последовательный файл надо использовать обычную функцию
открытия файла, 3DH прерывания 21H [5.3.3].
Рассмотрим сначала случай полной перезаписи файла. После того,
как файл открыт функцией 3CH, файловый указатель устанавливается
равным нулю, поэтому нет нужды устанавливать его. Поместите номер
файла в BX, а число записываемых байтов в CX. Затем установите
DS:DX на первый байт выводимых данных и выполните функцию 40H
прерывания 21H. При возврате, если флаг переноса установлен, то
была ошибка и AX содержит 5, если была ошибка дискового накопите-
ля и 6 - если неверный номер файла. В противном случае, AX будет
содержать число реально записанных байтов; при несовпадении ве-
роятнее всего проблема состоит в том, что диск полон. Не забудьте
о процедуре восстановления при сбоях, так как при крахе программы
первоначальное содержимое файла будет утеряно, так как он был
обрезан до нулевой длины. Как проверять дисковое пространство
описано в [5.1.2]. Вот пример:
;---в сегменте данных
PATH DB 'B:FILENAME.EXT',0 ;путь к файлу
DATA_BUFFER DB 2000 DUP (?)
;---открываем файл с помощью функции "создания"
LEA DX,PATH ;DS:DX указывают на путь к файлу
MOV CX,0 ;атрибуты файлы (здесь обычные)
MOV AH,3CH ;номер функции
INT 21H ;открываем файл
JC OPEN_ERROR ;проверка на ошибку
MOV HANDLE,AX ;запоминаем номер файла
;---записываем в файл 1000 байтов
MOV AH,40H ;номер функции
MOV BX,HANDLE ;номер файла в BX
MOV CX,1000 ;число байт, которые надо записать
LEA DX,DATA_BUFFER ;DS:DX указывают на буфер данных
INT 21H ;записываем данные
JC OUTPUT_ERROR ;проверка на ошибки
CMP CX,2000 ;и их обработка
JNE FULL_DISK ;
Для добавления записей в последовательный файл надо открыть
файл с помощью функции 3DH прерывания 21H, помещая 1 в AL, если
программа будет только писать данные и 2, если программа будет и
читать и писать. Длина файла остается неизменной, хотя он будет
увеличиваться по мере добавления данных. Файловый указатель дол-
жен быть установлен на конец файла, иначе существующие данные
будут перезаписаны. Это выполняется функцией 42H прерывания 21H.
Поместите номер подфункции 2 в AL, для установки указателя на
конец файла, а номер файла поместите в BX. CX:DX указывают на
смещение относительно конца файла, начиная с которого будет
производиться запись, поэтому обнулите эти регистры. Затем выпол-
ните функцию установки указателя. При возврате установленный флаг
переноса индицирует ошибку, при этом в AX будет 1, если номер
подфункции в AL был неверен, и 6 - если неверно был указан номер
файла. После того как файловый указатель установлен операция
записи выполняется в точности как в предыдущем случае:
;---в сегменте данных
PATH DB 'B:FILENAME.EXT',0 ;путь к файлу
DATA_BUFFER DB 1000 DUP(?)
;---открываем файл
LEA DX,PATH ;DS:DX указывают на путь
MOV AL,1 ;код открытия только для записи
MOV AH,3DH ;номер функции
INT 21H ;открываем файл
JC OPEN_ERROR ;уход по ошибке
MOV HANDLE,AX ;сохраняем номер файла
;---установка файлового указателя на конец файла
MOV BX,AX ;номер файла в BX
MOV CX,0 ;CX:DX дают смещение относительно конца
MOV DX,0 ;
MOV AL,2 ;код для конца файла
MOV AH,42H ;функция установки указателя
INT 21H ;устанавливаем указатель
JC POINTER_ERROR ;проверка на ошибку
;---добавляем к файлу 300 байтов
MOV AH,40H ;номер функции
MOV BX,HANDLE ;номер файла в BX
MOV CX,300 ;число записываемых байтов
LEA DX,DATA_BUFFER ;DS:DX указывают на буфер данных
INT 21H ;добавляем данные
JC OUTPUT_ERROR ;проверка на ошибки
CMP CX,300 ;и их обработка
JNE FULL_DISK ;
5.4.4 Чтение из последовательных файлов.
Чтение из последовательного файла мало чем отличается от запи-
си в него, за исключением того, что процесс обратный. В Бейсике
данные берутся из файла и присваиваются отдельным переменным или
элементам массива данных. В языке ассемблера данные помещаются в
буфер, расположенный в памяти. В последнем случае данные пере-
даются по записям и программа должна сама выделять элементы дан-
ных, составляющие записи. В этом случае под записью понимается
порция данных, которая считывается из файла.
Высокий уровень.
Чтение последовательных файлов в Бейсике проще, чем их запись,
поскольку имеется только две возможности, как обращаться с ними,
в зависимости от того, какие символы в файле используются в ка-
честве ограничителей элементов данных. Оператор INPUT# распознает
запятые и кавычки, как разделители данных, так же как и пары
возврат каретки/перевод строки. Оператор LINE INPUT# распознает
только комбинации CR/LF, поэтому он может использоваться для
чтения целых строк текста, содержащих другие ограничители. Эта
возможность удобна при обработке текстов.
Для чтения трех элементов оператором INPUT#, сначала откройте
файл, как обсуждалось в [5.3.3] (например, OPEN "A:NEWSEQ" FOR
INPUT AS #1). Если файл был открыт под номером 1, то оператор
INPUT #1, X$, Y$, Z$ присвоит значение первых трех элементов
данных трем строковым переменным. При вводе числовых переменных,
например, INPUT #1, X, Y, Z необходимо, чтобы соответствующие
данные в файле были числовыми. Число с двойной точностью должно
считываться в переменную двойной точности, с тем чтобы она могла
хранить восемь байтов такого числа. Другой способ прочитать три
элемента данных состоит в размещении их в массиве:
100 DIM ITEM$(40) 'создаем массив строк из 40 элементов
110 FOR N = 0 TO 39 'для каждого элемента
120 INPUT #1, ITEM$(N) 'считываем его и помещаем в массив
130 NEXT '
Чтобы прочитать n-ный элемент последовательного файла программа
должна прочитать все предшествующие ему элементы. Надо просто
создать цикл, в котором будут считываться элементы данных, но не
сохранять эти данные по мере их появления.
Оператор LINE INPUT# действует в основном аналогично оператору
INPUT#, за исключением того, что он может принимать только одну
переменную и это всегда строковая переменная. Переменная может
быть длиной до 254 символов и это максимально допустимый размер
строковых переменных в Бейсике. Пара возврат каретки/перевод
строки, содержащаяся в файле, включается в строку, возвращаемую
оператором LINE INPUT#. Это свойство позволяет обнаруживать конец
параграфа в текстовом файле.
Функция EOF (конец файла) может быть использована для опреде-
ления того, все ли элементы файла были прочитаны. Эта функция
возвращает -1, если файл исчерпан и 0 - в противном случае. Функ-
ции требуется номер файла, под которым он был открыт; например,
если был был открыт как #2, то X = EOF(2). В следующем примере
весь текстовый файл считывается в массив:
100 OPEN "TEXT.AAA" FOR INPUT AS #2 'открываем файл
110 DIM TEXT$(500) 'не более 500 строк
120 LINECOUNT = 0 'счетчик строк
130 LINE INPUT #2, TEXT$(LINECOUNT) 'получаем строку
140 IF EOF(2) THEN 170 'проверка на конец файла
150 LINECOUNT = LINECOUNT + 1 'увеличиваем счетчик
160 GOTO 130 'на следующую строку
170 ... 'файл прочитан
Оператор INPUT$ читает из последовательного файла указанное
число символов. На самой программе лежит забота о выделении от-
дельных элементов данных. Формат этого оператора для чтения 30
символов из файла #1 такой: S$ = INPUT$(30,#1). Хотя Вы можете
указывать число байт для чтения, необходимо чтобы это число не
превосходило 254, поскольку это максимальный размер строковой
переменной, в которую помещаются данные. INPUT$ полезен при пере-
даче массы данных в непрерывную область памяти. Например, в сле-
дующем примере делается дамп первых 200 байтов последовательного
файла в буфер монохромного дисплея, с тем чтобы они были выведены
на экран, включая управляющие коды:
100 OPEN "A:NEWFILE" FOR INPUT AS #1 'открываем файл
110 CLS: DEF SEG = &HB000 'указываем на буфер
120 FOR N = 0 TO 9 'получаем 10 групп
130 S$ = INPUT$(20,#1) 'по 20 байтов
140 FOR M = 1 TO 20 'берем каждый байт
150 POKE N*160 + M*2, ASC(MID$(S$,M,1) 'и помещаем его в буфер
160 NEXT M 'переход к следующему байту
170 NEXT N 'переход к следующей группе
Средний уровень.
Как и для всех файловых операций MS DOS может читать последо-
вательные файлы как методом управляющего блока файла, так и мето-
дом дескриптора файлов. Только первый из них имеет функцию спе-
циально предназначенную для чтения последовательных файлов. Метод
дескриптора файлов использует более общую функцию, манипулируя ей
особым образом, требуемым для последовательных файлов.
Метод FCB:
Функция 14H прерывания 21H читает последовательные файлы. Надо
создать управляющий блок файла и область обмена с диском, как
объяснено в [5.3.5]. Файл должен быть открыт функцией 0FH преры-
вания 21H [5.3.3]. DS:DX должны указывать на первый байт FCB,
после чего функция 14H будет читать по одной записи из файла при
каждом вызове. Вы можете установить размер записи по смещению 14
в FCB. Это надо делать после того, как файл открыт, так как при
открытии файла DOS вставляет в это поле значение по умолчанию,
равное 128.
Каждый раз при вызове функции данные загружаются в память,
начиная с первого байта DTA. Если DTA используется как небольшой
временный буфер, то перед чтением следующей записи содержимое DTA
должно быть перенесено в область данных файла, отведенную в памя-
ти. Можно наоборот установить указатель DTA на стартовый адрес
памяти, начиная с которого будет размещаться файл, а после чтения
каждой записи указатель увеличивать на размер записи, с тем чтобы
он указывал на место, где должна быть следующая запись.
Установкой полей текущей записи (DB, смещение 1FH) и блока
текущей записи (DW, смещение 0CH) отличными от нуля, последова-
тельный может читаться, начиная с любого требуемого места (ус-
тановка должна быть сделана после открытия FCB). После каждого
чтения поле текущей записи автоматически увеличивается на 1, а
после чтения 128 записей увеличивается поле текущего блока. При
возврате AL равен 0, если вся запись успешно прочитана. При обна-
ружении конца файла AL будет содержать 1, если функция 14H вообще
не возвратила данных и 3 - если запись прочитана частично.
В приведенном примере из файла считываются две записи и после-
довательно помещаются в нужную область памяти. Размер записи
установлен равным 256 байтам. Записи считываются в цикле и после
того, как первая запись считана, указатель на DTA изменяется
таким образом, чтобы он указывал на следующий пустой байт в об-
ласти данных.
;---помещаем FCB в сегмент данных
FCB DB 0,'OLDDATA DAT', 25 DUP(0)
DATA_AREA DB 512 DUP (?) ;используем как DTA
;---устанавливаем DTA на начало области данных
LEA DX,DATA_AREA ;DS:DX указывают на DTA
MOV DI,DX ;сохраняем копию
MOV AH,1AH ;функция установки DTA
INT 21H ;устанавливаем DTA
;---открываем файл
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,0FH ;функция открытия файла
INT 21H ;открываем файл
CMP AL,0 ;проверка на ошибку
JNE OPEN_ERROR ;
;---устанавливаем размер записи 256 байт
LEA BX,FCB ;DS:DX указывают на FCB
MOV AX,256 ;размер записи
MOV DS:[BX]+14,AX ;посылаем в поле размера записи
;---чтение данных
MOV CX,2 ;число читаемых записей
NEXT_REC: MOV AH,14H ;функция чтения файла
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;читаем одну запись
CMP AL,0 ;все в порядке?
JE CONTINUE ;
CMP AL,2 ;проверка на ошибку
JE READ_ERROR ; .
.
CONTINUE: ADD DI,256 ;увеличиваем указатель
MOV DX,DI ;DX указывает на новую DTA
MOV AH,1AH ;функция установки DTA
INT 21H ;устанавливаем DTA
LOOP NEXT_REC ;идем на чтение следующей записи
;---позднее, закрываем файл
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,10H ;функция закрытия файла
INT 21H ;закрываем файл
CMP AL,0FFH ;проверка на ошибку
JE CLOSE_ERROR ;
Метод дескриптора файлов:
Функция 3FH прерывания 21H может читать данные из файла после-
довательно. Эта функция используется для любого чтения из файла с
помощью метода дескриптора файлов, включая файлы прямого доступа.
Файл должен быть открыт функцией 3DH прерывания 21H с кодом 0 в
AL, если он открывается только для чтения, и с кодом 2 - если он
открывается для чтения и записи. При открытии файловый указатель
автоматически устанавливается на первый байт файла. Функция чте-
ния из файла указывает сколько байтов должно быть считано и после
того как это сделано файловый указатель указывает на байт, сле-
дующий за последним считанным байтом, подготавливая следующее
обращение к функции. Отметим, что файловый указатель уникален для
каждого файла - операции над другими файлами не меняют его пози-
цию.
Программа может создать небольшой временный буфер, размером,
скажем, 512 байт, и постоянно вызывать функцию чтения, не забо-
тясь о позиции файлового указателя. Другой метод состоит в считы-
вании всего файла прямо в то место памяти, где он должен быть
расположен. В этом случае надо просто потребовать, чтобы функция
прочитала больше байтов, чем реально содержится в файле, так как
чтение прекращается при достижении последнего байта файла. Однако
Вам необходимо знать точную длину файла, чтобы знать где кончают-
ся данные в буфере, в который Вы считали файл.
Размер файла можно определить, сдвинув файловый указатель на
конец файла. Это надо сделать сразу же после открытия файла.
Поместите в AL код 2 и вызовите функцию 42H, для того, чтобы
сдвинуть указатель на конец файла. CX и DX должны содержать 0,
так как в противном случае указатель будет сдвинут с конца файла
на величину, которая содержится в этих регистрах. При возврате
DX:AX будут содержать новую позицию указателя, как смещение отно-
сительно начала файла, т.е., в данном случае, длину файла. Не
забудьте снова вернуть файловый указатель на начало файла, перед
тем как читать его; это делается точно таким же образом, за иск-
лючением того, что в AL надо поместить 0. Если при выполнении
функции 42H возникает ошибка, то устанавливается флаг переноса, а
в AX возвращается 1, если неверен номер функции, и 6 - если ука-
зан неверный номер файла.
Теперь программа готова для чтения файла. Надо поместить номер
файла в BX, а требуемое число байтов в CX и выполнить прерывание.
При возврате AX будет содержать число реально прочитанных байтов.
Если AX равен нулю, то достигнут конец файла. При других ошибках
устанавливается флаг переноса, а AX содержит 5 - при ошибке обо-
рудования и 6 - если указан неверный номер файла. В следующем
примере в буфер памяти считывается весь небольшой файл. Для у-
добства буфер располагается в сегменте данных, что существенно
увеличивает размер программы на диске. В своих программах лучше
создавать буфер, используя технику распределения памяти, описан-
ную в [1.3.1].
;---в сегменте данных
PATH DB 'A:FILENAME.EXT'0 ;строка пути к файлу
DATA_BUFFER DB 1000 DUP (?) ;буфер данных
HANDLE DW ? ;номер файла
FILESIZE DW ? ;размер файла
;---открываем файл
LEA DX,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 ;номер файла
MOV CX,0 ;смещение равно нулю
MOV DX,0 ;
INT 21H ;устанавливаем указатель
JC POINTER_ERROR1 ;обработка ошибки
MOV FILESIZE,AX ;запоминаем размер (меньше 64K)
;---возвращаем указатель на начало
MOV AH,42H ;номер функции
MOV AL,0 ;код для начала файла
MOV CX,0 ;смещение равно нулю
MOV DX,0 ;
INT 21H ;устанавливаем указатель
JC POINTER_ERROR2 ;обработка ошибки
;---читаем весь файл
MOV AH,3FH ;номер функции чтения файла
MOV BX,HANDLE ;номер файла
MOV CX,FILESIZE ;число считываемых байтов
LEA DX,DATA_BUFFER ;DS:DX указывают на буфер
INT 21H ;читаем файл
JC READ_ERROR ;обработка ошибки
;---позднее, закрываем файл
MOV BX,HANDLE ;номер файла
MOV AH,3EH ;функция закрытия файла
INT 21H ;закрываем файл
JC CLOSE_ERROR ;обработка ошибки
5.4.5 Запись в файлы прямого доступа.
Физически файлы прямого доступа ничем не отличаются от после-
довательных файлов, они отличаются только режимом доступа. Файл
прямого доступа предполагает, что его данные организованы в виде
записей фиксированной длины, таким образом положение каждой запи-
си может быть вычислено (в последовательных файлах n-ный элемент
ищется путем подсчета разделителей между элементами, начиная с
начала файла). Операционная система автоматически выполняет эти
вычтсления. Однако любая программа может выполнять эту работу
сама, устанавливая файловый указатель на нужную позицию и считы-
вая последовательно такое число байтов, которое образует запись.
Высокий уровень.
В [5.3.3] объяснен формат открытия файдов прямого доступа в
Бейсике. В отличии от последовательного файла, файл прямого дос-
тупа может читаться и записываться в одно и то же время, без
закрытия и повторного его открытия. Оператор OPEN завершается
числом, дающим размер записи файла. Например, OPEN "R", 1, "NEW-
DATA", 20 устанавливает для файла NEWDATA размер записи в 20 байт
(при этом файл открывается как файл #1).
После того как файл открыт, его записи могут быть разбиты на
составляющие переменные с помощью оператора FIELD. Оператор FIELD
указывает сколько байтов записи отводится под каждую переменную.
Например, запись длиной 20 байт может быть разбита оператором
FIELD 1, 14 AS LASTNAME$, 2 AS DEPOSIT$, 4 AS ACCTNUM$. В этом
операторе первая цифра 1 указывает, что данный оператор FIELD
описывает разбиение записи для файла, открытого под номером #1.
Данные располагаются в записи точно в том порядке, в каком они
описаны в операторе FIELD. Опреаторы RSET и LSET сдвигают данные
в полях, выравнивая их по правому (RSET) или левому (LSET) краю и
заполняя остающиеся пустые места пробелами. Например, для того,
чтобы вставить фамилию "SMITH" в 14-байтное поле с именем LASTNA-
ME$, надо записать RSET LASTNAME$ = "SMITH", или если переменной
N$ было присвоено значение "SMITH", то RSET LASTNAME$ = N$. Вмес-
то RSET может быть использовано LSET. Когда впоследствии данные
считываются из поля в переменную, то переменной присваиваются все
14 байтов. При использовании RSET программа удалит все лишние
пробелы в начале строковой переменной, однако если будет исполь-
зоваться LSET, то пробелы будут удаляться справа.
Отметим, что все имена переменных в операторе FIELD относятся
к строковым переменным. В файлах прямого доступа Бейсик рассмат-
ривает все переменные - включая числовые - как строковые. Число-
вая переменная должна быть преобразована в специальный вид, преж-
де чем ее значение может быть присвоено полю, а когда она затем
считывается из поля то необходимо обратное преобразование. Слово
преобразование стоило бы заключить в кавычки, поскольку Бейсик на
самом деле не меняет способ представления числа в памяти; он
просто обрабатывает число особым образом. Числовые поля требуют
двух байтов для целых чисел, четырех байтов - для чисел с обычной
точностью и восьми байтов - для чисел с двойной точностью. В
точности такое же число байт требуется для представления этих
чисел в памяти. Для преобразования их в строковую форму надо
использовать функции MKI$, MKS$ и MKD$, которые осуществляют
преобразование число-строка для целых, вещественных и чисел с
двойной точностью, соответственно. Обычно эти функции комбини-
руются с операторами RSET или LSET, например, RSET = ACCTNUM$ =
MKI$(X), где X - целая переменная, если полю ACCTNUM$ было отве-
дено два байта в операторе FIELD.
После того как поля заполнены операторами RSET и LSET, запись
записывается на диск с помощью оператора PUT#. PUT #1, 245 поме-
щает данные в запись номер 245, файла открытого под номером #1.
Номер записи может быть опущен, в этом случае данные записываются
в запись с номером на единицу больше, чем номер последней запи-
санной записи (начиная с записи 1). Записывается вся запись цели-
ком, даже если не все поля были заполнены данными. Отметим, что
поля буфера не очищаются при выполении операции PUT, поэтому
элементы данных, такие как текущая дата, могут помещаться в буфер
только один раз, а затем они будут записаны во все записи, кото-
рые будут записываться в течение данной сессии. Функция LOC возв-
ращает номер последней записанной в файл записи. Если файл был
открыт под номером #3, то напишите X = LOC(3).
Функция LOF (длина файла) возвращает длину файла в байтах. Для
определения числа записей, содержащихся в файле, надо разделить
это значение на длину записи. Добавление 1 к этому значению дает
номер записи, который надо использовать, чтобы добавить к файлу
новые записи. Если файл был открыт под номером #2, а длина его
записей равна 32 байтам, то требуемое значение вычисляется как
RECORDNUM = LOF(2)/32 + 1.
В следующем примере файл прямого доступа открывается с длиной
записи 24 байта, причем запись разбита на три переменные. Пользо-
ватель программы запрашивается о содержимом всех трех полей, а
когда все они введены, то запись добавляется к файлу. В строке
120 вычисляется начальный номер записи. Отметим, что данные могут
не записываться физически на диск каждый раз при выполнении опе-
ратора PUT. В выходном буфере могут накапливаться несколько запи-
сей, прежде чем это будет сделано.
100 OPEN "R", 1, "A:NEWDATA.DAT", 24 'открываем файл
110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT
120 R = LOF(1)/24 + 1 'номер последней записи + 1
130 CLS 'чистим экран
140 INPUT "Enter name:",N$ 'получаем имя (строка)
150 INPUT "Enter age:",A% 'получаем возраст (целое)
160 INPUT "Enter weight:",W! 'получаем вес (вещественное)
170 RSET LASTNAME$ = N$ 'помещаем в поле имя
180 RSET AGE$ = MKI$(A%) 'помещаем в поле возраст
190 RSET WEIGHT$ = MKS$(W!) 'помещаем в поле вес
200 PUT #1, R 'записываем запись
210 R = R + 1 'увеличиваем счетчик
220 PRINT: PRINT "Do another (y/n)?" 'запрос пользователя
230 C$ = INKEY$: IF C$ = "" THEN 220 'ожидаем ответа
240 IF C$ = "y" THEN CLS: GOTO 130 'если да, то на начало
250 CLOSE 'иначе закрываем файл
Средний уровень.
Как и все другие операции с файлами в MS DOS имеется два мето-
да записи в файл прямого доступа, один с использованием управляю-
щего блока файла, а другой с помощью дескриптора файла. В обоих
случаях Вы должны создать буфер обмена данными, размер которого
должен быть не меньше, чем размер записи.
Метод управляющего блока файла:
Откройте управляющий блок файла с помощью функции 0FH и пусть
DS:DX указывают на него. После того как файл открыт поместите
номер записи для прямого доступа в поле записи прямого доступа
FCB. Затем вызовите функцию 22H прерывания 21H, которая пердаст
данные из DTA в файловый буфер, созданный при создании FCB. Дан-
ные могут не быть немедленно записаны на диск, если размер записи
меньше чем размер буфера. Реальная запись на диск будет происхо-
дить тогда, когда очередной вызов функции 22H заполнит буфер.
При возврате из функции 22H AL будет содержать 00, если обмен
прошел успешно. В противном случае в нем будет 1, если не хватает
пространства на диске и 2 - если область переноса мала для того,
чтобы записать одну запись (т.е. если размер буфера, установлен-
ный системой меньше, чем тот, который указан в FCB).
;---в сегменте данных
FCB DB 1, 'NEWDATA ', 25 DUP (0)
DTA DB 256 DUP (?)
;---открываем файл и устанавливаем поля FCB
MOV AH,0FH ;номер функции
LEA DX,FCB ;DS:DX указывают на FCB
MOV BX,DX ;копируем смещение для FCB
INT 21H ;открываем файл
MOV AX,256 ;размер записи
MOV [BX]+14,AX ;помещаем в поле размера записи
MOV AX,233 ;номер записи
MOV [BX]+33,AX ;помещаем в поле номера записи
MOV AX,0 ;обнуляем старший байт этого слова
MOV [BX]+35,AX ;
;---перенос данных из DTA в файл
MOV AH,22H ;номер функции записи с прямым доступом
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;записываем данные
CMP AL,0 ;проверка на ошибку
JNE WRITE_ERROR ;
;---позднее, закрываем файл
LEA DX,FCB ;DS:DX указывают на FCB
MOV AH,10H ;функция закрытия файла
INT 21H ;закрываем файл
CMP AL,0FFH ;проверка на ошибку
JE CLOSE_ERROR ;
Часто программа работает сразу с набором записей прямого дос-
тупа, передавая их в память и из памяти как единое целое. MS DOS
предоставляет специальную функцию для этого при использовании
метода FCB, называемую запись блока с прямым доступом. Это функ-
ция 28H прерывания 21H. При входе DS:DX должны указывать на отк-
рытый FCB, в котором поле записи прямого доступа должно быть
равно номеру первой из записываемых записей набора. Эта функция
совершенно аналогична вышеприведенному примеру. Единственное
отличие (кроме номера функции) состоит в том, что в CX должно
быть указано число записей в блоке (не путайте эти "блоки" с
блоками по 128 записей, с помощью которых система находит требуе-
мую запись - программа может читать любое число записей, начиная
с любого места).
В CX возвращается число реально прочитанных записей. AL будет
содержать 0, если все записи успешно записаны, 1 - если не хва-
тает пространства на диске, при этом не будет записана ни одна
запись. В отличии от функции 22H эта функция автоматически увели-
чивает поля текущей записи, текущего блока и записи прямого дос-
тупа в FCB, так что они будут указывать на запись, следующую за
последней прочитанной. Отметим, что если при выполнении этой
функции установить CX = 0, то размер файла будет установлен в
соответствии с числом записей, равным полю записи прямого досту-
па, и таким образом можно резервировать для файла дисковое прост-
ранство.
Метод дескриптора файлов:
При использовании для доступа метода дескриптора файлов систе-
ма не различает последовательные файлы и файлы прямого доступа.
Ваша программа должна вычислить позицию в файле, с которой начи-
нается требуемая запись, и установить на нее файловый указатель.
Файловый указатель позиционируется с помощью функции 42H прерыва-
ния 21H. Поместите номер файла в BX, а смещение в файле в CX:DX
(CX будет содержать старший байт значения). Затем поместите в AL
кодовый номер от 0 до 2. При AL = 0, указатель будет установлен
со смещением CX:DX байтов относительно начала файла; при AL = 1,
указатель будет установлен со смещением CX:DX относительно теку-
щей позиции, а при AL = 2, указатель будет установлен со смеще-
нием CX:DX относительно конца файла (т.е. таким образом файл
будет расширен). Отрицательные числа недопустимы в качестве сме-
щений. При возврате DX:AX будут содержать новое положение указа-
теля (старший байт в DX). Если устанавливается флаг переноса, то
произошла ошибка. В этом случае AX будет содержать 1, если указан
неверный код в AL и 6 - если указан неверный номер файла.
После позиционирования файлового указателя запись прямого
доступа записывается с помощью той же функции 40H прерывания 21H,
которая использовалась для записи в последовательный файл. При
входе BX содержит номер файла, а CX - число байтов, которое надо
записать. При возврате AX будет содержать число реально записан-
ных байтов. Если оно отличается от числа помещенного в CX, то
вероятно диск полон (см. [5.1.4]). Как обычно, при возникновении
ошибки устанавливается флаг переноса. В этом случае AX будет
содержать 5 при ошибке накопителя и 6 - если указан неверный
номер файла.
Файловый указатель играет ту же роль для образа файла на дис-
ке, что DTA для образа файла в памяти. Он может сдвигаться как
угодно для доступа к различным частям файла. Будьте внимательны,
манипулируя файловым указателем при работе с фалом прямого досту-
па, содержимое любого поля любой записи может быть сразу прочита-
но с диска и помещено в требуемое место в памяти.
;---в сегменте данных
HANDLE DW ? ;номер файла
FILEPATH DB 'A:NEWDATA',0 ;строка пути к файлу
REC_BUFFER DB 30 DUP (?) ;буфер выводимых записей
;---открываем файл
MOV AH,3DH ;номер функции
MOV AL,1 ;код открытия для записи
LEA DX,FILEPATH ;DS:DX указывают на путь
INT 21H ;открываем файл
JC OPEN_ERROR ;проверка на ошибку
MOV HANDLE,AX ;сохраняем номер файла
;---вычисляем позицию записи и устанавливаем файловый указатель
MOV AX,30 ;размер записи 30 байтов
MOV CX,54 ;номер записи #54 (55-я запись)
MUL CX ;теперь смещение для нее в DX:AX
MOV CX,DX ;помещаем старшее слово в DX
MOV DX,AX ;помещаем младшее слово в CX
MOV AL,0 ;устанавливаем указатель на начало
MOV AH,42H ;функция установки указателя
MOV BX,HANDLE ;номер файла
INT 21H ;устанавливаем указатель
JC POINTER_ERROR ;проверка на ошибку
;---пишем запись с прямым доступом
MOV AH,40H ;номер функции
MOV BX,HANDLE ;номер файла
MOV CX,30 ;размер записи
LEA DX,REC_BUFFER ;DS:DX указывают на буфер
INT 21H ;пишем запись
JC WRITE_ERROR ;проверка на ошибку
В отличии от метода FCB метод дескриптора файлов не предостав-
ляет специальной функции для записи блока записей прямого досту-
па. Однако Вашей программе необходимо только вычислить количество
байтов, составляющих блок записей, которое должно быть записано.
5.4.6 Чтение из файлов прямого доступа.
Чтение файлов прямого доступа является обратным процессом по
отношению к их записи. MS DOS вычисляет позицию в файле на диске,
затем считывает запись и помещает ее в память. Затем программа
должна разделить запись на поля в точности того же размера, кото-
рый был использован при конструировании записи. Не забудьте уда-
лить символы пробела, добавленные при заподнении полей. Обсужде-
ние записи данных в файлы прямого доступа [5.4.5] содержит инфор-
мацию, которая поможет Вам лучше понять информацию данного разде-
ла.
Высокий уровень.
Для чтения файла прямого доступа необходимо открыть файл и
определить поля записи, как объяснено в разделе, относящемся к
записи в файлы прямого доступа. Затем надо использовать оператор
GET# для чтения определенной записи с диска. GET #1,23 считывает
запись номер #23 из файла, открытого под номером #1. При чтении
записи переменной, именованной в операторе FIELD, автоматически
присваивается соответствующее значение из записи. Например, если
оператор FIELD имеет вид FIELD 1, 20 AS X$, 2 AS Y$, то после
выполнения оператора GET 1,23 переменной X$ будет присвоено зна-
чение первых 20-ти байтов записи 23, а переменной Y$ - следующих
10-ти байтов. Операторы, аналогичные RSET и LSET для выделения
полей данных отсутствуют.
В случае числовых полей, напоминаем, что они должны быть
преобразованы в строковый вид с помощью функций MKI$, MKS$ и
MKD$. Для восстановления их оригинальных значений, с тем чтобы
над ними можно было проводить операции и печатать их, надо преоб-
разовать эти строки с помощью функций CVI, CVS и CVD. Если Y$
содержит целое число, то для выполнения обратного преобразования
запишите Y% = CVI(Y$), при этом переменная Y% будет содержать
значение, которое имела переменная перед тем как она была спе-
циально обработана для записи в файл прямого доступа. Если Вы
выведете строковое значение переменной, то увидите, что это число
в интервале от 0 до 65535, закодированное в два символа ASCII.
В данном примере открывается файл, созданный в примере пункта
[5.4.5], и выводится содержимое любой из затребованных записей:
100 OPEN "A:NEWDATA" AS #1 LEN = 24 'открываем файл
110 FIELD 1, 18 AS LASTNAME$, 2 AS AGE$, 4 AS WEIGHT$
120 CLS: INPUT "What is the record number";R 'запрос записи
130 IF R*24 > LOF(1) THEN BEEP: PRINT"No such record": GOTO 120
140 GET #1,R 'читаем запись из файла
150 PRINT LASTNAME$, CVI(AGE$), CVS(WEIGHT$) 'выводим ее
160 PRINT: PRINT "Do another (y/n)?" 'будем повторять?
170 C$ = INKEY$: IF C$ = "" THEN 170 'ожидаем ввода
180 IF C$ = "y" OR C$ = "Y" THEN 120 'повторяем, если надо
190 CLOSE 'иначе закрываем файл
Средний уровень.
Метод FCB доступа к файлам имеет две функции для чтения запи-
сей с прямым доступом. С другой стороны, метод дескриптора файлов
использует ту же функцию, что и для чтения последовательных фай-
лов. Два метода доступа рассматриваются отдельно.
Метод FCB:
Функция 21H прерывания 21H читает одну запись из файла прямого
доступа. Вторая функция, 27H, читает блок последовательных запи-
сей. Создайте управляющий блок файла, как показано в [5.3.5] и
откройте его [5.3.3]. После того как FCB открыт, введите в него
значения полей размера записи (DW по смещению 14) и номера записи
прямого доступа (DD по смещению 33). Если DS:DX указывают на
первый байт FCB, то можно вызывать функцию 21H для чтения записи
и запись будет помещена в паямть, начиная с первого байта DTA.
Если запись успешно прочитана, то в AL будет возвращен 0.
Однако при этом нет гарантии, что чтение прошло без ошибок, пос-
кольку неверный размер записи может привести к тому, что части
прилегающих записей будут считаны, как будто это одна запись.
Если запрошена запись с номером большим, чем число записей в
файле, то в AL будет возвращено 1 или 3. Если был возвращен код
3, то был считан самый конец файла и была прочитана часть записи
данных. Если был возвращен код 1, то данные вообще не были счита-
ны.
Данный пример считывает одну запись и помещает ее в DTA:
;---в сегменте данных
FCB DB 1,'OLDDATA ', 25 DUP (0)
;---открываем файл и устанавливаем поля FCB
MOV AH,0FH ;номер функции
LEA DX,FCB ;DS:DX указывают на FCB
MOV BX,DX ;копируем смещение FCB
INT 21H ;открываем файл
MOV AX,55 ;размер записи 55 байтов
MOV [BX]+14,AX ;помещаем в поле размера записи
MOV AX,22 ;номер записи для чтения
MOV [BX]+33,AX ;помещаем в поле номера записи
MOV AX,0 ;обнуляем старшее слово этого поля
MOV [BX]+35,AX ;
;---перенос данных из файла в DTA
MOV AH,21H ;номер функции чтения с прямым доступом
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;читаем данные, помещая их в DTA
CMP AL,0 ;проверка на ошибку
JNE READ_ERROR ;
;---позднее, закрываем файл
MOV AH,10 ;номер функции закрытия файла
LEA DX,FCB ;DS:DX указывают на FCB
INT 21H ;закрываем файл
Для чтения блока последовательных записей в память за один
прием надо использовать функцию 27H прерывания 21H. Ее выполнение
подготавливается в точности так же, как и функции 21H, за исклю-
чением того, что вдобавок CX должен содержать число записей кото-
рые надо прочитать за один прием. При возврате CX будет содержать
число реально прочитанных записей. Значения возвращаемые в AL
совпадают с теми, которые возвращаеются функцией 21H. В отличии
от функции 21H поля FCB, в которых хранится информация о положе-
нии записи (поле записи прямого доступа, текущего блока и текущей
записи) автоматически увеличиваются, с тем чтобы они указывали на
следующую несчитанную запись после выполнения функции.
Отметим, что как в случае чтения одной, так и в случае чтения
нескольких записей, поля текущего блока и текущей записи FCB
устанавливаются по значению поля записи прямого доступа. Если Вы
знаете значение текущего блока и текущей записи, а не соответст-
вующий номер записи прямого доступа, то используйте функцию 24H
прерывания 21H, чтобы она проделала вычисления за Вас. У этой
функции нет входных регистров, надо только, чтобы DS:DX указывали
на открытый FCB. При возврате поле записи прямого доступа будет
заполнено значением, соответствующим установке двух других полей.
Метод дескриптора файлов:
В предыдущем разделе показано, как писать записи прямого дос-
тупа с помощью метода дескриптора файлов. Процедура чтения из
файла с прямым доступом подготавливается совершенно аналогичным
образом, путем вычисления смещения в файле, на которое должен
указывать файловый указатель. DS:DX должны указывать на буфер, в
который будет помещена запись, после чего надо выполнить функцию
3FH прерывания 21H. При входе CX должен содержать размер записи,
а BX - номер файла.
;---в сегменте данных
HANDLE DB ?
FILEPATH DB 'A:OLDDATA',0
REC_BUFFER DB 30 DUP(?)
;---открываем файл
MOV AH,3DH ;номер функции
MOV AL,0 ;код открытия для чтения
LEA DX,FILEPATH ;DS:DX указывают на путь к файлу
INT 21H ;открываем файл
JC OPEN_ERROR ;проверка на ошибку
MOV HANDLE,AX ;запоминаем номер файла
;---вычисляем позицию записи и устанавливаем файловый указатель
MOV AX,30 ;размер записи
MOV CX,54 ;читаем запись #54 (55-ю запись)
MUL CX ;смещение записи в DX:AX
MOV CX,DX ;помещаем старшее слово смещения в DX
MOV DX,AX ;помещаем младшее слово смещения в CX
MOV AL,0 ;устанавливаем указатель на начало файла
MOV AH,42H ;функция установки указателя
MOV BX,HANDLE ;номер файла
INT 21H ;устанавливаем указатель
JC POINTER_ERROR ;обработка ошибки
;---читаем запись с прямым доступом
MOV AH,3FH ;номер функции
MOV BX,HANDLE ;номер файла
MOV CX,30 ;размер записи
LEA DX,REC_BUFFER ;DS:DX указывают на буфер для записи
INT 21H ;читаем запись
JC READ_ERROR ;обработка ошибки
;---позднее, закрываем файл
MOV BX,HANDLE ;номер файла
MOV AH,3EH ;функция закрытия файла
INT 21H ;закрываем файл
JC CLOSE_ERROR ;проверка на ошибку
5.4.7 Проверка данных после операций чтения/записи.
MS DOS может проверять правильность производимого обмена с
диском прямо во время обмена. Ошибки происходят настолько редко,
что средства проверки обычно не используются, чтобы не замедлять
обмен с диском. Однако, если это необходимо, то имеется два спо-
соба проверки. Один состоит во включении команды VERIFY = ON в
файл CONFIG.SYS, который автоматически читается при загрузке
операционной системы. Впоследствии, все дисковые операции будут
проверяться. Это единственный способ проверки доступный в Бейси-
ке. Второй метод состоит использовании специальной функции DOS
для верификации только критических дисковых операций. Если проце-
дура верификации обнаруживает ошибку, то она сообщает об условии
критической ошибки, как описано в [7.2.5].
Средний уровень.
Функция 2EH прерывания 21H включает и выключает проверку.
Поместите в AL 1 - для включения верификации и 0 - для выключе-
ния. DL также должно быть равно 0. Затем надо выполнить прерыва-
ние. У этой функции нет выходных регистров.
;---включение верификации
MOV AL,1 ;номер кода
MOV DL,0 ;необходимый входной регистр
MOV AH,2EH ;номер функции
INT 21H ;включаем проверку
Для определения текущего режима верификации надо вызвать функ-
цию 54H прерывания 21H. У нее нет входных регистров. При возврате
AL = 1, если проверка включена и AL = 0, если выключена.
5.4.8 Определение дисковых ошибок и восстановление после них.
Дисковые операции настолько сложны, что имеется большое коли-
чество возможных ошибок. Большинство дисковых ошибок обсуждаются
вместе с операциями, при которых они могут происходить. В данном
разделе они собраны вместе, чтобы помочь Вам при разработке про-
цедуры общего назначения для восстановления после дисковых оши-
бок.
Дисковые ошибки бывают двух типов, которые мы будем называть
мягкими (soft) и жесткими (hard). Мягкие ошибки возникают из-за
неправильного запроса на доступ к файлу: запрошенный файл может
отсутствовать или дисковое пространство может кончиться прежде,
чем будет записан весь файл. С другой стороны, жесткие ошибки
возникают при неверных последовательностях или временных несоот-
ветствий при дисковых операциях, которые могут быть следствием
неверного выравнивания или проблем с накопителем. В этом случае,
лучше всего произвести сброс диска перед обработкой.
Высокий уровень.
В [7.2.5] объяснено как подготовить процедуру обработки оши-
бок. Оператор ON ERROR GOSUB заставляет программу перейти на
процедуру обработки ошибки при возникновении критической ошибки.
Процедура прежде всего определяет кодовый номер ошибки в Бейсике,
который для дисковых ошибок может быть одним из следующих:
52 Bad file number. (Неверный номер файла.) Файл не отк-
рыт под тем номером, к которому идет обращение (#1,
#2 и т.д.)
53 File not found. (Файл не найден.) Используется при
выполнении операторов LOAD, KILL, NAME, FILES и OPEN.
54 Bad file mode. (Неверный режим доступа.) Попытка дос-
тупа к файлу другим образом, по сравнению с тем, для
чего он был открыт, например, попытка записи в после-
довательный файл, открытый для чтения.
55 File already open. (Файл уже открыт.) Попытка открыть
файл, который уже открыт, или уничтожить (KILL) файл,
который еще не закрыт.
58 File already exists. (Файл уже существует.) Попытка
переименовать файл (с помощью NAME) на имя, которое
уже есть в каталоге.
61 Disk full. (Диск полон.) См. специальное обсуждение в
[5.1.4], относящееся к этой ошибке.
62 Input past end. (Чтение за концом файла.) Попытка
прочитать из последовательного файла больше перемен-
ных, чем он содержит. Чтобы избежать этой ошибки ис-
пользуйте функцию EOF, как объяснено в [5.4.4].
63 Bad record number. (Неверный номер записи.) Попытка
прочитать или записать запись с номером большим, чем
число записей в файле.
64 Bad file name. (Неверное имя файла.) Используется
операторами KILL, NAME и FILES.
67 Too many files. (Слишком много файлов.) В каталоге
больше нет места для записи информации о файлах. Дру-
гой возможный вариант состоит в том, что открытие еще
одного файла приведет к тому, что будет превышено
максимально допустимое число одновременно открытых
файлов.
70 Disk is write-protected. (Диск защищен от записи.)
71 Disk is not ready. (Диск не готов.) Наиболее вероятно,
не закрыт дисковод с дискетой.
72 Disk media error. (Диск поврежден.) Как правило, это
сообщение выдается при повреждении дискеты, однако
иногда оно появляется при сбоях оборудования.
74 Specified wrong disk in RENAME operation. (Указан
неверный диск в операции RENAME.)
75 Path/file access error. (Ошибка доступа к файлу.)
Попытка открыть подкаталог или метку тома, как файл.
Или попытка писать в файл, который защищен от записи.
Эта ошибка чаще всего выдается при попытке удалить
текущий каталог. Появляется при операциях OPEN, NAME,
MKDIR, CHDIR и RMDIR.
76 Path not found. (Путь не найден.) Неправильно указан
путь или его не существует. Появляется при операциях
OPEN, MKDIR, CHDIR и RMDIR.
После того как процедура распознала ошибку, необходимо инфор-
мировать об ошибке пользователя. Когда пользователь сообщит, что
причина ошибки устранена, то оператор RESUME посылает программу
назад на ту строку, где произошла ошибка. Оператор RESUME может
сопровождаться номером строки, поэтому программа может вернуться
к началу всей последовательности дисковых операций, независимо от
того, в какой строке произошла ошибка (отметим, что файлы не
закрываются при возникновении ошибки). В следующем примере прог-
рамма позволяет восстановить ситуацию после ошибок, связанных с
переполнением диска и защитой от записи:
100 ON ERROR GOSUB 5000 '
.
.
600 '''
.
.
5000 '''
5010 IF ERR = 61 PRINT "Disk full": GOTO 5100
5020 IF ERR = 70 PRINT "Disk is write protected": GOTO 5100
.
.
5100 PRINT "Correct the problem, then strike any key"
5110 C$ = INKEY$: IF C$ = "" THEN 5110
5120 RESUME 600
Средний уровень.
Функция 1 прерывания 13H возвращает в AL байт, дающий статус
дискового накопителя. Значение его битов следующее:
биты 0-1 01 = неверная команда, или, если бит 3 = 1, то
попытка обмена данными за границей 64K
10 = адресная метка не найдена
11 = попытка записи на защищенный от записи диск
2 1 = указанный сектор не найден
3 1 = переполнение DMA (потеря данных при обмене),
или, если бит 0 = 1, то попытка обмена дан-
ными за границей 64K
4 1 = данные прочитаны неверно, надо повторить
5 1 = ошибка контроллера
6 1 = ошибка операции поиска
7 1 = нет ответа от накопителя (тайм-аут)
Каждая из функций обращения к диску MS DOS использует только
некоторые из возможных кодов ошибок, а некоторые функции не сооб-
щают об ошибке. Однако во всех случаях при возникновении ошибки
устанавливается флаг переноса. Если произошла ошибка, то номер
кода этой ошибки возвращается в AX. Вот коды, относящиеся к дис-
ковым операциям:
1 Неверный номер функции
2 Файл не найден
3 Путь не найден
4 Уже открыто максимально допустимое число файлов
5 Отрицание доступа (ошибка оборудования)
6 Неверный номер файла
15 Указан неверный накопитель
16 Попытка удалить текущий каталог
17 Не то же устройство
18 Больше нет файлов (при поиске в каталоге с использова-
нием джокеров)
Восстановление после этих "мягких" ошибок несложно. Некоторые
предупреждают Вас о программных ошибках. Другие возникают из-за
ошибочных действий пользователя. Если же не отвечает сам накопи-
тель, то произошла критическая ошибка. В разделе [7.2.5] показано
как написать процедуру обработки критических ошибок.
В MS DOS 3.0 введены расширенные коды ошибок. Они могут быть
получены с помощью функции 59H прерывания 21H, когда флаг перено-
са индицирует возникновение ошибки. Обсуждение этого вопроса см.
в [7.2.5].