Предыдущий раздел | РАЗДЕЛЬНАЯ ТРАНСЛЯЦИЯ ПРОГРАММЫ | Следующий раздел |
Что касается машинной программы, записанной загрузчиком в ОП, то кроме собственно машинных команд программы и обрабатываемых ею данных она содержит вспомогательную информацию, называемую префиксом программного сегмента (PSP). Длина PSP 256 (100h) байтов и эта структура данных находится в начале любой машинной программы, вне зависимости от того, получена ли программа загрузкой com- или exe-файла. При этом ни com-, ни exe-файл PSP не содержит. Информация в PSP записывается загрузчиком и используется ОС при выполнении запросов со стороны программы, а также может быть использована самой программой. Структура PSP на рис.58.
Рис. 58. Структура PSP
Для наглядного знакомства со структурой PSP удобно использовать отладчик Debug. Допустим, что мы ранее получили загрузочный модуль программы с именем Prob.com. Тогда для анализа PSP вызовем Debug командой DOS: “Debug Prob.com parametr”, где вместо слова parametr может быть записана любая символьная строка (в коде ASCII), содержащая входные параметры нашей прикладной программы. Эти параметры совершенно не интересуют ни Debug, ни тем более DOS, а используются лишь для передачи в программу какой-то исходной информации. (Кстати, если рассматривать в качестве программы сам Debug, то в качестве его параметра выступает имя исследуемой программы, то есть Prob.com.)
После того как мы запустили Debug описанным выше способом, с помощью команды Debug – “d 0”, получим на экране дамп начальной части PSP. В результате на экран будут выведены первые 128 байтов PSP. Для вывода на экран каждых следующих 128 байтов достаточно просто ввести команду d.
В первых двух байтах PSP находится код машинной команды “int 20h”. (Чтобы убедиться в этом, введем команду Debug – “u 0”). Команда программного прерывания выполняет возврат в ОС при завершении программы. Сама же эта команда получает управление следующим образом.
Во-первых, сразу после загрузки программы в память загрузчик помещает в ее стек 16-битное нулевое слово (0000h). Во-вторых, если в конце главной подпрограммы прикладной программы стоит команда ret, то при попадании этой команды на ЦП, в качестве адреса возврата из стека будет выбран 0 и, следовательно, следующей исполняемой командой будет “int 20h”. Следует отметить, что это не единственный способ возвращения из программы в ОС. Например, вместо ret в главной подпрограмме можно записать оператор “int 20h”. В этом случае управление из программы возвращается в ОС непосредственно, минуя PSP. Другой способ возврата – запись в конце главной подпрограммы команды “int 21h” (функция 4Ch). Этот способ является наиболее предпочтительным, так как он позволяет возвращать из программы в ОС код возврата (в регистре AL), информирующий операционную систему о причине завершения программы.
В следующих двух байтах PSP находится верхняя граница (адрес-сегмент) ОП. Обычно она равна величине A000h. В дальнейшем мы еще вернемся к этой величине, когда будем рассматривать распределение ОП.
В пяти байтах PSP, начиная с 05h, находится вызов диспетчера функций DOS. Данный вызов представляет собой команду дальнего вызова процедуры. С помощью команды Debug – “u 5” мы можем получить на экране, например следующее: “call F01D:FEF0”, где F01D – адрес-сегмент, а FEF0 – адрес-смещение первой команды диспетчера функций DOS. (Соответствующий реальный адрес интересен тем, что он находится за пределами первого мегабайта. Мы вернемся к нему в вопросе о распределении памяти.)
Для того чтобы обратиться из программы к DOS (с целью получения помощи), достаточно поместить в нее команду “call 5”. В результате следующей командой, исполняемой на ЦП, будет команда вызова диспетчера функций. (Данный способ обращения к DOS менее предпочтителен по сравнению с командой программного прерывания “int 21h”.)
В двенадцати байтах PSP, начиная с 0Ah, содержатся копии векторов прерываний с номерами 22h, 23h, 24h. Эти прерывания DOS использует для управления выполнением программы. При этом прерывание 22h используется для вызова той подпрограммы, которая должна получить управление при завершении прикладной программы. Прерывание 23h происходит при нажатии клавиш <Ctrl>+<C>. (DOS использует для обработки этой комбинации специальный обработчик.) Причинами прерывания 24h являются аппаратные ошибки при работе с ПУ. Некоторые из этих ошибок: 1) попытка записи на защищенный диск; 2) нет бумаги на принтере; 3) неизвестное устройство. Хранение копий векторов перечисленных прерываний обусловлено тем, что в случае, если прикладная программа изменяет содержимое векторов (с целью выполнить свою обработку прерываний), то после ее завершения DOS восстанавливает прежнее содержимое векторов, используя копии из PSP.
По адресу 16h находится адрес-сегмент PSP родительской программы. Назначение этого поля будет понятно позже. Следующее поле PSP начинается по адресу 18h и имеет длину 20 байтов. В нем размещена таблица логических номеров файлов, которая будет рассмотрена в другом разделе.
Начиная с ячейки 2Ch, два байта PSP содержат адрес-сегмент блока окружения программы. Блок окружения – совокупность переменных окружения. Переменная окружения – символьная строка в коде ASCII, имеющая структуру:
<имя переменной> = <первое значение>; … ;<последнее значение>00h
После последней переменной окружения записывается не один, а два нулевых байта (0000h). Пример переменной окружения:
PATH = C:\WINDOWS; C:\WINDOWS\COMMAND00h
Данная переменная задает те каталоги (директории), в которых могут находиться используемые программой файлы. В том случае, если на вход программы поступает не имя-путь файла, а лишь завершающая часть этого имени, то для получения имени-пути программа должна последовательно просмотреть заданные значения переменной окружения PATH. В случае обнаружения в очередном каталоге искомого файла имя-путь каталога соединяется с именем файла в единое имя-путь файла.
Допустим, что с помощью команды Debug – “d 0” мы прочитали значение адреса-сегмента блока окружения, равное 2065. Тогда для получения на экране содержимого блока окружения следует воспользоваться командой “d 2065:0”. Первоначальное содержимое своего блока окружения программа получает «в наследство» от той программы, которая создает данную программу, используя системный вызов EXEC (int 21h, функция 4Bh). Данный вызов будет рассматриваться позже. А сейчас лишь заметим, что программа может не только читать свои переменные окружения, но и вносить в них изменения.
По адресу 5Ch находятся два блока управления файлами, используемые при работе с линейными файловыми структурами. Так как эти структуры сейчас полностью вытеснены иерархическими файловыми структурами, соответствующее поле PSP может использоваться для хранения любой другой информации. Например, учитывая, что подобно блоку окружения данные блоки управления передаются «по наследству», родительская программа может помещать в них исходные данные для дочерней программы. Другой способ передачи исходных данных в программу заключается в использовании «хвоста» команды.
Хвост команды – остаток команды ОС (после имени программы), запустившей прикладную программу. Иными словами, хвост содержит параметры команды, а также пробелы, которые были набраны в командной строке. В приведенном выше примере хвостом является слово parametr, предваряемое одним пробелом. Для размещения хвоста используется поле в PSP, начиная с ячейки 81h. Длина хвоста содержится в байте 80h. (Убедитесь в этом, используя Debug.)
Пример. Следующая простая com-программа выводит на экран свои параметры (хвост команды).
;
; Программа выводит на экран свои параметры
; -----------------------------------------------------------
;
absolute 80h ; Следующий байт имеет адрес-смещение 80h
Lparam resb 1 ; Длина хвоста команды
Param resb 1 ; Первый байт хвоста
segment code ; Виртуальный сегмент кода
org 100h
Start: xor cx, cx ; Обнуление CX
mov cl, [Lparam] ; CL ß Длина хвоста
cmp cx, 0 ; Хвост отсутствует ?
jz .Exit ; Если да
mov ah, 0eh ; 0Eh – функция вывода символа
mov bh, 0 ; Нулевая страница экрана
xor si, si ; SI ß 0
.Next: mov al, [Param+si] ; Вывод следующего
int 10h ; символа
inc si
loop .Next ; Повторить для след. символа
.Exit: mov ax, 4c00h ; Возврат в DOS с
int 21h ; кодом завершения 0
В начале данной программы псевдооператор absolute создает виртуальный сегмент данных. При этом параметр 80h задает его расположение относительно начала сегмента памяти программы. Термин виртуальный сегмент означает, что данный сегмент создается в исходной программе явно, с использованием специально предназначенных для этого псевдооператоров absolute или segment. В отличие от него логический сегмент существует не в исходной, а в машинной программе.
Виртуальный сегмент данных в рассматриваемой программе включает данные, объявленные с помощью псевдооператоров resb. Данный псевдооператор резервирует последовательность байтов ОП, присваиваивая первому байту заданное символьное имя (Lparam или Param). Число резервируемых байтов задается параметром оператора resb. В примере это число равно 1. Отличием псевдооператора resb от другого псевдооператора резервирования байтов db в том, что транслятор не производит инициализацию (заполнение) выделенных байтов. Аналогичное отличие имеет оператор резервирования слов resw от оператора dw.
Виртуальный сегмент, начинающийся с оператора absolute, закончится тогда, когда в программе начнется следущий виртуальный сегмент. Его начало задается или другим оператором absolute, или оператором segment. Этот псевдооператор будет рассмотрен нами позже, при рассмотрении exe-программы. Пока лишь заметим, что если расположить наш виртуальный сегмент данных в конце программы, то оператор segment становится необязательным.
В ы п о л н и т е два варианта приведенной выше программы – с использованием оператора segment и без него.
Заметим, что в данной программе для вывода символа на экран используется системный вызов BIOS – int 10h (функция 0Eh). Предварительно программа помещает код выводимого символа в регистр AL.
Предыдущий раздел | В начало | Следующий раздел |