Содержание


Отладка и тестирование модулей ядра: Часть 77. Отладка в ядре. Сборка и установка ядра

Comments

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

Обновление ядра

Если вас интересует только обновление версии ядра рабочей системы, то лучший способ сделать это — обновить ядро пакетной системой из репозитария используемого дистрибутива. Например, для дистрибутивов RedHat / Fedora / CentOS список обновлений можно получить так:

# yum list available kernel*
...
Доступные пакеты
kernel.i686                              2.6.32.26-175.fc12               updates
kernel-PAE.i686                          2.6.32.26-175.fc12               updates
kernel-PAE-devel.i686                    2.6.32.26-175.fc12               updates
...

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

Пересборка ядра

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

  1. ядро можно собрать с расширенным набором отладочных опций, что может быть необходимо для сложных сценариев отладки модулей, но неудачный выбор отладочных опций может сильно понизить быстродействие ядра (собственно, по этой причине многие отладочные опции отключены по умолчанию);
  2. в коде модуля требуется использовать вызовы API, которые появились только в более поздних версиях ядра, например, вызов сетевого API netdev_rx_handler_register() и родственные ему (большая группа), которые появляются только в ядре 2.6.37, а активно используются в разработках для ядер 3.0 и выше;
  3. отрабатываемый модуль следует тестировать и проверять (в сборке модуля) на некоторой "сетке" версий ядра, так как противном случае вы рискуете при переустановке модуля на следующую версию ядра получить рекламацию от заказчика: модуль просто не станет компилироваться из-за изменчивости API ядра (а бинарная поставка драйверов, без компиляции, как вы помните, в Linux не практикуется).

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

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

Собрать ядро можно в любом месте файловой системы: в /usr/src (как дань традиции), домашнем каталоге пользователя, в каталоге сборки пакета ядра, предопределённом спецификацией сборки (~/rpmbuild), или любом другом месте.

Откуда берётся код ядра?

Этот вопрос связан с тем, что существуют (поддерживаются и обновляются) официальные (ещё известные как ванильные) версии кода ядра, размещённые на [http://www.kernel.org/]. Именно эти версии считаются "эталоном" кода ядра. Но создатели дистрибутивов (Fedora, CentOS, Debian, Ubuntu, ...) вносят изменения в исходный код ядра, которые оформляют заплатками (используя утилиты diff и patch). Это связано с закрытием проблемных мест в безопасности, описанных ко времени сборки дистрибутива, или включением каких-то опубликованных кодов, которые не сочли нужным включать официальные разработчики ядра. В любом случае, возникает вопрос: какое ядро брать для сборки? Здесь нет однозначного ответа даже у дистрибьюторов, но можно предположить следующее:

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

Для простоты понимания мы рассмотрим эти возможности в обратном порядке.

Официальное ядро

До самого последнего времени, и на протяжении нескольких десятилетий, исходный код ядра брался в виде архивов *.tgz или *.bz2 из официального источника. Но в конце сентября 2011г. официальный источник ядра Linux был разрушен в результате хакерской атаки и больше чем на неделю выведен из строя, после чего было объявлено, что доступ к кодам ядра по протоколу HTTP прекращается. Взамен этого была создана новая техника распространения кода ядра, основанная на протоколе контроля версий GIT и программном средстве gitolite.

Если же вы получили код более ранних версий (до 3.04) в виде архива, то его следует развернуть в дерево исходных кодов. Для определённости будем считать, что дерево разворачивается и разархивируется в каталоге /usr/src:

$ cd /usr/src
$ ls -l linux*
-rw-rw-r-- 1 olej olej 73632687 Мар 13 13:33 linux-2.6.37.3.tar.bz2
$ tar -jxvf linux-2.6.37.3.tar.bz2
...

Удобно сразу сделать там же (/usr/src) символьную ссылку с именем linux на каталог этих рабочих исходных кодов, как на самый последний вариант. Тогда при последующих изменениях версии ядра вы сможете только переставлять ссылку. Теперь у нас есть дерево исходных кодов для дальнейших действий:

$ ln -s linux-2.6.37.3 linux
$ cd linux
$ du -hs
479M

Показан суммарный объём исходных кодов ядра, к которому мы ещё вернёмся.

Ядро из репозитария дистрибутива

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

Сначала создадим общую структуру для построения любого пакета ещё до его загрузки. Обратите внимание, что путь построенной структуры каталогов не зависит от места, в котором мы выполняем команду, а зависит от имени пользователя, запустившего команду: дерево каталогов создаётся в домашнем каталоге этого пользователя ($HOME/rpmbuild):

$ rpmdev-setuptree
$ tree rpmbuild
rpmbuild
|-- BUILD
|-- RPMS
|-- SOURCES
|-- SPECS
`-- SRPMS
5 directories, 0 files

Теперь можно загрузить пакет исходных кодов из репозитария дистрибутива, но на этом шаге, возможно, потребуется запретить или добавить дополнительные репозитарии:

$ yumdownloader --source --disablerepo=russianfedora-fixes* kernel*
...
   kernel-2.6.35.14-97.fc14.src.rpm     |     68 MB     02:20

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

$ sudo yum-builddep kernel-2.6.35.14-97.fc14.src.rpm
...
Зависимости разрешены

==============================================================================
 Пакет                Архитектура      Версия              Репозиторий  Размер
==============================================================================
Установка:
 asciidoc             noarch           8.4.5-5.fc14        fedora       183 k
 binutils-devel       i686             2.20.51.0.7-8.fc14  updates      733 k
 elfutils-devel       i686             0.152-1.fc14        updates      69 k
 newt-devel           i686             0.52.12-1.fc14      fedora       47 k
 perl-ExtUtils-Embed  noarch           1.28-146.fc14       updates      31 k
 python-devel         i686             2.7-8.fc14.1        fedora       376 k
 redhat-rpm-config    noarch           9.1.0-8.fc14        updates      64 k
 xmlto                i686             0.0.23-3.fc13       fedora       45 k
Установка зависимостей:
 elfutils-libelf-devel  i686           0.152-1.fc14        updates      31 k
 flex                   i686           2.5.35-11.fc14      fedora       278 k
 flex-static            i686           2.5.35-11.fc14      fedora       12 k
 slang-devel            i686           2.2.3-1.fc14        updates      91 k

Результат операции
============================================================================
Install      12 Package(s)

Объем загрузки: 1.9 M
Будет установлено: 5.6 M
Продолжить? [y/N]: y
...

Все эти шаги были подготовительными и вот теперь мы устанавливаем пакет в дерево ~/rpmbuild (здесь может быть множество предупреждений относительно пользователя и группы сборщика пакета, но это можно проигнорировать):

$ rpm -Uvh kernel-2.6.35.14-97.fc14.src.rpm
предупреждение: пользователь mockbuild не существует - используется root
предупреждение: группа mockbuild не существует - используется root
...

К этому моменту у нас уже имеются:

  • в каталоге SOURCES архив исходных кодов и много (150) файлов заплаток (*.patch) к нему;
  • в каталоге SPECS спецификация пакета;
  • пустой пока каталог BUILD, в котором производится сборка.
$ cd /home/olej/rpmbuild/SOURCES
$ ls -l linux*.bz2
-rw-r--r--. 1 olej olej 69305709 Авг  2  2010 linux-2.6.35.tar.bz2
$ ls -l *.patch | wc -l
150
$ cd /home/olej/rpmbuild
BUILD  RPMS  SOURCES  SPECS  SRPMS
$ ls BUILD/
$ du -hs BUILD/
4,0K    BUILD/
$ cd SPECS
$ ls
kernel.spec

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

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

$ uname -m
i686
$ rpmbuild -bp --target=`uname -m` kernel.spec
Платформы для сборки: i686
Сборка для платформы i686
Выполняется(%prep): /bin/sh -e /var/tmp/rpm-tmp.QV9ZSr
+ umask 022
+ cd /home/olej/rpmbuild/BUILD
...
Patch12086: linux-2.6-cgroups-rcu.patch
+ case "$patch" in
+ patch -p1 -F1 -s
+ ApplyPatch sched-05-avoid-side-effect-of-tickless-idle-on-update_cpu_load.patch
+ local patch=sched-05-avoid-side-effect-of-tickless-idle-on-update_cpu_load.patch
+ shift
+ '[' '!' -f /home/olej/rpmbuild/SOURCES/
             sched-05-avoid-side-effect-of-tickless-idle-on-update_cpu_load.patch ']'
...
+ mkdir configs
+ for cfg in 'kernel-2.6.35.14-*.config'
++ grep -c kernel-2.6.35.14-arm.config
++ echo kernel-2.6.35.14-i686-PAE.config kernel-2.6.35.14-i686-PAEdebug.config
        kernel-2.6.35.14-i686-debug.config kernel-2.6.35.14
+ '[' 0 -eq 0 ']'
...
+ find . '(' -name '*.orig' -o -name '*~' ')' -exec rm -f '{}' ';'
+ cd ..
+ exit 0
$ cd /home/olej/rpmbuild/BUILD
$ ls
kernel-2.6.35.fc14
$ du -hs
530M    .

Теперь у нас, в ещё недавно пустом каталоге, находится дерево исходных кодов, объёмом 530MБ, или, точнее, два дерева: официальное дерево (ванильное, о котором говорилось раньше), и то же самое дерево, на которое наложены заплатки от сборщиков дистрибутива:

$ cd /home/olej/rpmbuild/BUILD/kernel-2.6.35.fc14
$ ls
linux-2.6.35.i686  vanilla-2.6.35
$ du -hs linux-2.6.35.i686
457M    linux-2.6.35.i686

$ du -hs vanilla-2.6.35
452M    vanilla-2.6.35

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

$ cd /home/olej/rpmbuild/BUILD/kernel-2.6.35.fc14/linux-2.6.35.i686
$ ls *config*
config-arm       config-ia64-generic       config-powerpc32-smp    config-s390x
config-debug     config-local              config-powerpc64        config-sparc64-generic
config-generic   config-nodebug            config-powerpc-generic  config-x86_64-generic
config-i686-PAE  config-powerpc32-generic  config-rhel-generic     config-x86-generic

configs:
kernel-2.6.35.14-i686.config        kernel-2.6.35.14-i686-PAE.config
kernel-2.6.35.14-i686-debug.config  kernel-2.6.35.14-i686-PAEdebug.config
$ cd /home/olej/rpmbuild/BUILD/kernel-2.6.35.fc14/vanilla-2.6.35
$ ls *config*
ls: невозможно получить доступ к *config*: Нет такого файла или каталога

Копируем файл конфигурации, соответствующий нашей архитектуре, вместо текущего файла .config:

$ cp configs/kernel-2.6.35.14-i686-PAE.config ./.config

И объявляем это новое содержимое конфигурационным файлом ядра:

$ time make oldconfig
scripts/kconfig/conf -o arch/x86/Kconfig
#
# configuration written to .config
#
real    0m0.488s
user    0m0.224s
sys     0m0.163s

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

Конфигурация

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

$ ls /boot/config*
/boot/config-2.6.18-92.el5  /boot/config-2.6.24.3-1.rt1.2.el5.ccrmart

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

Перед запуском конфигуратора следует очистить каталог от следов предыдущей сборки:

$ make mrproper

На этом этапе вы можете подправить одну (4-ую) строку в Makefile, поменяв в ней:

EXTRAVERSION =

на

EXTRAVERSION = myOWN

Это приведет к тому, что сделанное ядро (и все сопутствующие файлы) будет называться linux-2.6.37.3-myOWN (то есть конкатенация версии ядра с суффиксом EXTRAVERSION), благодаря чему будет легче различать собственные модификации.

Переходим к заданию конфигурации. У нас есть на выбор несколько вариантов целей в Makefile для выполнения конфигурации:

$ make xconfig
$ make gconfig
$ make menuconfig
$ make config
$ make oldconfig

Запустим графический конфигуратор:

$ make gconfig
  HOSTCC  scripts/kconfig/gconf.o
...

Я запускал графический конфигуратор в среде GNOME и поэтому выбрал gconfig, но если вы используете среду KDE, то следует использовать конфигуратор xconfig.

На рисунке 1 представлен результат запуска графического конфигуратора.

Рисунок 1. Графический интерфейс конфигуратора ядра Linux
Графический интерфейс конфигуратора ядра Linux)
Графический интерфейс конфигуратора ядра Linux)

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

# configuration written to .config
#

Которое означает, что ядро сконфигурировано и мы можем приступать к его сборке.

Здесь можно ещё раз проверить, что созданный файл конфигурации .config отличается от ранее скопированного из каталога /boot как по размеру, так и по времени и дате создания.

$ ls -l .config
-rw-rw-r-- 1 olej olej 120513 Мар 13 17:00 .config

Компиляция

Компиляция ядра — это весьма продолжительная операция, и на 2-х ядерном процессоре 1.6ГГц процесс компиляции ядра занял порядка 25 минут:

$ time make bzImage
  HOSTLD  scripts/kconfig/conf
...
  BUILD   arch/x86/boot/bzImage
Root device is (253, 0)
Setup is 14908 bytes (padded to 15360 bytes).
System is 7420 kB
CRC e39e9d7b
Kernel: arch/x86/boot/bzImage is ready  (#1)
real	23m33.853s
user	19m2.748s
sys	1m48.754s
$ ls -l arch/x86/boot/bzImage
-rw-rw-r-- 1 olej olej 7612704 Мар 13 17:27 arch/x86/boot/bzImage

Затем выполним компиляцию модулей ядра, и этот процесс займет ещё в четыре раза больше времени!

$ time make modules
  CHK     include/linux/version.h
...
  Building modules, stage 2.
  MODPOST 2129 modules
...
real	100m0.165s
user	78m59.427s
sys	7m8.274s

Ещё раз измерим объём, занимаемый файлами в каталоге исходных кодов:

$ du -hs
2,6G

Объём после компиляции увеличился почти на 2ГБ (см. цифру ранее). Аналогичный объём свободного пространства обязательно должен присутствовать на диске для успешной компиляции ядра. Обращаю внимание, что все операции до этого места выполнялись без прав root!

Установка

Выполним установку модулей ядра (и сравним содержимое каталога /lib/modules до и после установки):

$ ls /lib/modules
2.6.32.9-70.fc12.i686.PAE
$ sudo make modules_install
...
  DEPMOD  2.6.37.3
$ ls /lib/modules
2.6.32.9-70.fc12.i686.PAE  2.6.37.3
$ cd /lib/modules/2.6.37.3/
$ du -hs
343M

Так, появился каталог модулей новой версии: /lib/modules/2.6.37.3, а итоговый размер собранных модулей оказался не таким уж большим. Выполним установку собранного ядра (и сравним содержимое каталога /boot до и после установки):

$ ls /boot
config-2.6.32.9-70.fc12.i686.PAE         lost+found
efi                                      System.map-2.6.32.9-70.fc12.i686.PAE
grub                                     vmlinuz-2.6.32.9-70.fc12.i686.PAE
initramfs-2.6.32.9-70.fc12.i686.PAE.img
$ sudo make install
sh /usr/src/linux-2.6.37.3/arch/x86/boot/install.sh 2.6.37.3 arch/x86/boot/bzImage \
	System.map "/boot"
$ ls /boot
config-2.6.32.9-70.fc12.i686.PAE         System.map
efi                                      System.map-2.6.32.9-70.fc12.i686.PAE
grub                                     System.map-2.6.37.3
initramfs-2.6.32.9-70.fc12.i686.PAE.img  vmlinuz
initramfs-2.6.37.3.img                   vmlinuz-2.6.32.9-70.fc12.i686.PAE
lost+found                               vmlinuz-2.6.37.3

У нас появились 3 новых файла

  • vmlinuz-2.6.37.3— ядро;
  • initramfs-2.6.37.3.img— образ начальной загружаемой системы;
  • System.map-2.6.37.3— таблица символов нового ядра.

Инсталляция ядра 2.6.37.3 в примере корректно отредактировала файл меню загрузки /boot/grub/grub.conf загрузчика GRUB, поэтому после перезагрузки система запустится с теми же установками, с которыми она запускалась раньше:

$ uname -r
2.6.37.3

К сожалению, загрузчик GRUB не всегда может безошибочно отредактировать меню стартовых конфигураций. Но это совсем не сложно, и можно подредактировать стартовое меню /boot/grub/grub.conf под потребности конкретного проекта.

Как ускорить сборку ядра

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

$ 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

Проверим такую возможность, для этого скопируем дерево исходных кодов в каталог /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

Как видно, время на сборку ядра сократилось до 10 минут (в 2.5 раза), что очень неплохо.

Ещё один способ существенно ускорить сборку ядра — это указать утилите make использовать при сборке ядра все доступные процессорные ядра:

$ make -j bzImage
...

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

$ make -j4 bzImage
...

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

Заключение

Мы только что собрали полностью новое ядро Linux, и теперь можем работать с новой, обновлённой до последней версии, операционной системой Linux. Означает ли это, что такими последовательными обновлениями можно поддерживать систему в самом свежем состоянии, адекватном новым дистрибутивам? К сожалению, нет, так как хотя мы обновляем ядро и его модули (драйвера), но версии всех утилит, библиотек, компиляторов и всего прочего остаются устаревшими. Кроме того, через некоторое время начнут возникать проблемы с устареванием репозитариев, указанных в пакетной системе для поиска обновлений программ. Отсрочить эту проблему можно, если аккуратно подредактировать вручную ссылки на репозитарии в каталоге /etc/yum.repos.d, но это совсем другая история.


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=950572
ArticleTitle=Отладка и тестирование модулей ядра: Часть 77. Отладка в ядре. Сборка и установка ядра
publish-date=10292013