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

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

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

Реализации

Microsoft Windows

Динамически подключаемая библиотека (англ. Dynamic-link library, DLL) — реализация концепции разделяемых библиотек в операционных системах Microsoft Windows и OS/2, созданная Microsoft. Обычно такие библиотеки имеют расширения файлов DLL, OCX (для библиотек, содержащих элементы ActiveX), или DRV (для устаревших системных драйверов). Форматы файлов DLL совпадают с таковыми для файлов Windows EXE: это PE для 32-битных и 64-битных версий Windows и NE для 16-битных версий. Как и EXE-файлы, DLL могут содержать машинный код, данные и ресурсы Windows, в любом сочетании.

Файлы с тем же форматом, что и у DLL, но с другими расширениями и зачастую содержащие только секции ресурсов, называются ресурсными DLL. Примеры подобных библиотек — мультиязыковые пользовательские интерфейсные библиотеки с расширением MUI, библиотеки иконок (ICL), а также файлы шрифтов с расширениями FON и FOT[1].

Unix-подобные системы, использующие ELF, и системы на базе Darwin

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

Когда исполняемый файл загружается, ядро читает из него путь к динамическому компоновщику и пытается загрузить и запустить этот дополнительный исполняемый файл; если по какой-либо причине это не удаётся (например, файл не найден), выполнение исходного исполняемого файла также невозможно. Затем динамический компоновщик загружает начальный образ исполняемого файла и все разделяемые библиотеки, от которых он зависит, и запускает программу. Таким образом, путь к динамическому компоновщику — часть интерфейса двоичного приложения операционной системы.

Системы, использующие ELF

В Unix-подобных системах, где для исполняемых образов и динамических библиотек используется формат ELF, например, Solaris, 64-битные версии HP-UX, Linux, FreeBSD, NetBSD, OpenBSD и DragonFly BSD, путь к динамическому компоновщику встраивается при компоновке в секцию .interp сегмента PT_INTERP исполняемого файла. В этих системах разделяемые библиотеки имеют обычно суффикс .so (shared object).

Динамический компоновщик может быть настроен на изменение своего поведения во время выполнения или компоновки программы, например, посредством переменных окружения LD_LIBRARY_PATH и LD_PRELOAD, которые позволяют изменять пути поиска библиотек и принудительно загружать определённые библиотеки[2].[3][4] Например, zlibc (также известная как uncompress.so), используя LD_PRELOAD, позволяет прозрачно распаковывать (декомпрессировать) файлы при их чтении, что, в частности, позволяет обрабатывать сжатые файлы как обычные на системах BSD и Linux. Этот механизм достаточно гибкий и может быть адаптирован для иного или дополнительного обработки данных во время чтения файла процессом пользователя.

macOS и iOS

В операционной системе Darwin компании Apple, а также в построенных на её основе macOS и iOS, путь к динамическому компоновщику встраивается при компоновке в одну из команд загрузки формата Mach-O. В этих системах разделяемые библиотеки отмечаются либо суффиксом .dylib, либо помещаются в bundles для соответствующих фреймворков.

Динамический компоновщик не только связывает исполняемый файл с разделяемыми библиотеками, но и размещает машинные инструкции по заранее определённым адресам памяти, известным программе при компоновке. Исполняемый файл может обращаться к компоновщику, выполняя специфические вызовы/переходы по этим адресам. Программы для macOS и iOS часто взаимодействуют с динамическим компоновщиком уже во время исполнения, и иногда могут загружать дополнительные библиотеки и разрешать новые символы спустя значительное время после запуска. Это связано с особенностями Cocoa, Cocoa Touch и Objective-C, на которых основаны основные API этих платформ.

Некоторые параметры поведения динамического компоновщика можно скорректировать, но, в отличие от других UNIX-подобных систем, они носят рекомендательный характер и могут игнорироваться. Пример — использование переменных среды DYLD_FRAMEWORK_PATH и DYLD_PRINT_LIBRARIES. Первая определяет пути поиска библиотек фреймворков, вторая выводит список библиотек, загружаемых во время работы программы. Динамический компоновщик macOS распространяется как открытое ПО в составе Darwin.

Unix-подобные системы на основе XCOFF

В Unix-подобных операционных системах, использующих XCOFF, таких как AIX, для динамически загружаемых разделяемых библиотек используется суффикс .a, как и у статических библиотек.

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

OS/360 и последующие

В IBM OS/360 и её последователях динамическое связывание из программ на ассемблере обычно реализуется с помощью макроинструкции LINK, которая вызывает команду режима супервизора для активации системных процедур загрузки необходимых библиотечных модулей. Модули могут находиться в библиотеках, определённых с помощью управляющих карт "STEPLIB" или "JOBLIB" — только для текущего исполнения, в библиотеке из LINKLIST в PARMLIB (задаётся при запуске системы) либо в области "link pack area" с резидентными переентерабельными модулями, загружаемыми при старте ОС.

Multics

В операционной системе Multics все файлы, включая исполняемые, реализованы как сегменты. При обращении к подпрограмме, не входящей в текущий сегмент, система находит целевой сегмент (в памяти или на диске) и подключает его к адресному пространству процесса. Динамическое связывание является стандартным способом работы, а статическая компоновка (через binder) — исключение.

Производительность

Динамическое связывание, как правило, медленнее (требует больше центрального процессорного времени), чем компоновка во время компиляции[5], поскольку этот процесс выполняется при каждом запуске программы. Однако, время загрузки можно уменьшить с помощью "ленивого связывания" — метода, при котором вызовы к функциям библиотеки связываются непосредственно с реализацией только при первом обращении; следствием этого является удлинение времени первого вызова такой функции.

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

Примечания

  1. Microsoft Corporation. Creating a Resource-Only DLL (англ.). Microsoft Developer Network Library. Дата обращения: 1 июня 2024.
  2. ZLIBC — Transparent access to compressed files (англ.). Дата обращения: 1 июня 2024. Архивировано 4 июня 2000 года.
  3. uncompress.so (англ.). delorie.com. Дата обращения: 1 июня 2024.
  4. zlibc.conf (англ.). delorie.com. Дата обращения: 1 июня 2024.
  5. Jiang, Xuxian Operating Systems Principles: Linking and Loading (англ.). North Carolina State University (2009). Дата обращения: 1 июня 2024.
  6. Jones, M. Anatomy of Linux dynamic libraries (англ.). IBM (28 августа 2008). Дата обращения: 1 июня 2024.
  7. Sivilotti, Paul Dynamic Linking and Loading (англ.). Ohio State University (август 2012). Дата обращения: 1 июня 2024.

Литература