Итак, приветствую вас во втором продолжении описания дисковой системы Opus Discovery; сегодня посмотрим на управление из ассемблера. Но прежде чем начну, хотел бы поблагодарить всех читателей, которые засыпали редакцию десятками (по словам Logoutа — сотнями) писем. Да, в исходной статье действительно одна ошибка: стандартный размер каталога — 7 секторов, а не 4, как я первоначально упоминал. А теперь уже поехали.
Дискета под лупой
Сначала посмотрим, как собственно данные на дискете хранятся.
Boot-сектор (сторона 1, дорожка 0, сектор 0) начинается меткой длиной 8 байт:
- Первые два байта всегда 18H 05H.
- Третий байт — число дорожек, то есть 28H (40 дорожек) или 50H (80 дорожек).
- Четвёртый байт — число секторов на дорожку, например 12H (18 секторов на дорожку).
- Пятый байт — плотность; она считается так:
Вес | Размер сектора + Вес | Число сторон дискеты ----+------------- ----+---------------- 0 | 128B 0 | Односторонний формат 1 | 256B 1 | Двусторонний формат 2 | 512B 3 | 1024B (вес64) + (вес16), то есть для формата с 256B на сектор и двумя сторонами получим (164)+(116)=80. Пятый байт так будет 50H.
- Байты 6 и 7 случайные (чтобы дискета была уникальной).
- Байт номер 8 всегда 7EH.
Тут на мгновение остановлюсь. Opus Discovery умеет обслуживать разные типы механизмов, но форматировать более чем на одну сторону и 40 дорожек — только если установлена внутренняя RAM. Более того, даже если RAM есть, после включения система всегда настроена на одностороннюю дискету и 40 дорожек. Если хотим форматировать на 800K, нужно выставить правильную конфигурацию диска именно в RAM. Простейший способ — загрузить дискету, у которой формат такой, какой хотим использовать. Достаточно CAT, и система прочитает настройку из BOOT-сектора и сохранит во внутренней RAM. Если затем отформатируем (любую — чистую или уже отформатированную) дискету, система использует настройки именно с последней загруженной дискеты.
Сразу после BOOT-сектора следует КАТАЛОГ. Это, собственно, первый файл на дискете; стандартно он длиной 7 секторов. Отдельные записи каталога всегда длиной 16 байт и выглядят так:
- Первые два байта — нижние 12 бит: значение, сколько байт занято в последнем секторе данного файла −1. Верхние 4 бита использует DOS.
- Байты 3 и 4 — номер первого сектора данного файла.
- Байты 5 и 6 — номер последнего сектора данного файла.
- Байты 7–16 — имя файла (10 символов; если имя короче — дополнено пробелами).
Первая запись в каталоге всегда содержит информацию о самом каталоге, и имя файла — это название дискеты.
Последняя запись в каталоге всегда конечная метка:
- Первый байт всегда FFH.
- Второй байт всегда 00H.
- Третий и четвёртый байт содержат общее число секторов на дискете.
- Пятый и шестой байт всегда значение FFH FFH.
- Последние 10 байт снова — название дискеты.
Здесь должен предупредить о способе, которым система работает с каталогом. Он хоть и занимает 7 секторов, но они заполняются постепенно по мере надобности. Каталог свежеотформатированной дискеты так будет содержать две записи — первую и последнюю — и сразу друг за другом. Остаток каталога затем заполнен значением E5H (229). Посмотрим какой-нибудь пример. Дискета содержит единственный файл, каталог так будет иметь 3 записи:
FF 00 00 00 06 00 RestoreMag 06 00 07 00 22 00 artIIscr FF 00 3F 0B FF FF RestoreMag E5 E5 E5 E5 E5 E5
В этом, собственно, преимущество: не всегда нужно загружать весь каталог — по метке FF FF можем узнать, где кончается его активная часть. В этом случае нам вполне достаточно загрузить первый сектор. Благодаря этому также можем менять размер каталога, не переформатируя дискету. Может пригодиться, если хотим либо увеличить число записей в каталоге, либо, наоборот, добавить немного места для самих данных.
Из этого вытекает ещё одна интересная вещь: файл всегда хранится как один непрерывный блок секторов. Это упрощает чтение; с другой стороны, при частом сохранении и удалении файлов возникает фрагментация свободного места, и может случиться, что система не сможет сохранить файл на дискету, если не найдёт достаточно большой непрерывный участок. Часто используемые дискеты поэтому нужно дефрагментировать (здесь это называется «compacting»), и для этого предназначена команда MOVE "d";1 TO "d";1. Итак, умеем найти файл на дискете — остаётся одно: узнать, что за файл перед нами. Это узнаём в начале самого файла: его первые 7 байт — заголовок:
- Первый байт — тип файла: 0 — BASIC 1 — Numeric array 2 — Character array 3 — CODE
- Второй и третий байт — длина файла (у BASIC — и с переменными).
- Четвёртый и пятый байт — AUTOSTART у BASIC или адрес LOAD у CODE.
- Шестой и седьмой байт — снова длина; у BASIC — без переменных.
Как в ROM зовут...
...так из ROM и откликается. Суть в том, что выбираем службу, которую хотим использовать (номер записи в правильной таблице), вызываем ROM Opus Discovery, и она возвращает нам адрес данной службы. Преимущество — совместимость разных версий ROM, и полностью отпадает забота о типе дискеты (односторонняя versus двусторонняя и тому подобное).
Но уже наконец загружаем!
Выше показали пример каталога с одним файлом — это вступительная картинка из программы Artist II. Давайте загрузим её в экран (код разделил на секции, комментарий ниже):
org 40000
Start call 5896 ld b,0 rst 48 defb 18
ld (LoadAddr+1),hl
ld hl,7 ld de,16384-7 ld c,0 ld b,27
LoadLoop call LoadSect inc d
inc hl djnz LoadLoop
ld c,7 call LoadSect
End jp 5960
LoadSect push bc push de push hl ld a,1 ld b,2 LoadAddr call 0 pop hl pop de
pop bc ret
Рутина релоцируема, так что адрес трансляции произвольный (все значения в десятичной системе).
CALL 5896 подкачивает ROM Opus Discovery. Регистр B затем заполняем записью в таблице служб, которую хотим использовать; в этом случае — сразу первая (нулевая). RST вызывает службу, а defb за ним содержит номер таблицы, которая нас интересует (в этом случае — запись «Call the hardware» таблицы SYSTEM UTILITY TABLE). Система возвращает адрес службы в HL.
Затем настраиваем счётчики. HL содержит сектор, который хотим читать. DE содержит адрес в памяти, куда данный сектор загрузится (снова напоминаю о 7-байтовом заголовке в начале файла — поэтому грузим несколько байт в ROM). Регистр C говорит системе, сколько байт из сектора хотим загрузить; 0 означает 256 байт. А регистр B используем как счётчик секторов.
Внимательный читатель наверняка заметил, что секторы нумеруем с 0, но при этом речь уже о начале КАТАЛОГА. Так и есть: для загрузки BOOT-сектора служит отдельная служба (и ей посвятим следующий раз).
В цикле LoadLoop постепенно загружаем то, что хотим. Заголовок файла, однако, испортил выравнивание на 256 байт, так что последние 7 загружаем в конце. JP 5960 обеспечивает подкачку ROM ZX Spectrum и возврат в BASIC.
Рутина LoadSect тоже в целом прямолинейна. Сохраняем регистры и вызываем load сектора с параметрами. Регистр A содержит номер механизма, а регистр B ещё уточняет службу; в нашем случае 2 означает READ (0=WRITE, 2=READ, 4=OPEN, 6=CLOSE, 8=LENGTH, 10=POSITION). CALL 0 — правильно: в начале мы ноль переписали правильным адресом из HL.
Изначально у меня был подготовлен и разбор каталога, но из-за нехватки времени (статью пишу уже неприлично поздно после дедлайна) оставляю это уважаемому читателю, либо до следующего номера.
Что дальше?
Итак, умеем загрузить сектор и, собственно, и файл с дискеты. Начал с этого намеренно, потому что это подошло к описанию дискеты как таковой. У Opus Discovery, разумеется, есть службы на загрузку и сохранение файла, чтение и просмотр каталога и тому подобное — на это посмотрим именно в следующей части. Рекомендую до тех пор вспомнить работу со STREAM-ами и каналами: система ими широко пользуется (и это же причина, почему сдвигает BASIC при работе с дискетой).
Так до следующего раза!
ellvis/zeroteam
Share your thoughts about the article