Г Л А В А X. УПРАВЛЕНИЕ РЕСУРСАМИ ПАМЯТИ MSX-КОМПЬЮТЕРОВ Мозг, хорошо устроенный, стоит больше, чем мозг, хорошо наполненный. Мишель Монтень X.1. К а р т а п а м я т и (д л я к о м п ь ю т е р о в MSX-1) Персональный MSX-компьютер имеет небольшой объем памяти - 96 Кбайтов для MSX-1 и 242 Кбайта для MSX-2. Поэтому полезной для пользователя оказы- вается информация о распределении ресурсов памяти и сведения о наличии и объеме в ней свободных мест в любой момент времени. Общий объем памяти у компьютеров серии MSX-1 равен 96 Кбайтам. Здесь и далее мы будем рассматривать только 64 Кбайта, с которыми обычно и работа- ет основная масса пользователей. Взгляните на приведенный ниже рис.1 ... Вся память разбита на две основные части: ┌─────────────────────────────────────────────────────────────────────┐ │ ROM ("Read Only Memory" - "Постоянное Запоминающее Устройство") и │ │ RAM ("Random Access Memory" - "Оперативное Запоминающее Устройство")│ └─────────────────────────────────────────────────────────────────────┘ ROM содержит те программы и данные, которые "заложены" в компьютер при изготовлении. Вот почему он всегда выводит определенные сообщения при включении и способен "понимать" программу на языке MSX-BASIC. В ROM находится и н т е р п р е т а т о р - программа на машинном язы- ке, которая переводит один за другим операторы языка MSX-BASIC в програм- му на машинном языке, т.е. на е д и н с т в е н н о м языке, который по- нимает компьютер. С помощью этой программы компьютер проверяет синтаксис, выводит при необходимости сообщение об ошибке, переходит к следующему опе- ратору или возвращается в командный режим и так далее. Здесь же находятся подпрограммы управления клавиатурой и экраном, кото- рые составляют э к р а н н ы й р е д а к т о р MSX-BASIC. ROM в основном разделена на две части: 1) подпрограммы BIOS ("Basic Input-Output System"); 2) другие подпрограммы. Так, например, при включении компьютера насту- пает небольшая пауза; в этот момент происходят различные инициализации эк- рана дисплея (установка определенного режима SCREEN, установка ширины эк- рана WIDTH и др.). Это происходит оттого,что "зашитые" в ROM подпрограммы инициализации "посылают" определенную информацию в рабочую область RAM, разговор о которой еще пойдет впереди. Подпрограммы BIOS осуществляют переход к другим подпрограммам. Они на- поминают последовательность операторов GOSUB, которую можно увидеть на первом уровне хорошо структурированной программы на MSX-BASIC. Подпрограм- мы BIOS расположены по одним и тем же адресам ROM независимо от версии MSX-BASIC и осуществляют переход к другим подпрограммам, положение кото- рых может быть изменено. В противоположность ROM, RAM не сохраняет информацию при выключении компьютера. Поговорим теперь о его структуре. "Верхушка" памяти (она изображена в н и ж н е й части таблицы) за- нята р а б о ч е й о б л а с т ь ю, которая состоит из: α) таблицы системных переменных, β) таблицы ловушек ("Hooks Table"). "Нижняя" область памяти (она изображена в в е р х н е й части таблицы) занята: 1) текстом программы ("Program Instruction Table", PIT); 2) таблицей переменных ("Variable Table", VT). VT содержит все перемен- ные, создаваемые в ходе выполнения программы; 3) таблицей массивов ("Array Variable Table"). Между "верхней" и "нижней" областью памяти располагаются: α) свободная область ("Free Area"); β) с т е к ("Stack Area"); стек содержит всю информацию, необходимую для выполнения программы. Например, именно здесь хранится адрес тех бай- тов PIT, которые содержат сведения о следующем выполняемом операторе Ва- шей программы; γ) с т р о к о в а я область ("Character String Area"); по умолчанию для нее отводится 200 байтов, однако размеры этой области можно менять оператором CLEAR (см. раздел X.7.); δ) б л о к у п р а в л е н и я файлами ("Files Control Block"). Если в Вашей программе присутствует оператор MAXFILES= (напомним, что он задает максимальное количество одновременно открытых файлов), то для каждого файла автоматически резервируется 267-байтное пространство для осуществления обмена информацией с файловыми устройствами. &H0000 ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ ∗▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧∗ ∗▧▧▧▧▧▧▧▧▧ ROM (Интерпретатор MSX-BASIC) ▧▧▧▧▧▧▧∗ ∗▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧∗ ─ ─ ─ ─∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗─ ─ ─ ─ &H8000 │ │ TXTTAB │ Программа на языке BASIC │ │ │ ("Program Instruction Table", PIT) │ ▼ │ │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │─ ─ ─ ─ │ │ VARTAB │ Простые переменные ("Variable Table") │ │ │ │ ▼ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │─ ─ ─ ─ │ Массивы ("Array Variable Table") │ ARYTAB │ │ │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ▼ │ │ │ С в о б о д н а я о б л а с т ь ("Free Area")│ │ │ R A M │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │ │ ▲ │ Стек ("Stack Area") │ │ │ │ STKTOP │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤─ ─ ─ ─ │ │ ▲ │ Строковая область ("Character String Area") │ │ │ │ FRETOP │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤─ ─ ─ ─ │ │ FILTAB │ Блок управления файлами ("Files Control Block")│ │ │ │ ▼ ─ ─ ─ ─ ┼────────────────────────────────────────────────│─ ─ ─ ─ &HF380 │ Таблица системных переменных │ HIMEM │ ("System Variable Table") │ │ ─ ─ ─ ─ │─ ─ ─ ─ ─ - - - - - - - - - - - - - - - - ─ ─ ─ │ ▼ &HF3A9 │ │ │ Таблица ловушек ("Hooks Table") │ &HFFFF │ │ ─ ─ ─ ─ └────────────────────────────────────────────────┘ Р и с. 1 Приведенная карта памяти справедлива и для компьютеров серии MSX-2. Но в отличие от компьютеров серии MSX-1 с объемом ROM в 32 Кбайта и RAM в 64 Кбайта, компьютеры серии MSX-2 имеют гораздо больший объем памяти (108 Кбайтов ROM и 134 Кбайта RAM). Спрашивается, где размещаются эта память? ┌────────────────────────────────────────────────────┐ │ Оказывается, вся память ПЭВМ разбита на блоки │ │ объемом по 64 Кбайта, называемые с л о т а м и ! │ └────────────────────────────────────────────────────┘ Однако рассмотрение этого вопроса потребует от читателя дополнительных знаний, и поэтому мы рассмотрим его позднее (см. раздел XI.1.9.). Память разделена на ячейки (б а й т ы), каждая из которых имеет адрес, закодированный д в у м я байтами: А д р е с я ч е й к и (байта) ┌─────────────────┬─────────────────┐ │ 1 1 0 1 1 0 1 1 │ 1 0 1 1 1 1 0 1 │ └────────▲────────┴────────▲────────┘ └── Б а й т ы ──┘ Поэтому максимально большой адрес байта равен 256·&B11111111+&B11111111+1 = 256·&hFF+&HFF+1 = 65535+1 = 65536 , а следовательно, и обратиться можно не более, чем к 65536 ячейкам памяти (подумайте, почему производится умножение именно на 256?). Говорят, что "объем непосредственно адресуемой памяти - 65536 байта". X.2. Ф у н к ц и я PEEK и о п е р а т о р POKE Функция PEEK позволяет Вам "посмотреть" содержимое любой ячейки памяти в адресном пространстве MSX-компьютера. Ее общий вид: ┌─────────────────────┐ │ PEEK (а д р е с) │ , └─────────────────────┘ где: PEEK ("to peek"-"заглядывать") - служебное слово; а д р е с - арифметическое выражение, значение которого находится в диапазоне от &h0 до &hFFFF. Функция PEEK возвращает целое число в интервале от 0 до 255, содержаще- еся в проверяемой ячейке памяти. Например: 1) 10 WIDTH 7:? PEEK(&HF3B0) 2) 10 SCREEN 2:PSET(15,18):SCREEN0:PRINT run PEEK(&HFCB3);PEEK(&HFCB5) 7 run Ok 15 18 Ok В первом примере мы "попросили" компьютер вывести на экран содержимое ячейки с адресом &HF3B0 (в байте по этому адресу хранится значение систем- ной переменной - длины дисплейной строки). Во втором примере мы использо- вали информацию из таблицы адресов системных переменных (см.Приложение 2). Величину, возвращаемую функцией PEEK, можно интерпретировать как код символа, команду MSX-BASIC, номер строки, число, "часть" числа, "храняще- гося" в нескольких байтах, и т.д. В некоторых случаях правильную интерпре- тацию можно дать по контексту, однако, если должной уверенности нет, надо а н а л и з и р о в а т ь не только содержимое одной ячейки, но и содер- жимое ячеек, находящихся в ее "окрестности"! П р и м е р 1. 10 X=&H8000'"Заглянем" в память,начиная с адреса &H8001! ───────────── 20 X=X+1:Y=PEEK(X) 30 IF Y<32 THEN ?"│";:GOTO 20 ELSE ?CHR$(Y);" ";:GOTO20 Наличие условия Y<32 в строке 26 связано с тем, что существуют "непеча- таемые" символы, имеющие код ASCII, меньший 32. Поговорим теперь об очень полезном операторе POKE. Общий вид оператора: ┌───────────┐ │ POKE A,D │ └───────────┘ где: POKE ("to poke"-"помещать") - служебное слово; A - арифметическое выражение, значение которого находится в диапазо- не от &h8000 до &hFFFF; D - арифметическое выражение, значение которого принадлежит отрезку [0,255] (поскольку оно должно умещаться в один байт). Оператор POKE вычисляет значения выражений А и D и сохраняет значение D (которое должно помещаться в одном байте!) по адресу А. Обратите внима- ние на то, что значение А может оказаться о т р и ц а т е л ь н ы м! Если значение А не удовлетворяет ограничениям, то компьютер сообщит об ошибке: "Overflow" ("П е р е п о л н е н и е"), а если значение D, то "Illegal function call" ("Н е п р а в и л ь н ы й в ы з о в ф у н к ц и и"). Вы можете использовать оператор POKE для: α) модификации текста Вашей программы; β) изменения значений переменных; γ) размещения в RAM программы, написанной на машинном языке (ее запись производится п о б а й т н о). Более того, этот оператор позволяет Вам экспериментировать с "подвалом" компьютера (рабочей областью). Но делайте так только в том случае, если Вы понимаете, что за этим последует! П р и м е р 2. Сравните результаты работы двух программ: ───────────── 10 SCREEN 1:PRINT"A" 10 SCREEN 1:PRINT"A" 20 WIDTH 10 20 POKE &HF3B0,10 X.3. Т а б л и ц а п р о г р а м м н ы х к о м а н д (PIT) Таблица PIT обычно начинается по адресу &H8000. Однако ее можно "сдви- нуть", изменив значение системной переменной TXTTAB в таблице системных переменных. П р и м е р 1. Для помещения PIT с адреса &HА000,достаточно выполнить ───────────── следующую программу: NEW Ok 5 'Адрес &HА001,находящийся в двух ячейках с номерами,начиная с &hF676 (слове TXTTAB (&HF676)),определяет место,с которого начнется текст про- граммы 10 POKE &HF676,&H01 'Заполнение младшего байта слова TXTTAB 20 POKE &HF677,&HA0 'Заполнение старшего байта слова TXTTAB 30 POKE &HA000,0 'Первый байт PIT(&HA000) должен быть нулевым! 40 NEW 'Стираем данную программу! Напомним Вам, что адрес &HА001 (как и любой другой!) размещается в двух байтах так: Адрес ──▶ &HF676 &HF677 ◀── Адрес младшего байта │ │ старшего байта ┌─────│─────────│────┐ Содержимое │ ┌───▼──┐ ┌───▼──┐ │ Содержимое младшего ◀───── &Н01 │ │ &HА0 ─────▶ старшего байта │ └──────┘ └──────┘ │ байта └────────────────────┘ Слово TXTTAB Очевидно, что в результате этих "манипуляций" размер свободной области уменьшится на &H2000 байтов (&HA000-&H8000=&H2000), и область, расположен- ная между &H8000 и началом PIT, будет защищена от "вторжения" программ на MSX-BASIC, и следовательно, "б е з о п а с н а" для программ на машинном языке. Ясно, что величина PIT зависит от размера текста программы. После выполнения данной программы нажмите кнопку сброса "RESET". А теперь мы расскажем Вам о том, как хранится программа, написанная на языке MSX-BASIC в PIT. ┌───────────────────────────────────────────────────────────────────────┐ │ Все строки программы на MSX BASIC начинаются с д в у х б а й т - │ │ н о г о указателя. │ │ За этим указателем идут д в а байта, содержащие номер строки. │ │ Затем идет текст строки с последующим нулевым байтом. │ │ За последней строкой следуют д в а дополнительных нулевых байта, │ │ адрес которых находится в указателе последней строки программы. │ │ Цифры и зарезервированные служебные слова записываются во внутрен- │ │ нем коде (один или два байта на слово, цифру) │ │ Для остального текста используется код ASCII. │ └───────────────────────────────────────────────────────────────────────┘ П р и м е р 2. Введем в память следующую короткую программу: ───────────── 10 B=5 20 END Теперь прочитаем, что же реально содержится в PIT, используя в непо- средственном режиме команду PRINT HEX$(PEEK(A)) , где значение переменной А (адреса) изменяется от &H8000 до &H8010. Вы обнаружите: ┌───────────┬─────────────┬─────────────────────────────────────────────┐ │ Значение А│HEX$(PEEK(A))│ К о м м е н т а р и и │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8000 │ 0 │ Первый байт PIT всегда нулевой │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8001 ∗∗│ 09 │ Указатель первой строки "говорит" нам, что │ │∗∗ 8002 ∗∗│ 80 │ указатель следующей строки находится по ад- │ │∗ ∗ ∗ ∗ ∗ ∗│ │ ресу &Н8009 │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8003 │ А │ Номер первой строки &H000А = 10 │ │ 8004 │ 0 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8005 │ 42 │ Шестнадцатеричный код ASCII буквы "B" │ │ 8006 │ EF │ Внутренний код знака равенства │ │ 8007 │ 16 │ Внутренний код цифры 5 │ │ 8008 │ 0 │ Конец первой строки │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8009 ∗∗│ 0F │ Указатель второй строки показывает, что │ │∗∗ 800A ∗∗│ 80 │ указатель следующей строки находится по │ │∗∗∗∗∗∗∗∗∗∗∗│ │ адресу &H800F │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800B │ 14 │ Номер второй строки &H0014 = 20 │ │ 800C │ 00 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800D │ 81 │ Внутренний код оператора END │ │ 800E │ 0 │ Конец второй строки │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 800F │ 0 │ Конец программы │ │ 8010 │ 0 │ │ └───────────┴─────────────┴─────────────────────────────────────────────┘ Теперь, надеемся, Вам стало ясно, как можно изменить программу с помо- щью оператора POKE. Попробуйте выполнить следующее: POKE &H8005,&H41 '41 - шестнадцатеричный код ASCII буквы "A" POKE &H8007,&H17 '17 ─ внутренний код цифры "6" А теперь наберите команду LIST, затем нажмите клавишу◀─┘ и ...: 10 A=6 20 END П р и м е р 3. Теперь Вам ясно,что "инструкции" PEEK и POKE таят в се- ───────────── бе поистине безграничные возможности. По существу, они позволяют нам распоряжаться памятью компьютера по своему усмотрению. Например, они позволяют нам при желании подшутить над компьютером: ес- ли известно, где хранится программа, то мы можем сделать так, что после одной из строк программы окажется строка с м е н ь ш и м номером. Пусть исходная программа имеет вид: 10 PRINT 4 20 PRINT 2 Вам, конечно, уже известно, что строки программы на языке MSX-BASIC на- чинаются с двухбайтного указателя, за которым следуют два байта, содержа- щие номер строки. Поэтому вначале выполним команду: PRINT HEX$(PEEK(&H8002));" ";HEX$(PEEK(&H8001)) 80 9 Ok Таким образом, указатель следующей (с номером 20) строки располагается в ячейках с адресами &H8009 и &H800A, а следовательно, номер второй стро- ки находится в ячейках с адресами &H800B и &H800C . Проверим этот факт: PRINT HEX$(PEEK(&H800C));" ";HEX$(PEEK(&H800B)) ┌──▶ PRINT &H14 0 14 ────────────────────────────────────────────┘ 20 Ok Ok А теперь: POKE &H800B,1 Ok list 10 PRINT 4 1 PRINT 2 Ok Программа действует, но строку с номером 1 нельзя ни стереть, ни испра- вить. Вы можете написать еще одну 1-ю строку и даже новую 20-ю строку! П р и м е р 4. Введем в память короткую программу: ───────────── 10 FOR AB=-2.23227 TO 7 STEP 32.671533782376# Теперь "просмотрим" содержимое PIT, используя в непосредственном режи- ме простейшие команды: A=···: PRINT HEX$(PEEK(A)) , где значение переменной А (адреса) изменяется от &H8000 до &H8020. Мы обнаружим массу интересных вещей: ┌───────────┬─────────────┬─────────────────────────────────────────────┐ │ Значение А│HEX$(PEEK(A))│ К о м м е н т а р и и │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8000 │ 0 │ Первый байт PIT всегда нулевой │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │∗∗ 8001 ∗∗│ 21 │ Указатель первой строки "говорит" нам, что │ │∗∗ 8002 ∗∗│ 80 │ указатель следующей строки находится по ад- │ │∗ ∗ ∗ ∗ ∗ ∗│ │ ресу &Н8021 │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8003 │ А │ Номер первой строки &H000А = 10 │ │ 8004 │ 0 │ │ ├───────────┼─────────────┼─────────────────────────────────────────────┤ │ 8005 │ 82 │ Код служебного слова FOR │ │ 8006 │ 20 │ Внутренний код символа "пробел" │ │ 8007 │ 41 │ Шестнадцатеричный код ASCII буквы "A" │ │ 8008 │ 42 │ Шестнадцатеричный код ASCII буквы "B" │ │ 8009 │ EF │ Внутренний код символа "=" │ │ 800A │ F2 │ Внутренний код символа "-" │ │ 800B │ 1D │Указатель на тип одинарной точности (знак"!")│ │ 800C │ 41 │ Вторая цифра числа указывает на порядок │ │ │ │ минимального значения параметра цикла │ │ 800D │ 22 │ 1 и 2-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 800E │ 32 │ 3 и 4-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 800F │ 27 │ 5 и 6-я цифры мантиссы минимального зна- │ │ │ │ чения параметра цикла │ │ 8010 │ 20 │ Внутренний код символа "пробел" │ │ 8011 │ D9 │ Код служебного слова TO │ │ 8012 │ 20 │ Внутренний код символа "пробел" │ │ 8013 │ 18 │ Внутренний код символа "7" │ │ 8014 │ 20 │ Внутренний код символа "пробел" │ │ 8015 │ DC │ Код служебного слова STEP │ │ 8016 │ 20 │ Внутренний код символа "пробел" │ │ 8017 │ 1F │ Указатель на тип двойная точность (знак #) │ │ 8018 │ 42 │ Вторая цифра числа указывает на порядок шага│ │ 8019 │ 32 │ 1 и 2-я цифры мантиссы шага │ │ 801А │ 67 │ 3 и 4-я цифры мантиссы шага │ │ 801B │ 15 │ 5 и 6-я цифры мантиссы шага │ │ 801C │ 33 │ 7 и 8-я цифры мантиссы шага │ │ 801D │ 78 │ 9 и 10-я цифры мантиссы шага │ │ 801E │ 23 │ 11 и 12-я цифры мантиссы шага │ │ 801F │ 76 │ 13 и 14-я цифры мантиссы шага │ │ 8020 │ 0 │ Конец строки │ │ 8021 │ 0 │ Конец программы │ └───────────┴─────────────┴─────────────────────────────────────────────┘ Однако не пытайтесь изменять с помощью оператора POKE длину строки или путать указатели: результат будет к а т a с т р о ф и ч е с к и м! Если Вы хотите защитить свою программу от "постороннего взгляда" (ко- манды LIST), то примените в непосредственном режиме команду: POKE &H8001,1 Ok (разумеется, Ваша программа должна располагаться с адреса &H8000). Ну а если Вы нечаянно нажали RESET, - не спешите отчаиваться! Вашу про- грамму еще можно спасти. Это очень легко сделать, набрав ту же команду POKE &H8001,1 а затем auto На экране появятся строки: 10* 20* и так далее ... Строки с "*" - спасенные. Теперь достаточно "скомандовать": LIST и ..., о, чудо! Но это еще не все! Оказывается, спасены и все строки между теми, номера которых не делятся нацело на 10! Если же Вы захотите защитить свою программу от запуска (команды RUN), то примените в непосредственном режиме команду: POKE &H8000,1 Ok (разумеется, Ваша программа должна располагаться с адреса &H8000).