Ранее уже рассматривался оператор SEGMENT. Теперь есть возможность рассмотреть его более подробно и исследовать дополнительные возможности, которые он предоставляет.
В рассматриваемом примере используется часть BIOS. В одной из последующих глав будет рассмотрено, как заменять части системы BIOS. Однако в данном случае нас интересует доступ к наборам данных, которые использует ROM BIOS. Если вы посмотрите ассемблерный листинг для ROM BIOS (он приводится в приложении A технического руководства по IBM PC), то увидите, что сегмент DATA располагается в сегменте 40H или по абсолютному адресу 400H. Приведенная на Фиг. 6.12 программа обращается в область данных ПЗУ системы BIOS c определенной целью. В сегменте DATA имеется переменная KB_FLAG, которая указывает текущее состояние переключателя регистров. Одна из жалоб, часто высказываемых по поводу клавиатуры IBM, состоит в том, что неизвестно, работаете ли вы в верхнем регистре (CAPS LOCK) или в нижнем. Программа на Фиг. 6.12 считывает значение бита, соответствующего CAPS LOCK, и изображает его в верхнем правом углу цветного графического дисплея. Хотя в данной программе это не реализовано, мы будем предполагать, что при реальном использовании этого фрагмента программы, верхний правый угол экрана зарезервируется для описанного индикатора. Сегмент DATA на Фиг. 6.12 показывает, как программист может передать в программу информацию, расположенную по абсолютным адресам. Оператор DATA SEGMENT использует директиву AT для того, чтобы обеспечить безусловную привязку данного сегмента к параграфу 40H.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:25
Фиг. 6.12 Использование сегментов Page 1-1
PAGE ,132
TITLE Фиг. 6.12 Использование сегментов
0000 DATA SEGMENT AT 40H
0017 ORG 17H
0017 ?? KB_FLAG DB ?
= 0040 CAPS_STATE EQU 40H
0018 DATA ENDS
0000 VIDEO SEGMENT AT 0B800H
009E ORG 158
009E ?? INDICATOR DB ?
009F VIDEO ENDS
0000 CODE SEGMENT
ASSUME CS:CODE
0000 CAPS PROC FAR
0000 1E START: PUSH DS ; Адрес возврата
0001 B8 0000 MOV AX,0
0004 50 PUSH AX
0005 B8 ---- R MOV AX,DATA ; Адрес сегмента DATA
0008 8E D8 MOV DS,AX
ASSUME DS:DATA
000A B8 ---- R MOV AX,VIDEO ; Адрес сегмента VIDEO
000D 8E C0 MOV ES,AX
Фиг. 6.12 Расположение сегмента (начало)
ASSUME ES:VIDEO
000F DISPLAY_CAPS:
000F B0 18 MOV AL,18H ; Символ "стрелка вверх" имеет код 18H
0011 F6 06 0017 R 40 TEST KB_FLAG,CAPS_STATE ; Определение состояния клавиши CAPS
0016 75 02 JNZ CAPS_LOCK
0018 B0 19 MOV AL,19H ; Символ "стрелка вниз" имеет код 19H
001A CAPS_LOCK:
001A 26: A2 009E R MOV INDICATOR,AL ; Вывод в верхний левый угол экрана
001E B4 06 MOV AH,6 ; Функция ДОС ввода с клавиатуры
; и вывода на дисплей
0020 B2 FF MOV DL,0FFH ; Направление - ввод с клавиатуры
0022 CD 21 INT 21H
0024 3C 00 CMP AL,0 ; Проверка на наличие символа
0026 74 E7 JZ DISPLAY_CAPS ; Нет символа
0028 3C 25 CMP AL,'%' ; Проверка на символ конца
002A 74 08 JE RETURN
002C B4 02 MOV AH,2 ; Функция вывода на дисплей
002E 8A D0 MOV DL,AL ; Выводимый символ
0030 CD 21 INT 21H
0032 EB DB JMP DISPLAY_CAPS ; Повторение
0034 RETURN:
0034 CB RET ; Возврат в ДОС
0035 CAPS ENDP
0035 CODE ENDS
END START
Фиг. 6.12 Расположение сегмента (продолжение)
Просматривая листинг ROM BIOS, мы находим переменную KB_FLAG со смещением 17H в сегменте DATA. Оператор ORG 17H данной программы задает смещение этой переменной в оттранслированной программе. Наконец, смысл оператора EQU, определяющего константу CAPS_STATE следует непосредственно из листинга BIOS ПЗУ. Заданный этой константой бит указывает текущее состояние переключателя CAPS LOCK.
Существуют и другие применения нескольких операторов SEGMENT в одной программе. Если программе требуется область данных объемом более 64 кбайт, то она должна организовать доступ к этим данным. Как правило, вы воспользуетесь для обращения к этой области данных некоторой схемой управления памятью. В такой ситуации вам будет доступна вся эта область данных (за исключением некоторых фиксированных участков) косвенную адресацию.
В случае .EXE-файла блок PSP находится не в том же сегменте, что и команды программы. Так как при передаче управления программе типа .EXE DOS устанавливает регистры DS и ES на сегмент PSP, то имеет смысл обращаться с PSP как с отдельным сегментом. Приведенный на Фиг. 6.13 фрагмент программы из сегмента CODE, показывает, как можно обращаться к данным в блоке PSP.
Microsoft (R) Macro Assembler Version 5.00 1/1/80 04:03:31
Фиг. 6.13 Структура Программного Префикса Page 1-1
PAGE ,132
TITLE Фиг. 6.13 Структура Программного Префикса
0000 PROGRAM_SEGMENT_PREFIX SEGMENT
0000 0002[ INT_20 DB 2 DUP (?)
??
]
0002 ???? MEMORY_SIZE DW ?
0004 0005[ LONG_CALL DB 5 DUP (?)
??
]
0009 ???????? TERMINATE_ADDR DD ?
000D ???????? CTRL_BREAK DD ?
005C ORG 05CH
005C 0010[ FCB1 DB 16 DUP (?)
??
]
006C ORG 06CH
006C 0010[ FCB2 DB 16 DUP (?)
??
]
0080 ORG 080H
0080 0080[ DTA DB 128 DUP (?)
??
]
0100 PROGRAM_SEGMENT_PREFIX ENDS
0000 CODE SEGMENT
ASSUME CS:CODE,DS:PROGRAM_SEGMENT_PREFIX
0000 A1 0002 R MOV AX,MEMORY_SIZE
0003 CODE ENDS
END
Фиг. 6.13 Префикс программного сегмента
Обратите внимание, что сегмент PSP на Фиг. 6.13 на самом деле не содержит никаких значений для переменных. Например, мы знаем, что в первых двух байтах PSP содержится код прерывания INT 20H. Однако мы решили показать, что в этом месте находится поле длиной 2 байта без каких-либо указаний о содержащихся там значениях. Мы должны делать именно так, чтобы в результате нашего описания сегмента ни редактор связей, ни загрузчик не попытались записать в память каких-либо данных. Фактически, мы используем этот сегмент как средство для объявления данных. Оператор SEGMENT объявляет структуру данных, которую мы называем префиксом программного сегмента. Ее местоположение в памяти не фиксировано, а определяется одним из сегментных регистров. В нашем примере на Фиг. 6.13 это местоположение определяется регистром DS.
Точно такой же способ можно использовать для обозначения любой структуры данных, которая может быть расположена в произвольном месте памяти микропроцессора 8088. Эта структура данных может быть, например, блоком управления для операционной системы, либо строкой текста для текстового редактора, или даже блоком параметров конкретной подпрограммы. Каждый объект структуры данных располагается в своем отдельном сегменте. Таким образом, при обращении программы к каждому элементу структуры данных сегментный регистр указывает на начало (или на близкую к началу точку) этого элемента. Программа не обращается к двум различным элементам с одним и тем же значением сегментного регистра. Для каждого элемента всегда устанавливается свой адрес сегмента.
Первый метод распределения памяти применяется в ситуации, когда вашей главной заботой является экономия памяти. При этом методе вы располагаете объекты данных в первых же свободных участках памяти. Программа, управляющая доступом к областям данных, должна при этом для каждой переменной использовать четырехбайтовый указатель. Из них два байта используется для смещения и еще два байта для значения сегмента. Когда программе нужно получить доступ к данным, она извлекает адрес из области хранения адресов с помощью команд LDS или LES. Если вам требуется еще большая экономия памяти, то вы фактически можете хранить указатель в трехбайтовом поле. Два байта содержат адрес сегмента данных, а оставшийся байт содержит смещение данного объекта внутри сегмента. Начальное смещение всегда будет иметь значение от 0 до 15, так как значение сегмента всегда кратно 16.
Хотя описанный метод наиболее эффективен в отношении объема памяти, занимаемой данными, у него имеются пара недостатков. Максимальная длина объекта данных немного меньше, чем 64 кбайт. В рамках данной стратегии наихудшим окажется случай, когда абсолютный адрес объекта данных кончается на 0FH. Так как максимальное значение смещения в любом сегменте равно 0FFFFH, то максимальная длина переменной будет 64К - 15, или 65521 байт. Второй недостаток этого метода связан с затратами памяти для хранения указателей к объектам данных. При большом числе объектов для хранения наряду с ними всех четырехбайтовых (или трехбайтовых) указателей потребуется много памяти.
Примером использования описанного метода распределения памяти может служить блок управления файлом FCB. В последнем примере работающей с DOS программы мы располагали блок FCB в произвольном месте программы. Какого-либо выравнивания местоположения этой структуры данных не производилось. Затем при обращении к DOS для выполнения файловой операции программе понадобился четырехбайтовый указатель. Идентификация блока FCB для DOS осуществлялось парой регистров DS:DX.
При втором методе распределения памяти все объекты данных располагаются на границах параграфов. Это сразу же упрощает указатель, определяющий объект данных. Этот указатель состоит только из двух байтов, которые определяют местонахождение сегмента с этими данными. Так как распределение памяти всегда начинается с границы параграфа, то начальное смещение данных будет всегда равно нулю. Однако при таком методе, расходуется дополнительная память. Каждый раз, когда вы располагаете в памяти новый объект, возможна потеря до 15 байт памяти. Это происходит, если последний байт предыдущего объекта попадает точно на границу параграфа. Так как граница следующего параграфа будет через 15 байт, то эти 15 байт в промежутке теряются. Кроме того, при такой стратегии минимальная длина объекта равна 16 байт. Даже если данные будут занимать меньше места, оставшиеся байты все равно не могут быть использованы.
Как было отмечено, второй метод распределения памяти используется загрузчиком DOS при запуске программ. DOS загружает программу на ближайшую границу параграфа. Так как DOS исходит из того, что в памяти располагается мало больших по размерам объектов, то при данном методе издержки памяти будут невелики. Однако, если ваша прикладная программа использует много небольших объектов, то выравнивание по параграфам может оказаться слишком дорогим.
Второй метод распределения памяти, использующий выравнивание по параграфам, позволяет определять области данных с помощью структуры SEGMENT. Если же хотите использовать первый метод распределения памяти, то вам потребуется другой способ определения структур данных. Такой способ объявления данных как раз рассматривается в следующем разделе.