Динамический компоновщик
Динамический компоновщик — это часть операционной системы, отвечающая за загрузку и связывание необходимых разделяемых библиотек при запуске исполняемой программы. Для этого он копирует содержимое библиотек из постоянного хранилища в оперативную память, заполняет таблицы переходов и перемещает указатели. Особенности функционирования и реализации динамического компоновщика зависят от конкретной операционной системы и формата исполняемых файлов.
Компоновка часто рассматривается как процесс, выполняющийся при компиляции программы, однако динамический компоновщик — это специальный компонент системы, который загружает внешние разделяемые библиотеки в работающий процесс и динамически связывает их с ним. Такой подход называют динамическим связыванием или поздним связыванием.
Реализации
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].
Примечания
- ↑ Microsoft Corporation. Creating a Resource-Only DLL (англ.). Microsoft Developer Network Library. Дата обращения: 1 июня 2024.
- ↑ ZLIBC — Transparent access to compressed files (англ.). Дата обращения: 1 июня 2024. Архивировано 4 июня 2000 года.
- ↑ uncompress.so (англ.). delorie.com. Дата обращения: 1 июня 2024.
- ↑ zlibc.conf (англ.). delorie.com. Дата обращения: 1 июня 2024.
- ↑ Jiang, Xuxian Operating Systems Principles: Linking and Loading (англ.). North Carolina State University (2009). Дата обращения: 1 июня 2024.
- ↑ Jones, M. Anatomy of Linux dynamic libraries (англ.). IBM (28 августа 2008). Дата обращения: 1 июня 2024.
- ↑ Sivilotti, Paul Dynamic Linking and Loading (англ.). Ohio State University (август 2012). Дата обращения: 1 июня 2024.
Литература
- Linkers and Loaders : [англ.]. — 1. — Сан-Франциско, США : Morgan Kaufmann, 2000. — ISBN 1-55860-496-0.
Ссылки
- Динамическая компоновка и загрузка, IECC.com
- Динамическая компоновка в Linux и Windows, часть первая, Symantec.com
- Анатомия динамических библиотек Linux, IBM.com