Содержание


Разработка модулей ядра Linux

Часть 16. Сборка модулей. Основные принципы

Comments

Серия контента:

Этот контент является частью # из серии # статей: Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

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

Параметры компиляции

Параметры компиляции модуля можно переопределить, изменив переменные, определённые в сценарии, выполняющем сборку, например:

EXTRA_CFLAGS += -O3 -std=gnu89 —no-warnings

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

EXTRA_CFLAGS += -D EXPORT_SYMTAB -D DRV_DEBUG

(обратите внимание на знак +=).

Примечание: Откуда берутся переменные, не определённые явно в тексте файла Makefile, как, например, EXTRA_CFLAGS? Или откуда берутся правила сборки по умолчанию (как в примере использования ассемблерного кода, представленном в предыдущей статье)? И как можно посмотреть эти правила? Утилита make имеет множество значений по умолчанию (переменных, суффиксов и т.д.), важнейшими из которых являются правила обработки суффиксов, а также определения внутренних переменных окружения. Вся эта информация называется базой данных make и может быть выведена по ключу -p. Но объем выведенной информации будет очень большой, поэтому разумно отправить этот вывод в файл, а уже потом изучить его:

Листинг 1. Определения внутренних переменных окружения make
$ make -p >make.suffix
make: *** Не заданы цели и не найден make-файл.  Останов.
$ cat make.suffix
# GNU Make 3.81
...
# База данных 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) $<
...

Теперь можно использовать все эти переменные в собственных сценариях сборки Makefile.

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

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

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

Как одновременно собрать несколько модулей?

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

Листинг 2. Одновременная сборка двух модулей
TARGET1 = md1
TARGET2 = md2
obj-m   := $(TARGET1).o $(TARGET2).o
...

Как собрать модуль вместе с относящимися к нему программами?

Часто требуется собрать не только сам модуль, но и несколько пользовательских программ, используемых совместно с модулем (тесты, утилиты, и т.д.). Зачастую в таком сценарии модуль и пользовательские программы используют общие файлы определений (заголовочные файлы). Вот как выглядит фрагмент Makefile для сборки в одном рабочем каталоге модуля и всех использующих его программ (архив ioctl.tgz, который будет рассматриваться в ближайшем будущем).:

Листинг 3. Одновременная сборка модуля и пользовательских программ
...
TARGET = hello_dev
obj-m      := $(TARGET).o

all: default ioctl

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

ioctl:  ioctl.h ioctl.c
        gcc ioctl.c -o ioctl             
...

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

#include "ioctl.h"

Такой связующий файл содержит общие определения, как для кода пространства ядра, так и для пользовательских приложений. В качестве примера показано содержимое файла ioctl.h:

typedef struct _RETURN_STRING {
   char buf[ 160 ];
} RETURN_STRING;
#define IOCTL_GET_STRING _IOR( IOC_MAGIC, 1, RETURN_STRING )

В связи с этим может возникнуть определенная проблема, так как при сборке приложений и модулей (использующих совместные определения) для поиска системных (#include <...>) заголовочных файлов используются разные каталоги по умолчанию: /usr/include для процессов и /lib/modules/`uname -r`/build/include для модулей. Приемлемым решением будет включение в общий заголовочный файл (ioctl.h) фрагмента подобного вида:

Листинг 4. Условные определения для ядра и для приложений
#ifndef __KERNEL__         // приложения из пространства пользователя
// это /usr/include/linux/types.h ! :
#include <linux/types.h>
#include <string.h>
...
#else                      // модули ядра
...
#include <linux/errno.h> 
// а это уже /lib/modules/`uname -r`/build/include/linux/types.h ! :
#include <linux/types.h> 
#include <linux/string.h>
...
#endif

При всей схожести имён заголовочных файлов (а иногда и полном совпадении, например: <linux/types.h>) это будут включения из абсолютно разных наборов API (API разделяемых библиотек *.so для пространства пользователя и API ядра - для модулей).

Пользовательские библиотеки

В дополнение к набору приложений пространства пользователей может оказаться удобным собрать в виде единой библиотеки целый ряд совместно используемых этими приложениями функций (так устраняется дублирование кода, упрощается внесение изменений и улучшается структура проекта). Фрагмент Makefile из архива time.tgz, который будет представлен в одной из следующих статей, демонстрирует, как это сделать, не указывая в явном виде все цели сборки (перечисленные списком в переменной OBJLIST) для каждого такого объектного файла, включаемого в библиотеку (реализующего отдельную функцию библиотеки). В данном случае собирается статическая библиотека libdiag.a:

Листинг 5. Сборка пользовательской статической библиотеки
LIBTITLE = diag 
LIBRARY = lib$(LIBTITLE).a 

all:    prog lib

PROGLIST = clock pdelay rtcr rtprd 
prog:   $(PROGLIST) 

clock:  clock.c 
        $(CC) $< -Bstatic -L./ -l$(LIBTITLE) -o $@ 
...
OBJLIST = calibr.o rdtsc.o proc_hz.o set_rt.o tick2us.o 
lib:    $(OBJLIST) 

LIBHEAD = lib$(LIBTITLE).h 
%.o: %.c $(LIBHEAD) 
        $(CC) -c $< -o $@ 
        ar -r $(LIBRARY) $@ 
        rm $@

В листинге 5 собираются две цели prog и lib, объединённые в одну общую цель all. При желании статическую библиотеку можно заменить на динамическую (разделяемую), что может быть востребовано в крупных проектах. При этом в Makefile потребуется внести только незначительные изменения (все остальные файлы проекта остаются в неизменном виде).

Листинг 6. Сборка пользовательской разделяемой библиотеки
LIBRARY = lib$(LIBTITLE).so 
...
prog:   $(PROGLIST) 
clock:  clock.c 
        $(CC) $< -L./ -l$(LIBTITLE) -o $@ 
...
OBJLIST = calibr.o rdtsc.o proc_hz.o set_rt.o tick2us.o 
lib:    $(OBJLIST) 

LIBHEAD = lib$(LIBTITLE).h 
%.o: %.c $(LIBHEAD) 
        $(CC) -c -fpic -fPIC -shared $< -o $@ 
        $(CC) -shared -o $(LIBRARY) $@
        rm $@

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

  • копированием файла с созданной библиотекой в /usr/lib;
  • манипулированием переменными окружения LD_LIBRARY_PATH и LD_RUN_PATH;
  • манипулированием файлом /etc/ld.so.cache (файл /etc/ld.so.conf и команда ldconfig).

Но все эти вопросы относятся уже к системному администрированию и выходят за рамки данной статьи.

Заключение

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


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


Похожие темы


Комментарии

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

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