Assembler для начинающих


Сегменты



Сегменты


    Ранее уже рассматривался оператор SEGMENT. Теперь есть возможность
    рассмотреть его более подробно и исследовать дополнительные
    возможности, которые он предоставляет.
 
      До сих пор в большинстве примеров программ присутствовал только
    один оператор SEGMENT. Так как программный код должен находиться в
    некотором сегменте, то нужно присвоить ему имя. Учитывая, что
    ассемблер должен суметь определить адрес сегмента, единственный
    оператор ASSUME в прграмме идентифицирует только один сегмент
    программы. В подобных случаях возможности сегментации программ
    микропроцессора 8088 используются не полностью, но часто это и не
    нужно. Если программа и ее данные помещаются в пределах одной и той


    же адресуемой области памяти объемом 64 кбайт, то нет необходимости
    использовать возможности процессора в сегментации памяти.
 
      Существуют ситуации, когда в программе нужно использовать более
    одного оператора SEGMENT. Одно из таких применений рассматривалоясв
    гл.5 в нескольких примерах, использующих DOS. В этих примерах в
    программе определялся сегмент STACK. Имя, выбранное для сегмента,
    несущественно, но его тип, указанный в операторе SEGMENT, должен
    быть STACK, так как файлу типа .EXE для выполнения программы
    необходимо отвести стековую область. Если в программе не задать
    сегмент STACK, то загрузчик DOS сохранит организацию стека в
    некотором месте памяти, которое может оказаться неприемлемым. В
    этом случае программа может работать недостаточно хорошо.
 
      Другое назначение оператора SEGMENT - расположением данных в
    определенном месте памяти. Как известно, при использовании DOS
    лучше всего, если программа имеет перемещаемый программный сегмент.
    В этом случае нас не заботит, куда DOS загружает программу. Но в
    некоторых случаях фактическое расположение команд или данных
    оказывается существенным. В этих случаях для задания местоположения
    данных можно воспользоваться директивой AT оператора SEGMENT.
 
      Чтобы понять значение указателя AT, рассмотрим пример. В этом
    примере программа использует как Отправную точку систему BIOS, хра-
    нящаяся в ПЗУ персональной ЭВМ. Хотя язык ассемблера является очень
    эффективным средством программирования, с другой стороны это
    довольно трудный инструмент, особенно для больших программ. Поэтому
    выбор языка ассемблера обусловливается свойствами, которые делают
    его выгодным для решения определенной задачи. В случае IBM PC язык
    ассемблера - лучший язык для программирования функций, выполняемых
    ROM BIOS. Эти функции можно охарактеризовать как управление устрой-
    ствами ввода-вывода, где обычно требуется оперировать с отдельными
    битами. Программирование подобных задач сводится к возможности ма-
    нипулировать содержимым точно заданных ячеек памяти и портов ввода-
    вывода. Язык ассемблера также используется в тех случаях, когда
    необходима минимизация размера программы или максимальное быстро-
    действие программы. Всем эти требования предъявляет и система ROM
    BIOS.
      В рассматриваемом примере используется часть 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.
 
      В приведенной на Фиг. 6.12 программе имеется еще один оператор
    SEGMENT. Он определяет сегмент VIDEO с адресом 0B800H. Это
    сегментный адрес буфера для адаптера цветного- графического
    дисплея. Этот адрес нужен для вывода состояния индикатора на экран
    дисплея. Если мы хотим поместить символ в правый верхний угол
    экрана, при условии, что строка на экране содержит 80 символов, то
    смещение соответствующей ячейки должно быть равно 158 в десятичном
    представлении. Программируемые характеристики оборудвания ПК
    описываются в гл.8, а пока вы можете принять сказанное на веру.
 
      Первая часть программы устанавливает необходимую адресацию
    сегментов. Регистр DS указывает на сегмент DATA, а регистр ES - на
    сегмент VIDEO. Хотя в программе эти сегменты объявлены директивой
    AT абсолютными, ассемблер все же обозначает их значком "R", как
    перемещаемые. Программа LINK, тем не менее, подставляет в
    соответствующие поля данных правильные значения.
 
      Программа тестирует переменную KB_FLAG, а ассемблер в
    результате генерирует правильное смещение, равное 17H. В данном
    примере символ стрелка вниз используется для обозначения обычного
    режима, а стрелка вверх обозначает режим CAPS LOCK. Введенные с
    клавиатуры символы считываются программой с помощью функции DOS,
    выводящей эим символя на дисплей. В данном примере для выхода из
    программы был произвольно выбран символ %. Если пользователь вводит
    любой другой символ, то программа выводит его на дисплей и
    возвращается к ожиданию ввода следующих.
 
      Если ввести и запустить данную программу, то вы увидите в
    верхнем правом углу цветного графического дисплея направленную вниз
    или вверх стрелку. Если для цветного дисплея установлен режим 40
    символов в строке, при выполнении данной программы
    стрелка-индикатор будет выводиться во второй сверху строке. Если
    нужно использовать эту программу с адаптером монохромного дисплея,
    то измените адрес сегмента VIDEO на адрес 0B000H, соответственно
    местоположению буфера монохромного дисплея.
 
      При выполнении данной программы с адаптером цветного
    графического дисплея в режиме 80 символов в строке вы увидите на
    экране сильную помеху, "снег". Эта интерференция на экране
    происходит из-за прямой передачи данных из программы в буфер
    дисплея. В случае монохромного адаптера или цветного-графического
    дисплея в режиме 40 символов в строке этой помехи не будет. О
    причинах этого эффекта и о том, как его избежать, мы узнаем при
    рассмотрении аппаратного обеспечения IBM PC.
 
      Существуют и другие применения нескольких операторов SEGMENT в
    одной программе. Если программе требуется область данных объмом
    более 64 кбайт, то она должна организовать доступ к этим данным.
    Как правило, вы воспользуетесь для обращения к этой области данных
    некоторой схемой управления памятью. В такой ситуации вам будет
    доступна вся эта область данных (за исключением некоторых
    фиксированных участков) косвенную адресацию.
 
      В качестве примера рассмотрим, как интерпретатор команд DOS
    загружает программы. DOS загружает транзитную программу на границу
    параграфа сразу за резидентной частью DOS. Размер этой резидентной
    части может варьироваться в зависимости от числа дисководов в
    системе. Кроме того, этот размер может существенно возрастать при
    использовании в DOS прерывания INT 27H, которое заканчивает
    выполнение программы, но оставляет ее резидентной в памяти. При
    этом программный загрузчик DOS должен адресоваться к сегментному
    префиксу PSP той программы, которую он загружает. Проще всего
    задать эту структуру данных с помощью отдельного оператора SEGMENT.
 
      На Фиг. 6.13 показано объявление сегмента, которое можно
    использовать в двух различных местах. Если бы можно было посмотреть
    текст исходной программы для загрузчика DOS, то мы бы обнаружили
    там подобное объявление. В случае программы, использующей структуру
    .EXE, такая сегментация могла бы обеспечить доступ к переменным в
    сегментном префиксе PSP. В приведенном на Фиг. 5.6 примере
    программы с применением функций DOS, использовалась структура файла
    типа .COM. Это позволяло нам обращаться к различным ячейкам
    сегмента PSP через смещение относительно блока PSP. Задача весьма
    облегчалась тем, что DOS загружала программу в тот сегмент, который
    содержал PSP.
      В случае .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. Эта структура данных может быть,
    например, болком управления для операционной системы, либо строкой
    текста для текстового редактора, или даже блоком параметров
    конкретной подпрограммы. Каждый объект структуры данных
    располагается в своем отдельном сегменте. Таким образом, при
    обращении программы к каждому элементу структуры данных сегментный
    регистр указывает на начало (или на близкую к началу точку) этого
    элемента. Программа не обращается к двум различным элементам с
    одним и тем же значением сегментного регистра. Для каждого элемента
    всегда устанавливается свой адрес сегмента.
 
      Здесь следует немного остановиться на том, какие вообще есть
    методы распределения памяти микропроцессора 8088. IBM PC с
    микропроцессором 8088 может адресовать до 1Мбайт оперативной
    памяти, но один сегмент может охватывать не более 64 кбайт. Даже с
    четырьмя сегментными регистрами программа не имеет возможности
    охватить всю память, не используя некоторых способов сегментации.
 
      Если все данные помещаются в 64К, то нет нужды волноваться:
    просто поместите все данные в один сегмент. Если же мы полагаем,
    что программе требуется область данных, превышающая 64К, то нам
    придется решать задачу распределения памяти. При этом возможны две
    стратегии. В обоих случаях мы будем предполагать, что вся
    совокупность данных может быть разбита на меньшие блоки (такие как
    отдельные переменные, строки текста, управляющие блоки или
    массивы), объемом не более 64К каждый.
 
      Первый метод распределения памяти применяется в ситуации, когда
    вашей главной заботой является экономия памяти. При этом методе вы
    располагаете объекты данных в первых же свободных участках памяти.
    Программа, управляющая доступом к областям данных, должна при этом
    для каждой переменной использовать четырехбайтовый указатель. Из
    них два байта используется для смещения и еще два байта для значе-
    ния сегмента. Когда программе нужно полусить доступ к данным, она
    извлекает адрес из области хранения адресов с помощью команд 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. Если же хотите использовать первый метод распределения
    памяти, то вам потребуется другой способ определения структур
    данных. Такой способ объявления данных как раз рассматривается в
    следующем разделе.




Содержание раздела