Здравствуйте, уважаемые подписчики. Этот выпуск являет собой введение в библиотеки Linux.
2. Программа
2.1. Описание
Для изучения библиотек воспользуемся простой до безобразия программой, в которой выводится два сообщения, но особым образом. Чтобы не тянуть за собой лишний груз, не будем создавать в репозитории каких-либо каталогов. Нас сейчас интересуют библиотеки, а не способы организации исходных текстов программы.
2.2. Файлы
main.c
#include "hello1.h"
#include "hello2.h"
int main (void)
{
hello1 ();
hello2 ();
return 0;
}
Здесь все тривиально: make и ./main. Наблюдательные заметят, что в репозитории
появился еще один странный файл libhello.so. Попробуйте запустить его. Ошибка?!
Так и должно быть: просто теперь вы знаете, что подобные файлы не могут
самостоятельно выполняться, хотя gcc и присваивает им права на выполнение.
3. Теория
3.1. Несколько слов о библиотеках
Библиотеки нужны для того, чтобы не изобретать велосипедов. Часто один и тот же набор функций можно использовать в разных программах. Повторная реализация схожих механизмов отнимает кучу времени и увеличивает размер исходных файлов. Даже при написании операционных систем используют библиотечную концепцию. Этой библиотекой всех библиотек является BIOS, в которой содержатся базовые функции ввода-вывода, используемые в различных программах. Однако не все так гладко. У библиотек есть и свои недостатки, о которых
будет сказано позже.
3.2. Статические и динамические библиотеки
Вообще говоря, библиотека - это набор объектных файлов, объединенных в один модуль. Библиотека подключается к программе в процессе линковки. В таком случае говорят, что "программа собирается с такой-то библиотекой" или "такая-то библиотека включается в программу".
Библиотеки делятся на две группы: статические (объектные архивы) и динамические (совместно используемые). В процессе статической компоновки, все содержимое библиотеки помещается в исполняемый модуль. Такая программа как правило имеет большой размер и тяжело модернизируется, однако за счет непосредственного включения библиотечных механизмов в исполняемый код, является самодостаточной, т. е. мало зависимой от конфигурации системы. При динамической компоновке в исполняемый модуль внедряется лишь ссылка на библиотеку.
Чтобы такая программа могла выполняться, необходимо, чтобы динамическая библиотека присутствовала в системе. Программы, построенные на динамических библиотеках, имеют маленький размер и легко модернизируются, но у таких программ могут возникнуть проблемы с запуском.
Статические библиотеки будут рассмотрены в следующем выпуске рассылки. Наша сегодняшнаяя программа использует концепцию динамической линковки.
Прежде, чем приступить к рассмотрению приведенного выше наитупейшего (простите меня за это!) примера, рассмотрим один важный момент, относящийся к библиотекам: если библиотека отсутствует в стандартных каталогах (/usr/lib, /lib и т. д.), то при компоновке необходимо явно указывать местоположение этой библиотеки, даже если она находится в текущем каталоге. Кроме того, в этом случае местоположение библиотеки следует указывать не только линковщику, но и самому исполняемому модулю.
3.3. Разбор полетов
Приведенная программа является типичным примером использования трех объектных модулей (клиент + два сервера). Два серверных модуля (hello1.o и hello2.o) мы объединяем в одну динамическую библиотеку. Теперь, при желании, этой библиотекой могут пользоваться разные программы (поэтому их и назвают совместно используемыми (shared)). Исходные файлы не содержат ничего нового, поэтому рассмотрим только Makefile.
При компиляции файлов hello1.c и hello2.c бросается в глаза опция -fPIC. Эта опция заставляет компилятор генерировать т. н. позиционно-независимый (Position Independent) код. Как уже говорилось в первых выпусках рассылки, объектный код отличается от исполняемого наличием символических имен вместо адресов. Указание опции -fPIC добавляет к объектному коду еще и т. н. плавающие позиции, благодаря которым объектные файлы могут пристыковываться к программе даже в процессе ее выполнения. Это называется динамической
загрузкой библиотеки, которая будет рассмотрена в последующих выпусках рассылки. Итак, объектные модули, из которых строится библиотека должны содержать позиционно-независимый код.
Для объединения объектных файлов в динамическую библиотеку используется опция -shared. Как правило имена совместно используемых библиотек начинаются с префикса lib и заканчиваются расширением .so. Это стандарт и его нужно придерживаться.
При компоновке (линковке) программы с динамической библиотекой используют опцию -l, после которой указывают имя библиотеки без префикса lib и суффикса .so. Если, например, нужно подключить библиотеку libqt.so, то пришут так: -lqt.
Опция -L указывает, где находится библиотека во время линковки, а опция линковщика '-rpath' (опция '-Wl,' в gcc обозначает, что следующие за ней оции будут переданы линковщику) включает в исполняемый модуль информацию о том, где будет находиться библиотека во время запуска программы. В нашем случае библиотека находится в текущем каталоге и никуда оттуда не переезжает, поэтому мы смело указываем '-L . -Wl,-rpath .'.
Теперь запустим программу командой './main'. Если все сделано правильно, то программа выведет следующее:
Однако, если переименовать или удалить файл libhello.so, то программа откажется запускаться:
-----------------------------------------------------------+
$ mv libhello.so hello.n |
$ ./main |
./main: error while loading shared libraries: |
libhello.so: cannot open shared object file: |
No such file or directory |
$ |
-----------------------------------------------------------+
В следующем выпуске мы продолжим изучать библиотеки, а сейчас настоятельно рекомендую попрактиковаться и даже поэкспериментировать с рассмотренным примером.