Работа с памятью и загрузка файлов.
Что же, выводить на экран текст занятие увлекательное, но пора бы двигаться дальше. Пожалуй (подумал я) попробую загрузить файл с диска... К счастью, мне не придется разбираться с устройством дисковых накопителей, поскольку на этом боюсь мои попытки начать программировать на ассемблере и закончатся, так как придется разбираться в самих этих накопителях и в том, как их нужно программировать, попутно изучив строение файловой системы... Возможно я к этому приду когда нибудь, но точно не сегодня. Среди прочих функций DOS, имеется и набор функций для работы с дисками. Что в значительной мере упростит мне задачу. Но и здесь не всё так просто. Если посмотреть на начало моей программы, то можно задастся вопросом, а почему код программы начинается с адреса #8100, а например не с #1000 или вообще не с #0000? Для этого нам стоит разобраться с устройством памяти компьютера.
Как видно из рисунка, вся наша память поделена на четыре окна по 16 килобайт, что вместе составляет всего лишь 64 килобайта памяти. Но как же так, заявлено же в описании, что данный компьютер имеет 4 Мегабайта Озу? Где оставшаяся память?! Нафига все эти окна и что означают цифры слева, справа и вообще!... Но... обо всём по порядку. Процессор данного компьютера имеет шестнадцатиразрядную адресную шину, что накладывает свои ограничения на объем адресуемого ОЗУ и которое равно 64 килобайтам, что по сегодняшним меркам это прям ОЧЕНЬ мало. Но где же тогда 4 Мегабайта ОЗУ? К счастью все на месте, но что бы их использовать, приходится обращаться к данной памяти через окна процессора, меняя текущую страницу окна на другую. Все 4 Мегабайта ОЗУ поделены на 256 страниц памяти по 16 килобайт. Цифры слева от таблицы, это порты компьютера, за которыми закреплены окна процессора, в которые я могу подключать нужные мне страницы, например используя для этих целей команду процессора OUT (port), A (port - одно из значений #82, #A2, #C2, #E2). Можно даже во все четыре окна установить одну и туже страницу. Однако делать этого не стоит, во всяком случае, пока не разберусь как это делать правильно. Сейчас объясню почему.
Дело в том, как видно из рисунка выше, в нулевом окне (с адреса #0000 по #3FFF) у нас находится наша операционная система DOS, функциями которой я благополучно до сих пор пользовался и если в данном окне заменить текущую страницу на какую нибудь другую, тогда теряется возможность использовать эти самые функции и уже придется самому ломать голову как вывести текст на экран, или например как опросить клавиатуру. По этой же причине не стоит менять страницу в окне номер два, поскольку там кроме нашего кода, находится ещё и стек. Стек - это такая область памяти, куда складываются данные, которые временно необходимо сохранить, например состояние регистров перед вызовом какой нибудь функции, либо адрес возврата из функции. И кроме меня этот стек активно использует DOS. Можно конечно заморочиться, и сохранить текущее значение положения стека где нибудь в памяти, поменять страницу указав новое положение стека, сделать всё что требуется, вернуть страницу обратно и восстановить значение положения стека обратно. Оно к слову хранится в регистровой паре SP. Но в это время нежелательно будет пользоваться функциями DOS. Иначе система может попросту зависнуть или ещё чего похуже. К слову стек растет от больших адресов к меньшим... Также нужно следить, чтобы код программы не пересекался со стеком иначе всё так же получу проблемы с различными зависаниями и прочее.
В итоге на мои нужды остаются два полных окна 1 и 3 и частично окно 2, где собственно на текущий момент и храниться код моей программы.
Но и здесь не всё так просто. Спринтер изначально разрабатывался как мне кажется как МЕГА СПЕКТРУМ, и что бы сохранить совместимость со спектрумом, пришлось несколько заморочиться. Например есть в компьютере ZX Spectrum порт #1F (31 dec), но в спринтере данный порт занят внутренней периферией микроконтроллера Z84C1516. И чтобы урегулировать данный момент, одну страницу памяти выделили под карту портов. Пока не стану вдаваться в эту тему подробно, просто приму во внимание. И получается, что пользоваться всеми четырьмя мегабайтами памяти, путём подключения нужных мне страниц памяти в требуемое окно я тоже не могу. т.е. могу, но на свой страх и риск. Поскольку не знаю какие изначально страницы памяти заняты самой системой, что бы не нарушить её нормальную работу. В целом можно заморочиться и считать текущие значения портов, сохранив их где нибудь в памяти, прежде чем вставлять другие страницы... Но я пока так делать не стану, а воспользуюсь тем, что предлагает мне сама операционная система. Думаю DOS должна знать, какая часть памяти у неё свободна.
В общем воспользуюсь я функцией GETMEM (код #3D). Данная функция выделяет блок памяти и присваивает ему идентификатор (попросту число/ байт) и требует параметр на входе в виде регистра B, в котором указывается количество необходимых страниц каждая размером 16 килобайт. Если необходимый объем запрашиваемой памяти свободен, то в регистре А вернется номер идентификатора блока, если данного объема доступной памяти нет, тогда вернется код ошибки в том же регистре А. Что бы определить, что возвращается, нужно проверить флаг процессора CF. Если он равен 1 - тогда возвращается ошибка, если 0 - тогда идентификатор блока.
Но данная функция только резервирует блок памяти, а его нужно ещё подключить в требуемое мне окно. Для чего имеется ещё одна функция DOS именуемая SETWIN (код #38). Данной функции требуется больше параметров на входе. Так например в регистре А указывается идентификатор банка памяти, выданный функцией GETMEM. В регистре В номер страницы (если их больше чем одна, либо 0 если она одна единственная), а в регистре H, значащие только биты 6 и 7, которые указывают в какое окно процессора устанавливать страницу памяти выделенного блока. На выходе если нет ошибок, то в регистре А номер замещенной страницы. Сразу скажу, что имеются ещё три подобные функции, только каждая закреплена за своим окном процессора (1, 2 и 3), кроме регистра H (поскольку уже не требуется указывать окно процессора) имеют всё те же параметры на входе. ID - выделенного банка памяти и номер подключаемой страницы.
Теперь коротко о работе с файлами...
Для того что бы загрузить файл в память компьютера, его вначале следует открыть. Для этого необходимо использовать функцию DOS - OPEN (код #11). В регистровой паре HL адрес в памяти компьютера, где указан путь к нашему файлу на диске (в формате обычной строки символами ASCII), а в регистре А, указываем режим доступа к файлу, чтение (1), запись (2) либо и чтение и запись (0). На выходе в регистре А получаем ID файла в памяти (как и с выделенным блоком памяти).
После того, как файл будет открыт и ему DOS назначит номер, с ним можно работать. В зависимости от выбранного режима, либо читать (READ (код #19)), либо записывать (WRITE (код #20)) файл. После работы с файлом, его следует закрыть (CLOSE (код #18)), освободив ресурсы операционной системы для работы с другими файлами. Открытие файла, означает, что операционная система сохраняет где то в своей памяти описание данного файла, его атрибуты и например положение указателя файла, что бы знать с какого места продолжать читать файл дальше. Функция CLOSE освобождает память от этих данных. Но опять же, пока не буду вдаваться в подробности на данном этапе. Просто приму это к сведенью.
В обоих случаях (Чтения/ Записи) в регистре А указываю ID файла, в регистровой паре HL - адрес памяти, а в регистровой паре DE - количество читаемых/ записываемых байт. На выходе в регистре А код ошибки, если таковой имеется. в DE реальное количество прочитанных/ записанных байт. Так же функция чтения в регистре А - если #00, то все байты прочитаны, если #FF, то есть еще что читать.
CLOSE на входе только регистр А с ID файла. Если всё нормально, то ничего не возвращает, если ошибка, то в А код ошибки.
На заметку! Текущая версия DOS позволяет открывать только десять файлов одновременно. Собственно поэтому закончив работу с файлом, его следует закрывать.
Ну думаю пора постараться применить полученные знания на практике. Для этого на диске создам текстовый файл test.txt с содержимым File read okey!
После чего пишу следующий код:
Выделенный блок памяти размером в 16 килобайт, подключаю в первое окно процессора, которое находится начиная с адреса #4000 до адреса #3FFF. Куда в последствии и загружается набранный нами текст.
Однако запустив данный код на выполнение мы ничего не увидим.
Что бы увидеть нашу строчку, нужно перед вызовом функции WAIT добавить вызов уже известной функции PCHARS, прописав в регистровой паре hl адрес по которому я загрузил созданный ранее текстовый файл.
Теперь если скомпилировать код и запустить его на выполнение, то на экране появится надпись, что файл загружен нормально. Ну т.е. содержимое текстового файла.
Что же, файлы загружать научился...
В следующей статье постараюсь разобраться с устройством экрана и постараюсь вывести что нибудь на экран.