Разработка ядра Linux

Лав Роберт

Глава 16

Модули

 

 

Несмотря на то что ядро является монолитным, в том смысле что все ядро выполняется в общем защищенном адресном домене, ядро Linux также является модульным, что позволяет выполнять динамическую вставку и удаление кода ядра в процессе работы системы. Соответствующие подпрограммы, данные, а также точки входа и выхода группируются в общий бинарный образ, загружаемый объект ядра, который называется модулем. Поддержка модулей позволяет системам иметь минимальное базовое ядро с опциональными возможностями и драйверами, которые компилируются в качестве модулей. Модули также позволяют просто удалять и перегружать код ядра, что помогает при отладке, а также дает возможность загружать драйверы по необходимости в ответ на появление новых устройств с функциями горячего подключения.

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

 

Модуль "Hello, World!"

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

Может показаться банальным, но иметь возможность написать программу, которая выводит сообщение "Hello World!", и не сделать этого- просто смешно. Итак, леди и джентльмены, модуль "Hello, World!".

/*

* hello.c - модуль ядра Hello, World!

*/

#include

#include

#include

/*

* 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 (как создавать новые конфигурационные параметры, рассмотрено ниже в этой главе в разделе "Управление конфигурационными параметрами"). В этом случае необходимо добавить строку следующего вида.

obj-$(CONFIG_FISHING_POLE) += fishing/

И наконец, в каталоге drivers/char/fishing необходимо добавить новый файл Makefile, содержащий следующую строку.

obj-m += fishing.o

При таких настройках система компиляции перейдет в каталог fishing/ и скомпилирует модуль fishing.ko из исходного файла fishing.c. Да, расширение объектного файла указано как .o, но в результате будет создан модуль с расширением .ko.

И снова, скорее всего, факт компиляции модуля будет зависеть от конфигурационного параметра, в таком случае в Makefile необходимо добавить следующую строку.

obj-$(CONFIG_FISHING_POLE) += fishing.o

Однажды драйвер удочки может стать очень сложным. Введение функции автодетектирования наличия лески может привести к тому, что модуль станет очень большим и теперь будет занимать больше одного файла исходного кода. Никаких проблем! Просто нужно внести в Makefile следующую запись.

obj-$(CONFIG_FISHING_POLE) += fishing.o

fishing-objs := fishing-main.o fishing-line.o

В последнем случае будут скомпилированы файлы fishing-main.c и fishing-line.c и скомпонованы в файл модуля fishing.ko.

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

EXTRA_CFLAGS += -DTITANIUM_POLE

Если вы желаете поместить ваши файлы в каталог drivers/char/, вместо того чтобы создавать новый подкаталог, то необходимо просто прописать указанные строки (те, что должны быть прописаны в файле Makefile подкаталога drivers/char/fishing/) в файле drivers/char/Makefile.

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

 

Компиляция вне дерева исходных кодов ядра

Если вы предпочитаете разрабатывать и поддерживать ваш модуль отдельно от дерева исходных кодов ядра и жить жизнью аутсайдера, просто создайте файл Makefile следующего вида в том каталоге, где находится модуль.

obj-m := fishing.o

Такая конфигурация позволяет скомпилировать файл fishing.c в файл fishing.ko. Если ваш исходный код занимает несколько файлов, то необходимо добавить две строки.

obj-m := fishing.o

fishing-objs := fishing-main.o fishing-line.o

Такая конфигурация позволяет скомпилировать файлы fishing-main.c и fishing-line.c и создать модуль fishing.ko.

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

make -С /kernel/source/location SUBDTRS=$PWD modules

В этом примере /kernel/source/location — путь к сконфигурированному дереву исходных кодов ядра. Вспомните, что не нужно хранить копию дерева исходных кодов ядра, с которой вы работаете, в каталоге /usr/src/linux, эта копия должна быть где-то в другом месте, скажем где-нибудь в вашем домашнем каталоге.

 

Инсталляция модулей

Скомпилированные модули должны быть инсталлированы в каталог /lib/modules/ version /kernel . Например, для ядра 2.6.10 скомпилированный модуль управления удочкой будет находиться в файле /lib/modules/2.6.10/kernel/drivers/char/fishing.ko, если исходный код находился непосредственно в каталоге drivers/char/.

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

make modules_install

Разумеется, эту команду необходимо выполнять от пользователя root.

 

Генерация зависимостей между модулями

Утилиты работы с модулями ОС Linux поддерживают зависимости между модулями. Это означает, что если модуль chum зависит от модуля bait, то при загрузке модуля chum модуль bait будет загружен автоматически. Информация о зависимостях между модулями должна быть сгенерирована администратором. В большинстве поставок ОС Linux эта информация генерируется автоматически и обновляется при загрузке системы. Для генерации информации о зависимостях между модулями необходимо от пользователя root выполнить следующую команду.

depmod

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

depmod -A

Информация о зависимостях между модулями хранится в файле /lib/modules/version/modules.dep.

 

Загрузка модулей

Наиболее простой способ загрузки модуля — это воспользоваться утилитой insmod. Эта утилита выполняет самые общие действия. Она просто загружает тот модуль, который ей указан в качестве параметра. Утилита insmod не отслеживает зависимости и не выполняет никакой интеллектуальной обработки ошибок. Использовать ее очень просто. От пользователя root необходимо просто выполнить команду

insmod module

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

insmod fishing

Удалить модуль можно аналогичным образом с помощью утилиты rmmod. Для этого от пользователя root нужно просто выполнить команду.

rmmod module

Например, удалить модуль управления удочкой можно следующим образом.

rmmod fishing

Тем не менее, эти утилиты тривиальные и не обладают интеллектуальным поведением. Утилита modprobe позволяет обеспечить удовлетворение зависимостей, оповещение об ошибках, интеллектуальную обработку ошибок, а также выполняет множество других расширенных функций. Её настоятельно рекомендуется использовать.

Для загрузки модуля в ядро с помощью утилиты modprobe необходимо от пользователя root выполнить команду

modprobe module [ module parameters ]

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

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

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

modprobe Pr modules

где параметр modules — имя одного или нескольких модулей, которые необходимо удалить. В отличие от команды rmmod, утилита modprobe также удаляет и все модули, от которых указанный модуль зависит, если последние не используются.

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

 

Управление конфигурационными параметрами

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

Благодаря новой системе компиляции ядра "kbuild", которая появилась в серии ядер 2.6, добавление нового конфигурационного параметра является очень простым делом. Все, что необходимо сделать, — это добавить новую запись в файл Kconfig, который отвечает за конфигурацию дерева исходных кодов ядра. Для драйверов этот файл обычно находится в том же каталоге, в котором находится и исходный код. Если код драйвера удочки находится в каталоге drivers/char/, то необходимо использовать файл drivers/char/Kconfig.

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

source drivers/char/fishing/Kconfig

в существующий файл Kconfig, скажем в файл drivers/char/Kconfig.

Конфигурационные записи в файле Kconfig добавляются очень просто. Для модуля управления удочкой эта запись может выглядеть следующим образом.

config FISHING_POLE

 tristate "Fish Master XL support"

 default n

 help

  If you say Y here, support for the Fish Master XL 2000 Titanium

  with computer interface will be compiled into the kernel

  and accessible via

  device node. You can also say M here and the driver

  will be built as a

  module named fishing.ko.

  If unsure, say N.

Первая строка определяет, какой конфигурационный параметр создается. Обратите внимание, что префикс CONFIG_ указывать не нужно, он добавляется автоматически.

Вторая строка указывает на то, что параметр может иметь три состояния (tristate), которые соответствуют следующим значениям: статическая компиляция в ядро (Y), компиляция в качестве модуля (M) или не компилировать драйвер вообще (N). Для того чтобы запретить компиляцию кода, который соответствует конфигурационному параметру, в качестве модуля (допустим, что этот параметр определяет не драйвер. а просто некоторую дополнительную функцию) необходимо указать тип параметра bool вместо tristate. Текст в кавычках, который следует после этой директивы, определяет название конфигурационного параметра и будет отображаться различными утилитами конфигурации.

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

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

Существуют также и другие директивы файла конфигурации. Директива depends указывает на конфигурационные параметры, которые должны быть установлены перед тем, как может быть установлен текущий параметр. Если зависимости не будут удовлетворены, то текущий параметр будет запрещен. Например, можно указать следующую директиву.

depends on FISH_TANK

При этом текущий модуль не будет разрешен, пока не будет разрешен модуль, соответствующий конфигурационному параметру CONFIG_FISH_TANK.

Директива select аналогична директиве depends, за исключением того, что она принудительно включает указанный конфигурационный параметр, если включается текущая конфигурационная опция. Ее не нужно использовать так же часто, как директиву depends, потому что она включает другие конфигурационные опции. Использовать ее так же просто.

select BAIT

В этом случае конфигурационный параметр CONFIG_BAIT автоматически активизируется при включении конфигурационного параметра CONFIG_FISHING_POLE.

Как для директивы select, так и для директивы depends можно указывать несколько параметров с помощью оператора &&. В директиве depends с помощью восклицательного знака перед параметром можно указать требование, что некоторый конфигурационный параметр не должен быть установлен. Например, следующая запись указывает, что для компиляции текущего драйвера необходимо, чтобы был установлен конфигурационный параметр CONFIG_DUMB_DRIVERS и не был установлен параметр CONFIG_NO_FISHING_ALLOWED.

depends on DUMB_DRIVERS && !NO_FISHING_ALLOWED

После директив tristate и bool можно указать директиву if, что позволяет сделать соответствующий параметр зависимым от другого конфигурационного параметра. Если условие не выполняется, то конфигурационный параметр не только запрещается, но и не будет отображаться утилитами конфигурации. Например, следующая строка указывает, что функция "Deep Sea Mode" будет доступна, только если разрешен конфигурационный параметр CONFIG_OCEAN.

bool "Deep Sea Mode" if OCEAN

Директива if также может быть указана после директивы default, что означает, что значение по умолчанию будет установлено, только если выполняется условие, указанное в директиве if.

Система конфигурации экспортирует несколько метапараметров, чтобы упростить процесс конфигурации. Параметр CONFIG_EMBEDDED устанавливается только тогда, когда пользователь указывает, что он хочет видеть вес параметры, отвечающие за запрещение некоторых ключевых возможностей ядра (обычно с целью сохранения памяти на встраиваемых системах). Параметр CONFIG_BROKEN_ON_SMP используется, чтобы указать, что драйвер не рассчитан на системы с симметричной многопроцессорностью. Обычно этот параметр не устанавливается, при этом от пользователя требуется, чтобы он сам убедился в возможности компиляции драйвера для SMP. Новые драйверы этот флаг использовать не должны.

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

 

Параметры модулей

Ядро Linux предоставляет возможность драйверам определять параметры, которые пользователь будет указывать при загрузке ядра или модуля. Эти параметры будут доступны коду модуля в качестве глобальных переменных. Указанные параметры модулей также будут отображаться в файловой системе sysfs (см. главу 17, "Объекты kobject и файловая система sysfs"). Определять параметры модуля и управлять ими просто.

Параметр модуля определяется с помощью макроса module_param() следующим образом.

module_param(name, type, perm);

где аргумент name — это имя неременной, которая появляется в модуле, и имя параметра, который может указать пользователь. Аргумент type — это тип данных параметра. Значения типа могут быть следующими: byte, short, ushort, int, uint, long, ulong, charp, bool или invbool. Эти значения соответствуют следующим типам данных: байт; короткое целое число; короткое целое число без знака; целое число; целое число без знака; длинное целое; длинное целое число без знака; указатель на строку символов; булев тип; булев тип, значение которого инвертируется по сравнению с тем, которое указывает пользователь. Данные типа byte хранятся в переменной типа char, а данные булевых типов — в переменных типа int. Остальные- типы соответствуют аналогичным типам языка С. Наконец, аргумент perm указывает права доступа к соответствующему файлу в файловой системе sysfs. Права доступа можно указать как в обычном восьмеричном формате, например 0644 (владелец имеет права на чтение и запись, группа имеет права на чтение и запись, остальные пользователи имеют право только на чтение), так и в виде определений препроцессора, объединенных с помощью оператора "|", например S_IRUGO | S_IWUSR (все могут считывать данные, а владелец также и записывать). Нулевое значение этого параметра приводит к тому, что соответствующий файл в файловой системе sysfs не появляется.

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

/* параметр модуля, который управляет переменной bait */

static int allow live bait = 1;            /* по умолчанию включено */

module_param(allow_live_bait, bool, 0644); /* булев тип */

Это определение должно быть в глобальной области видимости, т.е. неременная allow_live_bait должна быть глобальной.

Существует возможность дать внешнему параметру модуля имя, отличное от имени переменной. Это можно сделать с помощью макроса module_param_named().

module_param_named(name, variable, type, perm);

где name — это имя внешнего параметра модуля, a variable — имя внутренней глобальной переменной модуля, как показано ниже.

static unsigned int max_test = DEFAULT_МАХ_LINE_TEST;

module_param_named(maximum_line_test, max_test, int, 0);

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

static char *name;

module_param(name, charp, 0);

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

module_param_string(name, string, len, perm);

где name — это имя внешнего параметра, string — имя внутренней переменной, которая содержит указатель на область памяти массива, len — размер буфера string (или некоторое меньшее число, чем размер буфера, что, однако, обычно не имеет смысла), perm — права доступа к файлу на файловой системе sysfs (нулевое значение запрещает доступ к параметру через sysfs). Пример показан ниже.

static char species[BUF_LEN];

module_param_string(specifies, species, BUF_LEN, 0);

В качестве параметров модуля также можно передавать список значений, которые разделены запятой и в коде модуля будут записаны в массив данных. Эти параметры модуля можно обработать с помощью макроса module_param_array() следующим образом.

module_param_array(name, type, nump, perm);

В данном случае аргумент name — это имя внешнего параметра и внутренней переменной, type — это тип данных одного значения, a perm — это права доступа к файлу на файловой системе sysfs. Новый аргумент nump — это указатель на целочисленное значение, где ядро сохраняет количество элементов, записанных в массив. Обратите внимание, что массив, который передается в качестве параметра name, должен быть выделен статически. Ядро определяет размер массива на этапе компиляции и гарантирует, что он не будет переполнен. Как использовать данный макрос, показано в следующем примере.

static int fish[MAX_FISH];

static int nr_fish;

module_param_array(fish, int, &nr_fish, 0444);

Внутренний массив может иметь имя, отличное от имени внешнего параметра, в этом случае следует использовать макрос module_param_array_named().

module_param_array_named(name, array, type, nump, perm);

Параметры идентичны аналогичным параметрам других макросов.

Наконец, параметры модуля можно документировать, используя макрос MODULE_PARM_DESC().

static unsigned short size = 1;

module_param(size, ushort, 0644);

MODULE_PARM_DESC(size, "The size in inches of the fishing pole " \

 "connected to this computer.");

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

 

Экспортируемые символы

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

Функции, которые экспортируются, доступны для использования модулями. Функции, которые не экспортируются, не могут быть вызваны из модулей. Правила компоновки и вызова функций для модулей значительно более строгие, чем для основного образа ядра. Код ядра может использовать любые интерфейсы ядра (кроме тех, которые определены с ключевым словом static), потому что код ядра компонуется в один выполняемый образ. Экспортируемые символы, конечно, тоже не должны определяться как static.

Набор символов ядра, которые экспортируются, называется экспортируемым интерфейсом ядра или даже (здесь не нужно удивляться) API ядра.

Экспортировать символы просто. После того как функция определена, необходимо вызвать директиву EXPORT_SYMBOL().

/*

* get_pirate_beard_color — возвратить значение цвета бороды текущего

* пирата pirate — это глобальная переменная, доступная из данной

* функции цвета определены в файле

*/

int get_pirate_beard_color(void) {

 return pirate->beard->color;

}

EXPORT_SYMBOL(get_pirate_beard_color);

Допустим, что функция get_pirate_beard_color() объявлена в заголовочном файле и ее может использовать любой модуль.

Некоторые разработчики хотят, чтобы их интерфейсы были доступны только для модулей с лицензией GPL. Такая возможность обеспечивается компоновщиком ядра с помощью макроса MODULE_LICENSE(). Если есть желание, чтобы рассматриваемая функция была доступна только для модулей, которые помеченные как соответствующие лицензии GPL, то экспортировать функцию можно следующим образом.

EXPORT_SYMBOL_GPL(get_pirate_beard_color);

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

 

Вокруг модулей

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

В следующей главе будут рассмотрены объекты kobject и файловая система sysfs, которые являются основным интерфейсом к драйверам устройств и, следовательно, к модулям ядра.