Процедуры и прерывания. Использование стека при вызове процедур и прерываний

Часть 1. Первоначальные сведения об ассемблере и разработке программ для процессора 8086

Глава 3. Загрузка и выполнение программ в DOS

Использование DOS сегментной организации памяти для загрузки и выполнения программ

При загрузке программы в оперативную память DOS инициализирует как минимум три сегментных регистра: CS, DS и SS. При этом совокупности байтов, представляющих команды процессора (код программы), и данные, помещаются из файла на диске в оперативную память, а адреса этих сегментов записываются в регистры CS и DS соответственно. Сегмент стека либо выделяется в области, указанной в программе, либо совпадает (если он явно в программе не описан) с самым первым сегментом программы. Адрес сегмента стека помещается в регистр SS. Программа может иметь несколько кодовых сегментов и сегментов данных и в процессе выполнения специальными командами выполнять переключения между ними.

Для того чтобы адресовать одновременно два сегмента данных, например при выполнении операции пересылки из одной области памяти в другую, можно использовать регистр дополнительного сегмента данных ES. Кодовый сегмент и сегмент стека всегда определяются содержимым своих регистров (CS и SS), и поэтому в каждый момент выполнения программы всегда используется какой-то один кодовый сегмент и сегмент стека. Причем если переключение кодового сегмента – довольно простая операция, то переключать сегмент стека можно только при условии четкого представления логики работы программы со стеком, иначе это может привести к зависанию системы.

Все сегменты могут использовать различные области памяти, а могут частично или полностью совпадать (рис. 3.1).

Кодовый сегмент должен обязательно описываться в программе, все остальные могут отсутствовать. В этом случае DOS при загрузке программы в оперативную память инициализирует регистры DS и ES значением адреса префикса программного сегмента PSP (Program Segment Prefics) – специальной области оперативной памяти размером 256 (100h) байт. PSP может использоваться в программе для определения имен файлов и параметров из командной строки, введенной при запуске программы на выполнение, объема доступной памяти, переменных окружения системы и т.д. Регистр SS при этом инициализируется значением сегмента, находящегося сразу за PSP, т.е. первого сегмента программы. При этом необходимо учитывать, что стек "растет вниз", иными словами при помещении в стек каких-либо значений содержимое регистра SP, указывающего на вершину стека, уменьшается на 2, а при считывании из стека – на столько же увеличивается (цифра 2 связана с тем, что стек работает со словами, а не с байтами). Таким образом, при помещении в стек каких-либо значений они могут затереть PSP и программы, находящиеся в младших адресах памяти (например, DOS), что может привести к непредсказуемым последствиям. Поэтому рекомендуется всегда явно описывать сегмент стека в тексте программы, задавая ему размер, достаточный для нормальной работы.

В примере программы HELLO из главы 1 в тексте программы явно описаны два сегмента – кода с именем CODE и данных с именем DATA. Директива ASSUME связывает имена этих сегментов, которые в общем случае могут быть произвольными, с сегментными регистрами CS и DS соответственно (рис.3.2).

Как видно из рисунка, сегмент стека в данном случае установлен на конец PSP, что при его интенсивном использовании могло бы привести к неожиданным результатам (именно по этой причине компоновщик выдал предупреждение Warning: No stack).

После инициализации в регистре IP находится смещение первой команды программы относительно начала кодового сегмента, адрес которого помещен в CS. Процессор, считывая эту команду, начинает выполнение программы, постоянно изменяя содержимое регистра IP и при необходимости CS для получения кодов очередных команд до тех пор, пока не встретит команду завершения программы.

DS после загрузки программы установлен на начало PSP, поэтому для его использования в первых двух командах программы выполняется загрузка DS значением сегмента данных программы.

Процедуры и прерывания. Использование стека при вызове процедур и прерываний - student2.ru

Рис.3.2. Состояние сегментных регистров после загрузки программы HELLO.

Процедуры и прерывания. Использование стека при вызове процедур и прерываний

Все современные программы разрабатываются по модульному принципу – программа обычно состоит из нескольких небольших частей, называемых подпрограммами или процедурами, и одной главной программы, которая вызывает эти процедуры на выполнение, передавая им управление процессором. После завершения работы процедуры возвращают управление главной программе и выполнение продолжается с команды, следующей за командой вызова процедуры.

Достоинством такого метода является возможность разработки программ значительного объема небольшими функционально законченными частями. Кроме того, эти процедуры можно использовать в других программах, не прибегая к переписыванию частей программного кода. В довершение ко всему, так как размер сегмента не может превышать 64К, то при разработке программ с объемом кода более 64К, просто не обойтись без модульного принципа.

Ассемблер поддерживает применение процедур. Процедуры в программах на ассемблере могут быть двух типов – ближнего (near) и дальнего (far).

Процедуры ближнего типа должны находиться в том же сегменте, что и вызывающая процедура. Дальний тип процедуры означает, что к ней в принципе можно обращаться и из любого другого кодового сегмента.

При вызове процедуры в стеке сохраняется адрес возврата в вызывающую процедуру:

• при вызове ближней процедуры – слово, содержащее смещение точки возврата относительно текущего кодового сегмента;

• при вызове дальней процедуры – слово, содержащее адрес сегмента, в котором расположена точка возврата, и слово, содержащее смещение точки возврата в этом сегменте.

На рис. 3.3 приведен текст программы HELLO с изменениями, реализующими модульный принцип разработки программ. Как видно из текста программы, теперь она оформлена в виде двух процедур – главной процедуры с именем Main, имеющей тип far, и процедуры вывода сообщения на экран WriteMsg типа near.

AStack SEGMENT STACK

DW 12 DUP(?)

AStack ENDS

AData. SEGMENT

Hello DB 'Здравствуйте !$'

AData ENDS

ACode SEGMENT

ASSUME CS: ACode, DS: AData, SS: AStack

WriteMsg PROC NEAR

mov AH, 9

int 21h

ret

WriteMsg ENDP

Main PROC FAR

push DS

sub AX, AX

ush AX

mov AX, AData

mov DS, AX

mov DX, OFFSET Hello

call WriteMsg

ret

Main ENDP

ACode ENDS

END Main

Рис. 3.3. Программа HELLO, построенная по модульному принципу.

Появление трех новых операторов в главной процедуре (push DS – помещение в стек содержимого регистра DS; ub АХ,АХ – вычитание содержимого регистра АХ из содержимого регистра АХ, т.е. обнуление AX; push АХ – помещение в стек регистра АХ, т.е. 0) объясняется именно оформлением программы в виде процедур. В конце каждой процедуры находится команда ret, выполнение которой приводит к тому, что из стека восстанавливается адрес точки возврата (одно или два слова, в зависимости от типа процедуры) и по этому адресу осуществляется передача управления.

При вызове процедуры WriteMsg оператором call WriteMsg в стек помещается слово (так как WriteMsg имеет ближний тип), являющееся смещением следующей команды относительно текущего сегмента (в данном случае это команда ret процедуры Main), после чего указатель стека SP автоматически уменьшается на 2, а управление передается первому оператору процедуры WriteMsg (mov АН,9). При выполнении в процедуре WriteMsg команды ret все операции повторяются в обратном порядке: из стека восстанавливается слово, являющееся смещением точки возврат,] относительно кодового сегмента, и по этому адресу выполняется передача управления. При этом содержимое SP увеличивается на 2.

В процедуре же Main выполнение команды ret приводит к восстановлению из стека двух слов, адресующих точку передачи управления. Поскольку в начале процедуры Main в стек были помещены значения регистра DS в момент инициализации программы И 0, а начальное значение регистра DS устанавливается в DOS равным адресу PSP, эти два слова, восстанавливаемые из стека, есть не что иное, как адрес начала PSP, куда и передается управление программой. Первые два байта PSP, в свою очередь, представляют команды, принуждающие программу возвратиться в DOS, поэтому выполнение программы завершается. При помещении в стек в начале процедуры Main DS и 0 содержимое указателя стека уменьшается на 4, при выполнении командь1 ret – восстанавливается до исходного состояния.

Другим видом процедур являются прерывания DOS и BIOS. Прерывания – это обычные процедуры, написанные разработчиками операционной системы (прерывания DOS) или разработчиками аппаратных средств компьютера (прерывания BIOS). Поэтому к этим процедурам можно обращаться точно так жй, как и к другим процедурам. Отличает их лишь форма вызова: вместо команды типа

call ProcNeme

используются команда типа

int Number,

где Number – номер прерывания, например, int 10, int OAh, int lOlOb и т. п.

Прерывания могут быть аппаратными и программными. Аппаратные прерывания возникают от какого-нибудь устройства компьютера, например от клавиатуры, таймера; дисковода или какого-нибудь нестандартного устройства, подключенного к компьютеру через плату расширения. При возникновении аппаратного прерывания процессор прекращает выполнение текущей программы, сохраняет в стеке текущие значения регистров CS и IP, а также флагового регистра и переходит к выполнению программы обработки прерывания путем помещения в CS: IP адреса этой процедуры» Программы обработки аппаратных прерываний заканчиваются командой IRET, которая всегда выталкивает из стека три слова – старое значение CS, IP и регистра флагов, что приводит к продолжению выполнения прерванной программы с того места, в котором возникло аппаратное прерывание.

Механизм вызова программных прерываний аналогичен вызову аппаратных прерываний, за исключением того, что они вызываются в нужных местах программы с помощью команды INT. При вызове прерывания процессор ищет его адрес, используя номер прерывания, в таблице векторов прерываний (вектор – адрес программы обработки прерывания). Эта таблица начинается по адресу 0000:0000, т.е. с самых младших адресов оперативной памяти, и имеет длину 1024 байта. Учитывая, что векторы прерываний хранятся в этой таблице как 16-битовое смещение и 16-битовый сегмент, и каждый вектор, таким образом, имеет длину 4 байта, размер таблицы достаточен для размещения в ней 256 векторов. Чтобы получить адрес необходимого вектора прерывания, достаточно умножить его номер на 4. Например, вектор прерывания 9 (прерывание, аппаратно вызываемое при нажатии клавиши на клавиатуре) находится по адресу 0000:0024 (24h = 36 = 9 х 4).

В программе HELLO, приведенной на рис.3.3, в процедуре WriteMsg осуществляется вызов прерывания int 21h. Это прерывание называется "Вызов функции DOS" и в качестве параметра получает номер необходимой функции DOS в регистре АН (в данном случае 9 – вывод на дисплей строки, адрес первого символа которой должен находиться в DS:DX).

Наши рекомендации