1.- Процесс создания программы. Вступление.
В наши дни в условиях постоянного развития процесс создания программ является плодом эволюции навыков и опыта,
выработанного программистами и разработчиками.
Этот процесс состоит из следующих шагов:
Создание исходного кода на языке высокого уровня в текстовом редакторе.
Если мы попытаемся уместить очень большую программу в один файл, то ею будет трудно управлять. По этой причине исходный код
разделяется на функциональные модули, которые созданы из одного или более файлов исходного кода. Исходный код в этих
модулях вовсе не обязательно может быть написан на одном и том же языке, поскольку некоторые языки лучше других подходят
для решения конкретной задачи.
После создания файлов с исходным кодом программы, они дожны быть странслированы в блоки кода, которые может исполнять
машина. Обычно об этом коде говорят как об объектном коде. Этот код выполняет те же самые действия, что и
исходный код, за исключением того, что он написан на особом языке, который может непосредственно выполняться машиной.
Процесс трансляции исходного кода в объектный код известен как компиляция.
Компиляция выполняется по частям и за один раз компилируется (в зависимости от компилятора) часть программы и обычно один
или несколько файлов. Скомпилированный объектный код содержит программу, подпрограмму, переменные и т.д. -- части программ,
которые были странслированы и которые готовы к следующему шагу.
После того, как были созданы все файлы программы с машинным кодом, их необходимо соединить или скомпоновать при помощи
операции, выполняемой специальной утилитой, называемой компоновщик. На этой стадии все ссылки, которые код модуля
делает на код, принадлежащий другому модулю, "разрешаются" (такие, как вызов подпрограммы или ссылки на переменные,
принадлежащие или определеные в другом модуле). Результирующим продуктом является программа, которую можно непосредственно
загружать и исполнять.
Исполнение программы осуществляется особым видом програмного обеспечения, которое является существенной частью
операционной системы, в случае Линукса это системный вызов exec(). Эта функция находит файл, выделяет память
процессу, загружает определенные части содержимого файла (содержащие код и начальные значения переменных) и передает
управление процессору в точке 'text' в программе, которая обычно указывает на сам исполняемый файл.
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.- Я хочу сделать динамическую библиотеку.
Прежде чем начать создавать динамическую библиотеку, мы должны подумать, а действительно ли это надо. Динамические
библиотеки вызывают перегрузку системы по нескольким причинам:
Загрузка программы осуществляется в несколько этапов; один на загрузку основной программы, остальные для каждой
динамической библиотеки, которую использует эта программа (мы рассмотрим это для соответствующей динамической библиотеки,
поскольку этот последний пункт перестал быть редкостью и становится преимуществом).
Динамические библиотеки должны содержать перемещаемый код, поскольку адрес, выделяемый процессу в пространстве виртуальных
адресов, неизвестен до момента загрузки. В этом случае компилятор вынужден резервировать регистр для хранения положения
загрузки библиотеки и, как результат, для оптимизации кода мы имеем на один регистр меньше. Это не такая уж и проблема,
так как появляющаяся при этом перегрузка в большинстве случаев представляет собой не более 5% перегрузки.
Для динамической библиотеки наиболее приемлемым является случай, когда ее постоянно использует какая-либо программа (это
помогает избежать выгрузки текста библиотеки после завершения загрузившего ее процесса. Пока другие процессы используют
модули библиотеки, она остается в памяти).
Разделяемая библиотека загружается в память полностью (а не только необходимые модули), поэтому, чтобы быть полезной,
она должна использоваться полностью. Наихудшим примером использования динамической библиотеки является использование
только одной функции, а 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
Эта опция изменяет soname библиотеки, поэтому эта библиотека будет загружаться по запросу программ, которым требуется
указанное soname.
9.3.- Установка библиотеки
Что ж, у нас уже есть соответствующий исполняемый файл. Теперь, чтобы его можно было использовать, его необходимо
установить в соответствующее место.
Для компиляции программы, которая требует нашу новую библиотеку, необходимо использовать следующую команду:
gcc -o program libИмя.so.xxx.yyy.zzz
или, если библиотека была установлена в каталог (/usr/lib), будет достаточно:
gcc -o программа -lИмя
(если библиотека находится в /usr/local/lib, то достаточно добавить опцию '-L/usr/local/lib'). Для установки библиотеки
выполните следующее:
Скопируйте библиотеку в каталог /lib или /usr/lib. Если вы решите скопировать ее в другое место
(например в /usr/local/lib), то вы не сможете быть уверенными, что при компоновке программ компоновщик
ld(1) найдет ее автоматически.
Выполните ldconfig(1) для создания символьной ссылки libИмя.so.xxx.yyy.zzz на
libИмя.so.xxx. На этом шаге нам станет известно, правильно ли мы выполнили все предыдущие шаги и распознается ли
библиотека как динамическая. На этом шаге оказывается влияние только на загрузку библиотеки во время исполнения, а не на
компоновку программ.
Для того, чтобы компоновщик мог найти библиотеку по опции -l, создайте символьную ссылку с
libИмя.so.xxx.yyy.zzz (или с libИмя.so.xxx, soname) на libИмя.so. Чтобы этот механизм
заработал, необходимо, чтобы имя библиотеки удовлетворяло шаблону libИмя.so
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
|