Содержание


Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 12. Использование утилиты make

Comments

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

Утилита make

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

Утилита make доступна для разных ОС, и из-за особенностей выполнения наряду с «родной» реализацией во многих ОС присутствует GNU реализация gmake, и поведение этих реализаций в некоторых ОС, например, Solaris может существенно отличаться. Поэтому в сценариях сборки рекомендуется указывать имя конкретной утилиты. В ОС Linux эти два имени являются синонимами, реализованными через символическую ссылку, как показано ниже:

$ ls -l /usr/bin/*make
lrwxrwxrwx 1 root root      4 Окт 28  2008 /usr/bin/gmake -> make
-rwxr-xr-x 1 root root 162652 Май 25  2008 /usr/bin/make
...
$ make --version
GNU Make 3.81
...

По умолчанию имя файла сценария сборки - Makefile. Утилита make обеспечивает полную сборку указанной цели, присутствующей в сценарии, например:

$ make
$ make clean

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

$ make -f Makefile.my

Простейший файл Makefile состоит из синтаксических конструкций двух типов: целей и макроопределений. Описание цели состоит из трех частей: имени цели, списка зависимостей и списка команд интерпретатора оболочки, требуемых для построения цели. Имя цели — непустой список файлов, которые предполагается создать. Список зависимостей — список файлов, в зависимости от которых строится цель. Имя цели и список зависимостей составляют заголовок цели, записываются в одну строку и разделяются двоеточием (':'). Список команд записывается со следующей строки, причем все команды начинаются с обязательного символа табуляции. Многие текстовые редакторы могут быть настроены таким образом, чтобы заменять символы табуляции пробелами. Этот факт стоит учесть и проверить, что редактор, в котором редактируется Makefile, не замещает табуляции пробелами, так как подобная проблема встречается довольно часто. Любая строка в последовательности списка команд, не начинающаяся с табуляции (ещё одна команда) или символа '#' (комментарий) — считается завершением текущей цели и началом новой.

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

$ make -p >make.suffix
make: *** Не заданы цели и не найден make-файл.  Останов.
$ cat make.suffix
# GNU Make 3.81
# Copyright (C) 2006  Free Software Foundation, Inc.
...
# База данных Make, напечатана Thu Apr 14 14:48:51 2011
...
CC = cc
LD = ld
AR = ar
CXX = g++
COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
COMPILE.C = $(COMPILE.cc)
...
SUFFIXES := .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l .s .S .mod .sym \ 
            .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch...
# Implicit Rules
...
%.o: %.c
#  команды, которые следует выполнить (встроенные):
        $(COMPILE.c) $(OUTPUT_OPTION) $<
...

Значения всех этих переменных: CC, LD, AR, EXTRA_CFLAGS, ... могут использоваться файлом сценария как неявные определения со значениями по умолчанию. Кроме этого, можно определить и собственные правила обработки по умолчанию для выбранных суффиксов (расширений файловых имён), как это показано на примере выше для исходных файлов кода на языке С: %.c.

Большинство интегрированных сред разработки (IDE) или пакетов для создания переносимых инсталляций (например, automake или autoconf) ставят своей задачей создание файла Makefile для утилиты make.

Как ускорить сборку make

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

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

$ man make
...
    -j [jobs], --jobs[=jobs]
     Specifies the number of jobs (commands) to run simultaneously.
...

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

$ pwd
/usr/src/ntp-4.2.6p3

Сначала запустим сборку на 4-х ядерном процессоре Atom (не очень быстрая модель с частотой 1.66Ghz) но с очень быстрым твердотельным диском SSD:

$ cat /proc/cpuinfo | head -n10
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 28
model name	: Intel(R) Atom(TM) CPU  330   @ 1.60GHz
stepping	: 2
cpu MHz	: 1596.331
cache size	: 512 KB
$ make clean
# запускаем сборку в четыре потока
$ time make -j4
...
real    1m5.023s
user    2m40.270s
sys     0m16.809s
$ make clean
# запускаем сборку в стандартном режиме без параллелизма
$ time make
...
real    2m6.534s
user    1m56.119s
sys     0m12.193s
$ make clean
# запускаем сборку с автоматическим выбранным уровнем параллелизма
$ time make -j
...
real    1m5.708s
user    2m43.230s
sys     0m16.301s

Как можно заметить, использование параллелизма (явное или не явное) позволяет ускорить сборку почти в два раза – 1 минута против 2-ух. Выполним сборку этого же проекта на более быстром 2-х ядерном процессоре, но с достаточно медленным обычным диском HDD:

$ cat /proc/cpuinfo | head -n10
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 23
model name	: Pentium(R) Dual-Core CPU E6600 @ 3.06GHz
stepping	: 10
cpu MHz	: 3066.000
cache size	: 2048 KB
...
$ time make
...
real    0m31.591s
user    0m21.794s
sys     0m4.303s
$ time make -j2
...
real    0m23.629s
user    0m21.013s
sys     0m3.278s

Хотя итоговая скорость сборки и выросла в 3-4 раза, но улучшение от числа процессоров составляет только порядка 20%, так как «слабым звеном» здесь является медленный накопитель, допускающий задержку при записи большого числа мелких .obj файлов проекта.

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

Если данный способ ускорения процесса сборки основан на том, что сейчас подавляющее большинство систем являются многопроцессорными (многоядерными), то следующий способ использует тот факт, что объём памяти RAM современных компьютеров (2-4-8 ГБ) значительно превышает объём памяти, необходимый для компиляции программного кода. В таком случае, компиляцию, основным сдерживающим фактором для которой является создание множества объектных файлов, можно перенести в область специального созданного диска (RAM диск, tmpfs), расположенного в памяти:

$ free
             total       used       free     shared    buffers     cached
Mem:       4124164    1516980    2607184          0     248060     715964
-/+ buffers/cache:     552956    3571208
Swap:      4606972          0    4606972
$ df -m | grep tmp
tmpfs                     2014         1      2014   1% /dev/shm

Теперь можно временно перенести файлы собираемого проекта в tmpfs (мы по-прежнему используем NTP-сервер из предыдущего примера), в каталог /dev/shm:

$ pwd
/dev/shm/ntp-4.2.6p3
$ make -j
...
real    0m4.081s
user    0m1.710s
sys     0m1.149s

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

Этот способ ускорения можно применить к сборке ядра Linux, для которого, как уже было сказано, параллельная сборка не работает. Чтобы воспользоваться преимуществами RAM-памяти, скопируем дерево исходных кодов ядра в каталог /dev/shm:

$ pwd
/dev/shm/linux-2.6.35.i686
$ time make bzImage
...
  HOSTCC  arch/x86/boot/tools/build
  BUILD   arch/x86/boot/bzImage
Root device is (8, 1)
Setup is 13052 bytes (padded to 13312 bytes).
System is 3604 kB
CRC 418921f4
Kernel: arch/x86/boot/bzImage is ready  (#1)

real    9m23.986s
user    7m4.826s
sys     1m18.529s

Как видно, сборка ядра Linux заняла менее 10 минут, что является необычайно хорошим результатом.

В качестве вывода, можно посоветовать тщательно оптимизировать условия сборки проекта под используемое для этого оборудование, и, учитывая, что в процессе отладки сборка выполняется сотни раз, то можно сэкономить множество времени!

Сборка модулей ядра

Частным случаем сборки приложений является сборка модулей ядра Linux (драйверов). Начиная с версий ядра 2.6, для сборки модуля составляется Makefile, построенный на использовании макросов, и нам остаётся только записать (для файла собственного кода с именем mod_params.c), следующий шаблон для сборки модулей:

Листинг 1. Makefile для сборки модулей ядра
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
TARGET = mod_params
obj-m      := $(TARGET).o
default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules
...

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

$ make
make -C /lib/modules/2.6.18-92.el5/build \
      M=examples/modules-done_1/hello_printk modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-92.el5-i686'
  CC [M]  /examples/modules-done_1/hello_printk/hello_printk.o
  Building modules, stage 2.
  MODPOST
  CC      /examples/modules-done_1/hello_printk/hello_printk.mod.o
  LD [M]  examples/modules-done_1/hello_printk/hello_printk.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-92.el5-i686'
$ ls -l *.o *.ko
-rw-rw-r-- 1 olej olej 74391 Мар 19 15:58 hello_printk.ko
-rw-rw-r-- 1 olej olej 42180 Мар 19 15:58 hello_printk.mod.o
-rw-rw-r-- 1 olej olej 33388 Мар 19 15:58 hello_printk.o
$ file hello_printk.ko
hello_printk.ko: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ /sbin/modinfo hello_printk.ko
filename:       hello_printk.ko
author:         Oleg Tsiliuric <olej@front.ru>
license:        GPL
srcversion:     83915F228EC39FFCBAF99FD
depends:
vermagic:       2.6.18-92.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1

Заключение

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

В следующей статье мы начнём знакомство с библиотеками API, присутствующими в POSIX системах.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=969110
ArticleTitle=Инструменты ОС Linux для разработчиков приложений для ОС Windows. Часть 12. Использование утилиты make
publish-date=04212014