Схема работы динамического загрузчика.
Суть работы динамического загрузчика состоит в следующем. Сначала он размещает в памяти не всю программу целиком, как статический загрузчик, а только её основную часть (головной модуль программы). Все остальные модули загружаются в оперативную память по мере необходимости, когда к ним будет реальное обращение из головного модуля или других (уже загруженных) модулей программы. Иногда это называется загрузкой по требованию. Отметим здесь важную особенность такой загрузки по требованию. После размещения головного модуля в оперативной памяти динамический загрузчик не проверяет, что все другие модули, которые он может вызывать в процессе своей работы, на самом деле существуют (на это остаётся только надеятся изо всех сил ☺).[58] Естественно, что, если какой-нибудь модуль не будет найден, когда понадобится его вызвать, то будет зафиксирована ошибка времени выполнения программы.
Надо отметить, что динамические загрузчики современных ЭВМ достаточно сложны, поэтому мы рассмотрим только упрощённую схему работы такого загрузчика. Эта схема будет очень похожа на схему работы динамических загрузчиков в ЭВМ второго поколения средины прошлого века.
Сначала разберёмся с редактированием внешних связей при динамической загрузке модулей (это и называется динамическим связыванием модулей). Работу динамического загрузчика будем рассматривать на примере программы, головной модуль которой вызывает три внешних процедуры с именами A,Beta и C12. Ниже приведён фрагмент сегмента кода этого головного модуля на Ассемблере:
Code segment
assume cs:Code,ds:Data,ss:Stack
Start:mov ax,Data
mov ds,ax
. . .
extrn A:far
call A
. . .
extrn Beta:far
call Beta
. . .
extrn C12:far
call C12
. . .
Finish
Code ends
end Start; головной модуль
Пусть внешние процедуры с именами A,Beta и C12 расположены каждая в своём отдельном модуле, где эти имена, естественно, объявлены общедоступными (public). При своём вызове динамический загрузчик получает в качестве параметра имя головного объектного модуля, по существу это первый параметр редактора внешних связей (загрузочного модуля у нас нет, и не будет). Сначала динамический загрузчик размещает в оперативной памяти все сегменты головного модуля и начинает настройку его внешних адресов. Для этих целей он строит в оперативной памяти две вспомогательные таблицы: таблицу внешних имён (ТВИ) и таблицу внешних адресов (ТВА), каждая из этих таблиц располагается в своём сегменте памяти.
В таблицу внешних имён заносятся все внешние имена программы (в начале работы это имена внешних процедур головного модуля A,Beta и C12). Каждое имя будем представлять в виде текстовой строки, заканчивающейся, как это часто делается, символом с номером ноль в алфавите (будем обозначать этот символ \0, как это принято в языке С). Ссылка на имя – это смещение начала этого имени от начала ТВИ. В заголовке (в первых двух байтах) ТВИ хранится ссылка на начало свободного места в этой таблице (номер первого свободного байта). На рис. 10.3 приведён вид ТВИ после обработки головного модуля (в каждой строке таблицы, кроме заголовка, мы разместили по четыре символа).
ТВИ segment | |||||||
0 | Free=13 | ||||||
'A' | \0 | 'B' | 'e' | ||||
't' | 'a' | \0 | 'C' | ||||
10 | '1' | '2' | \0 | ||||
Рис. 10.3. Вид таблицы внешних имён после загрузки головного модуля. | |||||||
Другая таблица динамического загрузчика, таблица внешних адресов, состоит из строк, каждая строка содержит четыре поля. Первое поле имеет длину четыре байта, в нём находится команда близкого абсолютного перехода jmp LoadGo . Это переход на начало некоторой служебной процедуры динамического загрузчика Мы назвали эту служебную процедуру именем LoadGo, что будет хорошо отражать её назначение – загрузить внешнюю процедуру и перейти на её выполнение. Процедура LoadGo загружается вместе с головным модулем и статически связана с ним.
Во втором поле Offset длиной 2 байта находится адрес (смещение) внешней процедуры на специальном рабочем поле, о котором мы расскажем немного ниже. До первого обращения к внешней процедуре в это поле динамический загрузчик записывает константу 0FFFFh, что является признаком отсутствия данной процедуры на рабочем поле. В третьем поле длиной в два байта расположена ссылка на имя этой внешней процедуры в таблице внешних имён. И, наконей, четвёртое поле, тоже длиной в 2 байта, содержит различную служебную информацию (флаги режимов работы) для динамического загрузчика, о чём мы также немного поговорим далее. Таким образом, каждая строка таблицы внешних имён описывает одну внешнюю процедуру и имеет длину 10 байт. В заголовке (первых двух байтах) ТВА содержится ссылку на начало свободного места в этой таблице. Таким образом, перед началом счёта ТВА будет иметь вид, показанный на рис. 10.4.
Каждая команда вызова внешней процедуры в головном модуле заменяется динамическим загрузчиком на команду перехода с возвратом на соответствующую строку ТВА. Например, команда call Beta заменяется на команду call ТВА:12 , а команда в головном модуле call C12 заменяется на команду call ТВА:22 .
ТВА segment | ||||||
Free=32 | ||||||
jmp LoadGo | 0FFFFh | 2 ('A') | flags | |||
jmp LoadGo | 0FFFFh | 4 ('Beta') | flags | |||
jmp LoadGo | 0FFFFh | 9 ('C12') | flags | |||
32 | ||||||
Рис. 10.4. Вид таблицы внешних адресов после загрузки головного модуля. | ||||||
Проследим работу нашей программы. Пусть головная программа в начале своего выполнения вызывает внешнюю процедуру с именем Beta. Естественно, что при первой попытке основной программы вызвать процедуру Beta, управление получает служебная процедура LoadGo динамического загрузчика. Получив управление, процедура LoadGo последовательно выполняет следующие действия.
1. Сначала вычисляется величина TBA_proc, равная адресу строки вызываемой процедуры Beta в таблице TBA. Для случая вызова процедуры Beta величина TBA_proc=12.
2. Затем анализируется поле Offset в строке TBA_proc. Если Offset=-1, то это означает, что нужной внешней процедуры с именем Beta в оперативной памяти ещё нет. В этом случае процедура LoadGo производит поиск объектного модуля, содержащего требуемую процедуру (в паспорте этого модуля указана входная точка с именем Beta с типом дальней метки far). Если такой объектный модуль не найден, то фиксируется фатальная ошибка времени выполнения и наша программа завершается, иначе требуемая внешняя процедура Beta загружается служебной процедурой LoadGo в оперативную память и динамически связывается с основной программой. Для загрузки процедур в памяти выделяется специальная область, она часто называется рабочим полем процедур. Мы отведём под рабочее поле сегмент с именем Work, занимающий, например, 50000 байт:
Дата добавления: 2015-10-05; просмотров: 722;