355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Уильям Шоттс » Командная строка Linux » Текст книги (страница 22)
Командная строка Linux
  • Текст добавлен: 12 апреля 2017, 12:30

Текст книги "Командная строка Linux"


Автор книги: Уильям Шоттс


Жанр:

   

ОС и Сети


сообщить о нарушении

Текущая страница: 22 (всего у книги 30 страниц)

23. Компиляция программ

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

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

• Доступность. Несмотря на большое число предварительно скомпилированных пакетов в репозиториях дистрибутивов некоторые дистрибутивы могут включать не все необходимые приложения. В этом случае остается только один способ установить требуемую программу: скомпилировать ее из исходных кодов.

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

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

В этой главе будет представлена одна новая команда:

• make – утилита сопровождения программ.

Что такое компиляция?

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

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

Эта проблема была решена с появлением языка ассемблера, который заменил числовые коды (слегка) более простыми символическими мнемониками, такими как CPY (для обозначения операции копирования) и MOV (для обозначения операции перемещения). Исходный код на языке ассемблера преобразовывался в машинный код программой, называющейся ассемблером. Язык ассемблера используется и в наши дни для решения специальных задач программирования, таких как разработка драйверов устройств или встраиваемых систем.

Затем появились высокоуровневые языки программирования. Они называются так потому, что позволяют программисту меньше думать об особенностях работы процессора и больше – о решении задачи, стоящей перед ним. К числу этих первых языков (разработанных в течение 1950-х) относятся: FORTRAN (создавался для решения научных и технических задач) и СOBOL (для решения экономических задач). Оба продолжают ограниченно использоваться и по сию пору.

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

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

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

Все ли программы компилируются?

Нет. Как мы уже видели, некоторые программы, такие как сценарии на языке командной оболочки, не требуют компиляции и выполняются непосредственно. Они написаны на языках, которые называют языками сценариев, или интерпретируемыми языками. К числу этих языков, популярность которых только растет в последние годы, относятся Perl, Python, PHP, Ruby и многие другие.

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

Но почему тогда интерпретирующие языки так популярны? Для многих рутинных задач они оказываются «достаточно быстрыми», но самое большое их достоинство в простоте и скорости разработки интерпретируемых программ в сравнении с компилируемыми. Разработка программ – это обычно циклический процесс, включающий три этапа: создание исходного кода, компиляцию и тестирование. С увеличением программы в размерах этап компиляции в упомянутом цикле может оказаться весьма продолжительным. Интерпретирующие языки избавляют от необходимости компиляции и тем самым ускоряют их разработку.

Компиляция программ на C

Давайте что-нибудь скомпилируем. Для этого нам понадобятся некоторые инструменты, такие как компилятор, компоновщик и утилита make. Практически во всех системах Linux используется один и тот же компилятор языка C с именем gcc (GNU C Compiler), первоначально написанный Ричардом Столлманом. Многие дистрибутивы не устанавливают gcc по умолчанию. Проверить его присутствие в системе можно так:

[me@linuxbox ~]$ which gcc

/usr/bin/gcc

Результат свидетельствует о присутствии компилятора.

ПРИМЕЧАНИЕ

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

Получение исходного кода

В нашем упражнении мы скомпилируем программу с названием diction из проекта GNU. Эта маленькая удобная программка проверяет качество и стиль содержимого текстовых файлов. А поскольку она невелика, она легко компилируется.

Следуя соглашениям, мы сначала создадим каталог src для исходного кода и затем загрузим в него исходный код с помощью команды ftp:

[me@linuxbox ~]$ mkdir src

[me@linuxbox ~]$ cd src

[me@linuxbox src]$ ftp ftp.gnu.org

Connected to ftp.gnu.org.

220 GNU FTP server ready.

Name (ftp.gnu.org:me): anonymous

230 Login successful.

Remote system type is UNIX.

Using binary mode to transfer files.

ftp> cd gnu/diction

250 Directory successfully changed.

ftp> ls

200 PORT command successful. Consider using PASV.

150 Here comes the directory listing.

–rw-r–r–    1 1003  65534   68940 Aug 28  1998 diction-0.7.tar.gz

–rw-r–r–    1 1003  65534   90957 Mar 04  2002 diction-1.02.tar.gz

–rw-r–r–    1 1003  65534  141062 Sep 17  2007 diction-1.11.tar.gz

226 Directory send OK.

ftp> get diction-1.11.tar.gz

local: diction-1.11.tar.gz remote: diction-1.11.tar.gz

200 PORT command successful. Consider using PASV.

150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062

bytes).

226 File send OK.

141062 bytes received in 0.16 secs (847.4 kB/s)

ftp> bye

221 Goodbye.

[me@linuxbox src]$ ls

diction-1.11.tar.gz

ПРИМЕЧАНИЕ

Поскольку мы сами управляем процессом компиляции исходного кода, поместим его в каталог ~/src. Исходный код, устанавливаемый дистрибутивом, помещается в каталог /usr/src, а исходный код, предназначенный для использования множеством пользователей, обычно устанавливается в /usr/local/src.

Исходный код обычно распространяется в виде сжатого tar-файла. Иногда называемые тарболлами (tarball), эти файлы содержат дерево исходных текстов, или иерархию каталогов и файлов, составляющих исходный код. Подключившись к FTP-сайту, мы получили список доступных tar-файлов и выбрали для загрузки самую свежую версию. При помощи команды get программы ftp скопировали файл с сервера FTP на локальную машину.

После загрузки tar-файла его нужно распаковать. Делается это с помощью программы tar:

[me@linuxbox src]$ tar xzf diction-1.11.tar.gz

[me@linuxbox src]$ ls

diction-1.11        diction-1.11.tar.gz

ПРИМЕЧАНИЕ

Программа diction, подобно всем программам из проекта GNU, следует определенным стандартам упаковки исходного кода. Большая часть других исходных кодов, доступных в экосистеме Linux, также следует этому стандарту. Одним из элементов стандарта является создание каталога с деревом исходных текстов и именем project-x.xx после распаковывания tar-файла, то есть с именем, содержащим имя проекта и номер версии. Такая схема упрощает установку нескольких версий одной и той же программы. Однако перед распаковыванием хорошо бы исследовать организацию дерева. При распаковывании некоторых проектов каталог не создается, а файлы помещаются непосредственно в текущий каталог, что может вызвать неразбериху и путаницу в хорошо организованном каталоге src. Чтобы избежать этого, пользуйтесь следующей командой для исследования содержимого tar-файла:

tar tzvf tarfile | head

Исследование дерева исходных текстов

В результате распаковывания tar-файла был создан новый каталог new diction-1.11. Этот каталог содержит дерево исходных текстов. Давайте заглянем внутрь:

[me@linuxbox src]$ cd diction-1.11

[me@linuxbox diction-1.11]$ ls

config.guess  diction.c        getopt.c      nl

config.h.in   diction.pot      getopt.h      nl.po

config.sub    diction.spec     getopt_int.h  README

configure     diction.spec.in  INSTALL       sentence.c

configure.in  diction.texi.in  install-sh    sentence.h

COPYING       en               Makefile.in   style.1.in

de            en_GB            misc.c        style.c

de.po         en_GB.po         misc.h        test

diction.1.in  getopt1.c        NEWS

Здесь мы видим множество файлов. Программы, принадлежащие проекту GNU, а также многие другие поставляются вместе с файлами документации README, INSTALL, NEWS и COPYING. В них содержится описание программы, информация о порядке сборки и установки и условия лицензионного соглашения. Я рекомендую всегда внимательно прочитывать файлы README и INSTALL перед сборкой программы.

Другими интересными файлами в этом каталоге являются файлы с расширениями .c и .h:

[me@linuxbox diction-1.11]$ ls *.c

diction.c  getopt1.c  getopt.c  misc.c  sentence.c  style.c

[me@linuxbox diction-1.11]$ ls *.h

getopt.h  getopt_int.h  misc.h  sentence.h

Файлы с расширением .c содержат две программы на C, входящие в состав пакета (style и diction), разбитые на несколько модулей. Большие программы часто разбивают на более мелкие, более простые в сопровождении фрагменты. Файлы с исходным кодом содержат простой текст, и их можно исследовать с помощью less:

[me@linuxbox diction-1.11]$ less diction.c

Файлы с расширением .h известны как заголовочные файлы. Они тоже содержат простой текст – описание подпрограмм, подключаемых из файла с исходным кодом или библиотеки. Чтобы компилятор смог связать модули, он должен иметь описание всех модулей, составляющих единую программу. Ближе к началу в файле diction.c имеется следующая строка:

#include "getopt.h"

Она требует от компилятора прочитать фал getopt.h, прежде чем продолжать чтение исходного кода в diction.c, чтобы «узнать», что имеется в файле getopt.c. Файл getopt.c содержит код подпрограмм, используемых обеими программами, style и diction.

Инструкции подключения файла getopt.h предшествует еще несколько инструкций include:

#include

#include

#include

#include

#include

Они также ссылаются на заголовочные файлы, но эти файлы хранятся за пределами дерева исходных текстов. Они должны поставляться системой для поддержки компиляции программ. Эти файлы можно найти в каталоге /usr/include:

[me@linuxbox diction-1.11]$ ls /usr/include

Заголовочные файлы помещаются в этот каталог в процессе установки компилятора.

Сборка программ

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

./configure

make

Программа configure – это сценарий командной оболочки, поставляемый вместе с деревом исходных текстов. Его задача – проанализировать окружение сборки. Большинство исходного кода поддерживает переносимость. То есть такой исходный код спроектирован так, что допускает сборку в разных Unix-подобных системах. Но для этого во время сборки в исходный код может потребоваться внести небольшие изменения, учитывающие различия между системами. Программа configure также проверяет наличие необходимых внешних инструментов и компонентов.

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

[me@linuxbox diction-1.11]$ ./configure

В процессе проверки и настройки сборки configure выведет множество сообщений. Последние строки ее вывода должны выглядеть примерно так:

checking libintl.h presence... yes

checking for libintl.h... yes

checking for library containing gettext... none required

configure: creating ./config.status

config.status: creating Makefile

config.status: creating diction.1

config.status: creating diction.texi

config.status: creating diction.spec

config.status: creating style.1

config.status: creating test/rundiction

config.status: creating config.h

[me@linuxbox diction-1.11]$

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

Как мы видим, configure создала в каталоге с исходным кодом несколько файлов. Самым важным является Makefile. Файл Makefile – это конфигурационный файл с инструкциями для программы make, описывающими, как собрать программу. Без такого файла утилита make работать не будет. Makefile – обычный текстовый файл, то есть мы можем заглянуть в него:

[me@linuxbox diction-1.11]$ less Makefile

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

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

CC= gcc

определяющую, что роль компилятора C будет играть gcc. Далее в файле можно посмотреть, как используется это определение:

diction:        diction.o sentence.o misc.o getopt.o getopt1.o

                $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o

                getopt.o getopt1.o $(LIBS)

Здесь выполняется подстановка: во время выполнения конструкция $(CC) замещается командой gcc.

Большую часть файла сборки занимают строки, определяющие целевой файл (target) – в данном случае выполняемый файл diction – и файлы, от которых она зависит. Остальные строки описывают команды, которые необходимо выполнить для создания целевого файла из его компонентов. Мы видим, что выполняемый файл diction (одна из конечных программ) зависит от присутствия файлов diction.o, sentence.o, misc.o, getopt.o и getopt1.o. Далее в файле сборки присутствуют определения, в которых каждый из этих файлов играет роль целевого.

diction.o:      diction.c config.h getopt.h misc.h sentence.h

getopt.o:       getopt.c getopt.h getopt_int.h

getopt1.o:      getopt1.c getopt.h getopt_int.h

misc.o:         misc.c config.h misc.h

sentence.o:     sentence.c config.h misc.h sentence.h

style.o:        style.c config.h getopt.h misc.h sentence.h

Однако в этих определениях не видно ни одной команды. Обработка этих строк осуществляется определением общей цели, что находится выше в файле, где описывается команда компиляции всех файлов с расширением .c в файлы с расширением .o:

.c.o:

                $(CC) -c $(CPPFLAGS) $(CFLAGS) $<

На первый взгляд все это кажется очень сложным. Почему бы просто не перечислить все этапы компиляции? Ответ на этот вопрос станет очевиден чуть позже. А пока давайте запустим make и соберем наши программы:

[me@linuxbox diction-1.11]$ make

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

[me@linuxbox diction-1.11]$ ls

config.guess   de.po            en            install-sh   sentence.c

config.h       diction          en_GB         Makefile     sentence.h

config.h.in    diction.1        en_GB.mo      Makefile.in  sentence.o

config.log     diction.1.in     en_GB.po      misc.c       style

config.status  diction.c        getopt1.c     misc.h       style.1

config.sub     diction.o        getopt1.o     misc.o       style.1.in

configure      diction.pot      getopt.c      NEWS         style.c

configure.in   diction.spec     getopt.h      nl           style.o

COPYING        diction.spec.in  getopt_int.h  nl.mo        test

de             diction.texi     getopt.o      nl.po

de.mo          diction.texi.in  INSTALL       README

Среди них diction и style, программы, которые мы намеревались собрать. Примите заслуженные поздравления! Мы только что скомпилировали первые программы из исходного кода!

Но, исключительно ради любопытства, запустим make еще раз:

[me@linuxbox diction-1.11]$ make

make: Nothing to be done for `all'13.

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

[me@linuxbox diction-1.11]$ rm getopt.o

[me@linuxbox diction-1.11]$ make

Вы увидите, что make повторно собирает getopt.o и заново компонует программы diction и style, потому что они зависят от отсутствующего модуля. Такое поведение указывает на еще одну важную особенность make: она старается обеспечить актуальность целевых файлов. make гарантирует, что целевые файлы будут более новыми, чем их зависимости. В этом есть определенный смысл, потому что программист часто сначала изменяет исходный код, а затем запускает make, чтобы собрать новую версию программы. make гарантирует сборку всех целевых файлов, опирающихся на изменившийся код. Воспользуемся программой touch, чтобы «обновить» один из файлов с исходным кодом, и посмотрим, к чему это приведет:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c

–rwxr-xr-x 1 me      me      37164 2009-03-05 06:14 diction

–rw-r–r– 1 me      me      33125 2007-03-30 17:45 getopt.c

[me@linuxbox diction-1.11]$ touch getopt.c

[me@linuxbox diction-1.11]$ ls -l diction getopt.c

–rwxr-xr-x 1 me      me      37164 2009-03-05 06:14 diction

–rw-r–r– 1 me      me      33125 2009-03-05 06:23 getopt.c

[me@linuxbox diction-1.11]$ make

Когда make завершится, мы увидим, что целевой файл стал «свежее» зависимости:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c

–rwxr-xr-x 1 me      me      37164 2009-03-05 06:24 diction

–rw-r–r– 1 me      me      33125 2009-03-05 06:23 getopt.c

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

Установка программ

Старательно упакованный исходный код часто включает специальную цель для make, которая называется install (установить). Эта цель выполняет установку готового программного продукта в системный каталог. Обычно это каталог /usr/local/bin, традиционное место для установки программного обеспечения, собранного в локальной системе. Однако этот каталог, как правило, недоступен рядовым пользователям для записи, поэтому, чтобы выполнить установку, вам потребуются привилегии суперпользователя:

[me@linuxbox diction-1.11]$ sudo make install

После установки проверим готовность программы к использованию:

[me@linuxbox diction-1.11]$ which diction

/usr/local/bin/diction

[me@linuxbox diction-1.11]$ man diction

Все на месте!

Заключительное замечание

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

13 Для `all’ ничего не нужно делать. – Примеч. пер.


    Ваша оценка произведения:

Популярные книги за неделю