Shared library что это
Существует набор базовых действий, которые практически любая программа выполняет одинаково – открытие файла, чтение и запись данных и тому подобное. Разделяемые библиотеки предназначены для того, чтобы предоставить прикладным программам готовые интерфейсы функций для выполнения каких-либо более-менее стандартных действий. Разделяемая библиотека, как понятно из названия, может использоваться множеством программ. В настоящий момент стандартным форматом для разделяемых библиотек в Linux является ELF (Executable Linked Format).
Каждый файл ELF имеет заголовок, в котором описывается, какие секции содержит этот файл. Секции объединяют однотипные данные, и их детальное описание можно прочитать в справочном руководстве ( man elf ). Мы же выделим следующую информацию: каждая библиотека содержит список имен переменных и функций, которые она содержит и предоставляет другим (экспортирует) и список переменных и функций, которые необходимо взять в других библиотеках, а также секции инициализации и деинициализации. Экспортируемые и импортируемые объекты (переменные и функции) называют символами библиотеки.
Большинство исполняемых файлов программ также имеют формат ELF, и на самом деле отличаются от библиотек в основном тем, что не имеют экспортируемых функций. Загрузчик ELF (он же dl, dynamic linker и dynamic loader) умеет загружать в память код ELF-файла, анализировать его структуру для определения списков экспортируемых и импортируемых символов и загружать необходимые для работы программы библиотеки.
Когда пользователь пытается запустить какую–либо программу, первым начинает работу загрузчик ELF. Он загружает в память процесса бинарный файл и выделяет, какие символы и из каких библиотек необходимо догрузить в память. После дозагрузки каждой библиотеки загрузчик связывает символы (проставляет реальные адреса) из загруженной библиотеки и повторяет цикл анализа на предмет того, какую библиотеку нужно загрузить. Когда все нужные библиотеки загружены, загрузчик передает управление коду инициализации каждой из загруженных библиотек в порядке, обратном загрузке, после чего передает управление коду программы. По завершении программы загрузчик снова “проходится” по всем библиотекам и вызывает их функции деинициализации. Если на этапе загрузки какой – либо библиотеке возникает ошибка, загрузчик сообщит об этом пользователю. Наиболее типичные ошибки dl – это не найденный файл библиотеки или неразрешимый символ (символ не был найден в библиотеке, в которой ожидался).
Вполне естественно, что загрузчик ищет библиотеки не по всей файловой системе, а только в определенных каталогах. Это каталоги /lib , /usr/lib и те, которые были перечислены системным администратором в файле /etc/ld.so.conf . Уточним, что этот файл на самом деле используется только системной утилитой ldconfig , сам же загрузчик использует кэш-файл /etc/ld.so.cache . Обновить этот кэш-файл можно путем простого запуска ldconfig без параметров. Следствием этого является то, что если вы установили в систему новые библиотеки, не мешает вызвать ldconfig .
В некоторых дистрибутивах есть возможность включать в ld.so.conf дополнительные файлы без его изменения. Для этого в ld.so.conf включается специальная строка вида:
include ld.so.conf.d/*.conf
Это приводит к тому, что каталоги, перечисленные в файлах с расширением conf , расположенных в каталоге /etc/ld.so.conf.d будут использованы для поиска разделяемых библиотек:
$cat /etc/ld.so.confinclude ld.so.conf.d/*.conf /usr/lib/mysql /usr/X11R6/lib /usr/lib/qt-3.3/lib$ls /etc/ld.so.conf.d/oracle$cat /etc/ld.so.conf.d/oracle/opt/oracle/9i/lib
Нередко возникает ситуация, когда пользователю необходимо запустить какую-либо программу, которая не находится в каталогах, описанных в /etc/ld.so.conf . В таких ситуациях можно воспользоваться специальным “люком”, оставленным разработчиками dl специально для таких случаев: дело в том, что кроме загрузки библиотек с использованием данных из ld.so.cache загрузчик проверяет факт наличия библиотеки с указанным именем в каталогах, перечисленных в переменной среды LD_LIBRARY_PATH .
Разработчики часто используют еще одну возможность ld: если файл некоторой разделяемой библиотеки указан в переменной LD_PRELOAD , эта библиотека принудительно загружается и ее символы считаются более “приоритетными” и перекрывают одноименные символы, если таковые существуют в других библиотеках, загружаемых ld при запуске на выполнение бинарного файла ELF.
Попробуем рассмотреть примеры использования указанных возможностей dl: пусть есть некоторый программный продукт, в состав которого кроме собственно исполняемых программ входят разделяемые библиотеки (например, таковы практически все продукты, разработанные с помощью Borland Kylix). Если мы установим такой пакет, например, в /opt/program , его исполняемые файлы в /opt/program/bin а разделяемые библиотеки в /opt/program/lib , то программа, скорее всего, не будет запускаться, поскольку не сможет загрузить необходимых библиотек. Для того, чтобы программы пакета начали запускаться, мы должны “объяснить” ld где именно искать библиотеки. Рассмотрим возможные способы, которыми мы можем воздействовать на ld чтобы добиться нужного нам результата.
Первый способ – указать каталог с библиотеками перед запуском программы и уже затем запустить программу (ld воспользуется значением переменной для того, чтобы попытаться найти библиотеки по указанному пути):
$export LD_LIBRARY_PATH=/opt/program/lib$/opt/program/bin/filename
Второй способ – добавить каталог /opt/program/lib в файл /etc/ld.so.conf и запустить ldconfig , решив проблему с невозможностью нахождения этих библиотек для всех программ сразу:
$su -#echo /opt/program/lib >>/etc/ld.so.conf#ldconfig#exit$/opr/program/bin/filename
Можно также воспользоваться возможностью принудительной загрузки тех библиотек, которые необходимы программе для запуска:
$export LD_PRELOAD=/opt/program/lib/*$ /opr/program/bin/filename
Большая часть кода разделяемых библиотек находится в кэше и становится доступна процессам через отображение файла в память. Это отображение делается с правами доступа “только чтение”, что защищает код библиотек от переписывания его неправильно работающими или просто злонамеренными программами.
| В начало → Linux не для идиотов → LD, Shared Library, SO и много страшных слов |
Создание разделяемых библиотек
В наши дни в условиях постоянного развития процесс создания программ является плодом эволюции навыков и опыта, выработанного программистами и разработчиками.
Создание исходного кода на языке высокого уровня в текстовом редакторе. Если мы попытаемся уместить очень большую программу в один файл, то ею будет трудно управлять. По этой причине исходный код разделяется на функциональные модули, которые созданы из одного или более файлов исходного кода. Исходный код в этих модулях вовсе не обязательно может быть написан на одном и том же языке, поскольку некоторые языки лучше других подходят для решения конкретной задачи.
После создания файлов с исходным кодом программы, они дожны быть странслированы в блоки кода, которые может исполнять машина. Обычно об этом коде говорят как об объектном коде . Этот код выполняет те же самые действия, что и исходный код, за исключением того, что он написан на особом языке, который может непосредственно выполняться машиной. Процесс трансляции исходного кода в объектный код известен как компиляция . Компиляция выполняется по частям и за один раз компилируется (в зависимости от компилятора) часть программы и обычно один или несколько файлов. Скомпилированный объектный код содержит программу, подпрограмму, переменные и т.д. — части программ, которые были странслированы и которые готовы к следующему шагу.
После того, как были созданы все файлы программы с машинным кодом, их необходимо соединить или скомпоновать при помощи операции, выполняемой специальной утилитой, называемой компоновщик . На этой стадии все ссылки, которые код модуля делает на код, принадлежащий другому модулю, «разрешаются» (такие, как вызов подпрограммы или ссылки на переменные, принадлежащие или определеные в другом модуле). Результирующим продуктом является программа, которую можно непосредственно загружать и исполнять.
2.- Краткая история процесса создания программы.
Процесс создания программы постоянное развивается, это необходимо для достижения наиболее эффективного исполнения программ или лучшего использования системных ресурсов.
В начале программы писались непосредственно в машинном коде. Позднее стало ясно, что можно писать программы на языке более высокого уровня, так как последующую трансляцию в машинный код можно автоматизировать благодаря систематической природе трансляции. Это увеличило производительность программ.
После того, как научились компилировать программы (я упростил эволюцию компиляции, на самом деле это был очень трудный шаг, поскольку это очень сложный процесс), процесс создания программ стал состоять из создания файла с исходным кодом программы, его компиляции и в конце концов исполнения.
Скоро было замечено, что процесс компиляции очень трудоемок и отнимает очень много ресурсов, включая машинное время, и что многие функции, входящие в эти программы использовались еще и еще раз в различных программах. Более того, когда кто-то изменял часть программы, компиляция вставленного кода означала новую компиляцию всего исходного кода, включая новую трансляцию всего неизменившегося кода.
По этой причине стали применять модульную компиляцию. Ее смысл заключался в разделении программы на, с одной стороны, главную программу и, с другой стороны, те функции, которые часто использовались еще и еще раз и которые уже были скомпилированы и сохранены в особом месте (мы будем называть это предшественником библиотеки).
Тогда стало возможным разрабатывать программы, которые поддерживают эти функции без затраты дополнительных усилий по созданию их кода еще и еще раз. Даже тогда, процесс был сложен из-за того, что при компоновке программ было необходимо объединять все части и они должны были быть известны программисту (это внесло дополнительные затраты на проверку возможности использования известной функции, которая использует/требует другие неизвестные функции).
3.- Что такое библиотека?
Описанная выше проблема привела к созданию библиотек. Это ничто иное, как особый вид файла (если быть точным, то архив, tar(1) или cpio(1) ) с особыми параметрами, формат которых компоновщик понимает, и когда мы указываем ему библиотечный архив, КОМПОНОВЩИК ВЫБИРАЕТ ТОЛЬКО ТЕ МОДУЛИ, КОТОРЫЕ НУЖНЫ ПРОГРАММЕ и исключает все остальное. Появилось новое преимущество. Теперь можно было разрабатывать программы, которые используют большие библиотеки функций, и программисту вовсе не обязательно знать все зависимости функций в этой библиотеке.
Те библиотеки, которые мы пока обсудили, не пошли в развитии дальше. К ним только добавился файл, часто находящийся в начале архива, содержащий описания модулей и идентификаторы, которые компоновщик должен разрешать без чтения всей библиотеки (и тем самым устраняя необходимость в чтении библиотеки по нескольку раз). Этот процесс (добавление таблицы символов в архив библиотеки) в Линуксе выполняется командой ranlib(1). Описанные пока библиотеки известны как СТАТИЧЕСКИЕ БИБЛИОТЕКИ.
Прогресс произошел после появления первой многозадачной системы: разделение кода . Если в одной и той же системе были запущены две копии одного и того же кода, было бы желательно, чтобы оба процесса могли совместно использовать один код, так как обычно программа не изменяет свой собственный код. Эта идея устраняет необходимость в размещении в памяти нескольких копий, что освобождает большое количество памяти в огромных многопользовательских системах.
Развивая это последнее нововведение на один шаг дальше, кто-то (я не знаю, кто это был, но идея была отличная 😉 подумал, что очень часто многие программы используют одну и ту же библиотеку, но, будучи разными программами, части используемой библиотеки вовсе не обязательно были теми же частями, используемыми другой программой. Более того, основной код был другой (они же разные программы), поэтому их тексты не были разделяемыми. Что же, наш герой подумал, что если разные программы, использующие одну и ту же библиотеку, могли делить между собой эту библиотеку, то мы бы смогли выгадать немного памяти. Теперь разные программы, имея разный текст, работают с одним и тем же кодом библиотеки.
Однако теперь процесс стал сложнее. Исполняемая программа не полностью скомпонована, но разрешение ссылок на идентификаторы библиотек откладываются до момента загрузки программы. Компоновщик (в случае Линукса это ld(1)) распознает вызовы разделяемых библиотек и не включает их код в программу. Сама система, ядро, при исполнении exec() распознает запуск программы, использующей разделяемые библиотеки и исполняет специальный код, загружающий разделяемые библиотеки (выделяя разделяемую память для их текста, выделяя закрытую память для значений библиотеки и т.д.). Теперь этот процесс выполняется при загрузке исполняемого файла и вся процедура сильно усложнилась.
Конечно же, когда компоновщик встречается с обычной библиотекой, он продолжает вести себя также, как и раньше.
Разделяемая библиотека не является архивом, содержащим объектный код, скорее это файл, содержащий сам объектный код. Во время компоновки программы с разделяемой библиотекой, компоновщик не исследует библиотеку, какие модули надо добавлять в программу, а какие нет. Он только удостоверяется, что неразрешенные ссылки становятся разрешенными, и определяет, что необходимо добавить в список при включении библиотеки. Можно сделать архивную ar(1) библиотеку всех разделяемых библиотек, но часто это не делается, так как разделяемые библиотеки часто являются результатом компоновки различных модулей, поэтому библиотека понадобится позднее, во время исполнения. Возможно, название разделяемая библиотека не является самым уместным и было бы точнее назвать ее разделяемым объектом (тем не менее, поскольку нас могут не понять, мы не используем этот термин).
4.- Виды библиотек.
Как мы уже говорили, в Линукс существует два вида библиотек: статические и разделяемые. Статические библиотеки являются набором модулей, объединенных в архив при помощи утилиты ar(1) и проиндексированных утилитой ranlib(1). Эти модули часто хранятся в файле с окончанием .a (я не использую термин расширение потому, что в Линуксе концепция расширения файла не используется). Компоновщик распознает окончание файла .a и начинает искать модули, как если бы это была статическая библиотека, выбирает и добавляет в программу те модули, которые разрешают неразрешенные ссылки.
В отличие от статических, разделяемые библиотеки являются не архивами, а перемещаемыми объектами, обозначенными особым кодом (который обозначает их как разделяемые библиотеки). Компоновщик ld(1), как уже говорилось, не добавляет модули в программный код, а выбирает идентификаторы, предоставляемые библиотекой как разрешенные, добавляет те, которые необходимы самой библиотеке, и продолжает работу, не добавив никакого кода, считая, что требуемый код уже был добавлен в основной код. Компоновщик ld(1) распознает разделяемые библиотеки по окончанию .so (не .so.xxx.yyy, мы обсудим этот вопрос позднее).
5.- Процесс компоновки в Линукс.
Каждая программа состоит из объектных модулей, скомпонованных в исполняемый файл. Эту операцию выполняет используемый в Линуксе компоновщик ld(1).
ld(1) поддерживает различные опции, которые изменяют его поведение, но мы ограничимся здесь только теми, которые связаны с использованием библиотек в общем. ld(1) вызывается не непосредственно пользователем, а самим компилятором gcc(1) на его завершающей стадии. Небольшие познания о его modus operandis помогут нам понять способ использования библиотек в Линукс.
Для правильной работы ld(1) требуется список объектов, которые необходимо скомпоновать с программой. Эти объекты можно задавать в любом порядке (*) пока мы выполняем указанное выше соглашение, как уже говорилось, разделяемая библиотека распознается по окончанию .so (а не .so.xx.yy) и статическая библиотека по .a (и конечно же, простыми объектными файлами являются те, чье имя заканчивается на .o).
(*) Это не совсем верно. ld(1) включает только те модули, которые разрешают ссылки на момент включения библиотеки, поэтому в модуле, влкюченном позднее, все равно может быть ссылка, которая позднее, поскольку это не проявляется на момент включения этой библиотеки, может вызвать команду на влкючение необходимых библиотек.
С другой стороны, ld(1) позволяет включать стандартные библиотекии благодаря опциям -l и -L.
Но. Что мы понимаем под стандартной библиотекой, в чем разница? Никакой. Только то, что ld(1) ищет стандартные библиотеки в определенных местах, тогда как те, что описаны в списке параметров как объекты, ищутся по именам их файлов.
По умолчаиню библиотеки ищутся в каталогах /lib и /usr/lib (хотя я слышал, что в зависимости от версии/реализации ld(1) , могут быть дополнительные каталоги). -L позволяет нам добавить каталоги к тем, которые просматриваются при обычном поиске библиотек. Она используется заданием -L каталог для каждого каталога , который мы хотим добавить. Стандартные библиотеки указываются опцией -l Имя (где Имя указывает библиотеку, которую необходимо загрузить) и ld(1) будет искать, в соответствующем порядке, в соответствующих каталогах файл с именем libИмя.so. Если он не будет найден, то будет сделана попытка найти libИмя.a , его статическую версию.
Если ld(1) находит файл libИмя.so , он скомпонует ее как разделяемую библиотеку, тогда как если он найдет файл libИмя.a, он скомпонует модули, полученные из нее, если они разрешают любые неразрешенные ссылки.
6.- Динамическая компоновка и загрузка разделяемых библиотек
Динамическая компоновка выполняется в момент загрузки исполняемого файла особым модулем (на самом деле этот особый модуль является самой разделяемой библиотекой), называемым /lib/ld-linux.so .
На самом деле существуют два модуля для компоновки динамических библиотек: /lib/ld.so (для библиотек, использующих старый формат a.out) и /lib/ld-linux.so (для библиотек, использующих новый формат ELF).
Особенность этих модулей заключается в том, что они должнны загружаться каждый раз, когда происходит динамическая компоновка программ. Их имена стандартны (причина в том, что их нельзя перемещать из каталога /lib , а также нельзя изменять их имена). Если мы заменим имя /etc/ld-linux.so , то мы автоматически остановим использование любой программы, использующей разделяемые библиотеки, поскольку этот модуль отвечает за разрешение всех ссылок, не разрешенных во время исполнения.
Последнему модулю помогает существование файла /etc/ld.so.cache , в котором для каждой библиотеки указываются наиболее подходящий исполняемый файл, содержащий эту библиотеку. Мы вернемся к этой теме позднее.
7.- soname. Версии исполняемых библиотек. Совместимость.
Мы подошли к наиболее запутанной теме, связанной с разделяемыми библиотеками: их версии.
Часто встречается сообщение ‘library libX11.so.3 not found,’ оставляющее нас в растеряности: обладая библиотекой libX11.so.6 мы неспособны ничего сделать. Как стало возможным, что ld.so(8) признает взаимозаменяемыми библиотеки libpepe.so.45.0.1 и libpepe.so.45.22.3 и не признает libpepe.so.46.22.3?
В Линукс (и во всех операционных системах, использующих формат ELF) библиотеки идентифицируются отличающей их последовательностью символов: soname.
soname включается в саму библиотеку и эта последовательность определяется при компоновке объектов, формирующих библиотеку. При создании разделяемой библиотеки, чтобы дать значение этой символьной строке необходимо передать ld(1) опцию ( -soname ).
Эта последовательность символов используется динамическим загрузчиком для идентификации разделяемой библиотеки, которую необходимо загрузить, и идентификации исполняемого файла. Это выглядит примерно так:
Ld-linux.so определяет, что программа требует библиотеку и определяет ее soname. Затем идет поиск имени в /etc/ld.so.cache и определяется имя файла, содержащего эту библиотеку. Далее запрошенное soname сравнивается с именем существующей библиотеки, и если они идентичны, то значит она нам и нужна! Если нет, то поиск будет продолжаться до тех пор, пока она не будет найдена, или, если она не будет найдена, будет выдано сообщение об ошибке.
По soname можно определить, подходит ли библиотека для загрузки, потому что ld-linux.so проверяет, совпадает ли требуемое soname с требуемым файлом. В случае различия мы можем получить знаменитое ‘libXXX.so.Y not found’ . Ищется именно soname и выдаваемая ошибка определяется soname.
Если мы поменяем имя библиотеки, может возникнуть большая путаница, при этом сама проблема останется. Но изменять soname тоже не очень хорошая идея, потому что в сообществе Линукс есть соглашение по назначению soname:
soname библиотекии, по умолчанию, должно идентифицировать соответствующую библиотеку и ИНТЕРФЕЙС этой библиотеки. Если мы внесем изменения в библиотеку, которые затрагивают только внутреннюю функциональность, но интерфейс останется неизменным (количество функциий, переменных, параметры функций), то две библиотеки будут взаимозаменяемыми и в целом мы скажем, что изменения были незначительными (обе библиотеки совместимы и мы можем заменить одну на другую). Если это происходит, то часто изменяется минорный номер (который не входит в состав soname) и библиотека может быть заменена без значительных проблем.
Однако, когда мы добавляем функции, убираем функции и в целом ИЗМЕНЯЕМ ИНТЕРФЕЙС библиотеки, то уже невозможно утверждать, что эта библиотека взаимозаменяема с предыдущей (например замена libX11.so.3 на libX11.so.6 является частью перехода с X11R5 на X11R6, при этом вводятся новые функциии и поэтому изменяется интерфейс). Переход с X11R6-v3.1.2 на X11R6-v3.1.3 вероятно не вызовет изменений в интерфейсе и у библиотеки останется то же soname — хотя, чтобы сохранить старую версию, нам потребуется дать ей другое имя (по этой причине номер версии завершает имя библиотеки, тогда как в soname задаются только мажорные номера).
8.- ldconfig(8)
Как мы уже говорили раньше, /etc/ld.so.cache позволяет ld-linux.so конвертировать soname файла, содержащегося в библиотеке. Это бинарный (для большей эффективности) файл, созданный утилитой ldconfig(8) .
ldconfig(8) создает для каждой динамической библиотеки, найденной в каталогах, указанных в /etc/ld.so.conf , символическую ссылку с именем библиотеки soname. В этом случае, когда ld.so хочет получить имя файла, то что он делает, это выбирает в списке каталогов файл с требуемым soname. И поэтому нет необходимости каждый раз запускать ldconfig(8) при добавлении библиотеки. Мы запускаем ldconfig только когда мы добавляем каталог к списку.
9.- Я хочу сделать динамическую библиотеку.
Загрузка программы осуществляется в несколько этапов; один на загрузку основной программы, остальные для каждой динамической библиотеки, которую использует эта программа (мы рассмотрим это для соответствующей динамической библиотеки, поскольку этот последний пункт перестал быть редкостью и становится преимуществом).
Разделяемая библиотека загружается в память полностью (а не только необходимые модули), поэтому, чтобы быть полезной, она должна использоваться полностью. Наихудшим примером использования динамической библиотеки является использование только одной функции, а 90% библиотеки вряд ли когда используется.
Хорошим примером динамической библиотеки является стандартная библиотека C (она используется всеми программами, написанными на C). В среднем используются все функции.
В статическую библиотеку вовсе не обязательно включать нечасто используемые функции; пока они содержатся в своем собственном модуле, они не будут скомпонованы ни с одной программой, которой они не требуются.
9.1.- Компиляция исходных кодов
Компиляция исходных кодов выполняется точно также, как и в случае с обычным исходным кодом, за исключением того, что для создания кода, который можно загружать в различных позициях в пространстве виртуальных адресов процесса, мы будем использовать опцию ‘-f PIC’ (позиционно-независимый код).
Этот шаг является фундаментальным, так как в статической библиотеке положение библиотечных объектов разрешается при компоновке, поэтому занимает фиксированное время. Выполнить этот шаг в старых исполняемых файлах a.out было невозможно, что приводило к размещению каждой разделяемой библиотеки в фиксированном положении в пространстве виртуальных адресов. И как следствие, в любой момент могли возникнуть конфликты, если программа хотела использовать две библиотеки и загружала их в перекрывающиеся области виртуальной памяти. Это означало, что вы были вынуждены вести список, в котором каждый, захотевший сделать библиотеку динамической, должен был объявить диапазон используемых адресов с тем, чтобы никто другой не мог им воспользоваться.
Как мы уже отмечали, регистрация динамической библиотеки в официальном списке не необходима, так как когда библиотека загружена, она размещается в позиции, определенной на данный момент, не смотря на тот факт, что код должен быть перемещаем.
9.2.- Компоновка объектов в библиотеку
После компиляции всех объектов, их необходимо скомпоновать, задав опцию, которая создает динамически загружаемый объект.
gcc -shared -o libИмя.so.xxx.yyy.zzz -Wl,-soname,libИмя.so.xxx
-shared .
Здесь компоновщику говорится, что в итоге он должен созадть разделяемую библиотеку и поэтому в выходном файле, соответствующем библиотеке, должен содержаться исполняемый код.
-o libИмя.so.xxx.yyy.zzz .
Это имя выходного файла. Вовсе не обязательно следовать соглашению по имени, но если мы хотим, чтобы эта библиотека стала стандартом для будущих разработок, то лучше им следовать.
-Wl,-soname,libИмя.so.xxx .
Опция -Wl говорит gcc(1) , что далее идут опции (разделенные запятыми), которые предназначены для компоновщика. Этот механизм используется gcc(1) для передачи опций ld(1) . В примере мы передаем компоновщику следующие опции:
-soname libИмя.so.xxx
9.3.- Установка библиотеки
Что ж, у нас уже есть соответствующий исполняемый файл. Теперь, чтобы его можно было использовать, его необходимо установить в соответствующее место.
Для компиляции программы, которая требует нашу новую библиотеку, необходимо использовать следующую команду:
gcc -o program libИмя.so.xxx.yyy.zzz
или, если библиотека была установлена в каталог (/usr/lib) , будет достаточно:
gcc -o программа -lИмя
Скопируйте библиотеку в каталог /lib или /usr/lib . Если вы решите скопировать ее в другое место (например в /usr/local/lib) , то вы не сможете быть уверенными, что при компоновке программ компоновщик ld(1) найдет ее автоматически.
Выполните ldconfig(1) для создания символьной ссылки libИмя.so.xxx.yyy.zzz на libИмя.so.xxx. На этом шаге нам станет известно, правильно ли мы выполнили все предыдущие шаги и распознается ли библиотека как динамическая. На этом шаге оказывается влияние только на загрузку библиотеки во время исполнения, а не на компоновку программ.
10.- Создание статической библиотеки
Если, с другой стороны, потребуется создать статическую библиотеку (или требуется две версии для того, чтобы можно было создавать статически скомпонованные копии), то необходимо выполнить следующее:
Замечание: Компоновщик при поиске библиотек сначала ищет файл с именем libИмя.so , и лишь затем libИмя.a. Если мы назовем обе этих библиотеки (статическую и динамическую версии) одним и тем же именем, в общем-то будет невозможно определить, какая из двух будет скомпонована в каждом случае (динамическая всегда компонуется первой, поскольку компоновщик обнаруживает ее первой).
По этой причине, если необходимо иметь две версии одной библиотеки, всегда рекомендуется называть статическую в виде libИмя_s.a , а динамическую libИмя.so . Тогда при компоновке нужно будет указать:
gcc -o program -lИмя_s
для компоновки со статической версией, тогда как для динамической:
gcc -o program -lИмя
10.1.- Компиляция исходного кода
Для компиляции исходного кода вовсе не обязательно принимать каких-либо особых мер. Точно также положение объектов решается на стадии компоновки, не обязательно компилировать с опцией -f PIC (хотя возможно продолжать ее использовать).
10.2.- Компоновка объектов в библиотеку
В случае статических библиотек компоновка не выполняется. Все объекты архивируются в библиотечный файл командой ar(1) . Далее, чтобы быстро разрешить символы желательно выполнить команду ranlib(1) над библиотекой. Хотя это и не обязательно, невыполнение этой команды может разкомпоновать модули в исполняемом файле потому, что при обработке модуля компоновщиком во время создания библиотеки не все непрямые зависимости между модулями разрешаются немедленно: скажем, модулю, находящемуся в конце архива, требуется другой модуль, находящийся в начале архива, это означает, что для разрешения всех ссылок требуется несколько проходов по одной и той же библиотеке.
10.3.- Установка библиотеки
Статические библиотеки желательно называть в формате libName.a только в том случае, если есть желание иметь только статические библиотеки. В случае двух видов библиотек я бы порекомендовал называть их libИмя_s.a , с тем, чтобы было легче различать, когда загружать статическую, а когда динамическую библиотеку.
Процесс компоновки позволяет ввести опцию -static. Эта опция управляет загрузкой модуля /lib/ld-linux.so , и не влияет на порядок поиска библиотек, поэтому если кто-то укажет -static и ld(1) найдет динамическую библиотеку, то он будет работать с ней (а не продолжать искать статическую библиотеку). Это приведет к ошибкам во время исполнения из-за вызовов процедур в библиотеке, которая не входит в состав исполняемого файла — модуль для автоматической динамической загрузки не скомпонован и поэтому процесс не может быть выполнен.
11.- Сравнение статической и динамической компоновки
Предположим, что мы хотим создать дистрибутив программы, которая использует библиотеку, которую мы можем распространять только включенную в программу статически, и ни в какой иной форме (примером этого случая являются приложения, созданные с использованием Motif).
Создать такую программу можно двумя способами. Первый заключается в создании статически скомпонованного исполняемого файла (используя только библиотеки .a и не используя динамического загрузчика). Этот вид программ загружается один раз и не требуют ни одной библиотеки из системы (даже /lib/ld-linux.so) . Однако у них есть недостаток — все необходимое нужно держать в одном бинарном файле и поэтому это обычно очень большие файлы. Вторым вариантом является создание динамически скомпонованной программы, то есть в среде, в котором наше приложение будет выполняться, должны быть все соответсвующие динамические библиотеки. Исполняемый файл может быть очень маленьким, хотя иногда невозможно иметь абсолютно все библиотеки (например есть люди, у которых нет Motif).
Существует третий вариант, смешанный, в котором некоторые библиотеки скомпонованны динамически, а остальные статически. В этом случае было бы логично выбрать конфликтующую библиотеку в ее статической форме и все остальные в их динамической форме. Этот вариант является очень распространенной формой дистрибутива программ.
Например, можно скомпилировать три различные версии программы следующим образом:
gcc -static -o program.static program.o -lm_s -lXm_s -lXt_s -lX11_s\ -lXmu_s -lXpm_s gcc -o program.dynamic program.o -lm -lXm -lXt -lX11 -lXmu -lXpm gcc -o program.mixed program.o -lm -lXm_s -lXt -lX11 -lXmu -lXpm
В третьем случае только библиотека Motif (-lXm_s) компонуется статически, а все остальные компонуются динамически. Та среда, в которой будет запускаться программа, должны обладать соответствующими версиями библиотек libm.so.xx libXt.so.xx libX11.so.xx libXmu.so.xx и libXpm.so.xx
Перевод на русский: Владимир Попов
This website is mantained by Miguel A Sepulveda .
Shared library что это
Этот фрагмент makefile добавляет цель install, то есть теперь можно выполнить команду make install и библиотека будет установлена в /usr/lib.
- Команда «install» копирует заданный файл библиотеки по заданному пути и создает символьную ссылку на него с именем, заданным в soname. Опция -m позволяет задать также права доступа для файла (644 — разрешение на запись и чтение для владельца файла, чтение для пользователей той же группы, чтение для остальных пользователей).
- Команда «sudo ln -sf» создает символьную ссылку с перезаписью существующей (если такая уже есть) на файл только что установлненой библиотеки с именем для компилятора (soname без версии). После этой команды в заданном каталоге присутствуют файлы библиотеки со всеми тремя ранее перечисленными именами.
- Команда «sudo ldconfig -n» перечитывает заданный каталог (опция -n) и обновляет кэш. После этого линкер и другие программы могут запрашивать библиотеку по имени (например, по soname). Можно вывести список всехъ загруженных библиотек с помощью команды «ldconfig -p».
- Далее в случае необходимости можно скопировать заголовочные файлы библиотеки.
- Цель «install» должна создаваться как phony-target (потому, что в результате ее выполнения не собирается новый файл).
C: создание и применение shared library в Linux
Библиотека – это файл, содержащий скопилированный код из нескольких объектных файлов в один файл библиотеки, который может содержать функции используемые другими программами.
Библиотеки могут быть статичными (static) и динамическими или разделяемыми (dynamic, shared).
Ниже – краткий пример создания и применения shared library на C в Linux.
Доступ к общей библиотеке может осуществляться по нескольким именам:
- имя, используемое “линкером” /usr/bin/ld (linker name), в виде слова lib + имя библиотеки + расширение .so , например – libpthread.so
- Полное имя (fully qualified name или soname), в виде lib + name + .so + версия (например – libpthread.so.1 )
- реальное имя – полное имя файла, содержащего версию библиотеки, в виде lib + имя + .so + версия + минорная версия и опционально – версия релиза (например – libpthread.so.1.1 )
Версия для общей бибилиотеки меняется в случае, когда изменения в коде этой бибилиотеки делают её несовместимой с предыдущими версиями, например – если из библиотеки была убрана какая-то функция ( libpthread.so.1 )
Минорная версия меняется, если изменения не затронули совметимость библиотеки, например – какой-то фикс в одной из функций. В таком случае версия останется прежней, а изменится только минорная часть ( libpthread.so.1.1 ).
Такое соглашение об именах версий библиотек позволяет существование разных версий одной библиотеки в одной системе.
Программа, которая будет линковаться с этой бибилиотекой, не будет привязана к определённому файлу с последней версией библиотеки. Вместо этого, после установки последней версии – все связанные программы будут использовать её.
Создание библиотеки
Создадим простой файл libhello.c с одной функцией:
#include void print_hello()
Создаём заголовочный файл библиотеки libhello.h с прототипом функции:
void print_hello();
Приступаем к сборке библиотеки.
Создаём объектный файл, указав опцию PIC (Position Independent Code), Warning ( -Wall — warning all), -g для добавления дебаг-информации и -c – что бы создать только файл библиотеки, без вызова линкера:
$ gcc -fPIC -Wall -g -c libhello.c
Проверяем – теперь у нас имеется объектный файл .o :
$ ls -l total 16 -rw-r--r-- 1 setevoy setevoy 75 Jul 31 13:21 libhello.c -rw-r--r-- 1 setevoy setevoy 18 Jul 31 13:22 libhello.h -rw-r--r-- 1 setevoy setevoy 5032 Jul 31 13:33 libhello.o
Теперь создадим общую библиотеку с дебагом ( -g ), тип общая ( -shared ), указав (с помощь -Wl ) линковщику ( /usr/bin/ld ) на использование имени libhello и версии 0, результат сохранить в файл libhello.so.0.0 (опция -o ), и использовать объектный файл libhello.o :
$ gcc -g -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0 libhello.o -lc
$ gcc -g ls -l total 28 -rw-r--r-- 1 setevoy setevoy 75 Jul 31 13:21 libhello.c -rw-r--r-- 1 setevoy setevoy 18 Jul 31 13:22 libhello.h -rw-r--r-- 1 setevoy setevoy 5032 Jul 31 13:33 libhello.o -rwxr-xr-x 1 setevoy setevoy 10312 Jul 31 13:47 libhello.so.0.0
Далее – используем ldconfig , что бы создать файл с linker name, который будет являться симлинком на файл с soname (полным именем), который в свою очередь – будет ссылкой на реальное имя:
$ ldconfig -v -n . .: libhello.so.0 -> libhello.so.0.0 (changed)
$ ls -l total 28 -rw-r--r-- 1 setevoy setevoy 75 Jul 31 13:21 libhello.c -rw-r--r-- 1 setevoy setevoy 18 Jul 31 13:22 libhello.h -rw-r--r-- 1 setevoy setevoy 5032 Jul 31 13:33 libhello.o lrwxrwxrwx 1 setevoy setevoy 15 Jul 31 13:50 libhello.so.0 -> libhello.so.0.0 -rwxr-xr-x 1 setevoy setevoy 10312 Jul 31 13:47 libhello.so.0.0
Создаём символьную ссылку на файл (для linker name):
$ ln -sf libhello.so.0 libhello.so
$ ls -l total 28 -rw-r--r-- 1 setevoy setevoy 75 Jul 31 13:21 libhello.c -rw-r--r-- 1 setevoy setevoy 18 Jul 31 13:22 libhello.h -rw-r--r-- 1 setevoy setevoy 5032 Jul 31 13:33 libhello.o lrwxrwxrwx 1 setevoy setevoy 13 Jul 31 13:59 libhello.so -> libhello.so.0 lrwxrwxrwx 1 setevoy setevoy 15 Jul 31 13:50 libhello.so.0 -> libhello.so.0.0 -rwxr-xr-x 1 setevoy setevoy 10312 Jul 31 13:47 libhello.so.0.0
Ещё раз про имена:
- libhello.so : linker name файл, симлинк на Fully qualified name или soname файл, в данном случае это libhello.so.0
- libhello.so.0 : soname, который указывает на файл с real name – libhello.so.0.0
- libhello.so.0.0 : непосредственно файл общей библиотеки
Использование shared lib
Попробуем применить созданную библиотеку.
Создаём файл программы, hello.c :
#include «libhello.h» int main()
Собираем программу, указав через -l – имя библиотеки (без lib), и с помощью -L – путь, по которому необходимо искать файл библиотеки:
$ gcc hello.c -o hello -lhello -L.
Добавляем текущий каталог в $LD_LIBRARY_PATH :
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
$ ./hello Hello, World!
Проверить используемые программой библиотеки можно с помощью ldd :
$ ldd hello linux-vdso.so.1 (0x00007ffeef1b9000) libhello.so.0 (0x00007f44cf513000) libc.so.6 => /usr/lib/libc.so.6 (0x00007f44cf16d000) /lib64/ld-linux-x86-64.so.2 (0x00007f44cf715000)
Если убрать файл билиотеки – ldd сразу сообщит об ошибке:
$ mv libhello.so.0 /tmp/ $ ldd hello linux-vdso.so.1 (0x00007ffcab1ee000) libhello.so.0 => not found libc.so.6 => /usr/lib/libc.so.6 (0x00007fb85152e000) /lib64/ld-linux-x86-64.so.2 (0x00007fb8518d4000)
И программа откажется запускаться:
$ ./hello ./hello: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory