Управление разделяемыми библиотеками


Разделяемые (совместно используемые, shared) библиотеки — это фрагмент компилированного кода, предоставляющий свои функции другим фрагментам компилированного кода. Разделяемые библиотеки предоставляют часто вызываемые функции другим программам и спроектированы так, чтобы как можно больше программ могли многократно их использовать. Например, многие программы должны хешировать данные, или шифровать без возможности расшифровки. Однако если каждая программа будет включать в себя модули для хеширования, писать такие программы станет труднее, а поддерживать — менее приятно. Более того, у программ могут возникнуть трудности с взаимодействием, если хеширование в них будет реализовано по-разному, и авторам программ придется изучать принципы хеширования, чтобы использовать эту возможность. Если же программа, которой необходимо хеширование, может обратиться к соответствующей функции совместно используемой библиотеки (в данном примере libcrypt), проблемы с взаимодействием и поддержкой устраняются. Такой подход снижает средний размер программ, высвобождая значительный объем системной памяти, и уменьшает их сложность.

Версии и файлы совместно используемых библиотек

Совместно используемые библиотеки имеют интуитивно понятные имена, номера версий и размещаются в соответствующих файлах. Имя библиотеки обычно (но не всегда) совпадает с именем соответствующего файла. Например, версия 2 разделяемой библиотеки libpthread хранится в виде файла /lib/libpthread.so.2, а версия 10 библиотеки легковесной службы разрешения имен lwres — в файле /usr/lib/liblwres.so.10.

Со временем изменения в библиотеке становятся столь значительными, что она делается несовместимой с более ранними версиями, в этом случае номер версии увеличивается на единицу. Например, библиотека libpthread.so.2 превратилась в libpthread.so.3. Группа разработки FreeBSD никак не заботится о номерах версий, за исключением момента начала цикла разработки очередной версии (глава 13). Кроме того, для каждой библиотеки существует символическая ссылка, имя которой совпадает с именем библиотеки, но без номера версии, которая указывает на файл самой последней версии библиотеки. Например, вы можете найти файл /usr/lib/libwres.so, который в действительности является символической ссылкой, указывающей на файл /usr/lib/libwres.so.10. Это существенно упрощает компиляцию приложений, поскольку программа будет искать файл библиотеки с обобщенным именем, а не конкретную версию.

В операционную систему FreeBSD 7 был добавлен механизм контроля версий имен (symbol versioning), который позволяет разделяемым библиотекам поддерживать несколько программных интерфейсов. При наличии контроля версий имен разделяемая библиотека предоставляет каждой программе требуемую ей версию библиотеки. Если, например, имеется программа, которой требуется библиотека версии 2, то версия 3 библиотеки также будет поддерживать необходимые функции.

Поддержка механизма контроля версий имен операционной системой FreeBSD еще не означает, что его поддерживают все программные продукты, присутствующие в коллекции «портов». Помните о возможных проблемах, связанных с версиями библиотек.

Подключение разделяемых библиотек к программам

Так как же программа получает доступ к нужной разделяемой библиотеке? Доступ к разделяемым библиотекам в операционной системе FreeBSD обеспечивают утилиты ldconfig(8) и rtld(1), а также несколько дополнительных инструментов, позволяющих влиять на порядок управления разделяемыми библиотеками.

Программа rtld(1), пожалуй, самая простая для понимания. Всякий раз, когда запускается какая-нибудь программа, rtld(8) отыскивает для нее нужные разделяемые библиотеки. Поиск ведется в каталогах с библиотеками, и если библиотеки найдены, выполняется связывание библиотек с программой. Вообще-то rtld(1) позволяет сделать не так много, но она — важное связующее звено, обеспечивающее доступ к разделяемым библиотекам. Все современные библиотеки используют стандартную разделяемую библиотеку ld-elf.so, предназначенную для двоичных файлов в формате ELF. (ELF — это формат двоичных файлов, используемый в современных версиях FreeBSD; мы рассмотрим его ниже в этой главе.) Данная библиотека настолько важна для нормальной работы системы, что при обновлении FreeBSD предыдущая версия этой библиотеки не удаляется — на тот случай, если что-то пойдет не так.

Список каталогов с библиотеками: ldconfig(8)

Чтобы всякий раз при запуске какой-то программы, требующей динамического связывания с библиотеками, просеивать весь жесткий диск в поисках чего-то, напоминающего разделяемую библиотеку, система поддерживает с помощью ldconfig(8) список каталогов с разделяемыми библиотеками. (В старых версиях FreeBSD был реализован кэш библиотек, а в современных хранится только список каталогов, где производится поиск разделяемых библиотек.) Если программа не может обнаружить разделяемые библиотеки, которые, по вашему мнению, точно присутствуют в системе, это означает, что ldconfig(8) не знает о существовании каталога, где размещаются эти разделяемые библиотеки.* Чтобы увидеть список библиотек, которые могут быть найдены с помощью ldconfig(8), выполните команду ldconfig -r.

# ldconfig -r
/var/run/ld-elf.so.hints:
        search directories: /lib:/usr/lib:/usr/lib/compat:/usr/local/lib:/usr/local/lib/nss
        0:-lcrypt.3 => /lib/libcrypt.so.3
        1:-lkvm.3 => /lib/libkvm.so.3
        2:-lm.4 => /lib/libm.so.4
        3:-lmd.3 => /lib/libmd.so.3
...

При запуске с ключом -r программа ldconfig(8) выведет список всех разделяемых библиотек, хранящихся в каталогах разделяемых библиотек. В этом списке сначала выводится имя каталога, в котором производится поиск, а затем имена библиотек, расположенных в этом каталоге. На моем ноутбуке этот список содержит 433 разделяемых библиотеки.

Если запуск какой-либо программы завершается ошибкой с сообщением о том, что невозможно найти разделяемую библиотеку, это означает, что нужная библиотека отсутствует в данном списке. В этом случае вам нужно установить нужную библиотеку в каталог с разделяемыми библиотеками или добавить каталог с библиотекой в список каталогов, в которых ведется поиск. Можно было бы просто скопировать все разделяемые библиотеки в каталог /usr/lib, но такое решение серьезно осложнит обслуживание системы, например, как в случае с картотекой, где все папки хранятся под одной литерой «Б» (бумаги). Для долгоживущих систем лучшим решением является добавление каталогов в список разделяемых библиотек.

Добавление каталогов с библиотеками в список поиска

Если был создан новый каталог с разделяемыми библиотеками, его следует добавить в список, который используется программой ldconfig(8) для поиска библиотек. Взгляните на следующие записи в файле /etc/defaults/rc.conf с параметрами настройки ldconfig(8):

ldconfig_paths="/usr/lib/compat /usr/local/lib /usr/local/lib/compat/pkg" ldconfig_local_dirs="/usr/local/libdata/ldconfig"

В переменной ldconfig_paths хранится список каталогов с библиотеками. В свежеустановленной системе FreeBSD каталог /usr/local/lib отсутствует в списке, но в большинстве систем он появляется там вскоре после установки. Точно так же в списке появляется каталог /usr/lib/compat, где находятся библиотеки обеспечения совместимости с предыдущими версиями FreeBSD. В каталоге /usr/local/lib/compat/pkg размещаются старые версии библиотек, устанавливаемых пакетами. По умолчанию программа ldconfig(8) сначала просматривает каталоги /lib и /usr/lib, а затем каталоги из списка, которые являются типичными каталогами для размещения библиотек.

Вместо того чтобы бездумно сваливать все в каталог /usr/local/lib, «порты» и пакеты включают свои разделяемые библиотеки в список поиска с помощью переменной ldconfig_local_dirs. Каждый пакет может установить файл в один из этих каталогов. Файл называется по имени пакета и просто содержит список каталогов с библиотеками, устанавливаемыми пакетом. Программа ldconfig отыскивает файлы в этих каталогах, извлекает из файлов пути к каталогам и рассматривает их как дополнительные пути к библиотекам. Например, «порт» nss (/usr/ports/security/nss) устанавливает разделяемые библиотеки в каталог /usr/local/lib/nss. Кроме того, «порт» устанавливает файл /usr/local/libdata/ldconfig/nss, который содержит единственную строку — путь к этому каталогу. Сценарий запуска ldconfig добавляет пути к каталогам, указанные в этих файлах, в список мест, где могут находиться разделяемые библиотеки.

ldconfig(8) и необычные библиотеки

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

Операционная система FreeBSD поддерживает два различных формата двоичных исполняемых файлов, a.out и ELF. Системный администратор не обязан хорошо разбираться в этих двоичных форматах, но вы должны знать, что ELF — это современный формат, ставший стандартом FreeBSD, начиная с версии 3.0 в 1998 году. Старые версии FreeBSD использовали формат a.out. Скомпилированные программы одного типа не могут использовать библиотеки другого типа. И хотя двоичные файлы в формате a.out практически исчезли, затраты на поддержку этого формата настолько незначительны, что ее не стали удалять из операционной системы. Программа ldconfig(8) поддерживает отдельные списки каталогов для форматов a.out и ELF, что можно заметить в выводе сценария /etc/rc.d/ldconfig. Для библиотек формата a.out в файле rc.conf присутствуют отдельные параметры настройки ldconfig(8).

Другой крайний случай — запуск 32-битовых приложений в 64-битовой версии FreeBSD. Чаще всего такая ситуация встречается, когда возникает необходимость использовать в архитектуре amd64 программы из более ранних версий FreeBSD. 64-битовые программы не могут использовать 32-битовые библиотеки, поэтому ldconfig(8) имеет отдельные списки каталогов с библиотеками для разных архитектур. Для этого случая в файле rc.conf также присутствуют отдельные параметры настройки ldconfig(8). Не смешивайте 32-битовые и 64-битовые библиотеки!

/usr/local/lib или отдельный каталог с библиотеками для каждого «порта»?

Разве каталог /usr/local/lib не предназначен специально для библиотек, устанавливаемых «портами» и пакетами? Почему бы просто не помещать все разделяемые библиотеки в этот каталог? Большинство «портов» именно так и поступает, но иногда наличие отдельного каталога может существенно упростить сопровождение системы. Например, на своем ноутбуке я установил Python 2.4, и каталог /usr/local/lib/python24 содержит 587 файлов! Если записать все эти файлы в каталог /usr/local/lib, это привело бы к перемешиванию с библиотеками, не имеющими отношения к Python, и осложнило бы поиск файлов, установленных «портами».

Чтобы добавить свой каталог с разделяемыми библиотеками в список поиска, следует либо добавить путь к каталогу в переменную ldconfig_paths в файле /etc/rc.conf, либо создать файл со списком каталогов в /usr/local/libdata/ldconfig. Оба варианта дают один результат. Как только каталог будет добавлен в список поиска, библиотеки из этого каталога сразу же станут доступны программам.

LD_LIBRARY_PATH

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

Всякий раз, когда запускается программа rtld(1), она проверяет значение переменной окружения LD_LIBRARY_PATH. Если в переменной указан список каталогов, программа проверяет их на наличие разделяемых библиотек. Все библиотеки, присутствующие в этих каталогах, могут использоваться программами. В переменной LD_LIBRARY_PATH можно указать любое число каталогов. Например, если бы мне потребовалось протестировать библиотеки в каталогах /home/mwlucas/lib и /tmp/testlibs, перед следующим запуском программы я мог бы присвоить переменной следующее значение:

# setenv LD_LIBRARY_PATH /home/mwlucas/lib:/tmp/testlibs

Значение переменной можно устанавливать автоматически при входе в систему, добавив соответствующую команду в файл .cshrc или .login.

LD_LIBRARY_PATH и безопасность

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

Какие библиотеки нужны программе

Наконец, возникает вопрос: какие библиотеки нужны программе? Эту информацию можно получить с помощью ldd(1). Например, узнать, что требуется программе Emacs, можно посредством такой команды:

# ldd /usr/local/bin/emacs
/usr/local/bin/emacs:
        libXaw3d.so.8 => /usr/local/lib/libXaw3d.so.8 (0x281bb000)
        libXmu.so.6 => /usr/local/lib/libXmu.so.6 (0x2820e000)
        libXt.so.6 => /usr/local/lib/libXt.so.6 (0x28222000)
        libSM.so.6 => /usr/local/lib/libSM.so.6 (0x2826d000)
...

Этот вывод содержит имена разделяемых библиотек, необходимых Emacs, и указывает расположение файлов, содержащих эти библиотеки. Если программа не может отыскать необходимую библиотеку, ldd(1) сообщит об этом. Сама программа объявляет имя только первой не найденной библиотеки, a ldd(1) выведет полный список, благодаря чему можно найти все отсутствующие библиотеки с помощью механизма поиска.

Вооружившись утилитами ldconfig(8) и ldd(8), вы будете полностью готовы к управлению разделяемыми библиотеками в системе FreeBSD.

Комментарии запрещены.