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

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

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

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

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

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

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

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

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


 

AStack SEGMENT STACK

DW 12 DUP(?)

AStack ENDS

AData. SEGMENT

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

AData ENDS

ACode SEGMENT

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

WriteMsg PROC NEAR

mov AH, 9

int 21 h

ret

WriteMsg ENDP

Main PROC FAR

push DS

sub AX, AX

push 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).