Chapter_10 (1110562), страница 7
Текст из файла (страница 7)
Пусть головная программа в начале своего выполнениявызывает, например, внешнюю процедуру с именем Beta, которая имеет следующий вид:Beta proc far. . .extrn Delta:farcall Delta. . .ret18Beta endpКак видим, в процедуре Beta возможен вызов другой внешней процедуры с именем Delta. Естественно, что при первой попытке основной программы вызвать процедуру Beta, управление передаётся на 14-ую строку ТВА и вызывается служебная процедура LoadGo динамического загрузчика. Получив управление, процедура LoadGo последовательно выполняет следующие действия.•Сначала вычисляется величина TBA_proc, равная адресу строки вызываемой процедурыBeta в таблице TBA.
Например, для случая, как у нас, вызова процедуры Beta величинаTBA_proc=14.•Затем анализируется поле Offset в строке таблицы с адресом TBA_proc. Значение Offset=-1 означает, что нужной внешней процедуры с именем Beta в оперативной памятиещё нет. В этом случае процедура LoadGo производит поиск объектного модуля, содержащего требуемую процедуру (в паспорте этого модуля должна быть описана входная точка сименем Beta и типом дальней метки far). Если такой объектный модуль не найден, тофиксируется фатальная ошибка времени выполнения и наша программа завершается, иначеобъектный модуль с требуемой процедурой Beta загружается в оперативную память и динамически связывается с основной программой. Для этого корректируются таблица внешних адресов ТВА и, возможно, таблица внешних имён ТВИ (если, как в нашем примере, в загружаемой процедуре есть и свои внешние имена).
Для загрузки процедур в оперативнойпамяти компьютера выделяется специальная область, она часто называется рабочим полемпроцедур. Мы отведём под рабочее поле отдельный сегмент с именем Work, занимающий,например, 50000 байт (заметим, что рабочее поле в нашем простом примере должно помещаться в один сегмент):Work segmentdb50000 dup (?)Work endsРабочее поле размещается в оперативной памяти одновременно с сегментами головного модуля, ТВИ и ТВА. После загрузки процедуры Beta поле Offset в строке TBA_proc принимает значение адреса начала этой процедуры на рабочем поле, а поле Length будет равнодлине загруженной процедуры. Пусть длина процедуры Beta будет, например, 30000 байт.В нашем случае процедура Beta загружается с начала рабочего поля, так как оно пока несодержит других внешних процедур, так что поле Offset принимает значение 00000.•Анализируется адрес дальнего возврата, расположенный на вершине стека (по этому адресупроцедура Beta должна возвратиться после окончания своей работы).
Целью такого анализа является определение того, производится ли вызов процедуры Beta из головного модуля программы (как в нашем примере), или же из некоторой внешней процедуры, уже расположенной на рабочем поле (что, конечно, тоже возможно). Ясно, что такой анализ легкопровести по значению поля сегмента в адресе возврата (обязательно поймите, как это сделать).•Если, как в нашем примере, вызов внешней процедуры Beta производится из головногомодуля, то наша служебная процедура LoadGo производит дальний абсолютный переходна начало требуемой внешней процедуры, расположенной на рабочем поле, по команде вида jmp Work:Offset . Ясно, что в этом случае возврат из внешней процедуры по команде ret будет производиться в головной модуль нашей программы (дальний адрес возврата,как обычно, находится на вершине стека). В нашем примере динамический загрузчик перейдёт на выполнение процедуры Beta с помощью команды дальнего абсолютного перехода jmp Work:0 .На рис.
10.5 показан вид ТВИ, ТВА и рабочего поля после загрузки процедуры Beta.Теперь, после динамической загрузки процедуры Beta на рабочее поле и связывания внешнихадресов с помощью ТВА, вызов процедуры Beta будет производиться с помощью служебной процедуры LoadGo. Правда, необходимо заметить, что вызов стал длиннее, чем при статическом связывании, за счёт дополнительных команд, выполняемых процедурой LoadGo. Кроме того, как мы вскоревыясним, внешние процедуры могут неоднократно загружаться на рабочее поле и удаляться с него,что, конечно, может вызвать существенное замедление выполнения программы пользователя. Это,19однако, неизбежная плата за преимущества динамической загрузки модулей.
По существу, здесьопять работает уже упоминавшееся нами правило рычага: выигрывая в объёме памяти, необходимомдля счёта модульной программы, мы неизбежно сколько-то проигрываем в скорости работы нашейпрограммы. Важно чтобы выигрыш, с точки зрения конкретного пользователя, был больше проигрыша.026101418ТВИ segmentFree=19'A' \0'B''t' 'a' \0'1' '2' \0'e' 'l' 't'\0'e''C''D''a'ТВА segment214263850Free=50jmp LoadGojmp LoadGojmp LoadGojmp LoadGo0FFFFh00000h0FFFFh0FFFFh24913('A')('Beta')('C12')('Delta')00000300000000000000FlagsFlagsFlagsFlagsWork segment00000Процедура Beta30000Рис.
10.5. Вид ТВИ, ТВА и рабочего поля после загрузки процедуры Beta.Продолжим анализ работы динамического загрузчика на нашем примере. Пусть загруженная нарабочее поле процедура Beta, проработав некоторое время, в свою очередь вызывает свою внешнюю процедуру с именем Delta, которая имеет, например, длину 15000 байт. Так как командаcall Delta в процедуре Beta при загрузке этой процедуры на рабочее поле заменена динамическим загрузчиком на команду call ТВА:38 , то управление опять получает служебная процедураLoadGo. Она находит процедуру Delta 1 и размещает её на свободном месте рабочего поля (в нашем примере с адреса 30000), затем настраивает внешние адреса в этой процедуре (если они есть) исоответствующие строки в ТВА.На рис.
10.6 показан вид ТВА и рабочего поля после загрузки и связывания процедуры Delta.Далее процедура LoadGo определила, что вызов процедуры TBA_proc(Delta)=38 производится не из основной программы, а из процедуры TBA_proc(Beta)=14, расположенной на рабочем поле. В этом случае LoadGo производит следующие действия, при выполнении которых используется ещё один служебный сегмент динамического загрузчика, описанный, например, так:My_Stack segmentFreedw0dw 4000 dup (?)My_Stack endsЭтот сегмент используется как вспомогательный программный (не аппаратный) стек динамического загрузчика. Наш стек с именем My_Stack, в отличие от машинного стека, будет "расти" свер-1Точнее, как мы уже говорили, ищется объектный модуль, в котором расположена эта общедоступная(public) процедура.20ху вниз, при этом переменная Free играет роль указателя вершины программного стека, то есть регистра SP.ТВА segment214263850Free=50jmp LoadGojmp LoadGojmp LoadGojmp LoadGo0FFFFh000000FFFFh3000024913('A')('Beta')('C12')('Delta')00000Flags30000 Flags00000 Flags15000 FlagsWork segment00000Процедура Beta30000Процедура Delta45000Рис.
10.6. Вид ТВА и рабочего поля после загрузки процедуры Delta.Сначала LoadGo извлекает из аппаратного стека (на него, как обычно, указывает регистроваяпара <SS,SP>) адрес дальнего возврата (два слова). Затем в программный стек My_Stack сначалазаписывается одно слово (назовём его именем RETURN), которое имеет значение разности IPTBA_proc(Beta).offset, где TBA_proc(Beta).offset – адрес начала процедуры Beta нарабочем поле, т.е. значение поля offset строки TBA для процедуры Beta. Как легко понять, величина RETURN равна смещению точки возврата относительно начала процедуры, из которой производится вызов.Затем в программный стек записывается значение TBA_proc(Beta) (одно машинное слово), итаким способом запоминается, из какой процедуры с рабочего поля произошёл вызов.
Таким образом, во вспомогательном стеке запоминается, из какой процедуры и из какого места этой процедурыпроизводится вызов. И, наконец, LoadGo производит вызов необходимой внешней процедурыDelta, расположенной на рабочем поле со смещением Offset=TBA_proc(Delta).offset отего начала, командой дальнего вызова процедуры call Work:Offset . Очевидно, что в этомслучае и возврат из процедуры Delta по команде ret будет производиться не в вызвавшую её процедуру Beta, а в нашу служебную процедуру LoadGo.После возврата из внешней процедуры в LoadGo, она производит следующие действия (напомним, что в этот момент ей уже известно о том, что вызов внешней процедуры был осуществлён изнекоторой процедуры, расположенной на рабочем поле, иначе возврат из процедуры производитсясразу в основную программу, миную LoadGo).•Сначала из вспомогательного стека My_Stack извлекается значение TBA_proc той процедуры, в которую необходимо вернуться (это значение на вершине нашего программногостека по адресу My_Stack[Free]).•Затем анализируется значение поля Offset в этой строке TBA_proc.
Если величина Offset<>-1 , то это означает, что наша процедура всё ещё присутствует на рабочем поле. Вэтом случае из вспомогательного стека My_Stack извлекается значение RETURN смещенияточки возврата относительно начала процедуры, после чего динамический загрузчик производит возврат в процедуру на рабочем поле по команде дальнего безусловного переходаjmp Work:Offset+RETURN .•Разберём теперь случай, когда после возврата величина Offset=-1, это означает, что нашапроцедура была удалена с рабочего поля.
В этом случае производится повторная загрузкапроцедуры на рабочее поле (вообще говоря, начиная с другого свободного места этого поля). Адрес нового положения процедуры на рабочем поле записывается в поле Offset в21строке TBA_proc. Затем, как и в случае с Offset<>-1, из вспомогательного стекаMy_Stack извлекается значение RETURN смещения точки возврата относительно началапроцедуры, после чего динамический загрузчик производит возврат в процедуру, котораятеперь уже снова находится на рабочем поле, по команде дальнего безусловного переходаjmp Work:Offset+RETURN .На этом LoadGo завершает обработку вызова внешней процедуры. Как видим, эта процедураиграет роль своеобразного буфера, располагаясь между вызывающей программой и вызываемойвнешней процедурой. На рис.