© Иван Рощин, Москва
| ZXNet | : 500:95/462.53 |
| : bestview@mtu-net.ru | |
| WWW | : http://www.ivr.da.ru |
(«Радиомир. Ваш
компьютер» 6/2004)
Дополненная версия.
По различным причинам (прерывание дисковых операций, связанных с записью на
диск, использование программ, неправильно работающих с диском, ошибочные
действия пользователя при ручном редактировании каталога и т.д.) файловая
структура диска
Если работающая с диском программа не проверяет правильность файловой
структуры (а в
Ясно, что своевременное обнаружение ошибок в файловой структуре было бы
очень кстати. Значит, нужна процедура, проверяющая правильность каталога
диска. Такая процедура может быть использована как в специальной программе
проверки дисков, так и вообще в любой программе, работающей с диском: если
перед дисковыми операциями прочитать каталог и проверить его корректность, это
поможет избежать неприятных последствий, а если ваша программа выполняет
операции, связанные с записью на диск, работая непосредственно с каталогом, то
вызов этой процедуры будет полезен и перед записью изменённого каталога на
диск: если
Кратко расскажу о формате дисков
На каждом треке (они нумеруются с 0) находится 16 секторов (также нумеруются с 0). Длина каждого сектора 256 (#100) байтов.
Для системных нужд выделен нулевой трек, а область с первого трека и до конца диска используется для хранения содержимого файлов.
В секторах 07 нулевого трека находятся элементы каталога, содержащие информацию о записанных на диске файлах. Каждый элемент каталога занимает 16 байтов, его структура описана в табл. 1.
| Смещение от начала | Длина | Комментарий |
|---|---|---|
| 0 | 8 | Имя файла. Если первый байт равен 0 значит, элемент каталога не используется; если равен 1 элемент соответствует стёртому файлу. |
| 8 | 1 | Расширение файла (или первый символ расширения, если
используется |
| 9 | 2 | Стартовый адрес файла (или последние два символа
расширения, если используется |
| 11 | 2 | Длина файла в байтах.
|
| 13 | 1 | Длина файла в секторах. |
| 14 | 2 | Дисковый адрес начала файла (первый байт номер сектора, второй байт номер трека). |
Всего каталог содержит #800/16=128 элементов, то есть на диске может быть максимум 128 файлов. Используемые элементы каталога (соответствующие реальным файлам и удалённым файлам) расположены в его начале, неиспользуемые в конце.
Каждый файл занимает целое число секторов. Файлы расположены друг за другом без промежутков, именно в том порядке, в котором они перечислены в каталоге. Первый файл начинается с трека 1, сектора 0.
Сектор 8 каталога (служебный сектор) содержит информацию о диске в целом (см. табл. 2). Не все байты этого сектора используются.
| Смещение от начала | Длина | Комментарий |
|---|---|---|
| #00 | 1 | Этот байт должен быть равен 0 для правильного определения конца каталога, когда на диске 128 файлов. |
| #E1 | 2 | Дисковый адрес первого свободного сектора (первый байт номер сектора, второй байт номер трека). |
| #E3 | 1 | Тип дискеты: #16 80 треков, 2 стороны, #17 40 треков, 2 стороны, #18 80 треков, 1 сторона, #19 40 треков, 1 сторона. (Здесь имеются в виду физические треки, а не логические.) |
| #E4 | 1 | Общее количество файлов (включая удалённые). |
| #E5 | 2 | Количество свободных секторов (сначала младший байт, потом старший). |
| #E7 | 1 | Идентификационный код |
| #F4 | 1 | Количество удалённых файлов. |
| #F5 | 8 | Имя диска (вообще говоря, может занимать и все 11 байтов до конца сектора). |
Как видим, информация, представленная в каталоге, избыточна. Например, в описателе файла можно было бы обойтись без указания дискового адреса начала файла (так как для первого файла он известен трек 1, сектор 0, а для любого другого файла может быть вычислен, исходя из суммы длин в секторах всех файлов до него). Многие параметры, находящиеся в служебном секторе, также можно было бы не хранить там, а вычислять, исходя из содержимого элементов каталога.
С одной стороны, избыточность это плохо, так как данные занимают больше места. Но, с другой стороны, эта же избыточность упрощает работу с данными (скажем, когда при записи нового файла надо узнать адрес первого свободного сектора, то вместо того, чтобы вычислять его, складывая длины всех файлов, можно взять уже готовое число).
Именно за счёт избыточности удаётся обнаруживать ошибки, сравнивая записанные в каталоге значения параметров с вычисленными значениями, а также проверяя, чтобы значения параметров находились в правильных диапазонах (номер сектора в диапазоне 015, номер типа диска в диапазоне #16#19, и т.д.). Подробную информацию о производимых проверках вы можете узнать из комментариев в тексте программы.
Сначала я написал функцию, проверяющую корректность каталога, на языке C
(используя компилятор Turbo C 2.0), а потом уже перенёс её на ассемблер
Z80. С одной стороны, это облегчило мне тестирование и отладку, а с другой
стороны наличие функции на C поможет тем, кому потребуется
добавить проверку корректности каталога в программу для работы с дисками
При вызове функции ей передаются два параметра: адрес каталога в памяти (т.е. адрес содержимого первых девяти секторов нулевого трека) и максимально допустимое количество треков на диске (в функции производится проверка, чтобы количество занятых секторов диска, сложенное с количеством свободных секторов, указанным в служебном секторе, не превышало количества секторов на указанном при вызове функции количестве треков).
В принципе, можно было бы считать второй параметр равным 40, 80 или 160 трекам, в зависимости от типа диска (см. табл. 2), и определять его в самой функции. Решение о передаче этого параметра при вызове функции было принято с учётом того, чтобы можно было проверять диски, отформатированные на большее количество треков, чем следует из значения типа диска, а также RAM-диски, количество треков на которых определяется объёмом выделенной под них памяти.
Функция возвращает двухбайтное значение, где младший байт либо 0, если ошибок не обнаружено, либо код ошибки (см. табл. 3), а старший байт номер элемента каталога (нумеруются с 0), при рассмотрении которого ошибка была обнаружена (для тех кодов ошибок, которые помечены «*» в первом столбце табл. 3). Если в каталоге несколько ошибок, то возвращаемое функцией значение будет соответствовать первой обнаруженной ошибке.
| Код ошибки | Комментарий |
|---|---|
| 1 | Неправильный идентификационный код |
| 2 | Первый байт служебного сектора не равен 0. |
| 3 | Неправильный тип дискеты (не #16#19). |
| 4 | Номер первого свободного сектора не является числом в диапазоне 015. |
| 5* | Обнаружен файл (возможно, удалённый), номер начального сектора которого не является числом в диапазоне 015. |
| 6 | Фактическое общее количество файлов не совпадает с указанным в служебном секторе. |
| 7 | Фактическое количество удалённых файлов не совпадает с указанным в служебном секторе. |
| 8* | Обнаружен используемый элемент каталога, следующий после неиспользуемого. |
| 9 | Первый файл начинается не с трека 1, сектора 0. |
| 10* | Обнаружен файл (возможно, удалённый), дисковый адрес которого не равен дисковому адресу предыдущего файла, увеличенному на количество секторов в предыдущем файле. |
| 11 | Указанный в служебном секторе дисковый адрес первого свободного сектора не равен дисковому адресу последнего файла, увеличенному на количество секторов в последнем файле. |
| 12 | Файлов нет, но указанный в служебном секторе дисковый адрес первого свободного сектора не «трек 1, сектор 0». |
| 13 | Количество занятых секторов диска, сложенное с количеством свободных секторов, указанным в служебном секторе, оказалось больше, чем содержится секторов на указанном при вызове функции максимальном количестве треков. |
Ниже приведён листинг функции.
#define byte unsigned char /* 1 байт. */
#define word unsigned int /* 2 байта. */
/* ==========================================================================
Функция test_cat - проверка правильности каталога TR-DOS.
Вход: cat - адрес каталога в памяти, tracks - максимальное количество треков
на диске.
Выход: 2-байтное значение, где младший байт - код ошибки или 0, если нет
ошибок, а старший байт для некоторых кодов ошибок - номер элемента
каталога, при рассмотрении которого была обнаружена ошибка.
========================================================================== */
word test_cat (byte cat[0x900], byte tracks)
{
byte files=0; /* Общее количество файлов. */
byte del_files=0; /* Количество удалённых файлов. */
byte element=0; /* Номер текущего элемента каталога. */
/* Проверка идентификационного кода TR-DOS. */
if (cat[0x8E7]!=0x10) return (1);
/* Первый байт служебного сектора должен быть равен 0. */
if (cat[0x800]!=0) return (2);
/* Тип дискеты должен быть числом в диапазоне 0x16-0x19. */
if ((cat[0x8E3]<0x16)||(cat[0x8E3]>0x19)) return (3);
/* Номер первого свободного сектора должен быть числом в диапазоне 0-15. */
if (cat[0x8E1]>15) return (4);
/* Подсчитываем общее количество файлов и количество удалённых файлов,
одновременно проверяя, чтобы номер начального сектора каждого файла
был числом в диапазоне 0-15. */
while (cat[element*16]!=0)
{ /* Пока не обнаружим неиспользуемый элемент или не просмотрим весь каталог. */
files++;
if (cat[element*16]==1) del_files++;
if (cat[element*16+0x0E]>15) return (5+(element<<8));
element++;
}
/* Проверяем, правильно ли в служебном секторе указано количество файлов
и количество удалённых файлов. */
if (cat[0x8E4]!=files) return (6);
if (cat[0x8F4]!=del_files) return (7);
/* Если сейчас element=128, то просмотрен весь каталог, иначе остались
неиспользуемые элементы, и надо проверить, чтобы все они были помечены
как неиспользуемые. */
while (element<128)
{
if (cat[element*16]!=0) return (8+(element<<8));
element++;
}
if (files>0)
{/* Файлы есть. */
/* Первый файл должен начинаться с трека 1, сектора 0. */
if ((cat[0x0F]!=1)||(cat[0x0E]!=0)) return (9);
/* Дисковый адрес (т.е. начальный трек-сектор) каждого файла, начиная
со второго, должен быть равен дисковому адресу предыдущего файла,
увеличенному на количество секторов в предыдущем файле. */
for (element=1;element<files;element++)
{
if ((cat[(element-1)*16+0x0E]+cat[(element-1)*16+0x0F]*16+cat[(element-1)*16+0x0D])!=
(cat[element*16+0x0E]+cat[element*16+0x0F]*16)) return (10+(element<<8));
}
/* Указанный в служебном секторе дисковый адрес первого свободного сектора
должен быть равен дисковому адресу последнего файла, увеличенному на
количество секторов в последнем файле. */
if ((cat[(files-1)*16+0x0E]+cat[(files-1)*16+0x0F]*16+cat[(files-1)*16+0x0D])!=
(cat[0x8E1]+cat[0x8E2]*16)) return (11);
}
else
{/* Файлов нет. */
/* Указанный в служебном секторе дисковый адрес первого свободного сектора
должен быть "трек 1, сектор 0". */
if ((cat[0x8E2]!=1)||(cat[0x8E1]!=0)) return (12);
}
/* Количество занятых секторов диска, сложенное с количеством свободных
секторов, указанным в служебном секторе, не должно быть больше, чем
содержится секторов на указанном при вызове функции максимальном
количестве треков. */
/* Преобразование к типу long нужно, чтобы избежать возможного переполнения
при сравнении (для этого разрядность long должна быть больше двух байтов). */
if ((cat[0x8E1]+cat[0x8E2]*16+cat[0x8E5]+(((long)(cat[0x8E6]<<8))&0xFFFF)) >
(tracks*16)) return (13);
/* Все проверки успешно пройдены - делаем вывод, что ошибок нет. */
return (0);
}
А вот аналогичная процедура на ассемблере Z80:
;---------------------------------------------------------------
;TEST_CAT - проверка правильности каталога TR-DOS.
;
;Вход: каталог (#900 байтов) находится в памяти с адреса CAT,
; A - максимальное количество треков на диске.
;
;Выход: A - код ошибки или 0, если нет ошибок,
; D - для некоторых кодов ошибок - номер элемента
; каталога, при рассмотрении которого была обнаружена
; ошибка.
TEST_CAT EX AF,AF' ;Сохраняем количество треков.
;Проверка идентификационного кода TR-DOS.
LD A,(CAT+#8E7)
CP #10
LD A,1 ;*
RET NZ
;Первый байт служебного сектора должен быть равен 0.
LD A,(CAT+#800)
AND A
LD A,2 ;*
RET NZ
;Тип дискеты должен быть числом в диапазоне #16-#19.
LD A,(CAT+#8E3)
SUB #16
AND %11111100
LD A,3 ;*
RET NZ
;Номер первого свободного сектора должен быть числом в диапазоне
;0-15.
LD A,(CAT+#8E1)
AND #F0
LD A,4 ;*
RET NZ
;Подсчитываем общее количество файлов и количество удалённых
;файлов, одновременно проверяя, чтобы номер начального сектора
;каждого файла был числом в диапазоне 0-15.
LD IX,CAT
LD BC,16
LD D,B ;Общее количество файлов = 0.
LD E,B ;Количество удалённых файлов = 0.
;Цикл будет выполняться, пока не обнаружим неиспользуемый
;элемент или не просмотрим весь каталог.
TEST_CAT_1 LD A,(IX)
AND A
JR Z,TEST_CAT_3
DEC A
JR NZ,TEST_CAT_2
INC E
TEST_CAT_2 LD A,(IX+14)
AND #F0
LD A,5 ;*
RET NZ
INC D
ADD IX,BC
JR TEST_CAT_1
;Проверяем, правильно ли в служебном секторе указано количество
;файлов и количество удалённых файлов.
TEST_CAT_3 LD A,(CAT+#8E4)
CP D
LD A,6 ;*
RET NZ
LD A,(CAT+#8F4)
CP E
LD A,7 ;*
RET NZ
;Если сейчас D=128, то просмотрен весь каталог, иначе остались
;неиспользуемые элементы, и надо проверить, чтобы все они были
;помечены как неиспользуемые.
LD E,D ;E - общее количество файлов.
TEST_CAT_4 BIT 7,D ;D=128?
JR NZ,TEST_CAT_5
LD A,(IX)
AND A
LD A,8 ;*
RET NZ
INC D
ADD IX,BC
JR TEST_CAT_4
TEST_CAT_5 INC E
DEC E ;E=0?
JR Z,TEST_CAT_9
;Файлы есть.
;Первый файл должен начинаться с трека 1, сектора 0.
LD HL,(CAT+#0E)
DEC H
LD A,H
OR L
LD A,9 ;*
RET NZ
;Дисковый адрес (т.е. начальный трек-сектор) каждого файла,
;начиная со второго, должен быть равен дисковому адресу
;предыдущего файла, увеличенному на количество секторов
;в предыдущем файле.
LD IX,CAT+16
LD D,1 ;N текущего элемента каталога.
TEST_CAT_6 LD B,(IX-1) ;N трека предыдущего файла.
LD C,(IX-2) ;N сектора предыдущего файла.
CALL MAKE_CDA
LD A,(IX-3) ;Кол-во секторов в предыдущем файле.
ADD A,C
LD C,A
JR NC,TEST_CAT_7
INC B
TEST_CAT_7 LD H,B
LD L,C ;HL=BC.
LD A,D
CP E ;Все файлы обработаны?
JR Z,TEST_CAT_8
LD B,(IX+15) ;N трека текущего файла.
LD C,(IX+14) ;N сектора текущего файла.
CALL MAKE_CDA
SBC HL,BC ;Флаг C сброшен после MAKE_CDA.
LD A,10 ;*
RET NZ
LD BC,16
ADD IX,BC
INC D
JR NZ,TEST_CAT_6
;Указанный в служебном секторе дисковый адрес первого свободного
;сектора должен быть равен дисковому адресу последнего файла,
;увеличенному на количество секторов в последнем файле (т.е.
;должен быть равен HL).
TEST_CAT_8 LD BC,(CAT+#8E1)
CALL MAKE_CDA
SBC HL,BC ;Флаг C сброшен после MAKE_CDA.
LD A,11 ;*
RET NZ
JR TEST_CAT_A
;Файлов нет.
;Указанный в служебном секторе дисковый адрес первого свободного
;сектора должен быть "трек 1, сектор 0".
TEST_CAT_9 LD HL,(CAT+#8E1)
DEC H
LD A,H
OR L
LD A,12 ;*
RET NZ
;Количество занятых секторов диска, сложенное с количеством
;свободных секторов, указанным в служебном секторе, не должно
;быть больше, чем содержится секторов на указанном при вызове
;функции максимальном количестве треков.
TEST_CAT_A LD HL,(CAT+#8E5)
LD BC,(CAT+#8E1)
CALL MAKE_CDA
ADD HL,BC
LD A,13 ;**
RET C ;Переполнение! ;**
EX AF,AF' ;Максимальное количество треков.
LD B,A
LD C,0
CALL MAKE_CDA
;HL должно быть меньше BC+1.
SCF
SBC HL,BC ;HL=HL-(BC+1)
LD A,13 ;***
RET NC ;***
;Все проверки успешно пройдены - делаем вывод, что ошибок нет.
XOR A ;****
RET ;****
;MAKE_CDA - подпрограмма преобразования дискового адреса
;в компактный вид, более удобный для вычислений.
;
;Вход: B - N трека, C - N сектора.
;Выход: BC - N трека*16+N сектора.
MAKE_CDA LD A,B
RRCA
RRCA
RRCA
RRCA
LD B,A
AND #F0
OR C
LD C,A
LD A,B
AND #0F
LD B,A
RET
Длина этой процедуры 228 байтов. Если в программе нужно лишь выяснить, есть ошибка или нет, а какая именно ошибка значения не имеет, то процедуру можно упростить. Для этого надо исключить из листинга двенадцать строк, помеченных «*», заменить фрагмент из двух строк, помеченных «**», на следующие строки:
SBC A,A
RET NZ ;Переполнение!
заменить фрагмент из двух строк, помеченных «***», на следующие строки:
CCF
SBC A,A
RET
и убрать из листинга фрагмент из двух строк, помеченных «****».
В результате длина процедуры сократится на 27 байтов и составит 201 байт. Входные параметры останутся теми же, а выходные параметры будут такими: если флаг Z установлен, то всё в порядке, а если этот флаг сброшен значит, обнаружена ошибка.
В процедуре не используется самомодификация кода, так что при необходимости она может быть помещена в ПЗУ.
|
Другие мои статьи о
|