Глава 16
Модули
Несмотря на то что ядро является монолитным, в том смысле что все ядро выполняется в общем защищенном адресном домене, ядро Linux также является модульным, что позволяет выполнять динамическую вставку и удаление кода ядра в процессе работы системы. Соответствующие подпрограммы, данные, а также точки входа и выхода группируются в общий бинарный образ, загружаемый объект ядра, который называется модулем. Поддержка модулей позволяет системам иметь минимальное базовое ядро с опциональными возможностями и драйверами, которые компилируются в качестве модулей. Модули также позволяют просто удалять и перегружать код ядра, что помогает при отладке, а также дает возможность загружать драйверы по необходимости в ответ на появление новых устройств с функциями горячего подключения.
В этой главе рассказывается о хитростях, которые стоят за поддержкой модулей в ядре, и о том, как написать свой собственный модуль.
Модуль "Hello, World!"
В отличие от разработки основных подсистем ядра, большинство из которых были уже рассмотрено, разработка модулей подобна созданию новой прикладной программы, по крайней мере в том, что модули имеют точку входа, точку выхода и находятся каждый в своем бинарном файле.
Может показаться банальным, но иметь возможность написать программу, которая выводит сообщение "Hello World!", и не сделать этого- просто смешно. Итак, леди и джентльмены, модуль "Hello, World!".
/*
* hello.c - модуль ядра Hello, World!
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/*
* hello_init - функция инициализации, вызывается при загрузке модуля,
* В случае успешной загрузки модуля возвращает значение нуль,
* и ненулевое значение в противном случае.
*/
static int hello_init(void) {
printk(KERN_ALERT "I bear a charmed life.\n");
return 0;
}
/*
* hello_exit - функция завершения, вызывается при выгрузке модуля.
*/
static void hello_exit(void) {
printk(KERN_ALERT "Out, out, brief candle!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE{"GPL");
MODULE_AUTHOR("Shakespeare");
Это самый простой модуль ядра, который только может быть. Функция hello_init()
регистрируется с помощью макроса module_init()
в качестве точки входа в модуль. Она вызывается ядром при загрузке модуля. Вызов module_init()
— это не вызов функции, а макрос, который устанавливает значение своего параметра в качестве функции инициализации. Все функции инициализации должны соответствовать следующему прототипу.
int my_init(void);
Так как функция инициализации редко вызывается за пределами модуля, ее обычно не нужно экспортировать и можно объявить с ключевым словом static
.
Функции инициализации возвращают значение тина int
. Если инициализация (или то, что делает функция инициализации) прошла успешно, то функция должна возвратить значение нуль. В случае ошибки возвращается ненулевое значение.
В данном случае эта функция просто печатает сообщение и возвращает значение нуль. В настоящих модулях функция инициализации регистрирует ресурсы, выделяет структуры данных и т.д. Даже если рассматриваемый файл будет статически скомпилирован с ядром, то функция инициализации останется и будет вызвана при загрузке ядра.
Функция hello_exit()
регистрируется в качестве точки выхода из модуля с помощью макроса module_exit()
. Ядро вызывает функцию hello_exit()
, когда модуль удаляется из памяти. Завершающая функция должна выполнить очистку ресурсов, гарантировать, что аппаратное обеспечение находится в непротиворечивом состоянии, и т.д. После того как эта функция завершается, модуль выгружается.
Завершающая функция должна соответствовать следующему прототипу.
void my_exit(void);
Так же как и в случае функции инициализации, ее можно объявить как static
.
Если этот файл будет статически скомпилирован с образом ядра, то данная функция не будет включена в образ и никогда не будет вызвана (так как если нет модуля, то код никогда не может быть удален из памяти).
Макрос MODULE_LICENSE()
позволяет указать лицензию на право копирования модуля. Загрузка в память модуля, для которого лицензия не соответствует GPL, приведет к установке в ядре флага tainted
(буквально, испорченное). Этот флаг служит для информационных целей, кроме того, многие разработчики уделяют меньше внимания сообщениям об ошибках, в которых указан этот флаг. Более того, модули, у которых лицензия не соответствует GPL, не могут использовать символы, которые служат "только для GPL" (см. раздел "Экспортируемые символы" ниже в этой главе).
Наконец, макрос MODULE_AUTHOR()
позволяет указать автора модуля. Значение этого макроса служит только для информационных целей.
Сборка модулей
Благодаря новой системе сборки "kbuild", в ядрах серии 2.6 сборка модулей выполняется значительно проще, чем в старых сериях. Первое, что нужно сделать при сборке модулей, — это решить, где будет находиться исходный код модуля. Исходный код модуля необходимо правильно объединить с деревом исходных кодов ядра. Это можно сделать в виде заплаты или путем добавления в официальное дерево исходного кода ядра. Кроме этого, можно компилировать исходный код модуля отдельно от исходных кодов ядра.
Использование дерева каталогов исходных кодов ядра
В идеале модуль является частью официального ядра и находится в каталоге исходных кодов ядра. Введение вашей разработки непосредственно в ядро может вначале потребовать больше работы, но обычно такое решение более предпочтительно.
На первом этапе необходимо решить, где именно будет находиться модуль в дереве исходных кодов ядра. Драйверы необходимо хранить в подкаталогах каталога drivers/
, который находится в корне дерева исходных кодов ядра. Внутри этого каталога драйверы делятся на классы, типы и собственно на отдельные драйверы. Символьные устройства находятся в каталоге drivers/char/
, блочные — в каталоге drivers/block/
, устройства USB — в каталоге drivers/usb/
. Эти правила не есть жесткими, так как многие устройства USB также являются и символьными устройствами. Но такая организация является понятной и четкой.
Допустим, что вы хотите создать свой подкаталог и ваш воображаемый драйвер разработан для удочки с числовым программным управлением, которая имеет интерфейс Fish Master XL 2000 Titanium для подключения к компьютеру. Следовательно, необходимо создать подкаталог fishing
внутри каталога drivers/char/
.
После этого необходимо добавить новую строку в файл Makefile
, который находится в каталоге drivers/char/
. Для этого отредактируйте файл drivers/char/Makefile
и добавьте в него следующую запись.
obj-m += fishing/
Эта строка указывает системе компиляции, что необходимо войти в подкаталог fishing/
при компиляции модулей. Скорее всего, компиляция драйвера определяется отдельным конфигурационным параметром, например, CONFIG_FISHING_POLE
(как создавать новые конфигурационные параметры, рассмотрено ниже в этой главе в разделе "Управление конфигурационными параметрами"). В этом случае необходимо добавить строку следующего вида.