Введение
Библиотека кода, предназначенная для совместного использования несколькими программами.
Общая библиотека или разделяемый объект — это компьютерный файл, содержащий исполняемый код, предназначенный для использования несколькими компьютерными программами или другими библиотеками во время выполнения. При запуске программы, настроенной на использование общей библиотеки, операционная система загружает эту библиотеку из файла (отличного от исполняемого файла программы) в память либо во время загрузки, либо во время выполнения. В отличие от этого, программа может быть монолитной, то есть содержать исполняемый код библиотеки непосредственно в своем исполняемом файле, но такой встроенный библиотечный код недоступен другим программам. Общие библиотеки могут быть статически связаны во время компиляции, что означает, что ссылки на библиотеку разрешаются и для библиотеки выделяется память при создании исполняемого файла. Однако чаще связывание общих библиотек откладывается до момента их загрузки. Большинство современных операционных систем используют единый формат как для общих библиотек, так и для исполняемых файлов. Это дает два основных преимущества: во-первых, требуется только один загрузчик (разработка и поддержка единого загрузчика считается оправданной, несмотря на дополнительную сложность); во-вторых, исполняемый файл может использоваться как общая библиотека (при наличии таблицы символов). Примеры форматов файлов, используемых как для общих библиотек, так и для исполняемых файлов, включают ELF, Mach-O и PE. В некоторых старых средах, таких как 16-битная Windows или MPE для HP 3000, в коде общей библиотеки разрешалось использовать только локальные переменные (стековые данные) или на код библиотеки накладывались другие существенные ограничения.
Обмен памятью
Код библиотеки может совместно использоваться несколькими процессами как в памяти, так и на диске. Если используется виртуальная память, процессы будут выполнять одну и ту же физическую страницу оперативной памяти, отображенную в различные адресные пространства процессов. Это дает определенные преимущества. Например, в системе OpenStep приложения часто занимали всего несколько сотен килобайт и быстро загружались; большая часть их кода находилась в библиотеках, которые уже были загружены операционной системой для других целей. Программы могут обеспечивать совместное использование оперативной памяти, используя позиционно-независимый код, как в Unix, что приводит к сложной, но гибкой архитектуре, или используя общие виртуальные адреса, как в Windows и OS/2. Эти системы, различными способами, например, предварительным отображением адресного пространства и резервированием слотов для каждой общей библиотеки, обеспечивают высокую вероятность совместного использования кода. Третья альтернатива – одноуровневое хранилище, использованное в IBM System/38 и его последующих версиях. Оно позволяет использовать код, зависящий от позиции, но не накладывает существенных ограничений на размещение кода или способы его совместного использования. В некоторых случаях различные версии общих библиотек могут вызывать проблемы, особенно когда библиотеки разных версий имеют одинаковое имя файла, а различным приложениям, установленным в системе, требуется конкретная версия. Такой сценарий известен как "ад DLL" (DLL hell), названный по аналогии с файлами DLL в Windows и OS/2. Большинство современных операционных систем, появившихся после 2001 года, имеют средства для устранения подобных ситуаций или используют специфичные для приложений "приватные" библиотеки.
Динамическая связь
Динамическая связь, или поздняя привязка, – это привязка, выполняемая во время загрузки программы (время загрузки) или выполнения (время выполнения), а не при создании исполняемого файла. Динамически подключаемая библиотека (динамическая библиотека связей, или DLL, в Windows и OS/2; разделяемый образ в OpenVMS; динамический разделяемый объект, или DSO, в Unix-подобных системах) – это библиотека, предназначенная для динамической привязки. При создании исполняемого файла компоновщик выполняет лишь минимальный объем работы: он только фиксирует, какие библиотечные подпрограммы необходимы программе, а также индексные имена или номера этих подпрограмм в библиотеке. Основная часть работы по привязке выполняется в момент загрузки приложения (время загрузки) или во время его выполнения (время выполнения). Как правило, необходимая программа привязки, называемая «динамическим компоновщиком» или «загрузчиком-компоновщиком», является частью базовой операционной системы. (Однако возможно, и не слишком сложно, написать программу, использующую динамическую привязку и включающую свой собственный динамический компоновщик, даже для операционной системы, которая сама не предоставляет поддержки динамической привязки.) Динамическую привязку впервые разработали программисты в операционной системе Multics, начиная с 1964 года, и в MTS (Michigan Terminal System), созданной в конце 1960-х годов.
Оптимизация
Поскольку общие библиотеки на большинстве систем меняются нечасто, системы могут вычислить вероятный адрес загрузки для каждой общей библиотеки в системе до того, как она потребуется, и сохранить эту информацию в самих библиотеках и исполняемых файлах. Если каждая загружаемая общая библиотека прошла через этот процесс, то каждая будет загружаться по заранее определенному адресу, что ускоряет процесс динамической линковки. Эта оптимизация известна как prebinding на macOS и prelinking на Linux. IBM z/VM использует аналогичную технику, называемую "Discontinuous Saved Segments" (DCSS). К недостаткам этой техники относятся время, необходимое для предварительного вычисления этих адресов при каждом изменении общих библиотек, невозможность использования рандомизации размещения адресного пространства и требование достаточного объема виртуального адресного пространства для работы (проблема, которая будет решена с переходом на 64-битные архитектуры, по крайней мере, в настоящее время).
Определение библиотеки во время выполнения
Загрузчики общих библиотек значительно различаются по функциональности. Некоторые зависят от исполняемого файла, хранящего явные пути к библиотекам. Любое изменение в имени библиотеки или структуре файловой системы приведет к сбою этих систем. Чаще всего в исполняемом файле сохраняется только имя библиотеки (а не путь), а операционная система предоставляет способ поиска библиотеки на диске, основанный на определенном алгоритме. Если общая библиотека, от которой зависит исполняемый файл, удалена, перемещена или переименована, или если несовместимая версия библиотеки скопирована в место, которое находится выше в порядке поиска, исполняемый файл не сможет загрузиться. Это явление известно как "ад зависимостей" и существует на многих платформах. (Безымянный) вариант Windows обычно известен как "DLL hell". Эта проблема не возникает, если каждая версия каждой библиотеки имеет уникальный идентификатор, а каждая программа ссылается на библиотеки только по их полным уникальным идентификаторам. Проблемы с "DLL hell" в ранних версиях Windows возникли из-за использования только имен библиотек, которые не гарантировали уникальность, для разрешения динамических связей в программах. (Чтобы избежать проблем с "DLL hell", более поздние версии Windows в значительной степени полагаются на возможность установки программ приватных DLL-файлов – по сути, частичный отказ от использования общих библиотек – а также на механизмы, предотвращающие замену общих системных DLL-файлов более старыми версиями).
Microsoft Windows
Microsoft Windows проверяет реестр, чтобы определить правильное место для загрузки DLL, реализующих объекты COM, но для других DLL проверяет каталоги в заданном порядке. Сначала Windows проверяет каталог, из которого загружена программа. Приложения, использующие собственные DLL, разработанные для .NET Framework (с 2002 года), также проверяют Global Assembly Cache как основное хранилище общих DLL-файлов, чтобы избежать проблем, связанных с "DLL-адом".
ОткрытьStep
OpenStep использовал более гибкую систему, собирая список библиотек из нескольких известных мест (аналогично концепции PATH) при первом запуске системы. Перемещение библиотек не вызывает абсолютно никаких проблем, хотя пользователям требуется некоторое время при первом запуске системы.
Системы типа Unix
Большинство Unix-подобных систем имеют "путь поиска", определяющий каталоги файловой системы, в которых производится поиск динамических библиотек. В некоторых системах путь по умолчанию задается в файле конфигурации, в других он жестко прописан в динамическом загрузчике. Некоторые форматы исполняемых файлов могут указывать дополнительные каталоги для поиска библиотек для конкретной программы. Обычно это можно переопределить с помощью переменной окружения, однако для программ с установленными битами setuid и setgid эта возможность отключена, чтобы пользователь не мог заставить такую программу выполнять произвольный код с правами root. Разработчикам библиотек рекомендуется размещать свои динамические библиотеки в каталогах, входящих в путь поиска по умолчанию. Однако это может усложнить установку новых библиотек, а эти "известные" места быстро заполняются растущим числом библиотечных файлов, что затрудняет управление ими.
Динамическая нагрузка
Динамическая загрузка, являющаяся подмножеством динамической линковки, предполагает загрузку и выгрузку динамически подключаемой библиотеки во время выполнения по запросу. Этот запрос может быть сделан как неявно, так и явно. Неявные запросы возникают, когда компилятор или статический компоновщик добавляет ссылки на библиотеки, включающие пути к файлам или только имена файлов. Явные запросы делаются, когда приложения напрямую обращаются к API операционной системы. Большинство операционных систем, поддерживающих динамически подключаемые библиотеки, также поддерживают динамическую загрузку этих библиотек через API загрузчика во время выполнения. Например, Microsoft Windows использует API-функции LoadLibrary, LoadLibraryEx, FreeLibrary и GetProcAddress совместно с динамическими библиотеками Microsoft; системы на базе POSIX, включая большинство UNIX и UNIX-подобных систем, используют dlopen, dlclose и dlsym. Некоторые среды разработки автоматизируют этот процесс.