Реклама:

Стратегия компоновки, которую мы обсуждали в подразделе "Задачи компоновщика", имеет одну особенность: все процедуры, требуемые программе, компонуются до начала работы программы. Однако если мы будем устанавливать все связи до начала работы программы в компьютере с виртуальной памятью, то мы не используем всех возможностей виртуальной памяти. Многие программы содержат процедуры, которые вызываются только при определенных обстоятельствах. Например, компиляторы содержат процедуры для компиляции редко используемых операторов или исправления редко встречающихся ошибок.

Более гибкий способ компоновки раздельно скомпилированных процедур - компоновка каждой процедуры в тот момент, когда она впервые вызывается. Этот процесс называется динамической компоновкой. Впервые он был применен в системе MULTICS. Давайте рассмотрим примеры динамической компоновки в нескольких системах.

Динамическая компоновка в MULTICS

В системе MULTICS с каждой программой соотносится сегмент, так называемый сегмент компоновки (linkage segment). Он содержит один информационный блок для каждой процедуры, которая может быть вызвана. Этот блок начинается со слова, зарезервированного для виртуального адреса процедуры, за ним следует имя процедуры, которое сохраняется в виде символьной строки.

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

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

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

Динамическая компоновка

Рис. 7.6. Динамическая компоновка: процедура EARTH до вызова (а); процедура EARTH после вызова и компоновки (б)

Динамическая компоновка в Windows

Все версии Windows, в том числе Windows NT, поддерживают динамическую компоновку. При динамической компоновке используется специальный файловый формат, который называется DLL (Dynamic Link Library - библиотека динамической компоновки). Библиотеки динамической компоновки могут содержать процедуры, данные или то и другое вместе. Обычно они применяются для того, чтобы два и более процессов могли разделять процедуры и данные библиотеки. Большинство DDL-файлов имеют расширение .dll, но встречаются и другие расширения, например .drv (для библиотек драйверов - driver libraries) и .fon (для библиотек шрифтов - font libraries).

Самая распространенная форма DLL - библиотека, состоящая из набора процедур, которые могут загружаться в память и к которым имеют доступ несколько процессов одновременно. На рисунке 7.7 показаны два процесса, которые совместно используют DLL-файл, содержащий 4 процедуры, Л, В, С и D. Программа 1 использует процедуру А; программа 2 - процедуру С, хотя они вполне могли бы использовать одну и ту же процедуру.

Динамическая компоновка

Рис. 7.7. Два процесса используют один DLL-файл

DLL-файл строится компоновщиком из группы входных файлов. Построение DDL напоминает создание исполняемого двоичного кода, только при создании DLL компоновщику передается специальный флаг, сообщающий о том, что требуется именно библиотека DLL. DLL-файлы обычно собираются из набора библиотечных процедур, которые могут понадобиться нескольким процессам. Типичными примерами DLL-файлов являются процедуры сопряжения с библиотекой системных вызовов Windows и большими графическими библиотеками. Применяя DDL-файлы, мы экономим пространство в памяти и на диске. Если бы та или иная библиотека была связана статически с каждой использующей ее программой, эту библиотеку пришлось бы включать во все исполняемые двоичные программы в памяти и на диске, что не экономно. А при наличии DLL-файла на диске и в памяти будет находиться всего одна библиотека.

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

Основное различие между DLL и исполняемой двоичной программой состоит в том, что DLL-файл не может запускаться и работать сам по себе (поскольку у него нет главной программы). Он также содержит совершенно другую информацию в заголовке. Кроме того, DLL-файл имеет несколько дополнительных процедур, не связанных с процедурами в библиотеке. Например, существует одна процедура, которая вызывается автоматически всякий раз, когда новый процесс связывается с DLL-файлом, и еще одна процедура, которая вызывается автоматически всякий раз, когда связь процесса с DLL-файлом разрывается. Эти процедуры могут выделять и освобождать память или управлять другими ресурсами, необходимыми DLL-файлу.

Программа может установить связь с DLL-файлом двумя способами: путем неявной или явной компоновки. При неявной компоновке пользовательская программа статически компонуется со специальным файлом, так называемой библиотекой импорта. Библиотека импорта создается специальной утилитой, предназначенной для извлечения определенной информации из DLL-файла. Библиотека импорта предоставляет связующее звено, через которое пользовательская программа получает доступ к DLL-файлу. Пользовательская программа может быть связана с несколькими библиотеками импорта. Когда программа, в которой имеет место неявная компоновка, загружается в память для исполнения, Windows проверяет, какие DLL-файлы ей требуются и все ли они находятся в памяти. Те файлы, которых еще нет в памяти, немедленно туда загружаются (но необязательно целиком, поскольку они разбиты на страницы). Затем производятся определенные изменения структур данных в библиотеках импорта так, чтобы можно было определить местоположение вызываемых процедур (это похоже на изменения, которые иллюстрирует рис. 7.6). Их тоже нужно отобразить на виртуальное адресное пространство программы. С этого момента пользовательскую программу можно запускать, поскольку она может вызывать процедуры из DLL-файлов, как будто они статически с ней связаны.

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

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

Динамическая компоновка в UNIX

В UNIX используются библиотеки коллективного доступа, по сути напоминающие библиотеки DLL Windows. Как и DLL-файл, библиотека коллективного доступа представляет собой архивный файл, содержащий несколько процедур или модулей данных, которые присутствуют в памяти во время работы программы и одновременно могут быть связаны с несколькими процессами. Стандартная библиотека С и большинство сетевых программ являются библиотеками коллективного доступа.

UNIX поддерживает только неявную компоновку, поэтому библиотека коллективного доступа состоит из двух частей: главной библиотеки (host library), которая статически скомпонована с исполняемым файлом, и целевой библиотеки (target library), которая вызывается во время работы программы. Несмотря на некоторые различия в деталях, по существу эта концепция соответствует концепции DLL.

Время компоновки и динамическое перераспределение памяти || Оглавление || Краткое содержание главы7