Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Конференция «Технологии управления данными 2018»
СУБД, платформы, инструменты, реальные проекты.
29 ноября 2018 г.

Назад Содержание Вперед

Введение в POSIX'ивизм

(C) Алексей Федорчук, 2005
Опубликовано на сайте LinuxCenter

Глава 14. Принципы сборки и установки пакетов

Как уже говорилось в главе 2, дистрибутивы Linux организованы по пакетному принципу. Точно также, в виде пакетов, распространяются и любые программы, создаваемые независимыми разработчиками (из которых в основном и собираются дистрибутивы Linux). А в BSD-системы попакетно включаются все приложения, не входящие в состав базового комплекта. И потому одна из важных задач пользователя - это интеграция пакетов в свою систему.

Содержание

Очень элементарное введение

В большинстве случаев эта задача решается за пользователя разработчиками его операционки: системы управления пакетами (менеджеры пакетов) составляют неотъемлемую часть любого дистрибутива Linux, Free- и прочих BSD. Однако в ряде случаев пользователю приходится устанавливать пакеты и самостоятельно. К тому же понимание сути этого процесса зело способствует уяснению того, что же делают пакетные менеджеры, и помогает принять правильное решение в нештатных ситуациях. Так что вопрос этот заслуживает подробного рассмотрения.

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

Однако сначала - маленькое введение для тех, что компьютерное образование начиналось не с книжек Брябрина и Фигурнова, а с руководств типа "Word за 5 минут" или "Quark Press для полных идиотов" (это - не в обиду читателям, но в упрек писателям). Все прочие могут смело пропустить нижеследующие элементарные рассуждения.

Так вот, программы, то есть наборы инструкций, предписывающие машине выполнить то или иное действие (от самых элементарных, типа - скопировать файл, до предельно сложных), сочиняются программистами на языках программирования:-). И представляют они собой самые обычные тексты (называемые исходными текстами, исходниками или, уж совсем жаргонно, сырцами - sources), в которых необходимые инструкции описываются в соответствие с принятыми в данном языке правилами (синтаксисом языка). Собственно говоря, именно это мы и проделывали в параграфе о сценариях из главы 12.

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

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

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

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

Процесс интерпретации выполняется каждый раз при исполнении программы. И, соответственно, каждый раз время затрачивается на выполнение одних и тех же процедур. Так что возникает резонный вопрос - а нельзя ли оттранслировать исходник программы в машинные инструкции раз и навсегда, и в дальнейшем запускать на исполнение уже набор таких инструкций, не затрачивая время на их преобразование?

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

Предварительно оттранслированные (прекомпилированные) программы, по вполне очевидным причинам, выполняются много быстрее, чем программы интерпретированные, причем разница в скорости нарастает с объемом. И потому все масштабные программы, как правило, пишутся в рассчете на использование в откомпилированном виде. Хотя и роль интерпретируемых программ - сценариев разного рода - в POSIX-системах не стоит недооценивать.

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

К первым принадлежат постоянно упоминавшиеся ранее языки командных оболочек. Однако ими список интерпретируемых языков не исчерпывается. В POSIX-системах широко используются такие мощные средства, как Perl, Python, Ruby, Tcl/Tk. Они предоставляют большие возможности, вплоть до создания графических пользовательских инструментов. Однако принципиально написанные на них программы ничем не отличаются от сценариев командной оболочки.

Компилируемых языков - также великое множество, начиная с пресловутого Basic'а и заканчивая специализированными средствами типа Fortran. Однако в POSIX-системах наибольшее значение имеют программы на языке C. Который, собственно, и создавался для разработки первозданного Unix. И на котором написана большая часть ядра всех POSIX-совместимых систем (а ядро ОС - это почти такая же компилируемая программа, как и любая другая), а также большая часть их приложений.

Так что далее речь пойдет о сборке преимущественно C-программ. Однако для нас это существенного значения не имеет - ведь собственно программированием заниматься мы не будем, а принципы сборки практически не зависят от используемого языка. В частности, точно по той же схеме собираются и графические приложения, в которых широко используется язык C++.

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

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

Вот их, как правило, и не программируют. А объединяют соответствующие директивы в отдельные программные комплексы, именуемые библиотеками. Сами по себе они к автономному исполнению не пригодны. Однако любая программа, при необходимости совершить одно из типовых действий, вызывает из такой библиотеки некий фрагмент кода, содержащий требуемую последовательность директив.

Библиотеки обычно привязаны к определенным языкам программирования, синтаксису которого подчиняются описания директив (т.н. функции - о них вкратце говорилось в заключении главы 12). Поскольку наиболее употребимым в POSIX-системах и их приложениях является язык C, то его функции и требуются чаще всего. Они собираются в главную системную библиотеку, которая именуется обычно libc (Library C), хотя реально это разные комплексы, отличающиеся полнотой функций и их описанием и зависящие от конкретной операционной системы.

В подавляющем большинстве дистрибутивов Linux используется реализация главной системной библиотеки, именуемая glibc (GNU Library C); специализированные дистрибутивы могут использовать и другие библиотеки, например, uclibc, менее функциональную, но более компактную. Главная системная библиотека FreeBSD называется просто - libc, и функционально близка к glibc, хотя и не идентична ей.

Однако libc (glibc) список библиотек не исчерпывается. В POSIX-системах используются библиотеки свойств терминала (например, ncurces) для консольных программ и библиотеки, описывающие процедуры управления окнами - для графических программ системы X (xlib), библиотеки интерфейсных элементов и графических примитивов (Motif, Qt, Gtk), библиотеки описания графических и мультимедийных форматов. Короче говоря, существует тенденция к вынесению в разделяемые библиотеки всех повторяющихся действий и элементов. И в этом - одна из причин компактности большинства классических Unix-программ, в том числе и предназначенных для работы в графическом режиме.

Правила сборки

Ну все, с элементарным введением покончено. Переходим собственно к пакетам и их сборке.

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

Наборы исходников объединяются разработчиками в т.н. пакеты, о которых уже упоминалось на протяжении всего этого повествования. Пакет - понятие очень широкое и многогранное. Это может быть и простая монофункциональная утилита (например, строчный текстовый редактор ed или архиватор tar), более или менее обширный набор функционально связанных программ (скажем, coreutils) или огромный программный комплекс (примером чему - XFree86 или Xorg).

Следует оговориться, что термин пакет (английское package) постоянно употребляется в двух смыслах: как набор исходных текстов и как комплект скомпилированных из него программ и всех их служебных файлов. Обычно различие между ними ясно из контекста, в случаях же неоднозначности тот или иной смысл будет оговариваться явно.

Пакеты принято распространять в виде компрессированных архивов - файлов вида *.tar.gz (*.tgz) или *.tar.bz2 (*.tbz2, *.tbz), так называемых тарбаллов. Обычно действует правило: один тарбалл - один пакет. Очень большие пакеты могут быть поделены на несколько тарбаллов (примером чему те же XFree86 или Xorg), но делается это исключительно для удобства скачивания, все равно такой набор тарбаллов исходников сохраняет свою целостность.

Прекомпилированные пакеты подчас также распространяются разработчиками в виде точно таких же тарбаллов. Но тут уже корреляции пакет - тарбалл может и не быть. Так, XFree86, кроме исходников, доступен также в виде серии скомпилированных пакетов для нескольких дистрибутивов Linux, Free- и OpenBSD. Но это уже - именно самостоятельные пакеты, и не все они обязательны к установке. А сборщики дистрибутивов могут и далее дробить изначально единый пакет, как это обычно делается с теми же Иксами.

И еще. В предыдущих главах я говорил, что BSD-системы, в отличие от Linux, не имеют пакетной организации. Это не совсем точно. Конечно, например, из FreeBSD Distributions нельзя выделить ядро системы или наборы базовых утилит в виде отдельных пакетов. Однако сам по себе он - в сущности единый пакет, только очень большой. А то, что перед пользователем (на CD ли диске, или на ftp-сервере) он предстает перед пользователем в виде кучи мелких (по 1,44 Мбайт) пакетиков - просто наследие тех времен, когда системы еще устанавливались с дискет. В NetBSD и OpenBSD же базовая система собрана в виде единого тарбалла (так и называемого - base.tgz), и уже совсем ничем не отличается от обычных пакетов.

В последнее время и в некоторых дистрибутивах Linux прослеживается тенденция отказа от "квантования" базовой системы. Так, в Gentoo она вся собрана в три тарбалла (stage1, stage2, stage3), и может быть развернута (с различной полнотой, в зависимости от схемы инсталляции) из любого из них. А в Sorcerer и его клонах базовый тарбалл вообще единственный.

Однако я отвлекся, вернемся к нашим исходниками и посмотрим, что с ними нужно делать - ведь ясно, что в том виде, в каком они распространяются, использование их невозможно.

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

  • конфигурирование;
  • собственно сборку;
  • инсталляцию.

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

Понятие зависимостей - одно из основных при сборке программ. Суть его в том, что пакет pkgname1 для установки и (или) функционирования требует наличия в системе пакета pkgname2, тот, в свою очередь, может потребовать пакета pkgname3, и так далее. В качестве зависимостей выступают часто (хотя и не всегда) те самые системные библиотеки, о которых говорилось ранее.

Зависимости пакетов бывают разными. С одной стороны, различают зависимости при сборке (обычно называемые просто зависимостями - depends) и зависимости при запуске (по английски именуемые run depends).

Как следует из названий, при зависимости пакеты, от которых зависит данный, необходимы только на стадии сборки пакета, тогда как зависимости второго рода действуют постоянно. В большинстве случаев depends и run depends эквивалентны, однако это правило имеет многочисленные исключения. Так, при статической сборке (что это такое - будет говориться чуть позднее) библиотеки, которые использует данный пакет, требуются только в момент компиляции - в дальнейшем необходимости в них не возникает.

С другой стороны, следует различать зависимости жесткие и "мягкие". Удовлетворение первых абсолютно необходимо для сборки данного пакета. Так, практически любая программа использует (статически или динамически) главную системную библиотеку glibc (или libc), любое приложение для системы X - главную Иксовую библиотеку xlib, все приложения для интегрированной среды KDE - библиотеки qt и kdelibc.

"Мягкие" зависимости данного пакета не критичны для его функционирования - удовлетворение их лишь добавляет ему дополнительные функции (которые могут оказаться и лишними).

Понятие зависимостей пронизывает насквозь POSIX-совместимые системы, и особенно важно для свободных их представителей. В то же время пользователи Windows с ним сталкиваются очень редко, и потому постижение его вызывает определенные трудности у недавнего подоконника. Это связано с двумя факторами.

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

Возможно, разработчик Windows-программы с удовольствием последовал бы примеру братьев-POSIX'ивистов. Однако исходники Windows-библиотек в большинстве своем закрыты и защищаются всякого рода проприетарными лицензиями, препятствующими их свободному использованию. И потому Windows-разработчику волей-неволей приходится реализовывать требуемые ему функции самостоятельно. В результате чего программа, хотя и приобретает определенную самодостаточность, но зато разбухает в размерах: вспомним, сколько файлов вида *.dll устанавливает элементарный графический вьювер, идущий в комплекте со сканером или цифровой камерой.

За конфигурирование обычно отвечает сценарий, расположенный в корне дерева исходников данной программы и, по соглашению, носящий имя configure. Что именно делает конкретный конфигурационный скрипт - сугубо на совести разработчика программы. Как минимум, он обязан проверять жесткие зависимости устанавливаемого пакета и, при их нарушении, выдавать соответствующие сообщения. Кроме того, он может обеспечивать также подключение дополнительных функций - при наличии определенных условий, то есть удовлетворении зависимостей "мягких".

Так, для консольных программ в Linux существует возможность использования мыши в качестве указательно-позиционирующего устройства (а не только для выделения/вставки экранных фрагментов, как это имеет место в BSD-системах). Обеспечивается эта функция специальным сервисом - gpm. И во многих программах (таких, как файловый менеджер Midnight Commander или текстовый браузер links) конфигурационный скрипт проверяет, установлен ли пакет gpm, и при наличии его - автоматически задействует использование мыши.

Если процесс конфигурирования завершается успешно, в корне дерева каталогов создается специальный файл - Makefile, в котором и фиксируются все предусмотренные разработчиком настройки, выступающие в качестве директив на следующем этапе.

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

Образцово-показательный отчет о выполнении сценария configure выдают, по моему мнению, пакеты, штатно входящие в состав интегрированной среды KDE. Во-первых, их конфигурирование не обрывается сразу же после нахождения первой ошибки (первого нарушения зависимостей - например, отсутствия каких-либо мультимедийных или графических библиотек), как это бывает в большинстве других программ, а в любом случае доводится до конца. После чего сообщается, что такие-то компоненты необходимы для сборки данного пакета (то есть связаны с ним жесткими зависимостями), другие же - требуются для получения определенных функций (например, наличие пакета cups - для обеспечения печати на принтере, пакета sane - для сканирования, и так далее). И пользователю вольно решить - устанавливать ли ему "мягко-зависимые" пакеты, или он, за отсутствием сканера или принтера, вполне может обойтись без них.

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

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

И потому вторая стадия - это связывание (linking, в просторечии именуемое линковкой) сгенерированного кода с необходимыми библиотечными фрагментами. Линковка может быть двух видов - статическая и динамическая. В первом случае требуемый код из библиотеки встраивается внутрь собираемой программы, после чего получается готовый к исполнению бинарный файл, более в библиотеке не нуждающийся. Это - именно тот случай, когда понятия depends и rdepends приобретают разное значение: первое оказывается шире.

Второй случай - динамической линковки, - предполагает, что библиотечный код не встраивается в программу: вместо него устанавливается только ссылка на файл библиотеки и требуемую функцию (имя которой извлекается из так называемого заголовочного файла - header-файла). И в дальнейшем, при запуске исполняемого модуля программы, соответствующие библиотечные фрагменты извлекаются с диска и присоединяются к коду программы уже только в оперативной памяти. При этом сущности depends и rdepends оказываются идентичными: библиотека, с которой программа связывается при сборке, столь же необходима и для ее запуска.

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

Однако бывают ситуации, когда приходится прибегать к линковке статической. Так, во FreeBSD статически линкуются с главной системной библиотекой жизненно важные для запуска и восстановления системы утилиты (в предыдущих версиях этой ОС они располагались в каталогах /bin и /sbin, во FreeBSD 5-й ветки для них отведен специальный каталог /restore). В результате они оказываются доступными (и пригодными к исполнению) даже в случае аварийной загрузки, когда все файловые системы, кроме корневой, не монтируются (а разделяемые библиотеки вполне могут располагаться на самостоятельных физических носителях со своими файловыми системами).

Третий этап процесса сборки - инсталляция. Это - инкорпорация всех компонентов программы в структуру файловой системы данной машины. Или, по простому, по бразильскому - их копирование в соответствующие каталоги файлового древа (по завершении сборки они могут находиться в самых разных местах, обычно - в подкаталогах дерева исходников). Как правило, для разных компонентов пакета существуют традиционно предопределенные имена каталогов, в которых они должны размещаться: bin или sbin - для исполняемых модулей, lib - для библиотек, etc - для конфигурационных файлов, share - для всякого рода документации и примеров, и так далее.

Предопределенные имена каталогов не обязательно будут ветвями корня файловой системы (типа bin, /sbin и так далее). Точнее, в общем случае, не будут: более вероятно, что соответствующие компоненты собираемого пакета помещаются в каталоги /usr/bin, /usr/local/bin и так далее. Впрочем, к обсуждению этого вопроса мы скоро вернемся.

Так вот, процесс инсталляции и сводится к тому, что исполняемый файл (файлы) собранного пакета копируется в файл ~/bin (или, для программ системного назначения, в ~/sbin), ее конфиг - в ~/etc, страницы документации - в ~/man, и так далее (~/ в данном случае символизирует не домашний каталог пользователя, а некий условный префикс - см. далее).

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

Три волшебных слова

Теперь, разобравшись с принципами, посмотрим, как сборка пакетов осуществляется на практике.

Понятное дело, что перво-наперво тарбалл исходников следует декомпрессировать и развернуть в каком-либо подходящем каталоге. Для самостоятельно собираемых исходников я использую обычно каталоги вроде $HOME/src или, в некоторых случаях, /usr/local/src (разумеется, для этого нужно обладать правами на запись в тот или иной). Как станет ясным из дальнейшего, удаление развернутого дерева исходников установленных программ очень нежелательно, поэтому следует озаботиться наличием достаточного количества свободного места в той файловой системе, в которой выполняется распаковка.

Сама по себе распаковка делается обычным образом, например, командой tar:

$ tar xzpvf /path_to_src/tarball.tar.gz

или

$ tar xjpvf /path_to_src/tarball.tar.bz2

в зависимости от использовавшейся для компрессии программы (gzip или bzip2, соответственно). Кратко остановлюсь на смысле опций (команда tar будет предметом отдельного рассмотрения впоследствие).

Опция x (от eXtract) предписывает развертывание архива. Однако поскольку он был ранее сжат утилитой компрессии, его предварительно нужно декомпрессировать - этому служит опция z при gzip или j при bzip2. Опция f имеет своим значением имя подвергающегося развертыванию/декомпрессии файла - в примере tarball.tar.*. Опция v не обязательна - она заставляет выводить на экран сообщения о ходе распаковки.

А вот опция p может быть важной: она предписывает сохранять атрибуты доступа и принадлежности теми же, что были у оригинальных файлов до их упаковки в тарбалл. Без нее хозяином всех новораспакованных файлов оказался бы пользователь, выполняющий процедуру распаковки. Обычно это не имеет значения, но в некоторых случаях - нежелательно, или просто не должно быть (например, при распаковке прекомпилированных тарбаллов stage1-3 в Gentoo). Так что лучше взять себе за правило не забывать про эту опцию никогда.

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

$ find /path_to_src -name *.tar.gz -exec tar xzpvf {} \;

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

$ tar -tzvf tarball.tar.gz

где опция t (от lisT) и предписывает вывести список файлов тарбалла вместо его развертывания.

Дальнейшие действия по сборке пакета в большинстве случаев осуществляются путем последовательной отдачи трех команд - ./configure, make, make install.

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

$ cd /path2srcpkg
$ ./configure

Это - запуск того самого конфигурационного скрипта, о котором давеча говорилось. Обращаю внимание на ./ - эти символы являются указателями на текущий каталог (/path_to_srcpkg), в котором расположен файл сценария (а мы помним, что текущий каталог, как правило, не включается в число значений переменной $PATH).

Сценарий configure имеет некоторое количество опций. Число их и назначение определяются разработчиком, однако некоторые - встречаются практически всегда. И важнейшая из них - это опция --help, выводящая полный список всех других опций. Строго говоря, именно с команды

$ ./configure --help

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

В аккуратно написанных программах вывод команды ./configure --help обычно распадается на несколько секций. Первой, как правили, идет секция

Installation directories:

в которой указываются каталоги, куда в дальнейшем будут устанавливаться отдельные компоненты собранного пакета. Важнейшей опцией здесь является --prefix=PREFIX. Значением PREFIX будет выступать ветвь корневого каталога, в подкаталоги которого запишутся исполняемые файлы, конфиги, библиотеки и т.д. По умолчанию эта опция в большинстве случаев имеет значение /usr/local. То есть в случае, если значение опции --prefix при запуске скрипта ./configure не задано, то исполнимые файлы пакета будут инсталлированы в /usr/local/bin, конфиги - в >/usr/local/etc, и так далее.

Так что если желательно размещение компонентов собираемого пакета в каталоге, отличном от умолчального (например, в /usr), значение перфикса следует задать в явном виде, скажем, так:

./configure --prefix=/usr

В последнее время некоторые пакеты предполагают установку по умолчанию в подкаталоги каталога /opt - /opt/pkgname/bin, /opt/pkgname/lib, и так далее. А для KDE-приложений последних версий умолчальное значение перфикса - /opt/kde (/opt/kde/bin, /opt/kde/lib и так далее). Для таких программных комплексов лучше его не менять - во избежание осложнений при поиске библиотек.

А вообще, значение опции --prefix может быть любым. В частности, если предполагается сборка пакета для дальнейшего автономного его распространения в бинарном виде, целесообразно сосредоточить все его компоненты в отдельном подкаталоге, например, вида $HOME/my_pkg/pkg_name. Аналогично следует поступать и при тестировании пакета, предшествующем его инсталляции.

Далее в той же секции обычно имеет место быть опция --bindir=DIR. И здесь значением DIR выступает обычно PREFIX/bin. Однако в некоторых случаях исполнимые файлы пакета целесообразно поместить в иные ветви файловой системы. Например, если вручную собирается командная оболочка, которая будет выступать в дальнейшем как login shell, или любимый (=общесистемный) текстовый редактор, очень желательно, чтобы их исполняемые бинарники находились непосредственно в каталоге корневой файловой системы (иначе они могут быть недоступны в аварийных случаях или при старте в однопользовательском режиме). И тут, вне зависимости от того, задано ли значение опции --prefix или нет, конфигурационный скрипт следует запускать в такой форме:

$ ./configure --bindir=/bin

или, для программ административного назначения, -

$ ./configure --bindir=/sbin

Нередко в секции Installation directories можно обнаружить и другие опции, предписывающие размещение библиотек, страниц документации и тому подобных компонентов пакета.

Следующая почти непременная секция вывода помощи конфигурационного скрипта -

Optional Features:

Именно в ней, как легко понять из названия, перечисляются опции, позволяющие подключить/отключить дополнительные возможности собираемого пакета. Они имеют вид

--enable-FEATURE

и

--disable-FEATURE

Первая, естественно, подключает возможность с именем FEATURE, а вторая - отключает оную. Причем одна из этих опций может быть принята по умолчанию. Например, большинство свободных программ ныне собирается с поддержкой национальных языков (National Labguage Support - NLS), обеспечивающих вывод сообщений, пунктов меню, помощи т т.д. на языках, отличных от американского (при наличии соответствующих ресурсов, разумеется - если систему помощи некоего пакета никто не удосужился перевести на русский язык, то включай NLS, не включай - все едино, русского хелпа от нее не получишь). Однако в ряде случаев это может показаться нежелательным - и тогда при конфигурировании программы нужно задать соответствующею опцию:

$ ./configure --disable-nls

Обычно допустима и иная форма этой опции:

$ ./configure --enable-nls=no

Функции, подключаемые (или отключаемые) посредством опции --enable(disable)-FEATURE, берутся из библиотечных пакетов. В частности, за поддержку NLS отвечает библиотека gettext (подчеркну, что сама по себе эта библиотека не дает возможности волшебным образом получать сообщения на русском или там эскимосском языке, а только обеспечивает принципиальную возможность вывода таковых).

Сходный смысл имеют опции, входящие в секцию

Optional Packages:

общий вид которых таков:

with-PACKAGE

или

without-PACKAGE

Отличие опций этой секции от тех, что перечислены в секции Optional Features - в том, что в качестве значений PACKAGE выступаю не некие абстрактные функции, а имена конкретных пакетов, возможности которых добавляются к собираемому (или отнимаются от оного).

Опции вида with-PACKAGE могут также иметь значения - yes, no или auto. Последняя обычно принята по умолчанию. То есть, если в ходе выполнения конфигурационного скрипта пакет с именем PACKAGE будет обнаружен в данной системе, его возможности будут подключены автоматически, если нет - проигнорированы.

Так, в приводимом ранее примере с поддержкой указующе-позиционирующих свойств мыши при сборке консольных программ типа links или mc они будут автоматически задействованы по умолчанию, если в Linux-системе (к BSD это не относится) обнаружится установленный пакет gpm. Если же он имеется, но поддержка мыши для данного пакета представляется нежелательной, это следует указать в явном виде:

$ ./configure --without-gpm

И последняя секция, практически всегда присутствующая в выводе помощи конфигурационного скрипта -

Some influential environment variables:

Как следует из названия, здесь перечисляются различные переменные окружения, могущие оказывать влияние на процесс компиляции. Наиболее часто в качестве таких переменных предусматриваются флаги компилятора gcc типа CFLAGS и CXXFLAGS (для программ на языке Си и Си++, соответственно). Обычное употребление таких флагов - задание всякого рода оптимизаций - общего ее уровня, архитектуры целевого процессора, конкретных наборов его команд, и так далее. Например, оказание опции

$ ./configure CFLAGS="-O3 -march=pentium4"

обеспечит максимальный уровень оптимизации (значение -O3) для процессора Pentium4 (значение -march=pentium4) - опять же заостри внимание на кавычках, в которые эти значения заключены (дабы восприниматься как единый аргумент). Впрочем, тема оптимизации будет предметом особого рассмотрения.

Ранее я говорил, что при сборке пакеты могут связываться с библиотечными функциями как статически, так и динамически. Так вот, характер связи также определяется на стадии начального конфигурирования. По умолчанию во всех известных мне случаях используется динамическая линковка. Что связать исполняемый модуль с библиотекой статически, требуется специальный флаг - LDFLAGS. Значениями его в данном случае будут

$ ./configure LDFLAGS="-static"

или, в случае линковки с несколькими библиотеками -

$ ./configure LDFLAGS="-all-static"

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

Надо отметить, что некоторые пакеты не имеют конфигурационного сценария в дереве исходников. Это не обязательно следствие лени разработчика: может быть, что программа настолько проста, что в предварительном конфигурировании не нуждается (например, не использует функций никакой разделяемой библиотеки).

Другая возможная причина отсутствия скрипта configure - то, что предварительное конфигурирование уже выполнено разработчиком. В этом случае в дереве исходников можно обнаружить уже готовый Makefile, рассчитанный на некоторые типовые ситуации, или несколько его вариантов - для разных архитектур, операционных систем и так далее. Если же ни один из предложенных автором вариантов не отвечает в полной мере реалиям пользователя - у него остается последний выход: ручная правка Make-файла (обычно такие случаи документируются в файлах типа README или INSTALL./p>

Предварительное конфигурирование пакета - очень важный момент в его сборке: можно сказать, что успех ее на 90% определяется именно в результате исполнения скрипта configure. Однако рано или поздно оно завершается удачно (о случаях фатального невезения я скажу несколько позже). И наступает время собственно сборки, для чего предназначено второе из наших магических заклинаний - команда make.

Сама по себе команда make не выполняет ни компиляции (это - дело компилятора gcc, ни линковки (с этой ролью справляется редактор связей ld), ни каких-либо иных действий по превращению исходного текста в машинный код. Задача ее - интеграция всех требуемых средств (а в процессе сборки могут задействоваться и т.н. препроцессоры, и языковые анализаторы, возможно, и иные инструменты), чтобы автоматически получить (почти) готовые к употреблению бинарные компоненты пакета. Собственно говоря, от пользователя требуется только дать директивное указание - набрать в командной строке make и нажать Enter - все остальное произойдет как бы само собой. С другой стороны, у него нет и возможности вмешаться в процесс сборки (разве что прервать его комбинацией клавиш Control+C:-)).

Конечно, и команда make способна воспринимать многие параметры командной строки. Правда, в большинстве случаев они дублируют опции, заданные при конфигурировании. Так, при отдаче директивы make можно задать флаги оптимизации

$ make CFLAGS="-O3 -march=pentium4"

предписать статическую линковку с разделяемыми библиотеками

$ make LDFLAGS="-static"

или предписать последующую установку компонентов пакета в каталог, отличный от умолчального. Однако - и все: далее остается только дожидаться успешного (надеюсь) окончания сборки.

Однако у команды make есть еще один важный вид аргументов командной строки - так называемые цели (target). Собственно для сборки по умолчанию они обычно не требуются. Хотя некоторые пакеты требуют их задания в явном виде. Так, оконная система X штатным образом собирается с указанием цели world:

$ make world

В других случаях для достижения того же результата может применяться и иная цель, например

$ make all

или указание на конкретную архитектуру, операционную систему, и т.д. Все такого рода исключения, как правило, описаны в сопроводительной документации. На худой конец (если таковой не имеется), допустимые для данного пакета цели команды make можно подсмотреть в Makefile - там они будут определены обязательно.

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

$ make install

После чего мы наконец получаем готовую к употреблению программу.

В некоторых программах цель install разработчиком не предусматривается. И тут приходится вручную скопировать скомпилированные модули в подходящие каталоги. Правда, такое бывает очень редко и, как правило, для очень простых по устройству программ.

В принципе команду make install можно было бы дать и сразу по исполнении сценария ./configure. В этом случае сначала будет исполнена умолчальная цель make - компиляция и линковка пакета, а затем его инсталляция. Однако делать это не всегда целесообразно. Во-первых, такое совмещение целей затрудняет отслеживание ошибок. Во-вторых, их совместному исполнению может помешать отсутствие должных прав доступа.

Дело в том, что стадии конфигурирования и сборки обычно могут быть выполнены от имени обычного пользователя - это определяется правами доступа к каталогу, в который было распаковано дерево исходных текстов пакета. А вот установка собранных компонентов почти наверняка потребует административных привилегий. Ведь исполняемые файлы пакета после директивы make install копируются (если придерживаться умолчальной схемы) в каталог /usr/local/bin, документация - в /usr/local/share, и так далее. А все они закрыты для изменения кем бы то ни было, кроме root'а.

Так что, прежде чем начинать установку собранного пакета, следует озаботиться получением соответствующих полномочий командой

$ su

или, в некоторых случаях, даже

$ su -

которая приведет среду исполнения команды в соответствие с конфигурацией суперпользователя.

Впрочем, иногда полномочия администратора могут оказаться необходимыми и при сборке программы или ее конфигурировании. Типичный тому пример - сборка ядра Linux штатными средствами, в ходе которой задействуются скрипты, требующие root-доступа. Впрочем, это - тема отдельной беседы.

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

В случае ошибки при сборке пакета перед пользователем, как обычно, появляется два выхода: а) бросить это занятие, попробовав отыскать и установить прекомпилированный вариант пакета, и б) разобраться в причинах ошибки и попытаться ее устранить.

Наиболее часто сообщение об ошибке возникает в ходе предварительного конфигурирования. И, в большинстве случаев, связано с нарушением зависимостей, жестких или "мягких" - обычно это можно понять, внимательно читая экранный вывод.

С нарушением жестких зависимостей все ясно - нужно установить пакет, от которого зависит собираемый, и все - вариантов тут не предлагается. Нарушения же "мягких", но тем не менее принятых разработчиком по умолчанию, зависимостей обычно можно избежать посредством явного указания опций конфигурирования - типа disable-FEATURE и --without-PACKAGE.

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

$ ln -s libname.xxx libname.yyy

где xxx - номер старой версии библиотеки, а yyy - актуальный ее вариант.

Другой случай - когда сценарий конфигурирования пакета ищет библиотеку, от которой он зависит, не в том каталоге, где она реально располагается. Так, старые приложения KDE могут ожидать требуемых им библиотек в каталогах типа /usr/local/qt и /usr/local/kde, тогда как ныне они, скорее всего, будут располагаться в ветвях каталога /opt.

И тут выход из положения не сложен. Во-первых, можно задать переменную окружения

LDPATH="/opt/qt:/opt/kde"

значения которой точно определяют каталоги соответствующих программных комплексов.

Во-вторых, эти значения можно просто указать в качестве опций конфигурационного скрипта:

$ ./configure --with-qt-dir=/opt/qt \
	--with-kde-dir=/opt/kde

Реже бывают ошибки при исполнении команды make. Однако бороться с ними труднее. Общий рецепт тут дать очень трудно. Можно только посоветовать внимательно читать вывод сообщений о ходе компиляции, непосредственно предшествующих ее обрыву.

Ошибки при инсталляции связаны, почти всегда, с отсутствием прав на запись в каталоги, в которые помещаются устанавливаемые компоненты пакета. Иногда же они возникают вследствие того, что целевой каталог просто не существует. Например, в таких дистрибутивах Linux, как CRUX и Archlinux, из пакетов штатного комплекта изъята вся документация, кроме man-страниц. И, соответственно, отсутствуют каталоги для помещения документации в форматах info и html. А поскольку практически любая программа проекта GNU сопровождается info-документацией, попытка инсталляции ее вызывает ошибку. Побороть которую очень просто: нужно только уничтожить в дереве исходников соответствующие подкаталоги.

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

$ make clean

Она вычистит дерево исходников от объектных модулей, собранных при предыдущей компиляции. Сохранив, тем не менее, созданные при конфигурировании Make-файлы. Если нужно избавиться и от них - существует цель

S make distclean

по исполнении которой дерево исходников теоретически должно приобрести первозданный вид.

Пакеты, как правило, устанавливаются для того, чтобы запускать входящие в них программы. Однако не исключено, что первый же запуск новой программы показывает, что она делает что-то не то или не так, нежели это нужно пользователю. Или просто ему не нравится. И возникает вопрос - а как удалить такой неподходящий пакет?

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

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

$ make uninstall

реже -

$ make deinstall

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

Важно, что make uninstall не затрагивает пользовательских настроечных файлов - т.н. dot-файлов в его домашнем каталоге, которые часто генерируются автоматически при первом запуске программы. Такие файлы при необходимости в любом случае придется удалять вручную. Казалось бы - неудобство, однако сейчас мы увидим, что это не лишено резонов.

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

Так вот, единственный способ обновления собственноручно собранной программы - это собрать и инсталлировать ее по новой. При этом теоретически старые компоненты должны затереться новыми. Однако на практике от старой версии могу остаться хвосты, не только бесполезные, но, возможно, и конфликтующие с файлами новой версии.

И потому при обновлении версий вручную обычно рекомендуется сначала удалить старую версию, и лишь затем инсталлировать новую. Предварительно убедившись, конечно, в успешности сборки ее (то есть выполнив стадии ./configure и make - это еще одна причина для обособления цели make install) и работоспособности (для чего можно пробно запустить исполняемый файл пакета прямо из каталога исходников).

В то же время при смене версий, как правило, желательно сохранить все выполненные ранее настройки пакета - подчас это весьма трудоемкая процедура. И вот тут-то и оказывается, что пользовательские настройки удаленной версии остались в неприкосновенности в домашнем каталоге./p>

Особенности сборки ядра

Ядро свободной POSIX-системы - это (почти) такая же программа, как и любая другая, распространяемая в исходных текстах. И потому оно также должно быть приведено в пригодное к употреблению состояние посредством сборки. Однако ядро все же - не пользовательское приложение и даже не системная утилита, и потому процесс сборки ядра имеет свою специфику.

Кроме того, ядро - это тот компонент, который и отличает в первую очередь одну POSIX-систему от другой - например, Linux от FreeBSD. Поэтому процесс сборки ядра для каждой операционки имеет свою специфику. Тогда как все сказанное выше касаемо сборки пакетов имеет силу (с очень незначительными оговорками) для любой POSIX-системы - причем даже не обязательно свободной: открытые приложения и утилиты для проприетарной Solaris или AIX в принципе собираются точно так же, как для Linux или какой-либо BSD.

Тем не менее, сборка ядра любой из рассматриваемых ОС включает те же стадии, что и сборка иных приложений - предварительное конфигурирование, собственно сборку и инсталляцию.

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

Заметим, что, когда речь заходит о драйверах устройств, распространяемых отдельно от ядра операционной системы (например, производителями оборудования - некоторые из них признали ныне факт существования операционок, отличных от Microsoft Windows), имеются ввиду именно загружаемые модули ядра. Подобно другим программам, они могут существовать в виде исходников или в бинарном виде. В первом случае их теоретически можно собрать для любой версии ядра (или, по крайней мере, для диапазона близких версий), разумеется, только данной ОС (драйверы для Linux, как можно догадаться, не будут работать во FreeBSD, и наоборот). Бинарные же, прекомпилированные, драйверы обычно жестко привязаны к версии ядра, хотя иногда могут как-то работать и при смене оной.

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

Во FreeBSD традиционным инструментом конфигурирования ядра выступает обычный текстовый редактор. С его помощью конфиг умолчального ядра (т.н. ядра GENERIC, устанавливаемого при инсталляции системы) приводится в соответствие с потребностями пользователя - одни опции отключаются, другие - включаются.

С отключаемыми опциями все понятно - они просто изымаются из умолчального конфига (путем установки символа комментария на соответствующих строках). А вот откуда взять опции недостающие? Они отыскиваются в некоем образцово-показательном конфигурационном файле. Во FreeBSD 4-й ветки (и это унаследовано в DragonFlyBSD) такой файл носит имя LINT (во FreeBSD для конфигов ядра принято употреблять символы верхнего регистра - этим подчеркивается величие сей программы) и был похож на настоящий - хотя и не работал (в смысле - скомпилировать из него работоспособное ядро было невозможно). В 5-й ветке на смену ему пришел файл NOTES - уже без претензий на всамделишность, это просто список всех теоретически доступных опций конфигурирования, снабженных достаточно подробными комментариями.

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

В первозданном (или каноническом - том, что скачивается с http://www.kernel.org) ядре Linux никакого умолчального конфигурационного файла не предусмотрено: он генерируется при первом же запуске штатного инструмента для ядерной настройки, базируемого все на той же утилите make - то есть представляющего собой одну из обычных ее целей.

Точнее, не одну - для изначального конфигурирования ядра предусмотрено ажно четыре цели: make config, make menuconfig, make xconfig и make gconfig. Все они делают одно дело - но каждая по своему.

Команда make config вызывает текстовый конфигуратор ядра, работающий в диалоговом режиме. То есть он требует ответа на множество вопросов, для которых предусмотрены варианты ответов - Yes и No, а для многих опций - еще и M (Module), который и обеспечивает подключение модульной поддержки (как и во FreeBSD, таковая возможна не для всех опций).

Использование make config представляется не очень удобным - в случае малейшей ошибки имеется только одна возможность для ее исправления - оборвать программу (например, через Control+C) и начать все заново. И вообще, этот метод конфигурирования считается устаревшим, и в ядрах ветки 2.6.X цель make config штатно не документирована. Хотя и может использоваться при желании - ее преимущество (по моему, несколько сомнительное) в том, что, в отличие от прочих средств конфигурирования, она не требует прав суперпользователя.

Команда make menuconfig, напротив, загружает весьма наглядный меню-ориентированный конфигуратор, богато оформленный псевдографикой. В каждом из подпунктов меню достаточно отметить нужные опции для их включения и, напротив, снять отметки для отключения. Опции, для которых реализована модульная поддержка, могут быть отмечены соответствующим образом.

Использование make menuconfig - пожалуй, наиболее употребимый (и удобный) способ конфигурирования ядра Linux. Нужно только помнить, что запуск соответствующей команды требует полномочий root'а: не то чтобы при этом происходит что-то особо брутальное, просто таковы атрибуты доступа к используемым целью menuconfig скриптам.

Команды make xconfig и make gconfig вызывают конфигураторы ядра, работающие в графическом режиме и, соответственно, могут быть запущены только в терминальном окне сеанса Иксов. В версиях ядра до 2.4.X включительно предусматривалась только первая из указанных целей, которая вызывала достаточно простую меню-ориентированную программу. В ядрах ветки 2.6.X цель make xconfig претерпела существенные изменения: теперь ею вызывается весьма "тяжелая", основанная на библиотеке Qt, программа, требующая также наличия в системе установленной среды KDE и пакета разработки kdevelop, что делает ее практически недоступной для многих пользователей. Ну а make gconfig влечет загрузку аналогичного по функциональности конфигуратора, но основанного на библиотеке Gtk. По причине стойкой нелюбви к последней я этим методом никогда не пользовался и ничего сказать о нем не могу.

Есть в Linux и еще несколько целей, обеспечивающих конфигурирование ядра. Так, командой make defconfig можно сгенерировать некий умолчальный ядерный конфиг - в нем будут включены именно те опции и модули, которые отмечены по умолчанию в меню make menuconfig.

При смене версии ядра можно прибегнуть к цели make oldconfig. Она восстановит конфигурацию ядра прежнего, запросив ответы только на вопросы, касающиеся вновь появившихся опций.

И, наконец, (почти) полный список доступных для конфигурирования ядра и его сборки целей можно получить посредством

$ make help

Собственно сборка ядра и в Linux, и в BSD-системах осуществляется командой make - возможно, с указанием неких конкретных целей. Останавливаться на их описании я сейчас не буду - тому придет свое время. Замечу только, что в Linux дополнительно нужно собрать и модули - командой

make modules

тогда как во FreeBSD это произойдет по умолчанию одновременно с компиляцией ядра.

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

Файл образа ядра во FreeBSD всегда носит имя kernel. В Linux же существует несколько видов образов, за которыми традициями закреплены фиксированные имена - типа vmlinuz, bzImage и еще несколько, о чем мы подробно поговорим тогда, когда до этого дойдет дело.

Как и компоненты любого прикладного пакета, образ ядра и сопровождающие его модули должны быть инсталлированы в должное место - в те подкаталоги, где система ожидает обнаружить их при загрузке. В Linux таких мест традиционно два - каталог /boot или, реже, корень файловой системы, тогда как для модулей предназначается каталог /lib/modules/X.Y.Z, где X.Y.Z - номер версии ядра (например, 2.6.7). Во FreeBSD, начиная с 5-й ветки, под ядро и модули отведен каталог /boot/kernel.В DragonFlyBSD (и остальных BSD-системах) ядро по прежнему размещается в корне файловой системы, модули же - в каталоге /modules.

Так вот, процесс инсталляции и сводится к тому, что файл образа ядра вместе с объектными модулями (а это ныне файлы вида *.ko - для отличия от обычных объектных файлов) копируется в надлежащие каталоги. Для этого предназначены специальные цели команды make. Во FreeBSD это make install или make kernelinstall. В Linux для установки модулей предназначена цель make modules_install, а само ядро может быть инсталлировано различными способами (в том числе - и просто ручным копированием).

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

Вопросы оптимизации

Сказавши "а", логично было бы добавить "б". А именно, после пересборки ядра - выполнить оптимизацию приложений под имеющееся "железо", по крайней мере - критически важных для производительности системы в данных условиях. Тем более что современные версии компилятора gcc предоставляют к тому много возможностей.

Выбор версии компилятора gcc вообще очень важен для целей оптимизации. До недавнего времени практически на равных существовали две его ветки - 2.95.X и 3.X. Причем преимущества второй были отнюдь не очевидны. Дело в том, что ветка 3.X на протяжении длительного времени развивалась в сторону удучшения генерации кода Си++, тогда как код, генерируемый из программ на чистом Си, по свидетельству знатоков, получался даже хуже, чем при использовании gcc-2.95.X. И потому эта версия рекомендовалась для сборки наиболее ответственных частей Linux- и BSD-систем, таких, как ядро, гланая системная библиотека и сам компилятор, не содержащие кода Си++. А это и есть первые претенденты на оптимизацию, не считая "тяжелых" приложений типа KDE или GNOME, а также мультимедийных и узкоспециальных программ. В BSD-системах gcc-2.95.X вообще использовался по умолчанию. Однако компилятор этой ветки не предусматривал возможности оптимизации под современные процессоры с их специализированными наборами инструкций (типа SSE и 3DNow!) - ибо появился задолго до них.

Положение изменилось с выходом gcc-3.4.X, который первым из своей линии генерировал Си-код не хуже предшественника - а подчас и лучше. И потому использование его видится предпочтительным для всех компонентов любых систем. Правда, он по сию пору входит не во все, даже весьма современные, дистрибутивы Linux, а из BSD-семейства штатно предусмотрен только в DragonFlyBSD, но это, вероятно, вопрос времени.

Оптимизация приложений достигается заданием соответствующих флагов - параметров компилятора gcc, определяемых при его вызове. Первый среди них - собственно уровень оптимизации, который задается флагом -O# и может варьировать от нулевого (флаг -O0, при котором никакой оптимизации не происходит) до максимального -O3. Большинство пакетных дистрибутивов Linux, декларирующих свою оптимизированную природу, собирается, насколько мне известно, с флагом -O2. В BSD-семействе по умолчанию принята оптимизация уровня -O1 для всех ее компонентов - ядра, базовой системы и портов; более того, до недавнего времени выполнить операцию make world (полную пересборку системы) при более высоких уровнях оптимизации было практически невозможно.

Выбор уровня оптимизации - не столь однозначный вопрос, как может показаться. И максимально высокий уровень -O3 далеко не всегда дает лучшие результаты, а подчас производительность собранных с его использованием программ даже снижается. Не говоря уж о том, что некоторые программы вообще при этом уровне собираться отказываются. И в любом случае при уровне -O3 размер исполняемого кода оказывается больше, нежели при -O2, что ведет снижение скорости запуска приложений.

Воообще говоря, мои (и не только мои) наблюдения показывают, что самый большой скачек в быстродействии происходит при переходе от уровня -O0 (то есть отказа от оптимизации) к -O1. Флаг -O2 обычно обеспечивает лишь небольшой прирост скорости запуска и выполнения программ, а результаты применения флага -O3 неоднозначны. В частности, эксперименты с пересборкой ядра и "мира" DragonFlyBSD показали стабильное (хотя и очень маленькое численно) его оставание от уровня -O2.

Следующие флаги, весьма влияющие на производительность, задают конкретный процессор для целевой машины: -mcpu=значение или -march=значение. Различие между ними в том, что программа, собранная с флагом -mcpu, будучи оптимизированной под заданный в качестве значения "камень", сохраняет способность запуска на более младших моделях, тогда как флаг -march заоптимизирует программу до того, что она сможет запуститься только на указанном процессоре или более старшем.

Допустимые значения флагов -mcpu и -march зависят от версии компилятора gcc. Однако в современных версиях, входящих в состав большинства дистрибутивов (gcc от 3.3.X и выше) здесь могут быть заданы все используемые ныне процессоры семейства x86: pentium, pentium-mmx, pentiumpro, pentium2(а также 3 и 4), разнообразные athlon'ы (athlon просто, athlon-tbird, athlon-xp, athlon-mp), не считая всякой экзотики типа winchip etc. Детали можно посмотреть в документации на наличную версию gcc.

Особенно богатые возможности для оптимизации под современные процессоры предсотавляет gcc-3.4.X. В нем имеется поддержка и AMD64, и Prescott с его набором новых инструкций и улучшенным HyperThreading), а главное, судя по имеющимся сообщениям, существенно выросло качество оной (в том числе и поддержка HT). И если раньше (в версиях 3.3.X), сборка, например, под Pentium-4 подчас давала худшие результаты, нежели чем под абстрактный i686, то теперь для процессоров от Intel имеет смысл подбирать наиболее близкое значение флага -march (для процессоров AMD точное указание процессора всегда давало хорошие результаты).

Следующие процессорно-специфичные флаги оптимизации позволяют задействовать специальные наборы инструкций современных "камней" - от MMX до 3DNow и SSE всех номеров. Для этого сначала определяем, куда в этих случаях должен обращаться процессор - к стандартному сопроцессору или соответствующим блокам специализированных инструкций. Делается это с помощью флага -mfpmath=значение, в качестве какового могут выступать 387 (стандартный сопроцессор) или sse (блок SSE-расширений); согласно документации, можно задать оба параметра, правда смысл этого остается мне не вполне ясным.

Насколько я понимаю, при компиляции под Athlon'ы более оправданным будет флаг -mfpmath=387, учитывая максимально мощный сопроцессор этого "камушка". А вот в случае с Pentium-III и, особенно, Pentium4 резонным выбором будет скорее -mfpmath=sse. К чему следует приложить еще парочку флагов - -mmmx плюс -msse (для "пятой трешки") или -msse2 (для "пятой четверки").

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

Процессорно-привязанными флагами возможности оптимизации не исчерпываются. Есть еще флаги, специфическим образом обрабатывающие код для достижения максимальной производительности вне зависимости от типа процессора (вплоть до нарушений стандартов POSIX/ANSI, типа -ffast-math, благодаря чему теоретически можно собрать максимально быстрый код). Детальное их описание далеко выходит за рамки данной заметки. Тем более, что его можно найти в фундаментальном руководстве по gcc, написанном Ричардом Столлменом с соавторами (русский перевод доступен, например, на здесь - часть 1 и часть 2 - и номером его версии, весьма древним, смущаться не след, актуальности оно ничуть не потеряло).

Как уже говорилось, при использовании флагов оптимизации, особенно достаточно жестких (типа -O3 -march=значение, не говоря уже о -ffast-math), необходимо учитывать, что отнюдь не все программы гарантированно соберутся с ними. А те, что соберутся, вовсе не обязаны безукоризненно функционировать (даже, возможно, функционировать вообще - от ошибок сегментации гарантировать не может никто). А уж то, что при этом будет достигнут большой выигрышь в быстродействии - приходится только надеяться.

Я достаточно много экспериментировал с различными флагами оптимизации, в том числе и весьма жесткими, при разных версиях компилятора, и на разных системах (Gentoo, CRUX, Archlinux, FreeBSD 5-й ветви, DragonFlyBSD). И в конце концов пришел к весьма умеренному решению:

CFLAGS="-O2 -march=pentium4"
CXXFLAGS="$CFLAGS"

Вероятно, рекордных результатов при нем не добиться, но оно дает гарантированно стабильный результат. По крайней мере, и ядро, и "мир" DragonFlyBSD (при gcc-3.4.X) собираются без проблем, как и большинство используемых мной портов.

Тем не менее, любители экстремальной сборки могут попробовать нечто вроде этого:

CFLAGS="-O3 -march=pentium4 \
	-fomit-frame-pointer \
	-funroll-loops -pipe \
	-mfpmath=sse -mmmx -msse2"
CXXFLAGS="$CFLAGS"

А при желании еще и подобрать специальные флаги оптимизации для программ Си++, чем я никогда не озадачивался вообще.

К слову сказать, флаги оптимизации задаются различными способами:

  • в командной строке при конфигурировании программы:
  •     CFLAGS="значения" ./configure
        
  • в виде переменных окружения в данном сеансе шелла:
        export CFLAGS="значения"
        
    в sh-совместимы оболочках и
        setenv CFLAGS "значения"
        
    в csh и tcsh;
  • глобально, раз и навсегда, в профильном файле (например, в /etc/profile или /etc/cshrc);
  • в конфигурационном файле системы портов, если таковая используется (типа /etc/make.conf в BSD).

Осталось определить, что дает столь жесткая оптимизация? Компилятор gcc, пересобранный указанным мной ранее образом, на сборке ядра демонстрирует быстродействие на 10-15% выше, чем собранный по умолчанию. Учитывая, что на современных машинах ядро собирается минут за 10-15, казалось бы, немного. Однако при сборке монстров типа оконной системы Икс, интегрированной среды KDE или, скажем, OpenOffice, игра вполне стоит свеч. Да и прикладные программы, собранные с флагами оптимизации, работают существенно быстрее: Mozilla, для примера - чуть не в полтора раза, хотя для других Исковых программ столь высоких результатов получить не удается.

Средства управления пакетами

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

Средства управления пакетами чрезвычайно разнообразны. В первом приближении их можно разделить на два класса - системы портов и собственно системы пакетного менеджмента.

Системы портов пришли из мира BSD, где впервые появились во FreeBSD. Собственно, порты - это родовое имя системы пакетного менеджмента этой операционки. Все остальные портообразные системы возникли и развивались под сильным ее влиянием. Наибольшее распространение и известность получили pkgsrc и портежи (portages).

Первая система возникла первоначально в NetBSD, постепенно превратившись в кроссс-платформенное средство управления пакетами: существуют ее версии для всех BSD-систем (включая DragonFly), ряда дистрибутивов Linux и даже для таких проприетарных Unix'ов, как Solaris и IRIX.

Система портежей была разработана для дистрибутива Gentoo Linux. Однако и она быстро обрела черты кросс-платформенности: более или менее активно разрабатываются ее варианты для FreeBSD и NetBSD, поговаривают о переносе ее на Hurd и другие дистрибутивы Linux.

Тем не менее, практически каждый дистрибутив Linux, относимый к классу Source Based, обладает собственной, более или менее оригинальной, портообразной системой (почему я и полагаю, что их следует называть скорее портируемыми дистрибутивами). Тут следует упомянуть и ABS - Archlinux Building System из одноименного дистрибутива, и порты CRUX, и Sorcery из Sorcerer Linux и его потомков (Source Mage и Linar). И примеры портообразных систем множатся с каждым днем: только за истекший год появились дистрибутивы Rubyx и MyGeOS - каждый со своей портообразной системой.

Не смотря на такое разнообразие конкретных реализаций, общие черты любой портообразной системы вычленить очень легко: это набор правил для отыскания в Сети исходников устанавливаемых программ, их получения на локальную машину, развертывания тарбаллов, конфигурирования, сборки (компиляции и линковки), инсталляции в файловую систему и, возможно, постинсталляционного конфигурирования. То есть по этим правилам выполняется весь тот цикл действий, которые мы проделываем при ручной сборке любой программы (вспомним три волшебных заклинания ./configure, make, make install из предшествующих параграфов), но - в автоматическом режиме. Однако в добавок к этому система портов включает такойму важный для управления пакетами компонент, как средства выявления и отслеживания зависимостей, как "жестких", обязательных, так и "мягких", опциональных, коррекции последних в ту или иную сторону, ну и, конечно же, автоматического удовлетворения тех и других.

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

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

$ ./configure --prefix=/path_to/pkgname

то бинарные компоненты пакета будут собраны в отдельном каталоге с подкаталогами pkgname/bin, pkgname/lib, pkgname/share и так далее. И если этот каталог заархивировать утилитой tar и сжать посредством gzip или bzip2 - то получится автономный бинарный пакет в простейшей своей форме - в виде простого тарбалла. Для использования остается только развернуть его и инкорпорировать в древо файловой системы. Эту задачу и выполняют система пакетного менеджмента, обеспечивая заодно и контроль зависимостей при установке.

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

К первой группе относятся широко распространенные форматы пакетов rpm (Red Hat Packages Manager, характерный для одноименного дистрибутива и его многочисленных потомков) и deb (свойственный дистрибутиву Debian и на нем основанным). И тот, и другой, помимо собственно архива бинарных файлов и путей к ним, содержат данные о зависимостях, хотя и представленные в разной форме.

Пакеты без метаданных - это обычные тарбаллы, то есть компрессированные tar-архивы типа *tar.gz/*tar.bz2 (часто фигурирующие в форме tgz и tbz). Важно, что сами по себе tgz,tbz и им подобные - это форматы вовсе не пакета, а именно архива (то есть определяются используемой утилитой компрессии - gzip или bzip2, соответственно). А важно это потому, что те же самые tgz/tbz архивы могут прекрасно содержать в себе и метаинформацию, оказываясь более сходными с пакетами rpm или deb (и ниже мы столнемся с примерами этого). Характерным примером чего являются packages BSD-систем, включающих в себя полное описание зависимостей.

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

Средства пакетного менеджмента жестко привязаны к формату пакетов - для установки rpm-пакетов служит одноименная утилита (rpm), пакеты deb устанавливаются посредством утилиты dpkg, для пакетных тарбаллов предусмотрены собственные средства, обычно - дистрибутив-специфичные, не смотря на похожие, и даже подчас одинаковые, имена. Конечно, существуют средства взаимной трансформации пакетов разных форматов (типа rpm2cpio, rpm2tgz или почти универсальной утилиты alien), однако возможности их применения ограничены - очевидно, что из rpm-пакета (и тем более "чистого" трабалла) получить полноценный deb-пакет невозможно.

Однако существуют еще и средства пакетного мета-менеджмента, если так можно выразиться (собственно, только они-то и заслуживают названия систем управления пакетами). Наиболее известное и распространенное из них - apt. Появившийся сначала в Debian'е и рассчитанный, соответственно, на deb-пакеты, он очень быстро стал универсальным кросс-пакетным механизмом установки, удаления и обновления программ, успешно работая с пакетами rpm (дистрибутивы Connectiva, Altlinux), тарбаллами Slackaware (механизм slapt-get). И в принципе не видно препятствий к прикручиванияю его к тарбаллам любого формата - от "чистых" до сколь угодно насыщенных метаинформацией.

Под явным влиянием apt возникли и иные системы пакетного менеджмента - yum, urpmi и так далее. Однако они ориентированы только на rpm-пакеты (вероятно, их можно использовать и для иных форматов, но кому это нужно при наличии apt?) и потому не получили столь широкого распространения, оставаясь принадлежностью "родительских" дистрибутивов и более-менее точных их клонов (yum, насколько мне известно, используется только в Red Hat/Fedora и ASPlinux).

Следует помнить, что резкую границу между системами портов и средствами пакетного менеджмента провести нелегко. Все портообразные системы позволяют выполнить сборку автономных бинарных пакетов и, соответственно, включают средства их установки, регистрации и удаления. А в системах пакетного менеджмента можно обнаружить инструментарий для автоматической компиляции программ из исходников, хотя они и играют сугубо вспомогательную роль.

Назад Содержание Вперед

Новости мира IT:

Архив новостей

Последние комментарии:

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...