Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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   Инсталляция модуля