КАНАЛЫ И ПОТОКИ
Сегодня в номере нет традиционного раздела "Секреты ПЗУ". Причина проста -сейчас мы рассматриваем тот раздел, который очень широко оперирует с такими образованиями, как потоки и каналы и мы уже не можем двигаться дальше, не рассказав Вам о том, что это такое, ведь раньше в своих изданиях мы на этом вопросе не останавливались.
Мы обещали Вам, что в начале следующего года дадим широкую статью на эту тему, но время идет, планы меняются и в связи с тем, что материал по ПЗУ данного выпуска обязательно требует предварительного обсуждения потоков и каналов, мы пропустили эту статью вперед и даем ее всю целиком, в одном номере, без продолжений, чтобы не ущемить интересы тех немногих читателей, кто не подписался на будущий год.
В двух словах: каналы и потоки позволяют скрыть от пользователя сложную логику работы программного обеспечения и сделать для него возможность простыми командами выполнять сложные действия.
Это не более чем еще один прием организации программ, еще один вклад в копилку Ваших знаний о компьютере "Спектрум".
Для многих начинающих пользователей "СПЕКТРУМа" такие понятия как "каналы" и "потоки" могут звучать как некоторые непонятные жаргонные обозначения, но на самом деле за ними скрывается интересная концепция, которая может позволить Вам взять от компьютера то, что другими путями сделать непросто.
Итак, как всегда, начнем с самого простого. Вы начинаете использовать каналы и потоки уже тогда, когда даете команды PPINT или INPUT.
Так, PRINT на самом деле обозначает PRINT #2, хотя #2, как правило опускают. Точно так же INPUT на самом деле INPUT #0. Кстати и LPRINT на самом деле то же самое, что и PRINT #3.
Число, которое стоит после символа # является номером потока.
Не любое число может быть использовано в качестве номера потока. Так, если Вы захотите после включения "Спектрума" напечатать что-то по шестому потоку и дадите команду PRINT №6, то получите сообщение об ошибке "0: Invalid stream" ("ошибочно задан поток"). Это происходит потому, что поток номер шесть еще не подключен ни к какому каналу (потоки 0...3 подключены стандартно, здесь Ваше участие не требуется, этим занимаются программы, "зашитые" в ПЗУ), поэтому прежде чем двигаться дальше, давайте разберемся с каналами.
Листинг 1.
ORG B000 CLOSE NEW SCF
CLOSE_CL PUSH AF
EX AF,A'F'
CALL 1721,STR_DATA_A
LD A, B OR C RET Z
PUSH HL
Сигнал на выгрузку данных из буфера. Сохранение номера потока. Для сохранения флага C. Это обращение во вторую половину процедуры STR_DATA-1 (171E). На выходе HL содержит базовый адрес таблицы STRMS, а BC - адрес данных для данного потока. Проверка BC на ноль.
Возврат, если поток уже закрыт.
Сохранение на стеке адреса таблицы STRMS.
21E2A3 |
LD HL,A3E2 |
Число A3E2 взято потому, |
|
|
что если к нему прибавить |
|
|
5C1E (адрес первого |
|
|
пользовательского потока |
|
|
в STRMS, то возникнет |
|
|
переполнение и включится |
|
|
флаг переноса). |
09 |
ADD HL,BC |
Проверка на переполнение. |
E1 |
POP HL |
Возврат адреса STRMS со |
|
|
стека. |
D0 |
RET NC |
Возврат, если не включен |
|
|
флаг переноса, следова- |
|
|
тельно переполнения не |
|
|
было и поток не пользова |
|
|
тельский. |
DD2A4F5C |
LD IX,(CHANS) |
Установка в IX начала об |
|
|
ласти C.I.A. |
DD09 |
ADD IX,BC |
Установка адреса блока |
DD2B |
DEC IX |
информации о канале. |
DD7E05 |
LD A,(IX+05) |
Ввод идентификатора |
|
|
пользовательского канала. |
FE34 |
CP 34 |
Возврат, если канал не |
C0 |
RET NZ |
пользовательский; |
DD7E06 |
LD A,(IX+06) |
Ввод идентификатора |
|
|
пользовательского канала. |
FE12 |
CP 12 |
Возврат, если канал не |
C0 |
RET NZ |
пользовательский; |
3600 |
LD (HL),00 |
Обнуление данных |
23 |
INC HL |
по потоку |
3600 |
LD (HL),00 |
в таблице STRMS. |
C5 |
PUSH BC |
|
DD6E07 |
LD L,(IX+07) |
В HL устанавливается ад |
DD6608 |
LD H,(IX+08) |
рес процедуры, выгружаю |
|
|
щей буфер. |
08 |
EX AF,A'F" |
Вызов флага C. |
DC2C16 |
CALL C,162C,CALL JUMP |
Выгрузка буфера. |
DDE5 |
PUSH IX |
Переброска из |
E1 |
POP HL |
IX в HL. |
DD4E09 |
LD C,(IX+09) |
В регистре BC устанав |
DD460A |
LD B,(IX+0A) |
ливается длина блока. |
C5 |
PUSH BC |
|
CDE819 |
CALL 19E8,RECLAIM 2 |
Удаление блока из памяти. |
3E10 |
LD A,10 |
10 - количество потоков. |
21165C |
LD HL,5C16 |
5C16 - адрес STRMS 00. |
5E CLOSE LOOP |
LD E,(HL) |
Теперь HL указывает на |
23 |
INC HL |
данные по только что пе- |
56 |
LD D,(HL) |
ремещенному потоку, а DE |
E3 |
EX (SP),HL |
- по следующему. |
A7 |
AND A |
Сброс флага переноса. |
ED42 |
SBC HL,DF |
Проверка передвигался ли |
19 |
ADD HL,DE |
блок после стирания. |
300B |
JR NC,CLOSE NEXT |
Если не передвигался и в |
|
|
его STRMS_n изменения не |
|
|
вносятся - переход. |
EB |
EX DE,HL |
Нет операции SRC DE,BC, |
|
|
потому переброс в HL. |
A7 |
AND A |
Сброс флага переноса. |
ED42 |
SBC HL,BC |
Теперь в HL содержится |
|
|
измененное значение ука- |
|
|
зателя STRMS_n. |
EB |
EX DE,HL |
Перевод его в DE. |
E3 |
EX (SP),HL |
Установка |
2B |
DEC HL |
нового |
73 |
LD (HL),E |
значения |
23 |
INC HL |
указателя |
72 LD (HL),D ; в таблице STRMS.
E3 EX (SP),HL
E3 CLOSE_NEXT EX (SP),HL
23 INC HL ;HL указывает на следующий
;указатель.
3D DEC A ;Уменьшение параметра
;цикла.
20E6 JR NZ,CLOSE_LOOP ;Возврат к началу цикла.
F1 POP AF ;Балансировка стека.
C9 RET ;Возврат.
Можете представить себе канал в качестве некоего устройства, которое используется для ввода или вывода информации. Так, например Ваш телевизор - это канал, поскольку на его экране можно печатать символы.
Надо, правда оговориться, что канал - это не всегда техническое устройство. Каналом может быть например файл на диске (или в памяти компьютера). В файл ведь тоже можно заносить информацию, можно ее оттуда и принимать. Если Вы выполните печать чего-то в файл, то ни на экране, ни на принтере Вы результатов этой печати не увидите, но впоследствии, когда будете просматривать этот файл, увидите, что информация туда вошла, то есть произошла печать (вывод).
Еще лучше представить концепцию каналов и потоков на примере морской бухты. Представьте себе побережье с многочисленными заливами и бухтами. Со стороны суши в них впадают реки, речки и ручьи. Так вот заливы и бухта - это те самые КАНАЛЫ, а ручьи и речки, впадающие в них, - это ПОТОКИ, подключенные к КАНАЛАМ. Их можно и переподключить. Если Вы построите на ручье плотину, образуется водохранилище, уровень воды поднимется и Вы сможете отвести ручей (ПОТОК) в другой залив (КАНАЛ).
Эта аналогия хороша тем, что помогает преодолеть имеющуюся в русском языке небольшую мнемоническую путаницу. Дело в том, что мы обычно под словом "канал" понимаем что-то узкое, длинное, по чему перемещаются какие-то суда, грузы и течет вода. В общем, отождествляем канал с потоком. К сожалению это неверно. "Канал" в компьютере - не средство транспортировки данных - это именно залив, бухта, (экран, принтер, файл на диске, участок в области памяти и т.п.) в которые вливаются потоки данных (ручьи).
В "Спектруме" каждый канал имеет имя, которое выражено одной буквой алфавита. Так, экран дисплея - это канал "S" потому, что по-английски Screen - экран.
Оператор OPEN служит для того, чтобы подключить поток к каналу (канал к потоку). Так, Вы можете в БЕЙСИКе дать команду OPEN#6,"S" и тем самым подключите шестой поток к экрану и тогда команда PRINT# 6 будет печатать Ваш текст на экране точно так же, как это делает обычная команда PRINT.
Листинг 2.
ORG B061 CLEAR_NEW LD A,10 CLEAR_LOOP DEC A PUSH AF AND A
CALL B001,CLOSE_CL POP AF
JR NZ,CLEAR_LOOP RET
3E10 3D F5 A7
CD01B0 F1
20F7 C9
Выключение флага C - сигнал о том, что данные в буферах уничтожаются. Удаление канала, подключенного к данному потоку.
;Возврат для повтора.
Листинг 4.
Вектор переходов для ПЗУ "ZX-Spectrum-128".
Генерация сообщений об ошибках
C3641C |
V_ |
PAGE |
JP |
1C64 |
C3971D |
V_ |
NEWCAT |
JP |
1C97 |
C3F31C |
V_ |
SPACE |
JP |
1CF3 |
C3121D |
V_ |
FIND |
JP |
1D12 |
C3561D |
V_ |
CATEND |
JP |
1D56 |
Переключение текущей страницы ОЗУ. Создание новой записи в каталоге. Проверка на достаточность памяти RAM-диска. Поиск имени файла в каталоге. Оформление последней записи каталога.
Листинг 5.
Вектор переходов для ПЗУ "ZX-Spectrum+2"
|
|
|
ORG |
B6FC |
|
C3CB05 |
V_ |
ERROR |
JP |
05CB |
Генерация сообщений об ошибках. |
C3631C |
V_ |
PAGE |
JP |
1C83 |
Переключение текущей страницы ОЗУ |
C3B61C |
V_ |
NEWCAT |
JP |
1CB6 |
Создание новой записи в каталоге. |
C3121D |
V_ |
SPACE |
JP |
1D12 |
Проверка на достаточность |
|
|
|
|
|
памяти RAM-диска. |
C3311D |
V_ |
FIND |
JP |
1D31 |
Поиск имени файла в каталоге. |
C3751D |
V_ |
_CATEND |
JP |
1D75 |
Оформление последней |
записи каталога.
Аналогично Вы можете представить, что клавиатура - это устройство, предназначенное для ввода информации и потому это тоже канал. Он имеет имя "K" (от слова Keyboard = клавиатура). К этому каналу можно подключить поток точно так же, как мы это делали с экраном. Можете дать команду OPEN #n,"K" и подключить поток n к каналу "K".
Всего Вы можете иметь не более 16 потоков, пронумерованных от 0 до 15. Шестнадцатого потока не существует и, если Вы попробуете его использовать, то получите сообщение об ошибке.
Мы уже сказали о том, что потоки от 0 до 3 являются стандартными и организованы без нашего участия. Они стандартно подключены к стандартный каналам.
Потоки 0 и 1 подключены к каналу "K", поток 2 - к каналу "S", а поток 3 - к каналу "P". Канал "P" - принтер (Printer).
Каналы "S" и "P" предназначены только для вывода (ручей может только впадать в залив), поэтому например PRINT #2 или PRINT #3 возможны, а INPUT #2 или INPUT #3 -невозможны.
В отличие от них канал "K" может использоваться и для ввода и для вывода (из этого озера река может вытекать, как Нева из Ладоги).
INPUT #0 - это самая обычная команда INPUT.
Возможен и вывод:
PRINT #0 обеспечит печать Вашего сообщения в нижних двух строках экрана, которые выполняют роль "системного окна". Это те самые две строки, в которых появляется информация при работе команды INPUT.
В "родном" "Спектруме-48" есть еще один стандартный канал - "R", но из Бейсика он не достижим и мы пока его отставим, вернемся к нему позже.
Прочие каналы.
После подключения дополнительной периферии, располагающей своим ПЗУ, к стандартным каналам могут добавляться дополнительные. Так, например, подключение интерфейса-1 ("ZX-Interface One") создает несколько новых каналов:
- "M" - канал микродрайва;
- "N" - канал локальной сети;
- "T" - канал принтера для печати листинга программ (коды выше 165 интерпретируются как токены ключевых слов "Спектрума");
- "B" - канал принтера для печати данных (все коды интерпретируются по значению). Вот практически и все каналы, возможные для "Спектрума" на стандартном оборудовании. Но это не значит, что у Вас нет дополнительных возможностей. Вы можете создавать свои каналы в оперативной памяти и эффективно использовать их. Этим мы с Вами и займемся.
Листинг 6.
Процедура служит для открывания нового пользовательского канала. Это выполняется путем создания блока информации о канале. При входе в эту процедуру регистр ^альтернативный) должен содержать номер назначенного потока для данного канала, регистр A(основной) - код имени данного канала (код ASCII), BC - длину блока информации о канале, DE - адрес процедуры ввода (INPUT), HL - адрес процедуры вывода (PKINTJ, а регистр IX - адрес процедуры, используемой для выгрузки данных из буфера или адрес 0052 HEX, если работа с буфером не нужна.
ORG B06D
C5 OPEN_NEW PUSH BC
DDE5 PUSH IX
F5 PUSH AF
D5 PUSH DE
E5 PUSH HL
C5 PUSH BC
08 EX AF,A'F'
CD2117 CALL 1721,STR_DATA_A
78 LD A, B
B1 OR C
C1 POP BC
2602 JR Z,OPEN_NEW_2
CF17 RST 08
DEFB 17
E5 OPEN_NEW_2 PUSH HL
2A535C LD HL,(PROG)
2B DEC HL
CD5516 CALL 1655,MAKE_ROOM
23 INC HL
22515C LD (CURCHL),HL
E5 PUSH HL
DDE1 POP IX
23 INC HL
ED4B4F5C LD BC,(CHANS)
A7 AND A
ED42 SBC HL, BC
EB EX DE,HL
E1 POP HL
73 LD (HL),E
23 INC HL
72 LD(HL),D
DDE5 PUSH IX
Запоминаем на стеке входные значения регистров процессора.
^-номер потока. ;Загрузка в BC данных по ;этому потоку из системной ;таблицы STRMS.
;Проверка адреса на ноль.
;Переход, если поток не ;подключен.
;Вызов процедуры обработки ;ошибок с кодом перехвата ;17, что означает: ;"0: invalid stream" ("He-;верно задан поток").
;(PROG) - начало БЕЙСИКа. ;Теперь HL указывает на ;байт, содержащий маркер ;80H и отмечающий конец ;области информации о каналaх. ;Выделение места под новый ;блок информации о канале. ;Теперь HL указывает на ;начало нового информационного блока. ;Канал делается текущим и ;адрес начала его информационного блока переводит-;ся в IX.
;HL - указывает на 2-ой ;байт блока.
;BC указывает на начало ;области CHANS. ;Обнуление флага C как подготовка к операции SBC. ;Вычисленное значение ;является тем адресом, ко-;торый должен быть занесен ;в таблицу данных по ;потокам STRMS. ;Теперь оно - в DE, ;HL содержит адрес требуемой STRMS_n. ;Отправляем данные ;по потоку на свое место ;в STRMS_n.
E1 |
POP HL |
|
HL-адрес блока информации о канале. |
D1 |
POP DE |
|
DE - адрес процедуры вывода. |
C1 |
POP BC |
|
BC -адрес процедуры ввода |
CDAEB0 |
CALL B0AE,OPEN_STORE |
Сохранение этой инфор- |
|
|
|
мации в блоке информа- |
|
|
|
ции о канале. |
F1 |
POP AF |
|
A содержит имя канала. |
77 |
LD (HL) |
A |
Записали его в блок. |
23 |
INC HL |
|
Напомним: 1234 - сигнал |
3634 |
LD (HL) |
34 |
о том, что данный канал - |
23 |
INC HL |
|
пользовательский, а не |
3612 |
LD (HL) |
12 |
стандартный. |
23 |
INC HL |
|
|
D1 |
POP DE |
|
DE-адрес процедуры, закрывающей буфер. |
C1 |
POP BC |
|
BC-длина блока информации о канале. |
73 |
OPEN_STORE LD (HL) |
E |
Помещение в блок информа- |
23 |
INC HL |
|
ции о канале адреса |
72 |
LD (HL) |
D |
закрывающей процедуры. |
23 |
INC HL |
|
|
71 |
LD (HL) |
C |
Помещение в блок информа- |
23 |
INC HL |
|
ции о канале длины блока |
70 |
LD (HL) |
B |
информации. |
23 |
INC HL |
|
|
C9 |
RET |
|
Возврат в вызывающую процедуру. |
Область информации о каналах
Итак, как же все это работает? Вся концепция потоков и каналов базируется на области памяти, называемой "область информации о канале" (CHANNEL INFORMATION AREA).
Эта область памяти расположена непосредственно перед БЕЙСИК-областью, чуть ниже нее. То есть лежит между системными переменными и БЕЙСИКом. Начинается она с адреса, хранимого в системной переменной CHANS (23631, 2 байта)(5C4FH) и заканчивается байтом, в котором стоит маркер 80H. Далее уже идет Ваша БЕЙСИК-программа, на которую указывает PROG (23635, 2 байта)(5C53H).
Для того, чтобы создать свой канал Вам практически надо переорганизовать данные в этой области, может быть и раздвинуть эту область, сдвинув вверх маркер и поменяв значение PROG и разместить где-либо в памяти две процедуры. Одну - для обеспечения ввода в Ваш канал (INPUT) и вторую - для обеспечения вывода (PRINT).
Каждый канал должен иметь блок информации о канале (CHANNEL INFORMATION BLOCK). Этот блок и располагается в области информации о каналах.
В этом блоке содержится вся информация, необходимая для того, чтобы канал мог функционировать. Четыре стандартных канала "K","S","P" и "R" имеют каждый по пятибайтному блоку, но это исключение. Все остальные каналы, в том числе и те, которые создадите Вы, должны иметь не менее, чем по 11 байтов в этом блоке на каждый канал.
По четырем стандартным каналам эти блоки выглядят так:
Байты 0 и 1 - адрес процедуры вывода (PRINT#).
Байты 2 и 3 - адрес процедуры ввода (INPUT#).
Байт 5 - имя канала (код одной буквы "K", "S","R" или "P").
При подключении стандартной периферии, например ИНТЕРФЕЙСа-1 этот блок имеет длину 11 байтов и адресация к ним производится помещением в регистровую пару процессора IX базового адреса начала блока.
IX+0 (2 байта) - адрес процедуры обработки ошибок 0008.
IX+2 (2 байта) - адрес процедуры обработки ошибок 0008.
Листинг 7.
В области информации о каналах данная процедура выполняет поиск блока с заданным именем.
Данная процедура используется при работе процедуры V_OPEN (B966), см. Листинг
20.
В IX - базовый адрес начала области информации о каналах.
D содержит ASCII-код имени канала. IX указывает на адрес очередного блока.
Напомним, что 80H - маркер, отмечающий конец области информации о каналах.
Включение флага C. Выход с включенным C-фла-гом, если блок канала C таким именем не найден. A содержит ASCII-код имени канала. Сравнили с искомым. Выход, если найден. BC - длина блока информации о канале. Переход в начало цикла для продолжения поиска.
DD2A4F5C SEARCH CH ALL
011400 57
LD BC,0014 LD D,A
ADD IX,BC
LD A,(IX+00) CP 80
DD7E04
BA
C8
DP4E09 DD460A 18EA
LD A,(IX+04)
CP D RET Z
LD C,(IX+09) LD B,(IX+0A) JR SCH_CH_LOOP
Листинг 8.
Нижеприведенная процедура служит для обслуживания такой "хитрой" конструкции как "трехбайтный" регистр. Ранее мы писали, что в 128-килобайтных моделях для хранения адреса недостаточно двух байтов и приходится привлекать еще и кодовый номер страницы. Такой "адрес" хранится в трех регистрах процессора, например, B,H,L. Процедура выполняет операцию декремента (DEC BHL).
|
|
ORG B70E |
|
2B |
DEC_BHL |
DEC HL |
Декремент пары HL. |
78 |
|
LD A,B |
в A был код страницы. |
FE05 |
|
CP 05 |
Возврат, если это обычное |
C8 |
|
RET Z |
ОЗУ. |
CB74 |
|
BIT 6,H |
Возврат, если не было пе- |
C0 |
|
RET NZ |
рехода между страницами. |
CBF4 |
|
SET 6, H |
|
05 |
|
DEC B |
|
C9 |
|
RET |
|
IX+4 (1 байт) - имя канала.
IX+5 (2 байта) - адрес процедуры PRINT#, которая находится в "теневом" ПЗУ интерфейса.
IX+7 (2 байта) - адрес процедуры INPUT#, которая находится в "теневом" ПЗУ интерфейса.
IX+9 (2 байта) - длина блока информации о канале (не менее 000B). IX+0B (длина любая) - любая дополнительная информация.
Блоки информации о пользовательских каналах тоже лучше создавать и обслуживать,
используя адресацию через индексный регистр IX. Тогда такой блок имеет следующий вид:
IX+0 (2 байта) - адрес процедуры вывода (PRINT#).
IX+2 (2 байта) - адрес процедуры ввода (INPUT#).
IX+4 (1 байт) - имя канала,
IX+5 (2 байта) - число 1234, свидетельствующее о том, что этот канал не стандартный, а пользовательский.
IX+7 (2 байта) - адрес закрывающей процедуры (CLOSE#).
IX+9 (2 байта) - длина блока информации о канале (не менее 000B).
IX+0B (длина любая) - любая дополнительная информация.
Использование процедур ПЗУ
Процедура вывода, содержащаяся в ПЗУ (RST 10H) и процедура ввода INPUT_AD (15E6H) работают со стандартными каналами. Поэтому при выводе происходит сразу обращение в первые два байта блока информации о каналах для поиска адреса печатающей процедуры, а при вводе - обращение в 3-ий и 4-ый байты для поиска адреса читающей процедуры.
Если Вы используете стандартную периферию, например Интерфейс-1, то в этих байтах содержится адрес 0008 (адрес процедуры обработки ошибки). При попадании туда происходит "впечатывание" страницы "теневого" ПЗУ вместо стандартного, а уже из него выполняется выбор процедур печати и чтения (байты IX+5 ... IX+8).
И снова о потоках
Теперь, когда мы разобрались с каналами и поняли, что это не так сложно, что это всего лишь еще один способ организации памяти компьютера и взаимодействия процедур друг с другом, вернемся к потокам.
Листинг 9.
Данная процедура предназначена примерно для тех же целей, что и знаменитая команда процессора LDIR и служит для быстрого перемещения значительных блоков памяти в пределах RAM-диска.
Поскольку адресация в дополнительной памяти 128-килобайтных машин невозможна через двухбайтный регистр, эта процедура введена для обслуживания трехбайтных конструкций типа BHL, B'H'L', CDE и др.
Процедура выполняет три функции:
1) Выполняет декремент BHL и В'НЪ';
2) перегружает байт из BHL в B'H'L';
3) если BHL не равен CDE, переход на пункт 1.
|
|
ORG B71A |
CD0EB7 |
V_TRANSFER |
CALL B70E,DEC BHL |
D9 |
|
EXX |
CD0EB7 |
|
CALL B70E,DEC BHL |
D9 |
|
EXX |
78 |
|
LD A,B |
CDFFB6 |
|
CALL B6FF,V PAGE |
7E |
|
LD A, (HL) |
F5 |
|
PUSH AF |
D9 |
|
EXX |
7B |
|
LD A,B |
CDFFB6 |
|
CALL B6FF,V PAGE |
F1 |
|
POP AF |
77 |
|
LD (HL),A |
D9 |
|
EXX |
78 V |
TRANSFER_2 |
LD A,B |
Декремент BHL.
Декремент B'H'L'.
В A стал код страницы источника.
Впечатывание страницы ОЗУ Прием перебрасываемого байта и
сохранение его на стеке.
В A стал код страницы назначения.
Впечатывание страницы ОЗУ Восстановление пересылаемого байта и пересылка.
В A стал код страницы источника.
B9 |
CP C |
|
20E6 |
JR NZ,V TRANSFER |
На повтор цикла. |
ED52 |
SBC HL,DE |
Включение флага Z, если |
|
|
все байты переданы. |
19 |
ADD HL,DE |
Эта операция на Z флаг |
|
|
не влияет. |
20E1 |
JR NZ,V_TRANSFER |
На повтор цикла, если не |
|
|
все байты переданы. |
C9 |
RET |
|
Листинг 10.
Эта вспомогательная процедура рассчитывает адрес при страничной организации памяти, отстоящий от адреса, содержащегося в комплексе AHL на величину, содержащуюся в паре BC.
Предполагается, что величина в BC не превосходит размера одной страницы - 4000H (16 килобайт).
|
ORG B73A |
|
09 |
ADD AHL BC ADD HL,BC |
;16-разрядное сложение. |
FE05 |
CP 5 |
;Сравниваем код страницы с |
|
|
;числом 5. |
C8 |
RET Z |
:Возврат, если это стандартное ОЗУ |
CB74 |
BIT 6,H |
;Возврат, если не было пе |
C0 |
RET NZ |
рехода между страницами. |
CBFC |
SET 7,H |
|
CBF4 |
SET 6, H |
|
3C |
INC A |
;Увеличиваем код страницы, |
|
|
;если был переход. |
C9 |
RET |
|
Среди системных переменных компьютера есть переменная STRMS. Ее адрес - 23568 (5C10H). Ее назначение - указание на адреса каналов, подключенных к потокам. Длина этой системной переменной - 38 байтов и если говорить откровенно, то никакая это не переменная, а самая настоящая указательная таблица, в которой каждому потоку отданы два байта, содержащие адрес канала, к которому данный поток подключен.
Правда, у внимательного читателя может сразу возникнуть вопрос - почему же потоков 16, да на каждый по 2 байта, итого 32, а STRMS содержит 38 байтов.
Дело в том, что есть еще три потока, которые из БЕЙСИКа вам недоступны - только из машинного кода. Эти потоки занимаются своими "внутренними" делами при работе ПЗУ. Это "минус третий" поток (FD), "минус второй" (FE) и "минус первый" (FF). Таким образом, таблицу STRMS можно представить, как 19 двухбайтных системных переменных: STRMS_FD 5C10 (23568) STRMS_FE 5C12 (23570) STRMS_FF 5C14 (2357E) STRMS_00 5C16 (23574) STRMS_01 5C16 (83576)
STRMS_0F 5C36 (23606)
Поток FD подключен к каналу "K" и не должен переподключаться. Аналогично поток FE подключен к каналу "S". Интересен поток FF, подключенный к "внутреннему" "Спектрумовскому" каналу "R", который отвечает за динамическое копирование информации из одних областей памяти компьютера в другие, производя при этом "раздвигание" информации для вставки новой в середину имеющейся. Мы об этом писали в разделе "Секреты ПЗУ", когда рассматривали процедуры БЕЙСИКовского редактора.
Итак, с помощью таблицы переменных STRMS выполняется привязка потоков к каналам. Если какая-либо переменная из набора STRMS содержит 0000, то это означает, что к данному потоку не подключен ни один канал, иначе говоря, канал закрыт. Если же там не ноль, значит канал открыт и данный поток подключен к этому каналу. Фактически же поток подключен к тому каналу, информационный блок которого начинается с адреса, на который
указывает системная переменная CHANS (23631) плюс величина, содержащаяся в STRMS для данного потока минус единица: (CHANS) + (STRMS_n) - 1
Для тех, кто не знаком с программированием в машинных кодах, укажем, что круглые скобки в этой формуле означают, что речь не идет об адресе самой системной переменной CHANS, а об адресе, на который она указывает, т.е. который в ней хранится, то же и (STRMS_n).
Листинг 11.
Процедура предназначена для поиска в каталоге RAM-диска файла с именем, которое задано в блоке информации о канале. Если файл с таким именем в каталоге не существует, выдается системное сообщение об ошибке. При успешном окончании работы процедуры на выходе в индексной паре IX устанавливается адрес, указывающий на требуемую информацию в каталоге. При входе там должен быть выставлен адрес места расположения блока информации о канале.
ORG B747 PUSH IX POP HL LD BC,000E
ADD HL,BC LD C,0A
LD DE,5B67,NSTR_1
LDIR
CALL B708,V_FIND RET NZ
CALL B6FC,V_ERROR DEFB 23
DDE5 E1
010E00 09
0E0A 11675B
Переброска адреса блока через стек.
Имя в блоке начинается с байта IX+0E. HL указывает на имя. Длина имени - 10 знаков. B системной переменной NSTR_1 должно содержаться имя искомого файла. Переброска имени из блока информации в системную переменную.
Вызов процедуры ПЗУ для поиска по каталогу. Выход, если файл найден. Вызов системной процедуры генерации сообщения об ошибке с кодом перехвата 23H (h "File does not exist") - "Файл не существует."
Листинг 12.
Данная процедура выполняет важную задачу. Мы организовали в блоке информации о канале "V" полукилобайтный буфер и эта процедура устанавливает взаимное соответствие между данным буфером и областью оперативной памяти на RAM-диске. Эта область представляет тем самым как бы последовательность полукилобайтных секторов.
На входе в эту процедуру необходимо, чтобы в IX был установлен адрес начала блока информации о канале "V".
На выходе из этой процедуры устанавливаются:
- в регистрах BHL - адрес первого байта, следующего за последним сектором, заполненным на RAM-диске (для данного файла);
- в регистрах CDE - адрес начала сектора на RAM-диске;
- в регистрах B'H'L' адрес байта, следующего за буфером канала "V" (в блоке информации о канале).
ORG B75D
DDE5 V_MATCH PUSH IX
DD7E0D LD A,(V_CHREC)
F5 PUSH AF
CD47B7 CALL B747, FIND_FILE
Сохранили на стеке адрес блока информации о канале и номер записи в файле. Теперь IX указывает на запись о данном файле в каталоге, а
C1 POP BC
CB20 SLA B
0E01 LD C,01
37 SCF
08 EX AF,A'F'
DD6E0D LD (L,SF_LEN)
DD560E LD (H,SF_LEN+1)
DD7E0F LD (A,SF_LEN+2)
A7 AND A
ED42 SBC HL,BC
DE00 SBC A,00
A7 AND A
2008 JR NZ,V_M_N_EOF
110102 LD DE,0201
ED52 SBC HL,DE
19 ADD HL,DE
3604 JR C,V_M_EOF
210002 V_M_N_EOF LD HL,200
08 EX AF,A'F'
EB V_M_EOF EX DE,HL
DD6E0A LD L,(SF_START)
DD660B LD H,(SF_START+1)
DD7E0C LD A,(SF_START+2)
CD3AB7 CALL B73A,ADD_AHL_BC
C5 PUSH BC
D5 PUSH DE
E5 PUSH HL
F5 PUSH AF
42 LD B,D
4B LD C,E
CD3AB7 CALL B73A,ADD_AHL_BC
47 LD B,A
F1 POP AF
4F LD C,A
D1 POP DE
D9 EXX
D1 POP DE
C1 POP BC
DDE1 POP IX
DD7319 LD (V_RECLRN),E
DD731A LD (V_RECLRN+1),D
DDE5 PUSH IX
E1 POP HL
011в00 LD BC,001B
09 ADD HL,BC 19 ADD HL,DE
B - на номер записи в файле.
Удвоение содержимого B.
В BC теперь 200H*(номер записи)+1
Сигнал "конец блока" и
сохранение его.
В AHL устанавливается
длина
файла.
Обнуление флага переноса, чтобы не искажать следующую операцию. Вычитание с "займом" Вычитание "займа" из A, теперь в AHL - размер "остатка" файла. проверка на ноль. Переход, если старший байт AHL еще не обнулен.
Сравнение размера "остат-8c длиной буфера. Если больше, то включится флаг переноса и тогда переход. Длина записи max 200H. Запомнили выключенный флаг C , означающий "Не конец записи". DE - длина записи. В AHL устанавливается адрес начала файла. CM. B73A.
200*(номер записи)+1 Длина записи. Адрес сегмента и код его страницы.
B AHL помешается адрес очередного сектора на RAM-диске.
В CDE адрес сегмента и код его страницы. Включение альтернативных регистров. D'E'- длина записи. B,C,=200"(номер записи)+1 В IX - адрес блока информации о канале. Заносим длину записи в V_RECLEN.
в HL - адрес блока информации о канале. В блоке информации о канале до буфера 1B байтов. H'L' указывает на буфер канала "V".
H'L' указывает на байт, следующий за текущей записью.
0605 |
LD B,05 |
B'=05 - сигнал "страница |
|
|
стандартного ОЗУ". |
D9 |
EXX |
Основной набор регистров. |
DDCB188E |
RES 1,(V_CHFLAG) |
Сигнал "не последний |
|
|
блок в файле." |
08 |
EX AF,A'F' |
Напомним - флаг C несет |
|
|
информацию о конце файла. |
D0 |
RET NC |
Возврат, если блок не по- |
|
|
следний. |
DDCB18CE |
SET 1,(V_CHFLAG) |
Сигнал "конечный блок |
|
|
файла". |
C9 |
RET |
|
Из всего вышесказанного вытекает одна тонкость. Оказывается с программистской точки зрения проще создать и открыть новый канал, чем закрыть его. Действительно, если Вы решили создать канал, Вы в конце области информации о каналах, на которую указывает (CHANS), припишете блок информации о канале (11 байтов или более) и для желаемого Вами потока #n запишете в соответствующую переменную STRMS_n указание на этот блок.
А что же, если Вы хотите закрыть канал? Тогда в соответствующее значение STRMS_n Вы запишете нули, но надо еще уничтожить блок информации о канале. Хорошо, когда он -последний. Убрали его и все. А если он находится в середине, тогда придется все вышестоящие блоки сдвигать вниз с изменением при этом содержимого STRMS_n+1, STRMS_n+2.....
Как закрыть канал.
Итак, если мы хотим научиться сами создавать свои каналы, подключать к ним потоки, т.е. открывать эти каналы и использовать их для своих целей, то прежде всего надо научиться закрывать свои каналы, для чего и служит предлагаемая ниже процедура CLOSE_NEW (листинг 1).
При входе в эту процедуру регистр A микропроцессора должен содержать номер подключаемого потока. Если канал уже закрыт или если канал не является каналом, созданным пользователем, то процедура выполняет немедленный возврат и ничего не делает.
С другой стороны, если канал открыт (поток подключен) и это пользовательский канал, процедура "вычищает" память, занятую блоком информации о канале, смещает вышестоящие блоки вниз ("убирает мусор") и перестраивает системные переменные в таблице STRMS.
При работе процедуры, учитывается также состояние флага переноса (флаг C флагового регистра F). Если при входе в процедуру через адрес CLOSE_CL флаг C включен, значит в буферах есть данные и перед тем, как закрыть канал надо их очистить, выдав все данные, чтобы информация не пропала. Если флаг C выключен, то наличие данных в буферах можно проигнорировать.
Если однозначно необходимо буфер отгрузить, то входить в процедуру надо через точку входа CLOSE_NEW.
Ниже представлена также вспомогательная процедура CLEAR CHANS (листинг 2), служащая для закрывания всех пользовательских каналов. При вызове этой процедуры все данные, оставшиеся в буферах, уничтожаются.
Копирует данные в буфер. обнуляет указатель данных в буфере.
Листинг 13.
Процедура настраивает буфер канала, подготавливаясь к работе с файлом, открытым для чтения.
ORG B7C5
CD5DB7 V_ASSIGN CALL B75D, V_MATCH CD30B7 CALL B730,V_TRANSFER_2
DD360B00 V_BUF_EXIT LD(V_CHBYTE),00 DD360C00 LD(V_CHBYTE+1),00
C9 RET
Эти программы нерелоцируемы, то есть их загружать можно только в те адреса памяти, для которых они написаны, и которые стоят в директиве АССЕМБЛЕРа ORG. Это сделано потому, что за этими процедурами пойдут другие и все они должны работать совместно, как единое целое.
Файлы последовательного доступа на RАM-диске
Теперь, когда мы научились, в принципе, закрывать созданные каналы, можно попробовать их создавать. В качестве примера мы рассмотрим идее о том, как владельцы 128-килобайтных машин могут использовать их потенциал, прибегнув к концепции потоков и каналов.
Что еще можно делать с помощью каналов и потоков? Мы назовем несколько направлений. Например, организуется нестандартная печать, скажем 45, или 55 или 60 символов в строке. С их же помощью организуется, например, работа с "окнами" так, как это сделано в "МЕГАБЕЙСИКе" или, скажем, в "ЛАЗЕР-БЕЙСИКе". Вы можете расширить БЕЙСИК своего компьютера, придумав для него несколько новых команд. И самое интересное, пожалуй, - это организация обмена между разными машинами. Мы бы с удовольствием привели пример именно из этой области, но для этого пришлось бы освещать работу и этой (другой) машины, что никак не входит в наши планы. Приходится ограничиться вопросами обмена данными между различными областями одной машины.
Итак, в качестве примера мы создадим канал, позволяющий работать с файлами последовательного доступа на RAМ-диске 128-килобайтных компьютеров.
Примечание.
Это, конечно не значит, что Вам надо завтра сразу все бросить и побежать покупать 128-килобайтную машину, пока Вам кто-то не сумеет объяснить, что же хорошего, кроме лишнего десятка "игрушек" Вы получите от этих дополнительных 80 килобайтов, к которым у процессора нет и не может быть прямой адресации.
Кстати, уникальной особенностью этих машин (в импортном исполнении) является трехголосый звуковой синтезатор, ради которого все и затевалось. Пока у нас в стране нет никаких аналогов этой микросхемы, все попытки "продать" пользователю 128-килобайтную модель по цене хотя бы на 200 рублей дороже 48-килобайтного аналога - это просто эксплуатация его доверчивости и неинформированности.
Эта задачка так и осталась бы академической, но есть и факты: темп продаж 128 килобайтных машин на Западе так и не достиг и 10% темпа продаж обычных 48-килобайтных машин в период их расцвета. Даже попытка встроить дисковод в "ZX-Spectrum+3" ничего кроме очередного разочарования не дала и количество программ, поддерживаемых этой версией, можно пересчитать на пальцах одной руки.
Вообще история сэра К. Синклера это такой роман с потрясающими взлетами и глубокими падениями, изучая который можно уберечь пользователя от излишних тысячных затрат, а производителя - от многомиллионных.
Данная процедура принимает из аккумулятор процессора.
ORG B803
CD005B
2A5A5B
E5
D9
C5
D5
E5
DD2A515C
V INKEY
CALL 5B00,SWAP LD HL,(RETADDR) PUSH HL EXX
PUSH BC PUSH DE PUSH HL LD (IX,CURCHL)
Листинг 15.
"V''-канала единичный символ и помещает его в
;"Впечатывание" ПЗУ-0.
IX указывает на начало блока информации о канале.
DDCB1846 |
|
|
|
BIT 0,(V CHFLAF) |
Если это READ-файл - |
2804 |
|
|
|
JR Z,V INKEY 2 |
то перевод. Иначе |
CDFCB6 |
V_ |
_ERROR_ |
1 |
CALL B6FC,V ERROR |
генерация сообщения об |
1D |
|
|
|
DEFB 1D |
ошибке с кодом перехвата 0D ("b: Wrong file type") "Неверный тип файла". |
DD5E0B |
V_ |
_INKEY_ |
2 |
LD E,(V CHBYTE) |
В DE - позиция следующего |
DD560C |
|
|
|
LD D,(V CHBYTE+1) |
читаемого байта. |
DDCB184E |
|
|
|
BIT 1, (V CHFLAG) |
Проверка на содержание в |
280F |
|
|
|
JR Z,V INKEY RD |
блоке метки "конец файла" |
DD6E19 |
|
|
|
LD L,(V RECLEN) |
В HL - длина текущей за- |
DD661A |
|
|
|
LD H,(V RECLEN+1) |
записи в файле. |
A7 |
|
|
|
AND A |
|
ED52 |
|
|
|
SBC HL,DE |
Проверка на достижение |
2004 |
|
|
|
JR Z,V INKEY RD |
последней записи. |
CDFCB6 |
|
|
|
CALL B6FC,V ERROR |
Генерация сообщения об |
07 |
|
|
|
DEFB 07 |
ошибке с кодом перехвата 07 ("8: End of file") "Конец файла". |
DDE5 |
V_ |
_INKEY_ |
RD |
PUSH IX |
|
E1 |
|
|
|
POP HL |
HL указывает на блок информации по каналу "V". |
011B00 |
|
|
|
LD BC,001B |
|
09 |
|
|
|
ADD HL,BC |
HL указывает на буфер. |
19 |
|
|
|
ADD HL,DE |
HL указывает на очередной читаемый символ. |
7E |
|
|
|
LD A,(HL) |
В аккумулятор поступает символ от INKEY$. |
F5 |
|
|
|
PUSH AF |
|
13 |
|
|
|
INC DE |
Передвинули указатель. |
DD730B |
|
|
|
LD (V CHBYTE),E |
Запомнили положение |
DD7E0C |
|
|
|
LD (V CHBYTE+1),D |
указателя. |
15 |
|
|
|
DEC D |
Если D=2 значит пора об |
15 |
|
|
|
DEC D |
новлять бУФер. |
2006 |
|
|
|
JR NZ,V INKEY EXIT |
|
DD340D |
|
|
|
INC (V CHREC) |
Увеличили номер записи. |
CDC5B7 |
|
|
|
CALL B705,Y ASSIGN |
|
F1 |
V_ |
_INKEY_ |
EXIT |
POP AF |
В A - только что прочитанный байт. |
37 |
|
|
|
SCF |
|
E1 |
V_ |
_INPUT_ |
EXIT POP HL |
|
D1 |
|
|
|
POP DE |
|
C1 |
|
|
|
POP BC |
|
D9 |
|
|
|
EXX |
|
E1 |
V_ |
EXIT |
|
POP HL |
В HL - адрес возврата в ПЗУ-0. |
225A5B |
|
|
|
LD (RETADDR),HL |
Настройка системной переменной на адрес возврата. |
C3005B |
|
|
|
JP 5B00,SWAP |
"Впечатывание" ПЗУ-1 и |
;возврат.
Те, кто знаком с такими носителями информации как флоппи-диск или микродрайв, представляют, что такое файлы последовательного доступа, открытые для чтения (READ-файл) или для записи (WRITE-файл). В файл, открытый для записи, Вы можете "впечатывать" текст, затем этот файл может быть закрыт (CLOSE) и снова открыт (OPEN) для чтения. Вводить информацию (текстовую и числовую) в такой файл Вы сможете прямо из БЕЙСИКа.
Нужны ли Вам "файлы последовательного доступа" или нет - дело Ваше, но показать, каким путем идея потоков и каналов используется в приложениях мы должны, а Вы сами разберетесь, как Вам поступить.
RAM-диск
Файлы, размещенные в RAM-диске, который нередко еще называют виртуальный диском, работают точно так же, как и файлы, размещенные например на гибких дисках, когда Вы открываете (OPEN) на RAM-диске файл для записи, то в верхних страницах памяти компьютера создается файл с заданным именем. В него можно вводить какие-то данные. Когда файл закрыт (CLOSE), в него нельзя ввести ничего, но можно открыть файл для чтения и тогда из него можно ввести в какую-либо БЕЙСИК-переменную то, что там содержится посредством INPUT.
Как и при работе с обычным диском, файл на RАM-диске должен обязательно быть закрыт после того, как закончился ввод в него или вывод из него. Если файл, открытый для записи, не будет закрыт, то есть вероятность того, что часть данных (а может быть и все) будут безвозвратно утрачены, поскольку специальный буфер не будет очищен (отгружен в файл). Последствия для незакрытого READ-файла не столь неприятные, хотя надо признать, что каждый канал, обслуживающий RAM-диск, занимает пол-килобайта памяти и освободить их, не закрыв файл, нельзя. Поэтому ВСЕГДА ЗАКРЫВАЙТЕ ФАЙЛ, КОГДА ЗАКОНЧИЛИ РАБОТУ С НИМ.
Листинг 16.
Эта крупная и важная процедура выполняет операцию вставки байтов в уже существующий на RAM-диске файл. Те файлы, которые в результате такой вставки должны быть передвинуты, перемещаются и переиндексируются.
При входе в эту процедуру комплекс регистров AHL должен содержать адрес (с кодовой страницей), в который выполняется вставка байта (байтов), а пара BC - длину вставляемого блока.
|
ORG B85F |
|
C5 |
V_MAKEROOM PUSH BC |
|
E5 |
PUSH HL |
|
F5 |
PUSH AF |
|
AF |
XOR A |
Обнуление аккумулятора, |
|
|
выключение флага C. |
67 |
LD H,A |
|
6F |
LD L,A |
Обнуление HL. |
ED42 |
SBC HL,BC |
в результате в AHL - ми- |
9F |
SBC A,A |
нус число вводимых байтов |
CD05B7 |
CALL B705,V_SPACE |
Проверка на достаточность |
|
|
памяти для вставки. |
F1 |
POP AF |
Восстановление |
E1 |
POP HL |
исходных |
C1 |
POP BC |
данных. |
C5 |
PUSH BC |
|
E5 |
PUSH HL |
|
F5 |
PUSH AF |
|
3E04 |
LD A,04 |
4-ая страница ОЗУ содер- |
CDFFB6 |
CALL B6FF,V PAGE |
жит каталог. |
DD2A835B |
LD IX,(SF_NEXT) |
IX указывает на конец |
|
|
каталога. |
DD6E0A |
LD L,(SF START) |
AHL указывает на первый |
DD660B |
LD H,(SF START+1) |
свободный байт простран |
DD7E0C |
LD L,(SF START+2) |
ства на RAM-диске. |
F5 |
PUSH AF |
|
E5 |
PUSH HL |
Запомнили этот адрес. |
CD3AB7 |
CALL B73A,ADD_AHL_BC |
AHL указывает на первый |
|
|
свободный байт который |
|
|
будет после операции |
|
|
вставки блока байтов. |
47 |
LD B,A |
теперь этот адрес в BHL. |
D9 |
EXX |
Теперь он в B'H'L'. |
E1 |
POP HL |
BHL - адрес первого сво- |
C1 |
POP BC |
бодного байта (старый). |
F1 |
POP AF |
ADE - адрес куда идет |
D1 |
POP DE |
вставка. |
4F |
LD C,A |
Теперь это - CDE. |
D5 |
PUSH DE |
Запомнили |
F5 |
PUSH AF |
этот адрес. |
CD30B7
3E04
CDFFB6
C1
D1
DD6E0A V_MR_LOOP
DD660B
DD7E0C
B8
382E
ED52 19
3829
E3 EB 19
3005 CBFC CBF4 3C
EB V_MR_ADDR
E3
EB
DD750A DD740B DD770C C5
011400 DD09
C1
DD7510 DD7411 DD7712 18C6
DD6E0D V_MR_FOUND
DD660E
DD7E0F
EB
E3
EB
19
CE00
DD750D
DD740E
DD770F
78
42 4B E1
C9
CALL B730,V_TRANSFER_2 LD A,04
CALL B6FF,V_PAGE POP BC POP DE
LD L,(SF_START) LD H,(SF_START+1) LD L,(SF_START+2) CP B
JR C,V_MR_FOUND
SBC HL,DE ADD HL,DE JR C,V_MR_FOUND
EX (SP),HL EX DE,HL ADD HL,DE JR NC,V_MR_ADDR SET 7, H SET 6, H INC A EX DE, HL EX (SP),HL EX DE,HL LD (SF_START),L LD (SF_START+1),H LD (SF_START+2),A PUSH BC LD BC,0014 ADD IX,BC
POP BC
LD (SF_END),L LD (SF_END+1),H LD (SF_END+2),A JR V_MR_LOOP
LD (L,SF_LEN) LD (H,SP_LEN+1) LD (A,SF_LEN+2) EX DE, HL EX (SP),HL EX DE,HL
ADD HL,DE ADC A,00 LD (SF_LEN),L LD (SF_LEN+1),H LD (SF_LEN+2),A LD A,B
LD B,D LD C,E POP HL
RET
Перемещение байтов. 4-ая страница ОЗУ содержит каталог. BDE - адрес куда идет вставка.
AHL указывает на первый свободный байт (старый) на RAM-диске.
Переход, если файл находится до точки вставки (проверили старший байт). Переход, если файл находится до точки вставки (проверили младшие байты).
В DE теперь количество вставляемых байтов.
;B AHL новый адрес файла
BDE - позиция вставки. Откорректировали каталог под новые адресные данные файла.
IX - указывает на следующий файл.
BDE - позиция вставки. Откорректировали каталог под новые адресные данные следующего файла. Возврат к началу цикла для работы с этим файлом.
^HL-старая длина файла.
;DE-число вставляемых ;байтов.
^HL-новая длина файла. ^Откорректировали каталог ;под длину нового ;файла.
^-код страницы места ;вставки.
;BC-число вставляемых ;байтов.
;AHL - адрес, куда был ;вставлен блок.
Как только файл на RAM-диске открыт, он будет внесен в каталог, который Вы можете вызвать из БЕЙСИКа командой CAT!
Принцип, по которому работает приведенная ниже программа, основан на концепции организации электронного диска (RAM-диска) в расширенной памяти 128-килобайтных машин. Вы уже знаете из предыдущих наших материалов, что в этой области памяти можно
хранить программы, данные или машинный код в течение того времени, пока компьютер остается включенным. RAM-диск значительно быстрее любого физического магнитного диска, но зато он "погибает" при выключении питания машины и своевременно должен быть отгружен.
Все это относится и к файлам последовательного доступа, о которых мы ведем речь.
Память на электронном диске организована с помощью каталога. Каталог является как бы указателем к тому, что есть на этой диске. Сам каталог размещается на седьмой странице дополнительной памяти и фактически организован по принципу стека, начинающегося по адресу 7EBFF и расширяющегося вниз. Каждая запись в каталоге занимает 20 байтов. Ниже приведены значения каждого из этих 20 байтов. IX указывает на начало записи. В конце каталога стоит 20-байтный маркер, обозначающий "конец каталога" (хотя на самом деле из него используются только три байта). Системная переменная, имеющаяся в 128-килобайтных машинах SFNEXT, указывает на этот маркер и служит как бы "указателем стека". Таким образом, ведя каталог в требуемом формате, мы можем управлять RAM-диском из машинного кода.
Структура каталога.
IX+00 SF_NAME Имя файла.
IX+0A SF_START Адрес начала файла (с кодом страницы).
IX+0D SF_LEN Длина файла вместе с заголовком.
IX+10 SF_END Адрес байта, следующего за окончанием файла (с кодом страницы).
IX+13 SF_FLAG Флаговый байт. Выключен, если каталог не завершен (т.е. как правило он выключен).
Файлы RAM-диска начинаются на первой странице ОЗУ и развиваются вверх, проходя через страницы ОЗУ - 3,4,6 и 7. Необходимо принимать во внимание, что они в своем развитии не должны перекрыть область, отведенную под каталог и, тем самым, погубить всю информацию.
Чтобы избежать конфуза с такой "странной" нумерацией страниц, введено понятие "кодовой страницы", о чем мы писали в статьях "128 K" (см. стр. 114,115 ИНФОРКОМ). Это означает, что пока один регистр процессора запоминает "код страницы", другой в это время хранит ее реальный физический адрес. Номера этих "кодовых страниц" - 0,1,2,3 и 4, а номер 5 относится к тем файлам RAM-диска, которые размещены в обычных первых 48 килобайтах компьютера.
Канал "V"
Мы назовем наш пользовательский канал, с помощью которого мы будем управлять файлами на RAM-диске, латинской буквой "V" (от слова "виртуальный") и создадим его так, чтобы это управление можно было делать средствами обычного БЕЙСИКа.
Для организации такого канала нам потребуется блок информации о канале размером более 500 байтов, хотя большую его часть будет занимать не сама информация, а буфер. То, что Вы будете засылать в файл на RAM-диске, на самом деле будет поступать не туда, а в этот буфер и перебрасываться в файл только по полному заполнению буфера (или при закрытии канала).
Вам надо иметь в виду, что адрес места расположения файла на RAM-диске не является величиной постоянной. Все файлы могут мигрировать по мере стирания каких-то файлов или по мере вставки новой информации в готовые файлы. Таким образом, они могут перемещаться всякий раз, когда мы с ними работаем. Использование буфера позволяет нам сделать этот процесс непостоянным и, тем самым, сэкономить машинное время.
Ниже приведена структура блока информации о канале "V".
Обратите внимание на то, что используемые в этом блоке переменные V_CHREC, V_RECLEN и первый бит V_CHFLAG используются только при работе с файлами, открытыми для чтения (READ). Прочие же используются с файлами обоего типа.
Листинг 17.
Приведенная ниже процедура V_STORE выполняет перенос содержимого буфера канала "V" из блока информации о канале в соответствующее ему место на RAM-диске.
|
|
ORG B8FE |
|
DD4E0B |
V_STORE |
LD C,(V CHBYTE) |
|
DD460C |
|
LD B,(V CHBYTE+1) |
BC-число байтов в буфере |
DDES |
|
PUSH IX |
сохранили адрес блока информации о канале. |
C5 |
|
PUSH BC |
Сохранили число байтов в буфере. |
CD47B7 |
|
CALL B747,FIND-FILE |
IX-адрес информации о файле в каталоге. |
C1 |
|
POP BC |
Восстановили BC. |
DD6E10 |
|
LD L,(SF END) |
В AHL выставляем адрес |
DD6611 |
|
LD H,(SF END+1) |
первого байта за данным |
DD7E12 |
|
LD H,(SF END+2) |
файлом в каталоге. |
CD5FB6 |
|
CALL B85F,V MAKEROOM |
Выделение места в ОЗУ. |
DDE1 |
|
POP IX |
Восстановили BC. |
CD3AB7 |
|
CALL B73A,ADD_AHL_BC |
B AHL выставляем адрес первого байта за пересылаемым блоком байтов. |
C5 |
|
PUSH BC |
|
47 |
|
LD B,A |
То же в BHL, |
D9 |
|
EXX |
То же в B'H'L'. |
DDE5 |
|
PUSH IX |
Переброска IX |
E1 |
|
POP HL |
в HL. |
011B00 |
|
LD BC,001B |
HL указывает теперь |
09 |
|
ADD HL,BC |
на начало буфера. |
C1 |
|
POP BC |
Восстановили BC. |
E5 |
|
PUSH HL |
|
09 |
|
ADD HL,BC |
HL указывает теперь на байт за буфером. |
D1 |
|
POP DE |
Начало буфера. |
010505 |
|
LD BC,0505 |
Напомним: 05 - код страницы стандартного ОЗУ, в котором и размещен блок информации о канале "V". |
CD30B7 |
|
CALL B730,V TRANSFER 2 |
Копирование буфера |
76 |
|
LD A,B |
A=5 |
CDFFB6 |
|
CALL B6FF,V_PAGE |
"Впечатывание" стандартного ОЗУ. |
C3CBB7 |
|
JP B7CB,V_BUFF_EXIT |
Приведение указателя буфера в исходное состояние и "выход". |
Листинг 18.
Процедура, отвечающая за вывод информации. Она выполняет "печать" по каналу "V". Сначала символ, содержащийся в аккумуляторе помещается в буфер и только потом переправляется на RAM диск.
|
|
ORG B92B |
|
CD005B |
V_PRINT |
CALL 5B00,SWAP |
;Впечатывание страницы 0. |
2A5A5B |
|
LD HL,(RETADDR) |
|
E5 |
|
PUSH HL |
;Сохранили адрес возврата. |
D9 |
|
EXX |
|
с5 |
|
PUSH BC |
|
D5 |
|
PUSH DE |
|
E5 |
|
PUSH HL |
|
DD2A515C |
|
LD IX,(CURCHL) |
;IX указывает на адрес информации о канале. |
DDCB1846 |
|
BIT 0,(V CHFLAG) |
|
CA18B8 |
|
JP Z,B818,V_ERROR |
;Ошибка, если это файл для ;чтения. |
DD5E0B |
|
LD E,(V CHBYTE) |
;DE - количество байтов |
DD560C |
|
LD D,(V CHBYTE+1) |
;в буфере. |
DDE5 |
|
PUSH IX |
|
E1 POP HL ;HL - указывает на блок
;информации о канале.
011B00 LD BC,001B
09 ADD HL,BC ;HL-начало буфера.
19 ADD HL,DE ;НL-первый свободный байт.
77 LD (HL),A ;Байт - в буфер.
13 INC DE ;Новое число 6-в в буфере.
DD730B LD (V_CHBYTE),E ;Запомнили новое число
DD720C LD (V_CHBYTE+1),D ;байтов в буфере.
15 DEC D ;Если буфер заполнен,
15 DEC D ;он опорожняется
CCF2B8 CALL Z,B8F2,V_STORE ;в файл на RAM-диске.
A7 AND A ;Выкл. флага переноса.
C354B8 JP B854,V_INOUT_EXIT ;Переход на выходную
;процедуру.
Структура блока информации о канале "V".
IX+00 V_OUT - адрес процедуры вывода информации из файла на RAM-диске (B92B)
IX+02 V_IN - адрес процедуры ввода информации в файл на RAM-диске(B7D4).
IX+04 V_NAME - имя канала ("V")
IX+05 V_IDEN - идентификатор пользовательского канала ("1234").
IX+07 V_CLOSE - адрес процедуры, закрывающей файл (B960).
IX+09 V_LEN - Длина блока информации о канале (021B).
IX+0B V_CHBYTE- указатель буфера
IX+0D V_CHREC - номер записи в файле.
IX+0E V_CHNAME - имя файла.
IX+18 V_CHFLAG- вспомогательные флаги:
Бит 0 - выключен для файла, открытого для чтения и включен для файла, открытого для записи. Бит 1 - включен, если в конце текущей записи стоит маркер "конец файла", иначе выключен. Биты 2...7 - не используются.
IX+19 V_RECLEN- длина текущей записи в буфере.
IX+1B V_BUFFER- буфер, хранящий текущую запись. Ниже мы привели пакет процедур, необходимых для организации вышеописанной концепции. Чтобы открыть файл последовательного доступа на RAM-диске, необходимо предварительно в аккумуляторе процессора выставить номер потока, подключенного к каналу. Имя же самого файла хранится в системной переменной N_STR1 по адресу 5B67 (23399). Далее вызывается процедура V_OPEN (B988). Есть и другие точки входа. Это OPEN_4, OPEN_5, CLOSE_4 и CLOSE_5. Вызовом OPEN_4 Вы открываете на RAM-диске файл с условным именем FILE_1 и подключаете его к четвертому потоку.
Точно так же вызовом OPEN_5 открывается файл последовательного доступа с условным именем FILE_2 и подключается к пятому потоку.
Процедуры CLOSE_4 и CLOSE_5 закрывают эти файлы и отключают эти потоки.
Таким образом, вновь созданный нами канал "V может легко быть задействован из БЕЙСИКа. Нижеприведенная программа (Листинг 3) демонстрирует файлы RAM-диска в работе сначала в качестве WRITE-файлов, а затем в качестве READ-файлов.
Конечно, Вы вовсе не обязаны называть свои файлы именами FILE_1 и FILE_2, также как и не обязаны подключать к ним только потоки #4 и #5. Для этого в пакете процедур имеется более общая точка входа V_OPEN, при входе в которую, как уже было указано, регистр A процессора должен содержать номер подключаемого потока, а системная переменная N_STR1 - имя файла (если имя меньше положенных 10 символов, пробелы делаются последующими).
Листинг 19.
Данная процедура предназначена для закрывания "V" канала. Основным в ней является опорожнение буфера, если в нем что-то есть для файла, открытого для записи. Для файла, открытого для чтения, содержимое буфера может игнорироваться.
ORG B960
CD005B V_CLOSE CALL 5B00,SWAP 2A5A5B LD HL,(BETADDR)
E5 PUSH HL
DDE5 PUSH IX
2A3D5C LD HL,(ERR_SP)
E5 PUSH HL
21FEFF LD HL,FFFE
39 ADD HL,SP
E33D5C LD (ERR_SP),HL
DDCB1846 BIT 0,(V_CHFLAG)
C4F2B8 CALL NZ,B8F2,V_STORE
E1 POP HL
Впечатывание страницы 0.
Запомнили адрес возврата.
HL указывает на адрес процедуры обработки ошибки.
Запомнили указатель.
В HL помещен указатель стека минус 2. Новый адрес возврата. Если файл является файлом открытым для записи. Здесь отгружается буфер. Напомним, что данный адрес при работе V_STORE служил адресом возврата при любой ошибке.
223D5C LD (ERR_SP),HL
DDE1 POP IX
2158E7 V_OC_EXIT LD HL,2758
D9 EXX
C358BB JP B858,V EXIT
Листинг 20.
Эта процедура открывается наиболее универсальной точкой входа. Процедура предназначена для открывания канала. При входе в процедуру аккумулятор процессора должен содержать номер потока, подключенного к данному каналу, а в десяти байтах системной переменной N_STR1 должно быть записано имя открываемого файла. Если имя файла менее десяти символов, то пробелы выполняются как последующие.
ORG B988
CALL 5B00,SWAP LD H,(RETADDX) PUSH HL PUSH AF LD A,56 CALL B594
JR C,V_OP_OK PUSH IX POP HL
010E00 09
11675B 060A 1A 13 2006
10F8
LD BC,000E ADD HL,BC LD DE,N_STR1 LD B,0A LD A,(DE) INC DE
JR NZ,V OP RETRY
DJNZ V OP
CALL B6FC,V_ERROR DEFB 20
LD D,56 CALL B5AA
CD005B 2A545B E5 F5
3E56 CD94B5
381F DDES E1
Впечатывание страницы 0. Установка адреса возврата в ПЗУ-0.
Запомнили номер потока. CHR$ 55 = "V''-имя канала. Проверка на существование такого канала. Переход, если его нет.
HL указывает на область информации по уже существующему каналу. HL указывает на имя файла. DE-адрес имени файла. 0AH=10 DEC-длина имени.
Переход, если имя не совпадает.
Переход на вершину цикла для проверки всех десяти символов. Генерация сообщения "е: File already exists" "Файл уже существует". CHR$ 56 = "V''-имя канала. Поиск еще одного канала с тем же именем "V".
30E1 JR NC V_OP_LOOP
CD08B7 V_OP_OK CALL B708,V_FIND
F5 PUSH AF
2811 JR Z,V_OP_CONT
DD6E0A LD L,(SF_START)
DD660B LD H,(SF_START+1)
DD7E0C LD L,(SF_START+2)
CDFFB6 CALL B6FF,V_PAGE
7E LD A,(HL)
FE04 CP 04
20DE JR NZ, V_OP_ERROR
3E05 V_OP_CONT LD A,05
CDFFB6 CALL B6FF,V_PAGE
F1 POP AF
08 EX AF,A'F'
F1 POP AF
08 EX AF,A'F'
F5 PUSH AF
3E56 LD A, 56
011B02 LD BC,021B
11D4B7 LD DE,B7D4,V_INPUT
212BB9 LD HL,B92B,V_PRINT
DD2160B9 LD IX,B9650,V_CLOSE
EF6DB0 RST 28 DEFB 6D DEFB B0
DDE5 PUSH IX
E1 POP HL
010B00 LD BC,000B
09 ADD HL, BC
70 LD (HL),B
23 INC HL
70 LD (HL),B
23 INC HL
70 LD (HL),B
23 INC HL
EB EX DE, HL
21675B LD HL,5B67,N_STR1
0E0A LD C,0A
EDB0 LDIR
F1 POP AF
2809 JR Z,V_OP_WRITE
DDCB1686 RES 0,(V_CHFLAG)
CDC5B7 CALL B7C5,V_ASSIGN ;Возврат, если найден. ;Поиск имени файла на ^AM-диске. ;Сохранение флага Z. ;Переход, если файл не ;найден, т.е. Вы открыва-;ете файл "для записи". ;AHL указывает на начало ;файла с данным именем ;на RAM-диске. ;Выбор страницы, содержащей первый байт файла. ;A - код типа файла.
;На обработку ошибки, если ;это не READ-Файл.
;"Впечатывание" стандартного ОЗУ. ;Возврат флага Z.
^-номер потока. ;A,-номер потока. ;Сохранение флага Z, кото-;рый указывает тип Файла. ;CHR$ 56 = "V''-имя канала, ;длина блока информации о ;канале "V".
;DE-адрес процедуры ввода. ^L-адрес процедуры вывода ;IX-адрес закрывающей про-;цедуры.
;Вызов калькулятора. ;Это уже не команды машинного кода Z80 - это ;команды кода калькулятора ;о котором мы писали в ;нашем трехтомнике по программированию в машинных ;кодах.
;Здесь на стек калькулятора помещается адрес про-;цедуры OPEN_NEW (B06D). ;Адрес блока информации о ;канале переводится из IX ; в HL.
;В HL адрес V_CHBYTE.
;Обнулили V_CHBYTE.
;Обнулили V_CHREC.
;B DE адрес V_CHNAME. ;B HL адрес имени файла. ;0AH=10 DEC-длина имени ;копирование имени файла ;в блок информации о канале. ;Восстановление флага Z. ;Переход, если файл -;"для записи". ;Сигнал: "Файл для чтения" ;Привязка буфера к RAM ;диску.
1840 |
|
|
JR V_OP_EXIT |
DDCB18C6 CD02B7 |
V_OP_ |
WRITE |
SET 0,(V CHFLAG) CALL B703,V_NEWCAT |
21FFFF 7C
CD05B7 |
|
|
LD HL, FFFF LD A,H
CALL B705,V_SPACE |
3E04 |
|
|
LD A,04 |
CDFFB6 DD6E0A DD660B DD7E0C F5
CDFFB6 |
|
|
CALL B6FF,V PAGE LD L,(SF START) LD H,(SF START+1) LD L,(SF START+2) PUSH AF
CALL B6FF,V_PAGE |
F3 |
|
|
POP AF |
3604 |
|
|
LD (HL),04 |
010100 CD3AB7 |
|
|
LD BC,0001
CALL B73A,ADD_HL_BC |
5F
3E04
CDFFB6
DD7510
DD7411
DD7312
CD0BB7
3E05
CDFFB6
C381B9 |
V OP |
EXIT |
LD E,A LD A, 04
CALL B6FF,V PAGE LD (SF END),L LD (SF END+1),H LD (SF END+2),E CALL B70B,V CATEND LD A,05
CALL B6FF,V PAGE JP B981,V OC EXIT |
Переход на выходную процедуру.
Сигнал: "Файл для записи" Создание новой записи в каталоге.
В аккумуляторе минус 1. Проверка на достаточность места для вставки байта. Напомним: каталог - на странице 4. Выбор страницы 4. AHL указывает на первый свободный байт (старый) на RAM-диске. Запомнили код страницы. Выбор страницы, содержащей первый свободный байт AHL указывает на первый свободный байт. Записали 04 в качестве байта "тип файла".
AHL указывает на первый свободный байт. То же и EHL.
Выбор страницы 4. Откорректировали каталог под конец адресных данных данного файла. Оформили конец каталога.
Выбрали стандартное ОЗУ. Переход на выходную процедуру.
Листинг 3
1000 REM WRITE FILE DEMO
1010 RANDOMIZE USR 47713:
1020 RANDOMIZE USR 47720:
1030 FOR i=1 TO 512
1040 INPUT "": PRINT i
1050 FRINT #4; 2*i
1060 PRINT #5; i*i
1070 NEXT i
1080 RANDOMIZE USR 47736:
1090 RANDOMIZE USR 47740:
1100 REM READ FILE DEMO
1110 RANDOMIZE USR 47713:
1120 RANDOMIZE USR 47720:
1130 FOR i=1 TO 512
1140 INPUT #4; a
1150 INPUT #5; b
1160 PRINT a,b
1170 NEXT i
1180 RANDOMIZE USR 47736:
1190 RANDOHIZE USR 47740:
1200 STOP
REM открываем OPEN#4, Файл "FILE_1" REM открываем OPEN#5, Файл "FILE_2"
REM закрываем CLOSE#4, Файл "FILE_1"
REM закрываем CLOSE#5, Файл "FILE_2"
REM OPEN#4
REM OPEN#5
REM CLOSE#4 REM CLOSE#5
Ниже приведены листинги процедур, предназначенные для манипуляции с файлами на RAM-диске 128-килобайтных моделей.
Надо сразу оговориться, что существуют две основные отличающиеся друг от друга версии ПЗУ 128-килобайтных машин. Отличия касаются нулевой страницы ПЗУ. Первая
версия - это более ранняя модель "ZX-Spectrum+128", а вторая - "ZX-Spectrum+2". Поскольку в ПЗУ этих машин адреса некоторых процедур отличаются, то чтобы не писать два отдельных пакета для той машины и для другой, вводится таблица адресов переходов (вектор переходов). Вы можете использовать либо ту, либо другую. Если ПЗУ Вашей модели отличается и от той и от другой версии, то Вы имеете в принципе возможность (если у Вас хватает информации о точках входа своего ПЗУ) подкорректировать эти таблицы (Листинги 4,5) так, как Вам угодно. Во всем остальном приведенный пакет процедур идентичен и для той и для другой модели.
Листинг 21.
Последняя процедура служит для интеграции всего вышеприведенного пакета процедур с БЕЙСИКом компьютера. Приведенные в ней строки являются образцами и Вы можете переделать их под свои конкретные требования. OPEN_4 открывает файл FILE_1 и подключает к нему поток номер 4. OPEN_5 подключает поток 5 к открываемому файлу FILE_2. CLOSE_4 и CLOSE_5 соответственно закрывают четвертый и пятый потоки.
|
|
|
ORG BA4D |
|
46494C4531 |
FILE_ |
1 |
DEFM "FILE1" |
|
2020202020 |
|
|
DEFM " " |
Имя файла+5 пробелов. |
46494с4532 |
FILE_ |
2 |
DEFM "FILE2" |
|
2020202020 |
|
|
DEFM " " |
Имя файла+5 пробелов. |
3E04 |
OPEN_ |
4 |
LD A,04 |
A - номер потока. |
214DBA |
|
|
LD HL,FILE 1 |
HL-адрес имени файла. |
1805 |
|
|
JR OPEN_4_5 |
Переход на открывание потока. |
3E05 |
OPEN_ |
5 |
LD A,05 |
A - номер потока. |
2157BA |
|
|
LD HL,FILE 2 |
HL-адрес имени файла. |
11675B |
OPEN_ |
_4_5 |
LD DE, NSTR1 |
|
010A00 |
|
|
LD BC,000A |
|
EDB0 |
|
|
LDIR |
Кодирование имени файла в системную переменную. |
C388B9 |
|
|
JP B988,V_OPEN |
Переход на открывание канала. |
3E04 |
|
|
LD A, 04 |
|
1802 |
|
|
JR CLOSE_4_5 |
Переход на открывание канала. |
3E05 |
|
|
LD A, 05 |
|
C300B0 |
|
|
JP B0000 |
Закрываем каналы. |