Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Окружение и инструменты | Вперед |
О сборке модулей детальнее
Далее рассмотрим некоторые особенности процедуры сборки (make) проектов, и нарисуем несколько сценариев сборки (Makefile) для наиболее часто востребованных случаев, как например: сборка нескольких модулей в проекте, сборка модуля объединением нескольких файлов исходных кодов и подобные...
Параметры компиляции
Параметры компиляции модуля можно существенно менять, изменяя переменные, определённые в скрипте, осуществляющем сборку, например:
EXTRA_CFLAGS += -O3 -std=gnu89 —no-warnings
Таким же образом дополняем определения нужных нам препроцессорных переменных, специфических для сборки нашего модуля:
EXTRA_CFLAGS += -D EXPORT_SYMTAB -D DRV_DEBUG
Примечание: Откуда берутся переменные, не описанные по тексту файлу Makefile, как, например, EXTRA_CFLAGS? Или откуда берутся правила сборки по умолчанию (как в примере примере использования ассемблерного кода разделом ранее)? И как посмотреть эти правила? Всё это вытекает из правил работы утилиты make: в конце книги отдельным приложением приведена краткая справка по этим вопросам, там же приведена ссылка на детальное описание утилиты make.
Как собрать одновременно несколько модулей?
В уже привычного нам вида Makefile может быть описано сборка скольки угодно одновременно собираемых модулей (архив export.tgz):
Makefile :
... TARGET1 = md1 TARGET2 = md2 obj-m := $(TARGET1).o $(TARGET2).o ...
Как собрать модуль и использующие программы к нему?
Часто нужно собрать модуль и одновременно некоторое число пользовательских программ, используемых одновременно с модулем (тесты, утилиты, ...). Зачастую модуль и пользовательские программы используют общие файлы определений (заголовочные файлы). Вот фрагмент подобного Makefile - в одном рабочем каталоге собирается модуль и все использующие его программы (архив ioctl.tgz):
Makefile :
... 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 )
Некоторую дополнительную неприятность на этом пути составляет то, что при сборке приложений и модулей (использующих совместные определения) используются разные дефаултные каталоги поиска системных (<...>) файлов определений: /usr/include для процессов, и /lib/modules/`uname -r`/build/include для модулей. Приемлемым решением будет включение в общий включаемый файл фрагмента подобного вида:
#ifndef __KERNEL__ // ------------- user space applications #include <linux/types.h> // это /usr/include/linux/types.h ! #include <string.h> ... #else // --------------- kernel modules ... #include <linux/errno.h> #include <linux/types.h> // а это /lib/modules/`uname -r`/build/include/linux/types.h #include <linux/string.h> ... #endif
При всём подобии имён заголовочных файлов (иногда и полном совпадении написания: <linux/types.h>), это будут включения заголовков из совсем разных наборов API (API разделяемых библиотек *.so для пространства пользователя, и API ядра - для модулей). Первый (пользовательский) из этих источников будет обновляться, например, при переустановке в системе новой версии компилятора GCC и комплекта соответствующих ему библиотек (в первую очередь libc.so). Второй (ядерный) из этих источников будет обновляться, например, при обновлении сборки ядра (из репозитария дистрибутива), или при сборке и установке нового ядра из исходных кодов.
Пользовательские библиотеки
В дополнение к набору приложений, обсуждавшихся выше, удобно целый ряд совместно используемых этми приложениями функций собрать в виде единой библиотеки (так устраняется дублирование кода, упрощается внесение изменений, да и вообще улучшается структура проекта). Фрагмент Makefile из архива примеров time.tgz демонстрирует как это записать, не выписывая в явном виде все цели сборки (перечисленные списком в переменной OBJLIST) для каждого такого объектного файла, включаемого в библиотеку (реализующего отдельную функцию библиотеки). В данном случае мы собираем статическую библиотеку libdiag.a:
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 $@
Здесь собираются две цели prog и lib, объединённые в одну общую цель all. При желании, статическую библиотеку можно поменять на динамическую (разделяемую), что весьма часто востребовано в реальных крупных проектах. При этом в Makefile требуется внести всего незначительные изменения (все остальные файлы проекта остаются в неизменном виде):
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) на путях, где он будет найдена динамическим загрузчиком, размещение «текущий каталог» для этого случая неприемлем: относительные путевые имена не применяются для поиска динамических библиотек. Решается эта задача: манипулированием с переменными окружения LD_LIBRARY_PATH и LD_RUN_PATH, или с файлом /etc/ld.so.cache (файл /etc/ld.so.conf и команда ldconfig) ..., но это уже вопросы системного администрирования, далеко уводящие нас за рамки предмета рассмотрения.
Как собрать модуль из нескольких объектных файлов?
Соберём (архив mobj.tgz) модуль из основного файла mod.c и 3-х отдельно транслируемых файлов mf1.c, mf2.c, mf3.c, содержащих по одной отдельной функции, экспортируемой модулем (весьма общий случай):
mod.c :
#include <linux/module.h> #include "mf.h" static int __init init_driver( void ) { return 0; } static void __exit cleanup_driver( void ) {} module_init( init_driver ); module_exit( cleanup_driver );
mf1.c :
#include <linux/module.h> char *mod_func_A( void ) { static char *ststr = __FUNCTION__ ; return ststr; }; EXPORT_SYMBOL( mod_func_A );
Файлы mf2.c, mf3.c полностью подобны mf1.c только имя экспортируемых функций заменены, соответственно, на mod_func_B( void ) и mod_func_C( void ). Заголовочный файл, включаемый в текст модулей:
mf.h :
extern char *mod_func_A( void ); extern char *mod_func_B( void ); extern char *mod_func_C( void );
Ну и, наконец, в том же каталоге собран второй (тестовый) модуль, который импортирует и вызывает эти три функции как внешние экспортируемые ядром символы:
mcall.c :
#include <linux/module.h> #include "mf.h" static int __init init_driver( void ) { printk( KERN_INFO "start module, export calls: %s + %s + %s\n", mod_func_A(), mod_func_B(), mod_func_C() ); return 0; } static void __exit cleanup_driver( void ) {} module_init( init_driver ); module_exit( cleanup_driver );
Самое интересное в этом проекте, это:
Makefile :
... EXTRA_CFLAGS += -O3 -std=gnu89 --no-warnings OBJS = mod.o mf1.o mf2.o mf3.o TARGET = mobj TARGET2 = mcall obj-m := $(TARGET).o $(TARGET2).o $(TARGET)-objs := $(OBJS) all: $(MAKE) -C $(KDIR) M=$(PWD) modules $(TARGET).o: $(OBJS) $(LD) -r -o $@ $(OBJS) ...
- привычные, из предыдущих примеров, всё те же определения переменных — опущены.
Теперь мы можем испытывать то, что мы получили:
$ nm mobj.ko | grep T
00000000 T cleanup_module 00000000 T init_module 00000000 T mod_func_A 00000010 T mod_func_B 00000020 T mod_func_C
$ sudo insmod ./mobj.ko
$ lsmod | grep mobj
mobj 1032 0
$ cat /proc/kallsyms | grep mod_func
... f7f9b000 T mod_func_A [mobj] f7f9b010 T mod_func_B [mobj] ...
$ modinfo mcall.ko
filename: mcall.ko license: GPL author: Oleg Tsiliuric <olej@front.ru> description: multi jbjects module srcversion: 5F4A941A9E843BDCFEBF95B depends: mobj vermagic: 2.6.32.9-70.fc12.i686.PAE SMP mod_unload 686
$ sudo insmod ./mcall.ko
$ dmesg | tail -n1
start module, export calls: mod_func_A + mod_func_B + mod_func_C
И в завершение проверим число ссылок модуля, и попытаемся модули выгрузить:
$ lsmod | grep mobj
mobj 1032 1 mcall
$ sudo rmmod mobj
ERROR: Module mobj is in use by mcall
$ sudo rmmod mcall
$ sudo rmmod mobj
Рекурсивная сборка
Это вопрос, не связанный непосредственно со сборкой модулей, но очень часто возникающий в проектах, оперирующих с модулями: выполнить сборку (одной и той же цели) во всех включаемых каталогах, например, на каких-то этапах развития, архив примеров к книге имел вид:
$ ls
dev exec int80 netproto pci signal thread tools user_space
dma first_hello IRQ net parms proc sys time usb
Хотелось бы иметь возможность собирать (или очищать от мусора) всю эту иерархию каталогов-примеров. Для такой цели используем, как вариант, такой Makefile :
Makefile :
... SUBDIRS = $(shell ls -l | awk '/^d/ { print $$9 }') all: @list='$(SUBDIRS)'; for subdir in $$list; do \ echo "=============== making all in $$subdir ================="; \ (cd $$subdir && make && cd ../) \ done; install: @list='$(SUBDIRS)'; for subdir in $$list; do \ echo "============= making install in $$subdir =============="; \ (cd $$subdir; make install; cd ../) \ done uninstall: @list='$(SUBDIRS)'; for subdir in $$list; do \ echo "============= making uninstall in $$subdir =============="; \ (cd $$subdir; make uninstall; cd ../) \ done clean: @list='$(SUBDIRS)'; for subdir in $$list; do \ echo "=============== making clean in $$subdir ==============="; \ (cd $$subdir && make clean && cd ../) \ done;
Интерес здесь представляет строка, формирующая в переменной SUBDIRS список подкаталогов текущего каталога, для каждого из которых потом последовательно выполняется make для той же цели, что и исходный вызов.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Компилятор GCC | Инсталляция модуля |