Пространство, выделенное DOS для выполнения задачи, в технической документации принято называть блоком задачи. Он состоит из трех основных частей. Первые юоь (256) байтов блока занимает специальная структура данных — префикс программного сегмента (PSP). В нем хранятся величины, которые могут быть нужны при выполнении задачи. Сразу после PSP в памяти расположены сегменты, описанные в исходном тексте программы.
Как уже говорилось, порядок расположения сегментов в
блоке задачи зависит от их имен и от наличия директив .Alpha, .seq или
Dosseg, которые могут указываться в начале исходного текста программы.
Адрес свободного пространства
Для определения адреса свободного пространства надо знать, где заканчивается последний сегмент задачи. Для этого, в свою очередь, необходимо выяснить, какой из сегментов, описанных в исходном тексте программы, окажется последним в теле задачи.
Иногда рекомендуют включать в исходный текст задачи пустой сегмент, имя которого начинается на букву z, например:
Zero Segment ; начало сегмента Zero Zero Ends ; конец сегмента Zero
Если при этом задана директива .Alpha, т. е. надежда, что сегмент zero будет расположен в теле задачи последним. В таком случае он и является началом свободного пространства в блоке задачи.
Несмотря на очевидную простоту, этот способ не универсален, поскольку сегмент zero не всегда оказывается последним. Напомним, что имена могут состоять не только из букв. Например, если кодовый сегмент имеет имя _text, то сегмент Zero будет расположен перед ним. Поэтому нужен более надежный способ определения последнего сегмента.
Если указана директива Dosseg, то последним в теле задачи будет расположен стековый сегмент. Тот же результат получается при использовании специальных директив, показанных в примере Б.2. Учитывая, что они рекомендованы разработчиками в качестве основных, имеет смысл исходить из допущения, что последним в теле задачи расположен стековый сегмент.
Стековый сегмент имеет определенный размер, который надо учесть при вычислении первого свободного сегмента в блоке задачи. При входе в задачу регистр SP содержит адрес верхушки, который равен размеру стека, выраженному в байтах. Его надо преобразовать в параграфы (разделить на 16) и сложить с кодом стекового сегмента, хранящимся в регистре ss. Таким образом, адрес начала свободного пространства (код свободного сегмента) вычисляется по формуле:
freeseg = [ss] + ([sp] /16) + 1
В этой формуле квадратные скобки указывают на то, что используется содержимое регистров ss и sp. При программировании деление [sp] на 16 заменяется сдвигом на 4 разряда вправо. Для того чтобы не потерять один параграф, размер стека должен быть кратен 16-ти, в противном случае его надо округлить в сторону увеличения. Прибавление 1 нужно потому, что свободный сегмент должен начинаться после стекового, не перекрывая его.
Замечание
Какой бы сегмент вы не выбрали в качестве точки отсчета, советуем при
построении задачи обязательно задать файл листинга (карту памяти) и убедиться
в том, что выбранный сегмент расположен в теле задачи последним.
Размер свободного пространства
В PSP слово со смещением 2 содержит последний доступный для задачи адрес оперативной памяти, выраженный в параграфах, т. е. это код последнего доступного сегмента. После загрузки задачи DOS помещает код сегмента, содержащего PSP в регистры es и ds.
Содержимое регистра ds изменяется первыми командами задачи, а ев можно использовать для чтения указанной величины. Если ее уменьшить на freeseg, то получится размер свободного пространства, выраженный в параграфах. Теперь надо проверить, достаточно ли выделенное пространство для нужд задачи, и если да, то его можно использовать.
Вычисление SwpSeg и GenSeg
В приведенных в основной части книги примерах использовались буфер обмена и буфер общего назначения. Мы предполагали, что код сегмента, содержащего буфер обмена, хранится в переменной SwpSeg, а буфер общего назначения — в переменной GenSeg. Покажем, как можно сформировать значения этих переменных после вычисления размера и адреса начала свободного пространства описанным выше способом.
В примере Б.З приведен фрагмент начала программы, в котором выполняются все необходимые вычисления. Для описания сегментов в нем использованы обычные директивы (см. пример Б.1).
Пример Б.З. Вычисление значений переменных SwpSeg и GenSeg 1
.Alpha порядок расположения сегментов
Dosseg порядок расположения сегментов
stack Segment word stac : "stack" ; начало стекового сегмента
db 200h dup (?) размер области стека 200h байтов
stack Ends конец стекового сегмента
data Segment начало сегмента данных
prmpt db ODh,OAh, ' ! Для выполнения задачи не хватает памяти ! $ '
freeseg dw 0 первый свободный сегмент
msize dw 0 размер памяти в параграфах
needm dw 2000h требуемый размер памяти
SwpOffs dw 0 смещение в буфере обмена
SwpSeg dw 0 сегмент буфера обмена
GenOffs dw 0 смещение в буфере
GenSeg dw 0 сегмент буфера общего назначения
; Далее описываются другие используемые данные
data Ends конец сегмента данных
code Segment начало кодового сегмента
.386 набор команд процессора
start : mov ax, data ах = код сегмента данных
mov ds, ax ds = код сегмента данных
mov bx , sp bx = размер стека в байтах
shr bx, 04 превращаем его в параграфы
mov ax, ss ах = код стекового сегмента
add bx, ax bx = последний параграф стека
inc bx bx = первый свободный сегмент
mov freeseg, bx ireeseg = DX
mov ax, es: [02] ax = последний доступный сегмент
sub ax, bx ax = ax — bx
mov msize, ax размер памяти в параграфах
cmp ax, needm памяти достаточно ?
jae @F -> да
lea dx, prmpt dx = адрес аварийного сообщения
mov ah, 09 ah = код функции DOS
int 21h вывод текста сообщения
mov ax, 4COOh ah = 4С, код функции DOS
int 21h завершение выполнения задачи
@@: mov SwpSeg, bx SwpSeg = bx
add bx, lOOOh bx = bx + 65536/16
mov GenSeg, bx GenSeg = bx
; Далее расположен текст основной программы и подпрограмм
code Ends конец кодового сегмента
END start конец текста программы
В начале примера Б.З подряд расположены директивы .Alpha и Dosseg. При таком их сочетании стековый сегмент будет расположен в теле задачи последним, независимо от имен других сегментов (по крайней мере, так его располагает MASM 5.1).
Директива .386 определяет набор команд, которые можно использовать в программе. Если ее не указать, то по умолчанию будет выбран набор команд микропроцессора Intel 8086. В таком случае Макроассемблер обнаружит ошибку в записи команды shr bx, 04. Подробнее о назначении и месте расположения этой директивы сказано в приложении В.
После загрузки задачи DOS передает управление на метку start, для этого ее имя указано не только перед первой командой, но и после директивы END. Первые две команды записывают в регистр ds код сегмента данных, этот вопрос мы уже обсуждали. Шесть следующих команд вычисляют в регистре bx код первого свободного сегмента и сохраняют его в переменной freeseg. Затем в регистр ах считывается из 2-го слова PSP код последнего доступного для задачи сегмента, вычисляется размер свободной памяти в параграфах и сохраняется в переменной msize.
Реальный размер памяти сравнивается с необходимым (needm), и если он достаточен для выполнения задачи, то произойдет переход на локачьную метку @@. В противном случае на экран будет выведено аварийное сообщение и прекратится выполнение задачи. Способы вывода текстовых сообщений описаны в разделе основной части книги, а завершение выполнения задачи описано в разделе .
Если памяти достаточно, то в переменную SwpSeg копируется код первого доступного сегмента. Для буфера обмена отведено 65 536 байтов, что в параграфах составляет 1000h. Эта величина прибавляется к содержимому регистра bx, и результат записывается в GenSeg. Значения переменных swpoffs и GenOffs определяются в процессе выполнения задачи.
После выполнения команд примера Б.З размер буфера обмена ограничен, поскольку после него расположен буфер общего назначения, поэтому при работе с swpSeg нельзя выходить за пределы 65 536 байтов. Во всех ранее приведенных примерах такое ограничение нас вполне устраивало. Размер буфера общего назначения пока ограничен величиной (msize - ioooh)*16 байтов. В зависимости от конкретных особенностей задачи это пространство может быть разделено на блоки меньшего размера или использовано как большой буфер общего назначения.
При работе с блоками большого размера задача должна контролировать текущий адрес ОЗУ и при достижении границы 65 536 байтов изменять код в сегментном регистре, который используется для доступа к блоку (увеличивать его содержимое на 1000h). Необходимость работы с блоками ОЗУ большого размера возникает, например, при сохранении и восстановлении содержимого всей рабочей области экрана.
Мы описали простой пример размещения блоков в ОЗУ. Для выполнения более сложных функций, связанных с распределением памяти в текст задачи, придется включать специальные подпрограммы и поддерживать структуру данных, описывающих свободное и использованное пространство ОЗУ. Альтернативой является обращение к DOS для выполнения действий, связанных с распределением простраж тва оперативной памяти.