Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Lib

Большим программам часто требуются готовые библиотеки . Библиотека - это набор уже откомпилированных функций (например math-библиотека). Библиотека запрашивается при линковке , когда генерится exe-шник. Это делается с помощью опции -l :

 	cc -o badmath badmath.o -lm
Если нужно указать путь к нестандартной библиотеке :
 	cc -o badmath badmath.o -lm -L/usr/junk/lib -lcrud
Если имя библиотеки оканчивается на .a , то это статическая библиотека . Когда прилинковывается статическая библиотека,линкер просто копирует из нее целые куски в генерируемый ехе-шник , который в результате сможет работать автономно без нее . Если ехе-шник использует расшаренную библиотеку , то код подгружается лишь по мере необходимости . Обычно библиотеки лежат в каталогах /lib и /usr/lib. В каталоге /lib не должно лежать статических библиотек . В название расшаренной библиотеки входит префикс .so. Чтобы определить , какие библиотеки использует программа , нужно запустить команду :
 	ldd prog
Загрузку расшареных библиотек выполняет загрузчик ld.so. Путь к библиотеке может лежать внутри ехе-шника . Если его там нет , загрузчик смотрит файл /etc/ld.so.cache , в котором перечислены каталоги . Также имеется файл /etc/ld.so.conf. Если вы вручную добавили путь в файл /etc/ld.so.cache , вам нужно после этого выполнить команду :
 	ldconfig -v
Также имеется системная переменная LD_LIBRARY_PATH. Добавление путей в файл /etc/ld.so.conf не есть хорошо.Расшаренные библиотеки ложатся в системный кэш,и могут возникнуть неприятности . Лучше всего зашивать путь к библиотеке в ехе-шник :
 	cc -o myprog myprog.o -Wl,-rpath=/opt/obscure/lib -L/opt/obscure/lib -lweird
При разрастании расшаренных библиотек с ними возможны проблемы из-за перекрываемости и т.д. Использование LD_LIBRARY_PATH также таит в себе проблемы . Вместо этого можно использовать скрипт:
 	LD_LIBRARY_PATH=/opt/crummy/lib
 	export LD_LIBRARY_PATH
 	exec /opt/crummy/bin/my_prog $@
Основным инструментом компиляции в юниксе является make. Наиболее детально его описание дано в The UNIX Programming Environment [Kernighan and Pike] или в Managing Projects with make [Oram]. В make всегда есть цель - target - , которая может быть как файлом , так и меткой . Цель может быть главной и второстепенной . Для построения цели make ищет правила - rule . Например :
 	OBJS=aux.o main.o
 	# object files
 	all: myprog
 	myprog: $(OBJS)
 	        $(CC) -o myprog $(OBJS) 
Первая цель - all - по умолчанию главная . Правило для нее - myprog , которое может быть другим правилом , другой целью или файлом . OBJS - макрос для построения цели . Данный makefile породит следующий вывод:
 	cc    -c -o aux.o aux.c
 	cc    -c -o main.o main.c
 	cc -o myprog aux.o main.o
make можно запускать совместно с командными опциями , например :
 	make aux.o
Опции make:
    -n
    -f
Часто используемые макросы :
 	CFLAGS - опции компилятора
 	LDFLAGS - опции линкера
make имеет несколько стандартных целей :
 	clean
         distclean
         install
         test
 	depend
В начале Makefile обычно указываются либы и инклюды , например :
 	X_INCLUDES=-I/usr/X11R6/include
 	X_LIB=-L/usr/X11R6/lib -lX11 -Xt
 	NG_INCLUDES=-I/usr/local/include
 	PNG_LIB=-L/usr/local/lib -lpng
 
Дальше идут опции для компилятора и линкера
 	CFLAGS=$(CFLAGS) $(X_INCLUDES) $(PNG_INCLUDES)
 	LDFLAGS=$(LDFLAGS) $(X_LIB) $(PNG_LIB)
Дальше могут идти перечисления исходников :
 	UTIL_OBJS=util.o
 	BORING_OBJS=$(UTIL_OBJS) boring.o
 	TRITE_OBJS=$(UTIL_OBJS) trite.o
 	PROGS=boring trite
И только теперь идут цели и правила :
 	all: $(PROGS)
 	boring: $(BORING_OBJS)
         $(CC) -o $@ $(BORING_OBJS) $(LDFLAGS)
 	trite: $(TRITE_OBJS)
         $(CC) -o $@ $(TRITE_OBJS) $(LDFLAGS)
Разделяемые (shared) библиотеки - фундаментальный компонент юникс-системы . Стандартная C-library например , на Suse 9.1 , занимает 1.3 MB. Копия этой библиотеки для всех программ , которые ее используют в /usr/bin , займет не один гигабайт . Такие библиотеки требуют для себя не только дискового пространства , но и памяти . Ядро устроено так , что хранит в памяти одну копию разделяемой библиотеки . Необходимо помнить о том , что при статической линковке код библиотеки статически добавляется к коду исполняемого файла , и при его запуске нам библиотека не нужна - ее код уже прилинкован при компиляции к телу исполняемого файла . При динамической линковке разделяемая библиотека подключается во время исполнения этого файла .
Динамическая линковка - это преобладающий тип линковки библиотек . Только одна стандартная библиотека состоит более чем из тысячи системных вызовов . Для реально работающих программ придуман т.н. механизм Procedure Linkage Table (PLT) - таблица , состоящая из обращений к функциям , вызываемым из разделяемых библиотек . Первая проблема , которая может при этом возникнуть - это проблема совместимости . При статической компиляции такая проблема решается на этапе компиляции . При динамической линковке такой гарантии нет , потому что мы можем обновить разделяемую библиотеку уже после компиляции . В этом случае проверкой является номер версии - когда динамический линкер пытается прилинковать библиотеку , он проверяет номер версии , и если номер не соответствует , то она не прилинковывается . Номер версии состоит из 2-х частей - главный (major) номер и вторичный (minor). Если главный номер совпадает , то проблем как правило при загрузке библиотеки быть не должно . Если у нас имеется библиотека
 	libexample.so
то у нее будет линк на версию
 	libexample.so.N
где N - старший главный номер версии , откуда в свою очередь будет линк на
 	libexample.so.N.M
где M - старший вторичный номер версии . При этом программа будет искать именно нужную ей версию N .
В статических библиотеках код находится в файлах с расширением .a Для динамических библиотек файлы имеют расширение .so . Формат статической библиотеки генерируется с помощью утилиты ar , и формат похож на то , что генерирует утилита tar . Для динамических библиотек на последних линуксовых версиях этот формат как правило ELF binary. Он состоит из заголовка и сегментов , которые разбиваются на секции. При линковке код из статических библиотек напрямую добавляется в исполняемый файл программы , а из динамических библиотек берутся ссылки и добавляются в PLT . При загрузке программа загружает динамический линкер , который загружает нужные библиотеки . При динамической линковке программы можно добавлять пути для поиска библиотек на этапе компиляции . Для gcc этот синтаксис выглядит как
     -Wl,-R/path 
Если программа уже слинкована , можно использовать переменную
 	LD_LIBRARY_PATH
Можно также перед запуском программы запускать специальный wrapper script , кототорый специально настраивает библиотечные каталоги , как это делается например , при запуске Mozilla . Динамический линкер обращается к конфигу /etc/ld.so.conf , который включает в себя список каталогов . Но сначала линкер обращается к LD_LIBRARY_PATH . ldd (list dynamic dependencies) - утилита для дебага разделяемых библиотек . Она выводит список разделяемых библиотек для данного модуля , например :
      ldd /bin/sh
         linux-gate.so.1 =>  (0xffffe000)
         libreadline.so.4 => /lib/libreadline.so.4 (0x40036000)
         libhistory.so.4 => /lib/libhistory.so.4 (0x40062000)
         libncurses.so.5 => /lib/libncurses.so.5 (0x40069000)
         libdl.so.2 => /lib/libdl.so.2 (0x400af000)
         libc.so.6 => /lib/tls/libc.so.6 (0x400b2000)
         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Crash Course in Shared Libraries and Preloading

Ресурсы

К обязательному чтению :

  • David A Wheeler's program library howto
  • gcc, ld, ld-so, ld-linux.so info documents ( info gcc, manuals (man ld.so)).

Далее опционально :

Building a shared library

Давайте напишем небольшую библиотеку,которая ничего не делает,почти ничего, окромя "hello world" . Первый обьект :


 /* print1.c */
 
 #include <stdio.h>
 
 void print1(void) {
         printf("1\n");
 }
 

Аналогично второй обьект :


 /* print2.c */
 
 #include <stdio.h>
 
 void print2(void) {
         printf("2\n");
 }
 

Откомпилируем :

gcc -Wall -g -c -fpic print1.c
 gcc -Wall -g -c -fpic print2.c
 

Создаем shared library:

gcc -shared -Wlsoname=libprint.so.1 -g \
     -o libprint.so.1.0.1 print1.o print2.o
 

Делаем линк на библиотеку:

ln -sf libprint.so.1.0.1 libprint.so
 

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


 /* print.h */
 #ifndef PRINT_H
 #define PRINT_H
 void print1(void);
 void print2(void);
 #endif
 

Напишем тестовую программу :


 /* print.c */
 #include "print.h"
 
 int main() {
 	print1();
 	print2();
 	return 0;
 }
 

Откомпилируем ее:

gcc -Wall -g -L. -lprint -o print print.c
 

Для запуска программы , линкеру нужно подсказать , где лежит библиотека :

LD_LIBRARY_PATH=. ./print
 

Перегрузка функций

Перед загрузкой библиотеки мы всегда можем сказать лоадеру , откуда начинать ее поиск и загрузку.

Давайте добавим новую функцию в только что написанную библиотеку. Эта функция имеет имя fopen.

 
 /* fopen.c */
 
 #include < stdio.h>
 
 FILE* fopen(const char* fn, const char* mode)
 {
         printf("fopen called\n");
         return NULL;
 }
 
 

Откомпилируем эту функцию и положим в библиотеку :

gcc -Wall -g -c -fpic fopen.c
 gcc -shared -Wlsoname=libprint.so.1 -g \
     -o libprint.so.1.0.2 print1.o print2.o fopen.o
 ln -sf libprint.so.1.0.2 libprint.so
 

Напишем программу для тестирования :


 /* print-2.c */
 #include < stdio.h>
 
 int main() {
         FILE* f;
 
         f = fopen("abc", "r");
         if(f == NULL) {
 		printf("1");
         }
         else {
                 printf("2");
                 fclose(f);
         }
 
         return 0;
 }
 

Откомпилируем эту программу:

gcc -Wall -g -o print print.c
 

Запускаем ее обязательно со следующим префиксом :

LD_PRELOAD=./libprint.so ./print
 

Если что-то сделано неверно , выведется строка : fopen called


Процесс создания программы.

Перевод на русский: Владимир Попов

В наши дни в условиях постоянного развития процесс создания программ является плодом эволюции навыков и опыта, выработанного программистами и разработчиками.

Этот процесс состоит из следующих шагов: 

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

    После создания файлов с исходным кодом программы, они дожны быть странслированы в блоки кода, которые может исполнять машина. Обычно об этом коде говорят как об  объектном коде. Этот код выполняет те же самые действия, что и исходный код, за исключением того, что он написан на особом языке, который может непосредственно выполняться машиной. Процесс трансляции исходного кода в объектный код известен как компиляция.  Компиляция выполняется по частям и за один раз компилируется (в зависимости от компилятора) часть программы и обычно один или несколько файлов. Скомпилированный объектный код содержит программу, подпрограмму, переменные и т.д. -- части программ, которые были странслированы и которые готовы к следующему шагу. 

    После того, как были созданы все файлы программы с машинным кодом, их необходимо соединить или скомпоновать при помощи операции, выполняемой специальной утилитой, называемой компоновщик. На этой стадии все ссылки, которые код модуля делает на код, принадлежащий другому модулю, "разрешаются" (такие, как вызов подпрограммы или ссылки на переменные, принадлежащие или определеные в другом модуле). Результирующим продуктом является программа, которую можно непосредственно загружать и исполнять. 

    Исполнение программы осуществляется особым видом програмного обеспечения, которое является существенной частью операционной системы, в случае Линукса это системный вызов 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

Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье