Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
24
Добавлен:
02.05.2014
Размер:
164.86 Кб
Скачать

8.5.2. Общая характеристика динамического связывания

В этом случае загрузка и связывание подпрограммы происходят в момент первого обращения к ней.

Механизм реализации динамического связывания похож на реализацию оверлейной программы с учетом того, что менеджер оверлеев и таблица сегментов превращаются в средство, которое называется ДИНАМИЧЕСКИМ ЗАГРУЗЧИКОМ и которое является средством операционной системы.

Краткое описание процесса динамического связывания выглядит следующим образом.

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

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

Как уже говорилось, существует два вида динамического связывания:

  1. неявное связывание, на этапе загрузки программы

  2. явное связывание, на этапе выполнения программы.

Динамическая библиотека поставляется двумя файлами:

  1. .dll – собственно библиотека;

  2. .lib – заглушка библиотеки, предназначенная для связи программы с библиотекой.

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

extern "C" __declspec(dllimport) double Ex22cSquareRoot(double d);

В исходном коде самой библиотеки функция декларируется строкой

extern "C" __declspec(dllexport) double Ex22cSquareRoot(double d)

При явном связывании требуется загрузить библиотеку вызовом:

HINSTANCE hDLL = AfxLoadLibrary("ex22c");

Затем необходимо получить адрес точки входа в функцию. Это делается вызовом:

FARPROC ProcAddr = GetProcAddress(hDLL,"Ex22cSquareRoot");

Затем надо преобразовать тип возвращаемых данных (FARPROC) преобразовать к типу данных функции.

Например, для описанной функции необходимо выполнить следующие действия:

    1. Описать переменную: double (*SQRT)(double);

    2. Получить ее значение: SQRT = (double (*)(double))ProcAddr;

    3. Воспользоваться этой переменной, как именем функции: res = SQRT(arg);

После использования библиотеки ее можно выгрузить вызовом

AfxFreeLibrary(hDLL);

8.5.3. Механизмы динамического связывания

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

Сначала заметим, что разделение ресурсов в мультипрограммировании неизбежно. Если бы каждый процесс имел собственную копию некоторого ресурса, системные накладные расходы были бы очень велики. Они состояли бы из расходов на ввод-вывод при загрузке копий и расходов памяти на хранение этих копий.

По этой причине часто разделяется единственная копия ресурса в основной памяти.

Вопросы разделения данных мы с вами подробно рассматривали в разделе “Параллельное выполнение программ”. Критические секции – это и есть решение проблемы разделения данных.

Здесь рассмотрим вопросы разделения процедур.

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

В современной практике модификация команд не является хорошим стилем. Этому существуют две причины:

  1. Логика программы становится чрезвычайно запутанной

  2. Самоизменения не дают возможности разделять программу.

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

Появился термин “чистые процедуры”. Это процедуры, которые не модифицируют свои собственные команды и локальные данные. Любая программа, которая является только выполняемой и читающей, называется реентерабельной (повторно входимой), т.к. ее выполнение может быть продолжено в любое время с уверенностью, что ничего не изменилось. Чистые процедуры могут разделяться более чем одним процессом.

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

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

Пример.

Пусть ARG – символическое имя ячейки, содержащей некоторую переменную. [ARG] означает содержимое ARG.

Чтобы запомнить содержимое регистра Ri в ARG, можно использовать команду:

STO Ri, ARG; [ARG] := [Ri];

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

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

Правильная команда будет выглядеть так:

STO Ri, ARG(Rb); [[Rb] + ARG] := [Ri];

Таким образом, регистры процессора, которые запоминаются как часть вектора состояния процесса, обеспечивают простые средства связывания чистых процедур с данными.

Итак, мы видим, что внутренними ссылками легко управлять через базовый регистр. Однако, внешние ссылки, например, имя разделяемой процедуры, должны быть символическими. Только через символическое имя разделяемая процедура может быть доступна процессам.

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

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

Каждая разделяемая процедура состоит из трех частей:

  1. таблицы символов

  2. чистой программы

  3. секции связи.

Каждый процесс использует собственную копию секции связи, когда использует процедуру. Адрес копии секции связи хранится в регистре lp, так же, как адрес собственного стека хранится в регистре sp.

Таблица символов содержит таблицу определений и таблицу использования символов.

Внутри сегмента процедуры Р определены n символов E1, E2, … , En (например, точки входа). Они могут быть использованы как пары [P, Ei] в других сегментах для обращения к соответствующей точке. Имена e1, e2, .., en обозначают номера слов, соответствующих точкам входа Ei в Р. Значения lei являются указателями на соответствующую строку секции связи.

Процедура Р сама ссылается на слово Х в сегменте D и на слово E в процедуре Q.

Фрагмент чистой части Р показан ниже.

Здесь показано, что команда ADD [D, X] транслируется в команду ADD ldx, lp,

а команда CALL [Q, E], транслируется в команду CALL lqe, lp.

Т.е. данные вычисляются через секцию связи (ldx, lqe). Когда процесс связывается с P, он получает копию секции связи, а ее начальный адрес помещается в регистр lp. Т.е. все внешние ссылки доступны из Р через копию секции связи.

Посмотрим теперь, как происходит связывание данных с использованием перечисленных таблиц.

Когда процесс начинает выбирать операнд [D, X] команды ADD, происходит обращение к соответствующей строке копии секции связи.

Поскольку при первом обращении флаг прерывания установлен, возникает внутреннее прерывание, и управление передается программе, называемой установщиком связи.

Установщик связи, по сути, корректирует копию секцию связи, заменяя idx на [d, x] и сбрасывает флаг прерывания. Возможно, для этого первоначально потребуется обращение к файловой системе для загрузки сегмента D.

После этого происходит повторное обращение к копии секции связи.

Посмотрим теперь, как происходит вызов процедуры CALL lqe;

Система в этом случае помимо установления связи должна получить копию секции связи вызываемой процедуры, занести новое значение в lp (адрес секции связи), и новое значение в pbr (адрес сегмента, выполняющегося в данный момент).

Как и прежде вызывается программа установщика связи, которая заносит в секцию связи LSp адрес символа Е в виде ссылки на строку секции связи LSq (lq,le) и сбрасывает флаг прерывания.

По адресу данного символа находится программа, состоящая из двух инструкций. Первая инструкция загружает регистр lp значением lq, а вторая инструкция по значению le передает управление в саму точку e сегмента Q.

8. Управление информацией в операционных средах

8.1. Общая характеристика связывания объектов

8.2. Загрузка абсолютной программы

8.3. Загрузка перемещаемой программы

8.4. Редактирование связей

8.5. Динамическое связывание

8.5.1. Организация оверлейных программ

8.5.2. Общая характеристика динамического связывания

8.5.3. Механизмы динамического связывания

Соседние файлы в папке Шпоры по СПО