Разделяемые библиотеки Разделяемые библиотеки СОДЕРЖАНИЕ 1. Введение 2. Использование разделяемых библиотек 2.1. Что такое разделяемая библиотека? 2.2. Разделяемые библиотеки ОС UNIX 2.3. Создание выполняемых файлов 2.4. Исходный текст программ 2.5. Использовать ли разделяемую библиотеку? 2.6. Еще об экономии памяти 2.6.1. Как разделяемые библиотеки экономят память? 2.6.2. Как ОС UNIX работает с разделяемыми библиотеками? 2.6.3. Как применение разделяемых библиотек может привести к увеличению расхода памяти? 2.7. Как узнать, нужны ли выполняемому файлу разделяемые библио- теки 2.8. Отладка процессов, работающих с разделяемыми библиотеками 3. Создание разделяемой библиотеки 3.1. Порядок создания 3.1.1. Выбор адресов секций команд и данных 3.1.2. Выбор маршрутного имени для разделяемой библиотеки выпол- нения 3.1.3. Определение содержимого библиотеки 3.1.4. Подготовка исходного текста 3.1.5. Создание файла спецификаций библиотеки 3.1.6. Применение mkshlib для построения разделяемых библиотек сборки и выполненения 3.2. Пример 3.3. Рекомендации по разработке исходного текста для разделяемой библиотеки 3.3.1. Какие функции целесообразно включать в библиотеку? 3.3.2. Подготовка исходного текста для разделяемой библиотеки 3.3.3. Использование импортируемых имен 3.3.4. Обеспечение совместимости с архивной библиотекой 3.3.5. Настройка разделяемой библиотеки 3.3.6. Обеспечение совместимости с будущими версиями 4. Резюме 1. ВВЕДЕНИЕ Какой бы ни была мощность компьютера, работающего под управле- нием ОС UNIX, рано или поздно встает задача максимально эффек- тивного использования процессора, оперативной и дисковой памя- ти. Разделяемые библиотеки позволяют достичь экономии каждого из перечисленных ресурсов. Например, хорошо организованная раз- деляемая библиотека уменьшает расход как дискового пространст- ва, необходимого для хранения выполняемых объектных файлов, так и оперативной памяти, отводимой под процессы. Изучение раздела 2, Использование разделяемых библиотек, помо- жет Вам овладеть методами работы с разделяемыми библиотеками в ОС UNIX V. В разделе объясняется, что такое разделяемая библио- тека, и как ее использовать при построении выполняемых файлов. В нем также содержатся рекомендации относительно того, когда следует (или не следует) применять разделяемые библиотеки, и как определить, использует ли выполняемый файл подобную библио- теку. В разделе 3, Создание разделяемой библиотеки, описывается про- цесс построения разделяемых библиотек. Чтобы уметь пользоваться разделяемыми библиотеками, нет необходимости читать эту часть, она предназначена для хорошо подготовленных программистов, ко- торые, возможно, захотят создать свою собственную разделяемую библиотеку. Примечание Разделяемые библиотеки являются особенностью версии 3.0 ОС UNIX V. Выполняемые объектные файлы, использующие разделяемые библиотеки, в более ранних версиях ОС UNIX будут неработоспособны. 2. ИСПОЛЬЗОВАНИЕ РАЗДЕЛЯЕМЫХ БИБЛИОТЕК Если Вы умеете использовать при разработке своих программ ар- хивные библиотеки, Вы не встретите трудностей при освоении раз- деляемых библиотек. В данном разделе будет рассказано о том, что такое разделяемая библиотека, а также о том, как и когда следует ею пользоваться. 2.1. Что такое разделяемая библиотека? Разделяемая библиотека - это файл, содержащий объектные модули, которые могут одновременно использоваться (разделяться) нес- колькими процессами. С точки зрения организации как обычная (неразделяемая), так и разделяемая библиотеки являются архива- ми. Далее, однако, термины архивная библиотека или архив будут использоваться только в неразделяемом случае. Если при редактировании внешних связей программы используется разделяемая библиотека, то библиотечные модули, соответствующие внешним ссылкам программы, не копируются в выполняемый объект- ный файл. Вместо этого в объектном файле создается специальная секция .lib, содержащая ссылки на библиотеку. Когда ОС UNIX вы- полняет получившийся файл, информация из этой секции использу- ется для включения соответствующей библиотеки в адресное прост- ранство процесса. Тот факт, что содержимое разделяемых библиотек не копируется в выполняемый файл, дает следующие преимущества: Экономится дисковое пространство. Выполняемые файлы имеют меньший размер. Экономится оперативная память. За счет совместного использования несколькими процесса- ми одной разделяемой библиотеки уменьшается суммарный размер нужной им оперативной памяти. Упрощается сопровождение выполняемых файлов. Как было указано, содержимое разделяемой библиотеки по- мещается в адресное пространство процесса на этапе вы- полнения. Таким образом, внесение изменений в разделяе- мую библиотеку фактически изменяет все использующие ее выполняемые файлы, поскольку операционная система пре- доставит процессам новое содержимое библиотеки. После исправления ошибки в разделяемой библиотеке все процес- сы автоматически будут использовать скорректированные модули. Разумеется, архивные библиотеки не обладают этим свой- ством: изменения в архиве не приводят к изменению вы- полняемых файлов, так как архивные модули копируется в них не во время выполнения, а на этапе редактирования связей. Более подробно свойства разделяемых библиотек описываются да- лее, в разделе Использовать ли разделяемую библиотеку?. 2.2. Разделяемые библиотеки ОС UNIX Разделяемая библиотека представляет собой два файла: разделяе- мую библиотеку сборки и разделяемую библиотеку выполнения. Раз- деляемая библиотека сборки - это файл, который редактор связей просматривает для создания секции .lib выполняемого файла. Раз- деляемая библиотека выполнения - это файл, который ОС UNIX ис- пользует во время работы процесса. Разумеется, разделяемая биб- лиотека выполнения должна быть доступна процессу. Имена разделяемых библиотек (и сборки, и выполнения) характери- зуются суффкисом _s. Наличие этого суффикса отличает, например, разделяемую библиотеку языка C /lib/libc_s.a от стандартной библиотеки /lib/libc.a. Кроме того, суффикс _s обозначает, что эти библиотеки статически отредактированы (см. ниже). Как пра- вило, разделяемые библиотеки выполнения хранятся в каталоге /shlib. Отметим, что по умолчанию в данной версии ОС UNIX ис- пользуется разделяемая версия библиотеки языка C, рассчитанная на сопроцессор вещественной арифметики MC68881. Имя библиотеки сборки - /lib/libc881_s.a, библиотеки выполнения - /shlib/ libc881_s. 2.3. Создание выполняемых файлов Чтобы редактор связей просматривал разделяемую библиотеку, ее, как и обычную архивную, нужно задать с помощью опции -l команды cc(1): cc файл.c -oфайл ... -lбибл Например, чтобы обеспечить просмотр сетевой библиотеки, укажите такую командную строку: cc файл.c -oфайл ... -lnsl_s Чтобы откомпилировать все C-программы в текущем каталоге и от- редактировать внешние связи со стандартной разделяемой библио- текой языка C, можно воспользоваться следующей командной стро- кой: cc *.c -lc_s Как правило, опция -lc_s включается в командную строку послед- ней из всех опций -l. В этом случае разделяемая библиотека язы- ка C используется подобно архивной, то есть с ее помощью разре- шаются внешние ссылки, оставшиеся неразрешенными после просмот- ра всех остальных файлов и библиотек, указанных в командной строке. 2.4. Исходный текст программ Исходный текст программ на C или ассемблере не зависит от того, будет ли при редактировании связей использоваться разделяемая библиотека, или же архивная. Поэтому при переходе на разделяе- мую библиотеку не придется изменять текст уже написанных прог- рамм. Новые приложения Вы также можете создавать, не меняя при- нятых у Вас соглашений по записи программ. Следует иметь в виду две рекомендации, которые, впрочем, отно- сятся не только к случаю использования разделяемой библиотеки, а именно: Не переопределяйте библиотечные имена. Хотя возможны исключения, лучше не переопределять стан- дартные библиотечные подпрограммы, например, printf(3S) или strcmp(3C). Замены, несовместимые с библиотечным вариантом, могут привести к ошибкам при использовании любой библиотеки, разделяемой или архивной. Не пользуйтесь недокументированными библиотечными опре- делениями. Используйте только те подпрограммы и определения дан- ных, которые описаны в разделе 3 Справочника програм- миста. Например, не пытайтесь состязаться с разработчи- ками ctype(3C), манипулируя всем тем, что сопровождает это определение. 2.5. Использовать ли разделяемую библиотеку? Ваше решение о том, следует ли использовать разделяемую библио- теку, зависит от ответа на вопрос, сэкономит ли это дисковую и оперативную память. Хорошо спроектированная разделяемая библио- тека почти всегда обеспечит такую экономию, поэтому в качестве общего правила рекомендуем использовать разделяемые библиотеки там, где это возможно. Если есть как разделяемая, так и неразделяемая библиотеки, Вы можете для сравнения создать две версии Вашей программы, с раз- деляемой библиотекой и без нее. Напомним, что это возможно, так как для обоих видов библиотек годится один и тот же исходный текст (см. выше раздел Исходный текст программ). Сделайте две версии выполняемого файла и сравните их с точки зрения размера и эффективности. Пример: $ cat hello.c main () { printf ("hello\n"); } $ cc -o unshared hello.c -lc $ cc -o shared hello.c $ size unshared shared unshared: 10384 + 1292 + 2336 = 14012 shared: 472 + 944 + 2232 = 3648 Если программа обращается к небольшому числу библиотечных функ- ций, может оказаться, что применение разделяемой библиотеки потребует больше дисковой или оперативной памяти. Более подроб- ное обсуждение условий, при которых разделяемая библиотека дает экономию памяти, содержится в следующем разделе. Принимая решение относительно использования разделяемых библио- тек, помните о том, что они отсутствуют в версиях ОС UNIX, предшествующих 3.0, поэтому, если Ваши программы должны выпол- няться на этих версиях, Вам не следует применять разделяемые библиотеки. 2.6. Еще об экономии памяти Этот раздел поможет Вам понять, почему Ваши программы, как пра- вило, будут лучше работать при использовании разделяемых библи- отек. В нем объясняется: Как разделяемые библиотеки, в отличие от архивных, эко- номят память. Как ОС UNIX работает с разделяемыми библиотеками. В каких случаях разделяемые библиотеки могут потребо- вать больше памяти. 2.6.1. Как разделяемые библиотеки экономят память? Чтобы лучше понять это, сравним разделяемую и архивную библио- теки. Разделяемая библиотека сборки напоминает архивную библиотеку в трех отношениях. Во-первых, как было сказано выше, оба файла являются архивами. Во-вторых, в объектных модулях библиотеки обычно определяются имена функций и данных, предназначенных для библиотеки и доступные извне, мы будем называть экспортируемы- ми. Заметим, что в библиотеке могут также содержаться имена, которые в ней используются, но обычно не определяются, и кото- рые мы будем называть импортируемыми. В-третьих, редактор свя- зей просматривает библиотеки в поисках имен, которые он может использовать для разрешения внешних ссылок, после чего создает- ся выполняемый файл. Примечание Редактор внешних связей ОС UNIX является средством ста- тического редактирования. Это означает, что все внешние ссылки программы должны быть разрешены к моменту начала ее выполнения. Как с архивной, так и с разделяемой биб- лиотеками редактирование внешних связей является стати- ческим. Хотя между архивной и разделяемой библиотекой есть сходство, в целом они существенно различны. Главные различия, о которых мы уже кратко рассказывали выше, относятся к способу использования библиотеки для разрешения внешних ссылок. Рассмотрим работу ОС UNIX с библиотекой каждого из типов в про- цессе редактирования связей. Работая с архивной библиотекой, редактор связей копирует ее модули, в которых определяются внешние имена редактируемой программы, в секции .text и .data выполняемого файла. Если же редактор связей работает с разделя- емой библиотекой, он ничего не копирует из нее в выполняемый файл. Вместо этого редактор создает специальную секцию .lib, в которой идентифицируются модули разделяемой библиотеки, необхо- димые для выполнения программы, и разрешает внешние ссылки на эти имена. Когда ОС UNIX выполняет полученный файл, содержимое секции .lib используется для включения соответствующих модулей в адресное пространство процесса. На рисунке ниже изображена структура выполняемых файлов, полу- ченных с использованием обычной (архивной) и разделяемой библи- отек языка C. Исходный текст программы выглядит так: main () { . . . printf ("Как Вам нравится эта глава?\n"); . . . result = strcmp ("Весьма!", answer); . . . } Выполняемый файл, Выполняемый файл, │ архивная библиотека │ │разделяемая библиотека│ ------------------------ ---------------------- │Заголовок файла │ │Заголовок файла │ ------------------------ ---------------------- │.text (программа) │ │.text (программа) │ ------------------------ ---------------------- │.text (библиотека, │<- │.data (программа) │ │printf(3S) и strcmp(3C))│ │ ---------------------- ------------------------ Включаются~-->│.lib │ │.data (программа) │редактором│ ---------------------- ------------------------ связей │ │Таблица имен │ │.data (библиотека, │<-- │ ---------------------- │printf(3S) и strcmp(3C))│ │ │Таблица цепочек │ ------------------------ │ ---------------------- │ Таблица имен │ Создается редактором связей, ------------------------ содержит ссылки на библиотечные │ Таблица цепочек │ printf(3S) и strcmp(3C) ------------------------ Обратите внимание, что версия с разделяемой библиотекой меньше. На следующем рисунке будет изображена структура памяти, занима- емой соответствующими процессами в ходе выполнения. Рассмотрим теперь, что происходит, когда нескольким выполняемым файлам нужен один и тот же модуль из библиотеки. При использо- вании архивной библиотеки каждый файл содержит свою собственную копию этого модуля, что приводит к повторению соответствующих фрагментов как на диске, так и в оперативной памяти (во время выполнения). И наоборот, при использовании разделяемой библио- теки, ее модули существуют отдельно от выполняемых файлов, как показано на рисинке ниже. Такое разделение позволяет всем про- цессам, использующим разделяемую библиотеку, ссылаться на единственную копию ее содержимого. одновременно может включаться в другие процессы ^ ~---------- ~------------- ^ ^ │ │ │ │ │ │ │ │ │ │ │ │ ~- -------- - Адресное │ │ Архивная │ │ Разделяемая │ │ Библиотека │ пространство │ │ версия │ │ версия │ - ---------- │ │ │ │ │ │ Включается │ │ │ ------------- │ в адресное │ │ │ : : │ пространство │ │ │ : : <--- процесса v ---------- ------------- ^ содержимое библиотеки, на которую есть ссылки из .lib 2.6.2. Как ОС UNIX работает с разделяемыми библиотеками? Теперь, когда Вы понимаете, за счет чего разделяемые библиотеки позволяют экономить память, необходимо изучить, как ОС UNIX ра- ботает с ними. Это позволит понять, почему использование разде- ляемых библиотек может иногда привести к увеличению расхода па- мяти. Разделяемая библиотека сборки и разделяемая библиотека выполнения Как упоминалось ранее, каждая разделяемая библиотека состоит из двух частей: разделяемой библиотеки сборки и разделяемой библи- отеки выполнения. Первая должна находиться на компьютере сбор- ки, то есть на том компьютере, на котором Вы создаете выполняе- мый файл; вторая должна находиться на целевом компьютере, то есть там, где файл будет выполняться. Разумеется, упомянутые компьютеры могут совпадать, однако это не обязательно. Разделяемая библиотека сборки - это фактически то же самое, что и архивная библиотека. Каждый из ее элементов (обычно это за- конченный объектный файл) содержит имена некоторых функций и данных в своей таблице имен. Этот файл просматривается редакто- ром внешних связей, когда разделяемая библиотека сборки указы- вается при компиляции или редактировании связей программы. Просмотр производится с целью разрешения внешних ссылок. Одна- ко, как уже отмечалось, редактор связей не копирует элементы разделяемой библиотеки, разрешающие внешние ссылки, в объектный файл программы. Вместо этого редактор, найдя эти определения, помещает в объектный файл информацию, идентифицирующию их мес- тонахождение в библиотеке. В результате создается специальная секция выполняемого файла, которая обозначается .lib. См. также раздел Что такое разделяемая библиотека? выше. Разделяемая библиотека выполнения похожа на файл a.out(4). ОС UNIX читает файл, в котором находится библиотека, если она нуж- на какому-либо из процессов. Необходимые модули библиотеки ука- заны в секции .lib выполняемого файла. Когда ОС UNIX выполняет файл, эта секция используется, чтобы включить соответствующую библиотеку в адресное пространство процесса. Таким образом, к моменту выполнения вся необходимая информация из библиотеки становится доступной. Разделяемые библиотеки допускают одновременное использование своих секций .text. Хотя каждый из процессов, использующих раз- деляемую библиотеку, имеет свое собственное виртуальное адрес- ное пространство, все они работают с единственной физической копией секции команд библиотеки. Секция .data разделяемой библиотеки выполнения не может однов- ременно использоваться несколькими процессами. Каждый процесс, обращающийся к данным из библиотеки, получает свою собственную область, то есть непрерывный участок адресного пространства, идентичный секции .data разделяемой библиотеки выполнения. Что- бы не происходило нежелательного взаимодействия процессов, раз- деляющих общую секцию команд, эти процессы не разделяют данные и область стека. Как было указано выше, разделяемая библиотека выполнения подоб- на файлу a.out(4). В частности, выполняемые файлы также допус- кают разделение между несколькими процессами одной копии своих команд, но не данных. Кроме того, чтобы выполнять файл, обраща- ющийся к разделяемой библиотеке выполнения, процесс должен иметь право на выполнение по отношению к этой библиотеке. Таблица переходов ld(1) является средством статического редактирования связей, поэтому он должен назначить адреса всем именам, которые встре- чаются в процессе редактирования. Разделяемая библиотека сборки используется для разрешения внешних ссылок на имена из разделя- емой библиотеки. В полученном выполняемом файле все такие имена получат свои адреса. Что произойдет, если текст разделяемой библиотеки будет изме- нен, в результате чего изменится адрес какого-либо имени из библиотеки? Если бы это была архивная библиотека, ничего бы не произошло, поскольку в выполняемый файл была бы включена копия того элемента библиотеки, в котором имя определяется. Хотя это была бы копия старого варианта, такой файл можно было бы выпол- нять и в дальнейшем. Однако, если бы выполняемый файл создавал- ся с разделяемой библиотекой, подобное изменение библиотеки могло бы отразиться на нем неблагоприятно. Действительно, в та- ком файле нет самого содержимого библиотеки, а есть только ин- формация, указывающая его местонахождение. После реорганизации библиотеки ее структура могла бы измениться, в результате чего ОС UNIX, выполняя файл, предоставила бы ему не тот вариант биб- лиотеки, который нужен данному файлу. Таким образом, чтобы Ваши программы работали правильно, их связи пришлось бы редактиро - вать после каждого изменения библиотеки. Для предотвращения подобных эффектов ОС UNIX, работая с разде- ляемой библиотекой, использует таблицу переходов. Имена связы- ваются с абсолютными адресами входов в этой таблице. Эти адреса остаются неизменными при модификации содержимого библиотеки. Каждый элемент таблицы переходов содержит команду безусловного перехода по адресу, соответствущему имени. Таким образом, ссыл- ки на имена из библиотеки разрешаются с помощью элементов таб- лицы переходов, а не с помощью собственно содержимого библиоте- ки. На следующем рисунке показано, как два выполняемых файла обра- щаются к printf(3S). Процесс слева был создан с архивной библи- отекой, поэтому в него включена копия функции printf(3S). Про- цесс справа создавался с разделяемой библиотекой. Этот файл ссылается на абсолютный адрес (10) в таблице переходов разделя- емой библиотеки выполнения. По этому адресу находится команда безусловного перехода на нужный адрес в библиотеке. архивная библиотека разделяемая библиотека не использует использует таблицу таблицу переходов: переходов: ~------------------ ~---------------- │ ... │ │ ... │ │ вызов printf(3S) │ │вызов printf(3S)│ │ 1140 -------- ------- │ 10 -------- ----- │ ... │ │ │ ... │ │ ------------------ │ ---------------- │ │ printf │1140<-- разде-~----------------- │ ------------------ ляемая│ | ... │ │ │ ... │ библиотека ----------------- │ ------------------ ~----- на printf │10<-- │ │ 300 │табли- │ ----------------- ца пе- │ │ на close │реходов │ ----------------- │ │ ... │ │ ----------------- ->300│ printf │ ----------------- │ close │ ----------------- │ ... │ ----------------- 2.6.3. Как применение разделяемых библиотек Использование разделяемой библиотеки сборки при редактировании связей может привести к увеличению размера выполняемого файла. Напомним, что в ОС UNIX V версии 3.1 редактирование связей яв- ляется статическим, что означает необходимость разрешения всех внешних ссылок программы к моменту ее выполнения. Напомним так- же, что разделяемая библиотека может обращаться к внешним (им- портируемым) именам, которые в ней не определяются. Эти имена порождают при редактировании связей внешние ссылки, для разре- шения которых редактор связей включает в выполняемый файл соот- ветствующие секции .text и .data, что увеличивает размер файла. Разделяемая библиотека выполнения может увеличить размер про- цесса. Напомним, что в разделе Как ОС UNIX работает с разделяе- мыми библиотеками? указывалось, что к процессу присоединяются секции команд и данных разделяемой библиотеки выполнения. Сек- ция команд разделяется между всеми процессами, которым она нуж- на, однако это не касается секции данных. Каждый процесс, ис- пользующий библиотеку, получает свою собственную копию всей секции данных библиотеки. Это, разумеется, увеличивает объем памяти, нужной процессу. В результате, если процессу в действи- тельности нужна лишь малая часть функций и данных разделяемой библиотеки, он может занять больше памяти, чем если бы он соз- давался с архивной библиотекой. Так, неразумно было бы исполь- зовать разделяемую библиотеку языка C, если Вам нужна только функция strcmp(3S). Хотя разделяемое использование самой strcmp(3S) и экономит память, в этом случае перевешивают поте- ри, связанные с необходимостью копирования в процесс всей сек- ции данных разделяемой библиотеки языка C, поэтому лучше ис- пользовать архивную версию библиотеки. Отметим, что приведенные соображения важны, на наш взгляд, лишь с теоретической точки зрения. Полезно понимать, как ОС UNIX ра- ботает с разделяемыми библиотеками, как размещаются процессы в памяти. Собственно накладные расходы, связанные с использовании ем разделяемых библиотек, как правило, малы по сравнению с раз- мером секции данных выполняемой программы. 2.7. Как узнать, нужны ли выполняемому файлу разделяемые библиотеки? Предположим, Вы хотите знать, использует ли некоторый выполняе- мый файл разделяемые библиотеки. Для этого нужно просмотреть заголовки секций файла при помощи команды dump(1): dump -hv файл Если в файле есть секция .lib, он работает с некоторой разделя- емой библиотекой. Ненамного труднее узнать, какие именно разде- ляемые библиотеки требуются, просмотрев содержимое секции .lib: dump -L файл 2.8. Отладка процессов, работающих с разделяемыми библиотеками Возможности такой отладки пока ограничены. Команды и данные из разделяемых библиотек не копируются в выполняемый файл, а sdb(1) не работает с таблицей имен разделяемой библиотеки. Если у Вас создастся впечатление, что ошибка находится вне Вашего файла, Вам, возможно, легче будет провести отладку, пересоздав выполняемый файл, на этот раз с архивной версией библиотеки. 3. СОЗДАНИЕ РАЗДЕЛЯЕМОЙ БИБЛИОТЕКИ В этой части описывается процесс построения разделяемой библио- теки. Рассматриваются основные шаги этого процесса и использо- вание утилиты mkshlib(1) для создания разделяемых библиотек сборки и выполнения. Приводятся также некоторые пояснения п поводу разработки программ для включения в разделяемую библио- теку. Предполагается, что читатель - квалифицированный программист, пишущий на языке C, которому нужно создать разделяемую библио- теку. Предполагается также знакомство с процессом построения архивной библиотеки. Вам нет необходимости читать эту часть, если Вы собираетесь ограничиться лишь использованием ранее соз- данных деляемых библиотек. 3.1. Порядок создания Процесс построения разделяемой библиотеки системы UNIX состоит из шести основных шагов: Выбор адресов секций команд и данных. Выбор маршрутного имени разделяемой библиотеки выполне- ния. Определение содержимого библиотеки. Подготовка исходного текста. Создание файла спецификаций библиотеки. Создание с помощью mkshlib(1) разделяемых библиотек сборки и выполнения. Далее подробно рассматривается каждый из этих шагов. 3.1.1. Выбор адресов секций команд и данных В первую очередь следует выбрать адреса секций для разделяемой библиотеки. Секции разделяемых библиотек должны располагаться с границы, кратной 1 Мб. В следующей таблице приведен список зарезервиро- ванных областей памяти (на момент написания данного руководст- ва) и виртуальные адреса, которые могут использовать вновь соз- даваемые разделяемые библиотеки. ~------------ ------------------------------ ------------------ │Виртуальный │ Описание │ Маршрутное имя │ │ адрес │ │ │ ------------ ------------------------------ ------------------ │ 0x70000000 │Разделяемая библиотека языка C│ /shlib/libc881_s │ │ 0x70200000 │ Сетевая библиотека │ /shlib/libnsl_s │ │ 0x70400000 │ Системный резерв │ не назначено │ │ 0x74000000 │ Для частного использования │ не назначено │ │ . . . │ │ │ │ 0x77FFFFFF │ │ │ ------------ ------------------------------ ------------------ Чем полезна такая таблица? Во-первых, разделяемая библиотека языка C и сетевая разделяемая библиотека находятся там, где это указано, и используют адреса из указанных диапазонов. Если Вы создадите разделяемую библиотеку, использующую зарезервирован- ные адреса, Вы рискуете войти в конфликт с возможными будущими разработками. Примечание В принципе разные библиотеки могут использовать одни и те же виртуальные адреса, если работа с ними ведется в рамках разных процессов. Конфликты по адресам разделяе- мых библиотек возможны не между процессами, а только внутри процесса. Далее, некоторые области памяти предназначены для частного ис- пользования. Если Вы разрабатываете большую систему, состоящую из множества процессов и выполняемых файлов, создание разделяе- мых библиотек может улучшить показатели ее работы. До тех пор, пока Вы не собираетесь создавать разделяемые библиотеки как от дельные программные продукты, Вам следует использовать "част- ные" области, чтобы не возникало конфликтов с коммерческими библиотеками. То же касается и случая, когда Вы будете владель- цем всех выполняемых файлов, работающих с Вашей разделяемой библиотекой. Избегайте конфликта адресов! Примечание Если Вы собираетесь разработать коммерческую разделяе- мую библиотеку, настоятельно рекомендуем создать также совместимую архивную версию этой библиотеки, так как некоторые потенциальные пользователи могут найти ис- пользование разделяемой версии невыгодным, а другим нужно будет выполнять свои программы на версиях ОС UNIX, не поддерживающих разделяемых библиотек. 3.1.2. Выбор маршрутного имени разделяемой библиотеки выполнения После определения адресов секций, необходимо выбрать маршрутное имя для разделяемой библиотеки выполнения. Так, было выбрано имя /shlib/libc_s для стандартной разделяемой библиотеки языка C и /shlib/libnsl_s для сетевой библиотеки (ранее отмечалось, что в маршрутных именах всех статически отредактированных биб- лиотек используется суффикс _s). Чтобы выбрать маршрутное имя для Вашей разделяемой библиотеки, просмотрите список каталогов, имеющихся на Вашем компьютере, или обратитесь к системному ад- министратору. Примите во внимание, что, как правило, разделяе- мые библиотеки, необходимые для загрузки ОС UNIX, находятся в /shlib; разделяемые библиотеки других приложений обычно нахо- дятся в /usr/lib или в каталогах, предоставленных для этих при- ложений. Разумеется, если разделяемая библиотека предназначена только для Вашего личного пользования, можно выбрать любое под- ходящее маршрутное имя для разделяемой библиотеки выполнения. 3.1.3. Определение содержимого библиотеки Самая важная часть процесса создания разделяемой библиотеки - определение ее содержимого. Некоторые подпрограммы являются очевидными кандидатами для разделяемого использования, другие же - нет. Следует включать в разделяемую библиотеку большие и часто используемые подпрограммы, в отличие от маленьких и редко вызываемых. Наполнение библиотеки определяется конкретными пот- ребностями программистов и других пользователей, для которых библиотека разрабатывается. Впрочем, есть несколько общих прин- ципов, которым целесообразно следовать. Они обсуждаются далее в разделе Какие функции целесообразно включать в библиотеку? См. также разделы Использование импортируемых имен и Настройка раз- деляемой библиотеки. 3.1.4. Подготовка исходного текста Если Вы решили включить текст из существующей архивной библио- теки в создаваемую Вами разделяемую, некоторая его модификация облегчит дальнейшее ведение разделяемой библиотеки. См. ниже раздел Подготовка исходного текста для разделяемой библиотеки. 3.1.5. Создание файла спецификаций библиотеки После подготовки исходного текста для разделяемой библиотеки Вам нужно создать файл спецификаций. Этот файл содержит всю ин- формацию, необходимую mkshlib(1) для создания файлов разделяе- мой библиотеки сборки и выполнения. Пример файла спецификаций приводится ниже, в разделе Пример. В файл спецификаций включа- ются следующие директивы [см. также mkshlib(1)]: #address секция адрес Указывает начальный адрес секции разделяемой биб- лиотеки выполнения. Эта директива обычно использу- ется для задания начальных адресов секций .text и .data. #target маршрутное_имя Указывает маршрутное_имя разделяемой библиотеки выполнения, по которому она будет находиться на целевом компьютере. По указанному маршрутному име- ни операционная система ищет разделяемую библиоте- ки выполнения, когда она нужна выполняемому файлу. Обычно, хотя и не обязательно, маршрутное_имя за- дает полный маршрут. Эта директива должна встре- чаться в файле спецификаций один и только один раз. #branch Отмечает начало спецификаций таблицы переходов. Строки, следующие за этой директивой, воспринима- ются как строки спецификации таблицы переходов. Последние имеют следующий формат: имя_функции пробел_или_табуляция позиция Здесь имя_функции - это имя, для которого задается элемент таблицы переходов, а позиция задает пози- цию этого элемента в таблице. Позиция может быть целым числом или диапазоном целых чисел в формате позиция1-позиция2. Каждая позиция должна быть больше нуля и не может быть указана дважды. В таб- лице переходов не должно быть пропущенных позиций, то есть каждое число от 1 и до максимального ука- занного должно быть задействовано. Если для имени указывается несколько элементов таблицы переходов (путем спецификации для него ди- апазона позиций или указания этого имени в нес- кольких строках спецификаций таблицы переходов), то используется максимальная из указанных позиций. Остальные выделенные для него позиции можно счи- тать пустыми и выделять их другим именам в будущих версиях библиотеки. Элементы таблицы переходов следует задавать только для функций, доступных извне библиотеки (экспорти- руемых). Данная директива должна встречаться в файле специ- фикаций один и только один раз. #objects Задает имена объектных модулей, из которых собира- ется разделяемая библиотека выполнения. Строки, следующие за этой директивой, воспринимаются как список объектных файлов, в том порядке, в котором они должны быть загружены в разделяемую библиотеку выполнения. Этот список состоит из имен объектных файлов, разделенных пробелами или символами табу- ляции. Из этих объектных файлов и будет состоять разделяемая библиотека. Данная директива должна встречаться в файле специ- фикаций один и только один раз. #init объектный_файл Указывает, что объектный_файл требует включения инструкций инициализации. Последующие строки расс- матриваются как спецификации таких инструкций и должны иметь следующий формат: указатель пробел_или_табуляция имя Здесь указатель - это указатель на внешнее (импор- тируемое) имя; он должен определяться в объект- ном_файле. Для каждой такой строки генерируются инструкции, соответствующие следующему оператору присваивания языка C: указатель=&имя; Об инициализации указателей см. ниже. Все специфи- кации инициализации для указанного объектного_фай ла должны быть собраны вместе, повторное указание того же объектного_файла не допускается. #ident цепочка_символов Задает цепочку_символов, которая будет включена в секцию .comment разделяемой библиотеки выполнения, а также в одноименные секции каждого из объектных модулей разделяемой библиотеки сборки. Данная директива должна встречаться в файле специ- фикаций один и только один раз. ## комментарий Остаток строки игнорируется. 3.1.6. Создание с помощью mkshlib(1) разделяемых библиотек сборки и выполнения Как разделяемая библиотека сборки, так и разделяемая библиотека выполнения создаются утилитой ОС UNIX mkshlib(1), которая вызы- вает другие программы, например, as(1) или ld(1). Это делается при помощи системного вызова execvp [см. exec(2)], который ищет нужные программы в директориях, определяемых значением перемен- ной окружения $PATH. Утилита mkshlib(1) обрабатывает префиксы подобно команде cc(1), то есть вызываемые программы получают тот же префикс. Например, 3b2mkshlib будет обращаться к 3b2ld. Относительно всех перечисленных утилит см. Справочник пользова- теля. Чтобы использовать mkshlib(1), необходимо подготовить файл спе- цификаций (это мы только что обсудили) и указать опции команд- ной строки. Эти последние мы сейчас и рассмотрим. Синтаксис вы- зова mkshlib(1) таков: mkshlib -s файл_спецификаций [-t библ_выполнения] [-h библ_сборки] [-n] [-L каталог] [-q] Рассмотрим допустимые опции по очереди. -s файл_спецификаций Задает файл_спецификаций разделяемой библиотеки, содержащий всю информацию, необходимую для описа- ния этой библиотеки (о его структуре см. предыду- щий раздел). В него входят спецификации таблицы переходов, маршрутное имя файла, в котором будет находиться разделяемая библиотека выполнения, на- чальные адреса секций команд и данных разделяемой библиотеки выполнения, спецификации инициализации для разделяемой библиотеки сборки, и, наконец, пе- речень объектных файлов, которые должны быть вклю- чены в разделяемую библиотеку. -t библ_выполнения Задает файл, в который будет помещена создаваемая разделяемая библиотека выполнения. На стадии вы- полнения разделяемая библиотека должна находиться в том месте, которое было указано в ее файле спе- цификаций (см. описание директивы #target в преды дущем разделе). Если указана опция -n, генерация разделяемой библиотеки выполнения не производится. -h библ_сборки Задает файл, куда будет помещена разделяемая биб- лиотека сборки. Если эта опция не указана, разде- ляемая библиотека сборки не генерируется. -n Указывает, что не нужно генерировать разделяемую библиотеку выполнения. Эта опция может быть полез- на при создании новой разделяемой библиотеки. При указании опции -n, необходимо тем не менее указы- вать и опцию -t, так как для построения разделяе- мой библиотеки сборки требуется версия разделяемой библиотеки выполнения. -q Не выдавать предупреждающих сообщений. 3.2. Пример В качестве примера мы пройдем все шаги создания небольшой раз деляемой библиотеки. В текст включены необходимые пояснения. Несколько надуманный текст программ примера важен не сам по се- бе, а как иллюстрация к возникающим проблемам. Наша библиотека будет называться libexam. Пусть ее исходный текст первоначально представляет собой единственный файл, со- держащий следующее: /* первоначальный вид exam.c */ #include extern int strlen (); extern char *malloc, *strcpy (); int count = 0; char *Error; char *excopy (e) char *e; { char *new; ++count; if ((new=malloc (strlen (e)+1))==0) { Error="Нет памяти"; return 0; } return strcpy (new, e); } excount () { fprintf (stderr, "сч. вып. %d\n", count); return count: } Для начала выберем адреса библиотечных секций команд и данных (из адресов областей, выделенных для личного пользования). За- метим, что начальные адреса секций должны быть кратны 1 Мб: .text 0x74000000 .data 0x74100000 Далее, определим маршрутное имя разделяемой библиотеки выполне- ния: /my/directory/libexam_s Теперь нужно определить внешние (импортируемые) имена библиоте- ки (рекомендации по этому поводу см. в разделе Использование импортируемых имен): malloc, strcpy, strlen, fprintf и _iob. Макросы для этих имен мы поместим во включаемый файл, причем обратите внимание, что на _iob нет прямой ссылки, это делается через макрос из , определяющий stderr. Заметим также, что именам даны префиксы _libexam_. Указатели на импортируемые имена, будучи доступными извне, могут совпасть с какими-либо другими именами. Использование имени библиотеки в качестве пре- фикса уменьшает вероятность такого конфликта. /* новый файл import.h */ #define malloc (*_libexam_malloc) #define strcpy (*_libexam_strcpy) #define strlen (*_libexam_strlen) #define fprintf (*_libexam_fprintf) #define _iob (*_libexam__iob) extern char *malloc (); extern char *strcpy (); extern int strlen (); extern int fprintf (); Примечание Объект _iob не описывается в файле import.h как внеш- ний. Предполагается, что это делается во включаемом файле . Нам понадобится еще исходный файл, содержащий указатели на им- портируемые имена. Помните, что все глобальные данные должны быть проинициализированы. /* новый файл import.c */ #include char *(*_libexam_malloc) () = 0; char *(*_libexam_strcpy) () = 0; int (*_libexam_strlen) () = 0; int (*_libexam_fprintf) () = 0; FILE (*_libexam__iob) [] = 0; Наконец, посмотрим, какие глобальные переменные библиотеки должны быть доступны использующим ее программам (см. ниже реко- мендацию "уменьшать объем глобальных даннных"). К переменной count обращаются через excount(), поэтому она может не быть описана как внешняя. Опишем ее как static (это уже должно было быть сделано и для архивной перемещаемой библиотеки). Глобальные данные библиотеки необходимо переместить в отдельные исходные файлы (см. ниже рекомендацию "хранить функции и гло- бальные данные в различных исходных файлах"). Осталась единст- венная глобальная переменная Error, и ее нужно проинициализиро- вать (см. ниже рекомендацию "инициализировать глобальные пере- менные"). Error должна остаться глобальной, поскольку через нее передается информация в вызывающую программу. /* новый файл global.c */ char *Error = 0; В связи с перечисленными изменениями исходный файл должен те- перь выглядеть так (обратите внимание, что имена функций необ- ходимо описать как внешние): /* измененный exam.c */ #include "import.h" #include extern int strlen (); extern char *malloc, *strcpy (); static int count = 0; extern char *Error; char *excopy (e) char *e; { char *new; ++count; if ((new=malloc (strlen (e)+1))==0) { Error="Нет памяти"; return 0; } return strcpy (new, e); } excount () { fprintf (stderr, "сч. вып. %d\n", count); return count: } Примечание Файл import.h должен включаться ранее файла . Теперь нужно ввести файл спецификаций разделяемой библиотеки для последующего выполнения mkshlib(1): /* новый файл libexam.sl */ 1 #target /my/directory/libexam_s 2 #address .text 0x74000000 3 #address .data 0x74100000 4 #branch 5 excopy 1 6 excount 2 7 #objects 8 import.o 9 global.o 10 exam.o 11 #init import.o 12 _libexam_malloc malloc 13 _libexam_strcpy strcpy 14 _libexam_strlen strlen 15 _libexam_fprintf fprintf 16 _libexam__iob _iob (Номера строк включены для удобства ссылок; они не являются частью примера.) Вкратце, в этом файле спецификаций указано следующее. В строке 1 задается маршрутное имя разделяемой биб- лиотеки выполнения. В дальнейшем библиотека должна быть помеще- на в этот файл, чтобы использующие ее программы могли выпол- няться. Виртуальные адреса для секций команд и данных библиоте- ки задаются в строках 2 и 3 соответственно. В строках 4-6 опи- сывается таблица. В строках 5 и 6 функциям excopy() и excount() назначаются элементы 1 и 2 таблицы переходов. Напомним, что ее элементы должны назначаться только именам внешних функций. В строках 7-10 перечисляются объектные файлы, из которых и бу- дут собраны разделяемые библиотеки сборки и выполнения. В раз- деляемой библиотеке сборки каждый из них будет оформлен как от- дельный элемент. В разделяемой библиотеке выполнения указанный порядок следования этих файлов сохранится. Первыми нужно указы- вать файлы данных, иначе, например, изменение размера статичес- ких переменных в exam.o привело бы к изменению адресов внешних переменных, после чего ранее созданные программы не смогли бы больше работать с этой библиотекой. В строках 11-16 находятся данные о внешних именах файла import.o. Их можно понимать как своего рода операторы присваи- вания, например, _libexam_malloc будет содержать адрес malloc и т.д. Далее, как и для архивной библиотеки, нужно создать объектные файлы: cc -c import.c global.c exam.c Осталось запустить mkshlib(1) для создания разделяемой библио- теки сборки и разделяемой библиотеки выполнения: -s libexam.sl -t libexam_s -h libexam_s.a Если компиляция исходных текстов прошла нормально, mkshlib(1) создаст разделяемую библиотеку сборки libexam_s.a и разделяемую библиотеку выполнения libexam_s. 3.3. Рекомендации по разработке исходного текста для разделяемой библиотеки Главным преимуществом разделяемой библиотеки перед архивной яв- ляется экономия памяти за счет ее разделенного использования. Следующие далее рекомендации имеют целью помочь Вам добиться такой экономии, а также решить проблему открытости, то есть возможности создания следующих версий библиотеки, совместимых с ее предыдущими версиями. Здесь совместимость означает возмож- ность выполнения с новой версией программ, создававшихся со старой версией библиотеки. Рекомендации иллюстрируются примерами, заимствованными из опыта разработки разделяемой библиотеки языка C. Советуем прежде всего прочитать последующий текст полностью, чтобы получить представление о том, что нужно помнить при соз- дании разделяемой библиотеки; затем этот текст можно использо- вать в качестве справочника в процессе планирования и принятия решений при ее разработке. Прежде чем перейти к самим рекомендациям, упомянем ограничения, существенно влияющие на разработку разделяемой библиотеки. Эти ограничения связаны со статическим редактированием связей, и некоторые из них ниже разъясняются подробнее. Пока же перечис- лим их: Адреса экспортируемых (доступных для внешнего использо- вания) имен фиксированы. Если какой-либо адрес изменит- ся (будь то функции или переменной), Вам придется пере- редактировать все работающие с разделяемой библиотекой выполняемые файлы. Если во время выполнения содержимое разделяемой библио- теки изменяется, оно изменяется для всех процессов. По- этому все изменения, относящиеся лишь к одному процес су, должны касаться данных, а не команд, так как только область данных используется процессами индивидуально (кроме того, область команд доступна только для чте- ния). Если библиотека использует какое-либо имя явно (то есть не через ссылку), его значение (то есть адрес) времени выполнения должно быть известно на стадии создания биб- лиотеки. Нельзя явно ссылаться на импортируемые имена. Их адреса на стадии создания библиотеки неизвестны, а во время выполнения могут оказаться различными для разных про- цессов. Такие имена следует использовать косвенно, че- рез ссылку, находящуюся в области данных библиотеки. 3.3.1. Какие функции целесообразно включать в библиотеку? Включайте большие, часто используемые функции Они являются основными кандидатами для разделенного использова ния, которое уменьшит размер выполняемых файлов и сэкономит оперативную память за счет работы нескольких процессов с одним экземпляром подпрограммы. Хорошим примером являются функции се- мейства printf(3S) из библиотеки языка C (см. Справочник прог- раммиста). ~------------------------------------------------------ │ Когда мы создавали разделяемую библиотеку языка C... │ ------------------------------------------------------ │ Функции из семейства printf(3S) используются часто, │ │ поэтому мы включили их в разделяемую библиотеку. │ ------------------------------------------------------ Не включайте редко используемые функции Их включение в библиотеку может уменьшить эффективность работы компьютера, особенно в системах со страничной организацией вир- туальной памяти. Выполняемые файлы, созданные без применения разделяемой библиотеки, содержат в себе все необходимые им подпрограммы, а память процесса в какой-то степени соответству- ет содержимому выполняемого файла. Поэтому, если процесс обра- щается к функции, велика вероятность того, что она уже находит- ся в оперативной памяти. Если же функция находится в разделяе- мой библиотеке, она, вероятно, к этому моменту будет вытеснена из оперативной памяти. Вряд ли одному выполняемому файлу пона- добится все содержимое разделяемой библиотеки. Если же несколь- ко процессов будут случайным образом вызывать функции библиоте- ки, интенсивность страничного обмена с диском, скорее всего, возрастет. См. также пункт "увеличивайте локальность обраще- ний". ~------------------------------------------------------ │ Когда мы создавали разделяемую библиотеку языка C... │ ------------------------------------------------------ │ Мы удалили из первоначального варианта разделяемой │ │ библиотеки несколько небольших, редко используемых │ │ функций. Дело в том, что разделенное использование │ │ функций, нужных немногим выполняемым файлам, не при- │ │ ведет к значительной экономии на размерах файлов, и │ │ в то же время может понизить эффективность выполне- │ │ ния за счет более интенсивного страничного обмена. │ ------------------------------------------------------ Не включайте функции, которым нужно много статических данных Такие функции увеличивают объем памяти процессов. В разделах Как ОС UNIX работает с разделяемыми библиотеками? и Использо- вать ли разделяемую библиотеку? отмечалось, что каждый процесс получает свою копию секции данных разделяемой библиотеки, неза- висимо от того, какая ее часть действительно необходима. Данные не используются процессами совместно и не могут предоставляться выборочно. Например, getgrent(3C) (см. Справочник программиста) редко при- меняется в стандартных утилитах ОС UNIX. Некоторые ее версии требуют более 1400 байт данных. Такие функции лучше не включать в разделяемую библиотеку. Можно импортировать глобальные, но не локальные (статические) данные. Не включайте функции, затрудняющие сопровождение библиотеки Адреса всех имен, доступных для внешнего использования, должны быть постоянными. Таблица переходов обеспечивает это для функ- ций, однако подобный механизм для данных отсутствует. Чем боль- ше переменных определяется в библиотеке, тем больше вероятность того, что длину некоторых из них придется когда-нибудь изме- нить, а любое изменение размеров каких-либо экспортируемых пе- ременных может привести к изменению адресов других. В результа- те библиотека станет несовместимой со своими предыдущими верси- ями. Включайте функции, используемые другими функциями библиотеки Лучше, чтобы библиотека была замкнутой. Например, printf(3S) обращается ко многим подпрограммам стандартной библиотеки вво- да/вывода. Разделяемая библиотека, содержащая printf(3S), долж- на содержать и эти подпрограммы. Примечание Из перечисленных в этом разделе, эта рекомендация - на- именьшая по значимости. Основываясь на других рекомен- дациях, Вы, возможно, исключите некоторые нужные библи- отеке функции, сделав их внешними (импортируемыми). 3.3.2. Подготовка исходного текста для разделяемой библиотеки Все, что написано на языке C и годится для разделяемой библио- теки, может использоваться и в архивной библиотеке. Обратное, вообще говоря, неверно, так как в разделяемой библиотеке могут оказаться внешние (импортируемые) имена. Рекомендации этого раздела помогут Вам перерабатывать исходный текст функций для разделяемой библиотеки так, чтобы они становились лишь немного медленнее и больше по размеру, чем их версии для архивной биб- лиотеки. Здесь, главным образом, освещаются вопросы организации данных, так как большинство проблем совместимости связано со структурами данных в разделяемых и архивных библиотеках. Уменьшайте объем глобальных данных В настоящее время все внешние переменные разделяемой библиотеки глобальны, то есть доступны прикладным программам, что может затруднить поддержку библиотеки. Вам необходимо стремиться уменьшить объем глобальных данных, следуя изложенным ниже сове- там. Во-первых, используйте, где возможно, стековые (automatic) пе- ременные. Это уменьшает объем статических данных и количество переменных, доступных прикладным программам. Во-вторых, проверяйте, действительно ли та или иная переменная должна быть внешней. Статические переменные невидимы для прик- ладных программ, поэтому их адреса могут быть изменены в после- дующих версиях библиотеки. Только адреса внешних переменных должны оставаться постоянными. В-третьих, не определяйте память для переменных при компиляции, а запрашивайте ее на стадии выполнения. Это важно по двум при- чинам. Уменьшается размер области данных библиотеки, что эконо- мит память процессов, которые будут получать только ту память, которая им действительно нужна. Кроме того, размеры динамически размещаемых переменных могут изменяться без ущерба для совмес- тимости (размеры статически размещаемых переменных нельзя изме- нить, не изменяя адресов других переменных). Храните функции и глобальные данные в различных исходных файлах Отделение команд от данных уменьшает вероятность перемещения данных. Если нужно определить новые экспортируемые переменные их можно добавить в конец соответствующего файла, не меняя ад- ресов уже определенных переменных. Из архивной библиотеки редактор внешних связей извлекает от- дельные элементы, поэтому программисты обычно помещают функцию и соответствующие ей данные в один и тот же исходный файл. Это хорошо только при условии, что адреса данных определяются в процессе редактирования связей, однако для разделяемых библио- тек имеется ряд ограничений. Если внешние (экспортируемые) пе- ременные разбросаны по различным модулям библиотеки, они пере- мешиваются с невидимыми пользователю данными. Изменение этих невидимых извне данных, например цепочки символов "hello" в следующем примере, приводит к смещению остальных данных, в том числе и экспортируемых. Рассмотрим два варианта программы: int head=0; int head=0; func() func() { { . . . . . . p="hello"; p="hello, world"; . . . . . . } } int tail=0; int tail=0; Пусть относительный виртуальный адрес переменной head будет ну- левым для обоих примеров. Текстовые константы тоже будут иметь одинаковые адреса, но их длины различны. Старый адрес tail бу- дет равен 12, а новый - 20, поэтому, если предполагается, что переменная tail будет доступна извне библиотеки, две приведен- ные версии программы будут несовместимы. Добавление новых экспортируемых переменных в разделяемую библи- отеку может изменить адреса статических имен, но это не влияет на совместимость. Выполняемый файл не может прямо ссылаться на статические имена, поэтому он не зависит от их адресов. В связи с этим, следует собрать вместе все экспортируемые данные и раз- местить их выше (с меньшими адресами) статических (невидимых) данных. Для этого в списке объектных файлов файла спецификаций указывайте первыми файлы с глобальными данными: #objects data1.o . . . lastdata.o text1.o text2.o . . . Если модули с данными включать не первыми, безобидное, казалось бы, изменение, например, замена текстовой константы, может при- вести к потере работоспособности уже созданных выполняемых фай- лов. Пользователи разделяемой библиотеки, независимо от организации исходных файлов библиотеки, получают всю ее область данных во время выполнения. Поэтому Вы можете без опасений хранить описа- ния всех экспортируемых переменных в одном исходном файле. Ра- зумеется, их можно хранить и в нескольких файлах. Инициализируйте глобальные переменные Инициализируйте экспортируемые переменные, в том числе и указа- тели на внешние (импортируемые) имена, хотя это и увеличивает размер разделяемой библиотеки выполнения. Это увеличение каса- ется только файла самой библиотеки. Инициализация этих перемен- ных дает дополнительную гарантию неизменности их адресов в бу- дущем. C-компилятор ОС UNIX V собирает неинициализированные переменные в отдельную секцию, а редактор внешних связей назначает им ад- реса в непредсказуемом порядке. Иными словами, порядок следова- ния неинициализированных переменных может измениться после сле- дующего редактирования внешних связей библиотеки. Порядок же инициализированных переменных остается неизменным, что помогает обеспечить совместимость будущих версий библиотеки с предыдущи- ми. Сохраняйте порядок следования функций в таблице переходов Новые функции добавляйте в конец таблицы переходов. Создавая новый файл спецификаций библиотеки, старайтесь добиться совмес- тимости с более ранними ее версиями. Для этого добавлять новые функции нужно так, чтобы адреса уже включенных функций не изме- нялись. В этом случае с новой версией поставляемой Вами библио- теки смогут без перередактирования работать все выполняемые файлы, созданные с предыдущей версией. 3.3.3. Использование импортируемых имен Функции разделяемой библиотеки не могут прямо ссылаться на оп- ределяемые вне библиотеки имена, но существует способ обойти это ограничение: в области данных нужно определить указатель на это имя и обеспечить правильную его инициалазацию на этапе вы- полнения. Внешними (импортируемыми) могут быть как имена функ- ций, так и имена переменных. Более того, импортируемыми могут быть имена, определяемые пользователем, определяемые в другой библиотеке, либо даже определяемые в самой разделяемой библио- теке. На рисунке, приведенном ниже, внешние имена _libc.ptr1 и _libc.ptr2 определяются пользователем, а _libc_malloc определя- ется внутри разделяемой библиотеки. Разделяемая библиотека Выполняемый файл Адреса ~------------------ ~------------------ │ ... │ │ ... │ ------------------ ------------------ │ malloc() │ 420 <- ~----> 5000 │ ptr1 │ ------------------ │ │ ------------------ │ ... │ │ │ │ ... │ ------------------ │ │ ------------------ │ _libc.ptr1 │ │ │ 5020 │ ptr2 │ │ 5000 │ ----- -- ^ ------------------ ------------------ │ │ │ ... │ │ _libc_malloc │ │ │ ------------------ │ 420 │ ----- │ ------------------ │ │ _libc.ptr2 │ │ │ 5020 │ --------------- ------------------ │ ... │ ------------------ Далее мы рассмотрим некоторые вопросы использования внешних имен. Внешние имена, определяемые вне библиотеки Архивные библиотеки обычно состоят из перемещаемых файлов, со- держащих неразрешенные внешние ссылки. Хотя разделяемая библио- тека сборки и является архивом, нельзя забывать о разделяемой библиотеке выполнения, которая во многом похожа на выполняемый файл. В частности, разделяемая библиотека выполнения не может содержать неразрешенных внешних ссылок. Следовательно, разделяемая библиотека должна обеспечивать адре- сацию всех имен, которые в ней используются, но не определяют- ся, то есть импортировать эти имена. Очевидно, некоторые разде- ляемые библиотеки будут строиться на основе уже существующих архивных библиотек. В силу изложенных выше причин, не все моду- ли архива целесообразно включать в разделяемую библиотеку. Если что-либо, необходимое библиотеке, останется вне ее, Вам придет- ся определить указатель на соответствующее имя. Внешние имена, которые пользователь мог бы переопределить Разделяемые библиотеки могут объявлять свои внутренние имена импортируемыми. С первого взгляда это может показаться излиш- ним, однако рассмотрим следующую проблему. Функции семейства malloc имеются в двух архивах, libc и libmalloc. Хотя команды ОС UNIX, как правило, используют malloc из libc, они могут пользоваться любой библиотекой, или же определять malloc самос- тоятельно. ~------------------------------------------------------ │ Когда мы создавали разделяемую библиотеку языка C... │ ------------------------------------------------------ │ У нас было три варианта работы с malloc. Во-первых, │ │ мы могли не включать malloc(3X) в библиотеку. malloc │ │ вызывается другими функциями библиотеки, поэтому она │ │ стала бы импортируемой. Недостаток этого варианта │ │ заключается в уменьшении экономии памяти. │ │ │ │ Во-вторых, мы могли бы включить в библиотеку семей- │ │ ство malloc(3X), не импортируя его. Это дало бы эко- │ │ номию памяти для большинства приложений. Недостаток │ │ этого варианта заключается в том, что некоторые при- │ │ ложения переопределяют malloc(3X). Обращения из биб- │ │ лиотечных функций, однако, по-прежнему направлялись │ │ бы к библиотечной malloc. Кроме того, редактор внеш- │ │ них связей мог бы обнаружить повторное определение │ │ malloc(3X), и чтобы избежать этого, пришлось бы либо │ │ отказаться от использования разделяемой библиотеки, │ │ либо разработчику библиотеки нужно было бы изменить │ │ ее исходный текст, устранив оттуда имя malloc(3X). │ │ │ │ Наконец, объявив имя malloc(3X) импортируемым, мы │ │ могли бы, тем не менее, ключить malloc(3X) в разде- │ │ ляемую библиотеку, что мы и сделали. Хотя malloc(3X) │ │ и находится в библиотеке, на нее нигде нет прямых │ │ ссылок. Если прикладная программа не переопределяет │ │ malloc(3X), как пользовательские, так и библиотечные │ │ обращения к ней используют библиотечную версию. Со- │ │ ответственно, если malloc переопределяется пользова- │ │ телем, все обращения направляются к его версии. │ ------------------------------------------------------ Можно обеспечить возможность переопределения всех без исключе- ния функций библиотеки. Для этого нужно объявить импортируемыми все имена, определяемые в библиотеке, в дополнение к тем, кото- рые в библиотеке используются, но не определяются. Хотя это несколько уменьшает быстродействие и увеличивает затраты памя- ти, таким образом обеспечивается стопроцентная совместимость с существующими архивными библиотеками как во время редактирова- ния внешних связей, так и на этапе выполнения. Техника импорта имен Пусть в разделяемой библиотеке нужно сделать malloc импортируе- мым. Далее слева приведен исходный текст для архива, а справа - переработанный текст для разделяемой библиотеки. /* см. ниже pointers.c */ extern char *malloc(); extern char *(*_libc_malloc)(); export () export () { { . . . . . . p=malloc (n); p=(*_libc_malloc) (n); . . . . . . } } Метод преобразования исходного текста очевиден, но все же нужно одновременно поддерживать два варианта исходного текста: для архивной и для разделяемой библиотеки. Несколько простых макро- определений обеспечат трансформацию текста на этапе трансляции, сделав исходные тексты совместимыми. Нужно только хранить две версии включаемого файла, содержащего макроопределения. Чтобы указать препроцессору C-компилятора, в каком каталоге ис- кать файл с макросами, можно использовать опцию -I команды cpp(1). Далее снова приведены два варианта исходного текста включаемого файла import.h, причем справа записан вариант для разделяемой библиотеки: /* пустой файл */ /* Макросы для импортируемых имен: по одному на имя. */ . . . #define malloc (*_libc_malloc) . . . Эти включаемые файлы позволяют использовать один и тот же ис- ходный текст как для архивной, так и для разделяемой библиоте- ки, поскольку в последнем случае обеспечиваются косвенные ссыл- ки на импортируемые имена. Общий исходный текст: #include "import.h" extern char *malloc (); export() { . . . p=malloc (n); . . . } В случае использования разделяемого варианта import.h описание malloc() в действительности есть описание указателя _libc_malloc. Другой вариант: поместить #include внутрь #ifdef: #ifdef SHLIB # include "import.h" #endif extern char *malloc (); export() { . . . p=malloc (n); . . . } Разумеется, преобразование исходного текста этим не ограничива- ется. Нужно еще определить указатель _libc_malloc. Сделаем это в файле pointers.c: char *(*_libc_malloc) () = 0; Поскольку указатель _libc_malloc доступен извне библиотеки, ему присваивается начальное значение. Команды разделяемой библиотеки не должны использовать этот ука- затель до тех пор, пока в нем не будет находиться правильное значение, которое устанавливается командами инициализации, включаемыми в результирующий загрузочный файл средствами пост- роения разделяемой библиотеки, на основе файла спецификаций. В примере выше адрес функции malloc должен быть присвоен указате- лю _libc_malloc. Фрагменты инициализации указателей В модуле разделяемой библиотеки сборки могут определяться один или несколько указателей на импортируемые имена. Независимо от их количества, каждый такой указатель должен быть правильно проинициализирован. Соответствующие команды помещаются в выполняемый файл и обеспе- чивают следующее: во-первых, появляются неразрешенные внешние ссылки на импортируемые имена, которые разрешаются при редакти- ровании внешних связей, и, таким образом, импортируемые имена определяются. Во-вторых, указатели на импортируемые имена полу- чат правильные значения еще до того, как программа дойдет до функции main. Тем самым импортируемые имена определяются и ста- новятся доступными для использования. Примечание Фрагменты инициализации указателей находятся в разде ляемой библиотеке сборки, а не в разделяемой библиотеке выполнения, поэтому они помещаются в выполняемый файл редактором внешних связей. В файле спецификаций библиотеки указывается, как именно нужно инициализировать указатели на импортируемые имена. Например, следующая строка в файле спецификаций обеспечит присваивание адреса malloc указателю _libc_malloc: #init pmalloc.o _libc_malloc malloc Когда утилита mkshlib(1) будет создавать разделяемую библиотеку сборки, она модифицирует файл pmalloc.o, добавив туда переме- щаемые команды, соответствующие оператору присваивания: _libc_malloc = &malloc; Эти команды редактор внешних связей извлечет из разделяемой библиотеки сборки вместе с файлом pmalloc.o и поместит в выпол- няемый файл. Затем редактор разрешит внешние ссылки и соберет все фрагменты инициализации вместе. При запуске выполняемого файла стартовая процедура crt1 выполнит эти фрагменты, устано- вив таким образом правильные значения библиотечных указателей. Избирательное использование импортируемых имен В каждом элементе архива лучше определять небольшое количество указателей на импортируемые имена. Это предотвращает включение в выполняемый файл лишних объектных модулей. Например, если в элементе архива определены три указателя, все три ссылки будут разрешены редактором внешних связей, даже если реально будет использоваться только одна из них. Чтобы уменьшить количество дозагружаемых модулей, можно сделать несколько исходных файлов, определяющих указатели на импорти- руемые имена по одному, либо небольшими группами. Если импорти- руемая функция будет использоваться индивидуально, указатель на нее следует поместить в отдельный исходный файл (тогда он попа- дет в отдельный элемент архива), что позволит редактору внешних связей строить оптимальные выполняемые файлы. Рассмотрим несколько примеров. В первом приближении можно опре- делить все указатели в одном исходном файле pointers.c: . . . int (*_libc_ptr1)() = 0; char *(*_libc_malloc)() = 0; int (*_libc_ptr2)() = 0; . . . Чтобы соответствующие функции можно было бы использовать по от- дельности, нужно иметь несколько исходных файлов, и, соответст- венно, несколько элементов архива. В каждом исходном файле дол- жен определяться один указатель или небольшая группа совместно используемых указателей. Пример: ~----------- ------------------------------ │ Файл │ Содержимое │ ----------- ------------------------------ │ ptr1.c │ int (*_libc_ptr1)() = 0; │ │ pmalloc.c │ char *(*_libc_malloc)() = 0; │ │ ptr2.c │ int (*_libc_ptr2)() = 0; │ ----------- ------------------------------ Ранее все указатели определялись в единственном объектном фай- ле, pointers.o. Его извлечение из библиотеки в ходе редактиро- вания внешних связей потребовало бы определения ptr1, malloc и ptr2. Разделение этого файла на три позволяет использовать каж- дый указатель по отдельности, избегая таким образом возникнове- ния неразрешенных внешних ссылок на ненужные функции. 3.3.4. Обеспечение совместимости с архивной библиотекой Совместимость библиотек облегчает замену одной на другую. Почти во всех случаях такой совместимости можно достичь без каких-ли- бо изменений в исходных или make-файлах. Проще всего пояснить это на примере: ~------------------------------------------------------ │ Когда мы создавали разделяемую библиотеку языка C... │ ------------------------------------------------------ │ Мы взяли за основу существующую архивную библиотеку, │ │ что, очевидно, дало нам исходные тексты подпрограмм │ │ и модель для определения содержимого разделяемой │ │ библиотеки. │ │ │ │ Мы хотели, чтобы разделяемая библиотека сборки была │ │ совместима с существующей перемещаемой библиотекой │ │ языка C. Вместе с тем, нам не хотелось включать в │ │ разделяемую библиотеку выполнения все подпрограммы │ │ из архивной библиотеки, так как это понизило бы эф- │ │ фективность работы разделяемой версии. │ │ │ │ Достичь обеих целей было проще, чем может показать- │ │ ся. Мы создавали разделяемую библиотеку сборки в два │ │ этапа. На первом мы использовали средства построения │ │ разделяемых библиотек и получили разделяемую библио- │ │ теку сборки, точно соответствующую разделяемой биб- │ │ лиотеке выполнения. Затем мы добавили в разделяемую │ │ библиотеку сборки недостающие объектные (перемещае- │ │ мые) модули из архивной библиотеки языка C. Хотя со- │ │ ответствующие функции и отсутствуют в разделяемой │ │ библиотеке выполнения, их наличие в библиотеке сбор- │ │ ки обеспечивает совместимость разделяемой и переме- │ │ щаемой библиотек языка C. │ ------------------------------------------------------ 3.3.5. Настройка разделяемой библиотеки В этом разделе мы приведем ряд советов по настройке разделяемой библиотеки с целью достижения наибольшей эффективности ее ис- пользования. Они основаны на опыте разработки разделяемой биб- лиотеки языка C и касаются систем со страничной организацией виртуальной памяти, например ОС UNIX V версия 3.1. Архивная библиотека языка C содержит несколько разнородных групп функций. Процессы могут использовать различные функции из этих групп, поэтому трудно определить заранее характер странич- ного обмена, возникающего при работе любой разделяемой библио- теки C. Вероятно, использование разделяемой библиотеки дает бо- лее занчительное преимущество при более однородном ее составе. Например, применение разделяемой библиотеки для управления ба- зами данных могло бы существенно уменьшить страничный обмен, если бы можно было предварительно определить статические и ди- намические зависимости между ее функциями. Постройте временной профиль программы [см. profil(2)] Выясните предварительно временной профиль функций, которые можно было бы включить в разделяемую библиотеку. Определите содержимое библиотеки Основываясь на информации, полученной на предыдущем шаге, опре- делите, что именно следует включить в разделяемую библиотеку. Размер выполняемого файла - статическая характеристика, а ин- тенсивность страничного обмена - динамическая. Вытекающие отсю- да требования могут противоречить друг другу, поэтому Вам, воз- можно, придется решать, оправдана ли экономия дисковой памяти на фоне ухудшения показателей времени выполнения. См. выше раз- дел Какие функции целесообразно включать в библиотеку?. Группируйте взаимосвязанные функции Будучи помещенной в выполняемый файл, функция может оказаться на одной странице с более часто используемыми функциями (см. выше рекомендацию "не включать редко используемые подпрограм- мы"). Старайтесь свести вместе одновременно используемые функ- ции. Если каждый вызов funcA порождает обращения к funcB и funcC, постарайтесь разместить все три функции на одной страни- це. Информацию о такого рода статических зависимостях выдает cflow(1) (см. Справочник пользователя). Используйте ее, наряду с информацией о структуре библиотечных функций, для определе- ния, какие функции действительно вызываются, в отличие от тех, которые только могли бы вызываться. Выравнивайте на границы страниц Важно так организовать объектные файлы для разделяемой библио- теки, чтобы каждая часто вызываемая функция, по возможности, размещалась бы внутри одной страницы. Организуя объектные файлы для разделяемой библиотеки выполнения, не забудьте отделять данные от команд. Порядок следования объектных файлов с коман- дами можно изменить без ущерба для совместимости, что неверно для объектных файлов, в которых определяются глобальные данные. Опять-таки, лучше всего пояснить это на примере: ~------------------------------------------------------ │ Когда мы создавали разделяемую библиотеку языка C... │ ------------------------------------------------------ │ У нас был компьтер со страницами размером 2K. Изучив │ │ таблицы перекрестных ссылок и результаты дизассемб- │ │ лирования разделяемой библиотеки выполнения, мы оп- │ │ ределили, где будут находиться границы страниц. │ │ │ │ Разбив функции на группы взаимосвязанных, мы разде- │ │ лили их далее на небольшие, размером со страницу, │ │ порции (большинство функций, хотя и не все, помеща- │ │ ются внутри одной страницы). Между этими порциями мы │ │ разместили редко вызываемые функции. Поскольку они │ │ вызываются реже, чем функции, находящиеся внутри │ │ страниц, вероятность вытеснения нужных страниц из │ │ памяти уменьшается. Определив таблицу переходов, мы │ │ затем переупорядочили объектные файлы библиотеки без │ │ ущерба для совместимости. Мы собрали вместе несвя- │ │ занные, но часто вызываемые функции, так как решили, │ │ что случайные обращения к ним обеспечат удержание │ │ соответствующих страниц в памяти. Системные вызовы │ │ образовали группу для другой страницы, и т.д. │ ------------------------------------------------------ Следующий пример показывает, как изменился порядок следования объектных файлов в библиотеке (слева приведен первоначальный порядок, справа - модифицированный): #objects #objects . . . . . . printf.o strcmp.o fopen.o malloc.o malloc.o printf.o strcmp.o fopen.o . . . . . . Учитывайте особенности оборудования Изучение особенностей используемой аппаратуры может обеспечить дальнейшее повышение эффективности. Например, следует принять во внимание способ управления виртуальной памятью. Частью соот- ветствующего оборудования является кэш для преобразования вир- туального адреса в физический. Следует добиваться того, чтобы выполнение типичного процесса не вызывало конфликтов по исполь- зованию кэша. 3.3.6. Обеспечение совместимости с будущими версиями Здесь мы приводим ряд рекомендаций по конструированию таких библиотек, для которых облегчается создание новых версий, сов- местимых с более ранними. Заметим, что необходимость заботиться об этом возникает не всегда. Если разделяемая библиотека явля- ется частью некоторой большей системы и не поставляется в ка- честве отдельного программного продукта, Вы может учесть все работающие с библиотекой выполняемые файлы и перередактировать их всякий раз, когда Вы изменяете библиотеку. Разумеется, это затруднит развитие Вашей системы, но это - вполне реальный путь. Проверка версий библиотеки на совместимость Обычно разработчики разделяемых библиотек хотят, чтобы новая версия была совместима с предыдущей, иначе выполняемые файлы, созданные со старой библиотекой, не смогут работать с новой. Опишем процедуру проверки двух библиотек на совместимость. Мы предполагаем, что библиотеки совместимы, если их экспортируемые имена имеют одинаковые адреса. Этот критерий надежен, хотя и не является достаточным. Например, если изменить количество аргу- ментов у некоторой функции, не меняя ее адреса, библиотеки не будут совместимы. Пусть нам нужно сравнить две разделяемые библиотеки выполнения: new.libx_s и old.libx_s. Мы используем команду nm(1) для полу- чения списка имен и sed(1) для исключения всех имен, кроме внешних. Работу может облегчить небольшой sed-сценарий, который мы поместим в файл cmplib.sed: /|extern|.*/!d s/// /^.bt/d /^etext /d /^edata /d /^end /d Согласно этому сценарию будут удалены все строки, кроме тех, где упомянуты внешние имена, а затем останутся только сами эти имена и их значения (адреса). Последние четыре строки удаляют специальные имена, не влияющие на совместимость библиотек, так как они невидимы для прикладных программ. Теперь можно получить списки имен и их адресов для обеих библи- отек, старой и новой: nm old.libx_s | sed -f cmplib.sed > old.nm nm new.libx_s | sed -f cmplib.sed > new.nm Сравним теперь соответствующие адреса: diff old.nm new.nm Утилита diff(1) не выведет ничего, если все соответствующие имена обеих библиотек имеют одинаковые адреса. В этом случае библиотеки совместимы. Если diff(1) что-либо выведет, адреса некоторых имен отличаются, поэтому библиотеки, возможно, несов- местимы. О diff(1) и sed(1) см. также в Справочнике пользовате- ля. Как работать с несовместимыми библиотеками Если две библиотеки оказались несовместимыми, Вы можете отреа- гировать на это двумя способами. Во-первых, можно вновь создать все выполняемые файлы, работающие с библиотекой. Если это воз- можно, лучше всего так и сделать. К сожалению, эти файлы не всегда можно найти, а тем более заставить их владельцев вновь создавать их с Вашей новой библиотекой. Поэтому Вы можете воспользоваться другим вариантом: дать новой версии разделяемой библиотеки выполнения новое маршрутное имя. Маршрутные имена разделяемых библиотек сборки и выполнения не- зависимы, поэтому маршрутное имя разделяемой библиотеки сборки можно не менять. Новые выполняемые файлы будут работать с новой разделяемой библиотекой выполнения, а старые - по-прежнему со старой. Как разработчику разделяемой библиотеки, Вам необходимо прове- рить новую версию на совместимость с предыдущей, и, если нужно, назначить новое маршрутное имя для разделяемой библиотеки вы полнения. Если Вы этого не сделаете, выполняемые файлы, исполь- зующие Вашу библиотеку, потеряют работоспособность. Примечание Лучше избегать параллельного использования нескольких версий одной разделяемой библиотеки, поскольку все эти версии вместе могут потребовать больше дисковой и опе- ративной памяти, чем могло бы понадобится соответствен- ной архивной (перемещаемой) версии. 4. РЕЗЮМЕ Мы описали разделяемые библиотеки ОС UNIX с точки зрения их разработки и применения. Использование любой разделяемой библи- отеки, как правило, обеспечит экономию дисковой и оперативной памяти, а также ресурсов процессора. Поэтому применение разде- ляемых библиотек следует настоятельно рекомендовать.