ZX-Ревю 1991 №11-12 1990 г.

Каналы и потоки - рассматриваем тот раздел ПЗУ, который очень широко оперирует с такими образованиями, как потоки и каналы.


КАНАЛЫ И ПОТОКИ

Сегодня в номере нет традиционного раздела "Секреты ПЗУ". Причина проста -сейчас мы рассматриваем тот раздел, который очень широко оперирует с такими образованиями, как потоки и каналы и мы уже не можем двигаться дальше, не рассказав Вам о том, что это такое, ведь раньше в своих изданиях мы на этом вопросе не останавливались.

Мы обещали Вам, что в начале следующего года дадим широкую статью на эту тему, но время идет, планы меняются и в связи с тем, что материал по ПЗУ данного выпуска обязательно требует предварительного обсуждения потоков и каналов, мы пропустили эту статью вперед и даем ее всю целиком, в одном номере, без продолжений, чтобы не ущемить интересы тех немногих читателей, кто не подписался на будущий год.

В двух словах: каналы и потоки позволяют скрыть от пользователя сложную логику работы программного обеспечения и сделать для него возможность простыми командами выполнять сложные действия.

Это не более чем еще один прием организации программ, еще один вклад в копилку Ваших знаний о компьютере "Спектрум".

Для многих начинающих пользователей "СПЕКТРУМа" такие понятия как "каналы" и "потоки" могут звучать как некоторые непонятные жаргонные обозначения, но на самом деле за ними скрывается интересная концепция, которая может позволить Вам взять от компьютера то, что другими путями сделать непросто.

Итак, как всегда, начнем с самого простого. Вы начинаете использовать каналы и потоки уже тогда, когда даете команды 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'

37

F5 06

CD2117

CALL 1721,STR_DATA_A

78 B1 CB

E5

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".

Генерация сообщений об ошибках

ORG B6FC JP 05AC

V_ERROR

C3AC05

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 - длина блока информации о канале. Переход в начало цикла для продолжения поиска.

ORG B594 LD IX,(CHANS)

DD2A4F5C SEARCH CH ALL

011400 57

LD BC,0014 LD D,A

ADD IX,BC

LD A,(IX+00) CP 80

DD09

DD7E00 FE80

SCH_CH_LOOP

SEARCH_CH

37 C8

SCF RET Z

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

EDB0

CD08B7 C0

CDFCBP 23

LDIR

CALL B708,V_FIND RET NZ

CALL B6FC,V_ERROR DEFB 23

DDE5 E1

010E00 09

0E0A 11675B

FIND FILE

Переброска адреса блока через стек.

Имя в блоке начинается с байта 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

V OP LOOP

010E00 09

11675B 060A 1A 13 2006

10F8

V_OP_NAME

LD BC,000E ADD HL,BC LD DE,N_STR1 LD B,0A LD A,(DE) INC DE

JR NZ,V OP RETRY

CDFCB6 20

1656 CDAAB5

V_OP_ERROR

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

V OPEN

Впечатывание страницы 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

Закрываем каналы.




СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Железяки - расширение памяти ZX Spectrum (подключение SIMM 4Mb).
Разборки - Здравствуй, Дядя Сэм! (письмо от Goblin'а)
Help for games - разбор игры THE BARDS TALE (часть 2).
Слава! Слава! Слава!
Обзор - обзор игрушек: Boovie, Motor Massacre, Soccer Pinball, Huxley 1 & 2.

В этот день...   29 марта