Статическая библиотека может состоять из множества объектных модулей, но компоновщик выберет из них только те, которые нужны программе.
Скомпоновав программу, можно ее запустить как обычно:
$ ./prog
Called mod1-x1
Called mod2-x2
Когда программа компонуется со статической библиотекой (или вовсе без использования библиотек), итоговый исполняемый файл содержит копии всех объектных модулей, скомпонованных с программой. Таким образом, несколько разных программ могут содержать в себе копии одних и тех же объектных модулей. Подобная избыточность несет в себе несколько недостатков:
• дисковое пространство уходит на хранение нескольких копий одних и тех же объектных модулей. Такие потери могут быть значительными;
• если несколько программ, применяющих одни и те же модули, выполняются одновременно, каждая из них будет хранить в виртуальной памяти свою отдельную копию этих модулей, увеличивая тем самым потребление виртуальной памяти в системе;
• если объектный модуль статической библиотеки требует каких-либо изменений (возможно, нужно закрыть дыру в безопасности или исправить ошибку), придется заново компоновать все исполняемые файлы, в которых этот модуль используется. Данный недостаток усугубляется тем фактом, что системному администратору необходимо знать, с какими приложениями скомпонована библиотека.
Для устранения представленных недочетов были придуманы разделяемые библиотеки. Их ключевая идея состоит в том, что одна копия объектного модуля разделяется между всеми программами, задействующими его. Объектные модули не копируются в компонуемый исполняемый файл; вместо этого единая копия библиотеки загружается в память при запуске первой программы, которой требуются ее объектные модули. Если позже будут запущены другие программы, использующие эту разделяемую библиотеку, они обращаются к копии, уже загруженной в память. Благодаря применению разделяемых библиотек исполняемые файлы требуют меньше места на диске и в виртуальной памяти (при выполнении).
Код разделяемых библиотек является общим для нескольких процессов, однако глобальные и статические переменные, объявленные внутри библиотеки, предоставляются в виде копий.
Кроме того, разделяемые библиотеки обладают следующими преимуществами:
• общий размер программ уменьшается, в связи с чем в некоторых случаях они могут быстрее загружаться в память, что ускоряет их запуск. Это относится только к большим разделяемым библиотекам, которые уже используются другими программами. На самом деле программа, первой загружающая разделяемую библиотеку, запускается дольше, поскольку данную библиотеку сначала нужно найти и загрузить в память;
• объектные модули не копируются в исполняемые файлы, а хранятся в единой разделяемой библиотеке, поэтому можно изменять общий код без необходимости выполнять повторную компоновку (ограничения описаны в разделе 41.8). Изменения можно вносить, даже когда запущенные программы уже применяют существующую версию разделяемой библиотеки.
Однако за эти дополнительные возможности приходится платить:
• разделяемые библиотеки более сложные по сравнению со статическими — как с точки зрения самой концепции, так и на практике, при их создании и сборке программ, которые их используют;
• разделяемые библиотеки должны быть скомпилированы с поддержкой адресно-независимого кода (см. подраздел 41.4.2), который ввиду применения дополнительных регистров (см. [Hubicka, 2003]) имеет издержки в большинстве архитектур;
• перемещение символов должно выполняться во время работы программы. Эта процедура требует, чтобы каждый символ в разделяемой библиотеке (переменная или функция) был изменен с учетом своего реального местоположения в виртуальной памяти. По этой причине программе, использующей разделяемую библиотеку, может понадобиться немного больше времени для выполнения, чем ее статически скомпонованному аналогу.
Еще одно применение разделяемых библиотек заключается в построении на их основе интерфейсов JNI (Java Native Interface), которые позволяют коду, написанному на языке Java, напрямую задействовать возможности операционной системы; для этого достаточно вызывать функции из разделяемой библиотеки. Подробную информацию см. в [Liang, 1999] и [Rochkind, 2004].
Для начала, чтобы понять, как устроены разделяемые библиотеки, рассмотрим минимальную цепочку операций, необходимых для их сборки и применения. Пока что намеренно проигнорируем общепринятую систему именования файлов разделяемых библиотек, описанную в разделе 41.6; она позволяет программам автоматически загружать самые свежие версии нужных им библиотек, а также делает возможным бесконфликтное сосуществование разных (так называемых